Skip to content

Commit 62de0b5

Browse files
Merge pull request #80 from navapbc/worktree-20260508-140855
feat(attribution): wire hook_record_agent_attribution into post-agent.sh dispatcher (SC-1)
2 parents 18a18cd + 89fbb04 commit 62de0b5

12 files changed

Lines changed: 3229 additions & 9 deletions

.claude/settings.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
{
22
"enabledPlugins": {
33
"dso-dev@digital-service-orchestra": true
4+
},
5+
"hooks": {
6+
"PostToolUse": [
7+
{
8+
"matcher": "Skill",
9+
"hooks": [
10+
{
11+
"type": "command",
12+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/post-skill.sh"
13+
}
14+
]
15+
},
16+
{
17+
"matcher": "Agent",
18+
"hooks": [
19+
{
20+
"type": "command",
21+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/post-agent.sh"
22+
}
23+
]
24+
}
25+
]
426
}
527
}

.test-index

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ plugins/dso/hooks/check-plugin-self-ref.sh:tests/hooks/test-enforcement-gate.sh
100100
plugins/dso/hooks/check-portability.sh:tests/hooks/test-enforcement-gate.sh
101101
plugins/dso/hooks/check-tickets-boundary.sh:tests/hooks/test-check-tickets-boundary.sh
102102
plugins/dso/hooks/compute-diff-hash.sh:tests/hooks/test-compute-diff-hash-staging-invariance.sh,tests/hooks/test-compute-diff-hash-autostash-invariance.sh,tests/hooks/test-behavioral-equivalence-allowlist.sh
103-
plugins/dso/hooks/dispatchers/post-agent.sh:tests/hooks/test-post-agent-sentinel.sh,tests/hooks/test-post-dispatcher-err-handler.sh
103+
plugins/dso/hooks/dispatchers/post-agent.sh:tests/hooks/test-post-agent-sentinel.sh,tests/hooks/test-post-dispatcher-err-handler.sh,tests/hooks/test-post-agent-attribution.sh [test_post_agent_dispatcher_writes_attribution_jsonl_entry]
104104
plugins/dso/hooks/dispatchers/post-all.sh:tests/hooks/test-post-dispatchers.sh,tests/hooks/test-post-dispatcher-err-handler.sh
105105
plugins/dso/hooks/dispatchers/post-bash.sh:tests/hooks/test-post-dispatchers.sh,tests/hooks/test-post-dispatcher-err-handler.sh
106106
plugins/dso/hooks/dispatchers/post-edit.sh:tests/hooks/test-post-dispatchers.sh,tests/hooks/test-post-dispatcher-err-handler.sh
@@ -118,6 +118,7 @@ plugins/dso/hooks/lib/enforcement-gate.sh:tests/hooks/lib/test-enforcement-gate-
118118
plugins/dso/hooks/lib/hook-error-handler.sh:tests/hooks/test-hook-error-handler.sh
119119
plugins/dso/hooks/lib/merge-helpers.sh:tests/integration/test-merge-to-main-pr-thread-resolution.sh,tests/integration/test-conflict-data-schema-parity.sh,tests/scripts/test-merge-to-main-dispatcher.sh,tests/hooks/test-merge-helpers-pr-repo-cache.sh,tests/hooks/test-merge-helpers-special-chars.sh,tests/scripts/test-merge-to-main-pr.sh
120120
plugins/dso/hooks/lib/planning-config.sh:tests/hooks/test-planning-config.sh
121+
plugins/dso/hooks/lib/post-functions.sh:tests/hooks/test-post-functions-attribution.sh,tests/hooks/test-post-skill-attribution.sh
121122
plugins/dso/hooks/lib/pre-all-functions.sh:tests/hooks/test-hook-inline-trap-consolidation.sh
122123
plugins/dso/hooks/lib/pre-bash-functions.sh:tests/hooks/test-hook-inline-trap-consolidation.sh
123124
plugins/dso/hooks/lib/preconditions-validator-lib.sh:tests/hooks/test-preconditions-validator-lib.sh,tests/integration/test-preconditions-chain-5stage.sh
@@ -413,6 +414,11 @@ tests/scripts/test-ticket-link-duplicates-supersedes.sh:tests/scripts/test-ticke
413414
tests/scripts/test-ticket-transition-deleted.sh:tests/scripts/test-ticket-transition-deleted.sh
414415
plugins/dso/scripts/commit-override.sh:tests/scripts/test-commit-override.sh [test_requires_reason_flag]
415416
plugins/dso/hooks/prepare-commit-msg-override-audit.sh:tests/hooks/test-prepare-commit-msg-override-audit.sh [test_no_token_no_trailer] [test_token_appends_trailer]
417+
plugins/dso/hooks/post-commit-override-cleanup.sh:tests/hooks/test-post-commit-override-cleanup.sh [test_cleanup_removes_token],tests/scripts/test-reinstall-hooks.sh [test_post_commit_registered]
418+
plugins/dso/scripts/commit-step.sh:tests/scripts/test-commit-step.sh [test_writes_result_artifact],tests/scripts/test-commit-step-worktree.sh [test_cwd_mismatch_fails_fast] [test_harvest_mode_bypasses_cwd_check],tests/scripts/test-commit-step-truncation.sh [test_pipe_to_tail_artifact_intact],tests/hooks/test-verifier-baseline.sh [test_bug_7b41_breadcrumb_not_empty]
419+
plugins/dso/hooks/pre-commit-compliance-verifier.sh:tests/hooks/test-pre-commit-compliance-verifier.sh [test_blocks_missing_artifact],tests/scripts/test-reinstall-hooks.sh [test_compliance_verifier_registered],tests/hooks/test-verifier-worktree-context.sh [test_verifier_reads_correct_worktree],tests/hooks/test-overlay-verification.sh [test_security_overlay_flagged_missing],tests/hooks/test-verifier-baseline.sh [test_validate_ci_clean],tests/hooks/test-skip-markers.sh [test_skipped_marker_accepted]
420+
plugins/dso/scripts/apply-attribution-trailers.sh:tests/unit/scripts/test-apply-attribution-trailers.sh [test_resolve_scalar_trailers_produces_task_story_epic_flags,test_resolve_scalar_trailers_produces_review_score_flag,test_resolve_scalar_trailers_produces_no_flags_when_no_context] [test_script_exits_0_without_modifying_commit_msg_when_attribution_disabled] [test_script_exits_0_without_modifying_commit_msg_when_jsonl_absent] [test_script_exits_0_without_modifying_commit_msg_when_jsonl_empty] [test_injection_appends_dso_agent_trailer_to_commit_message] [test_two_distinct_agents_produce_two_dso_agent_lines] [test_injection_emits_trailer_skipped_and_exits_0_when_git_below_2_6] [test_placement_scan_emits_warning_when_dso_trailer_in_message_body] [test_truncate_clears_populated_jsonl_to_zero_bytes] [test_truncate_exits_0_when_jsonl_absent] [test_truncate_emits_warning_and_exits_0_on_truncation_failure] [test_skips_injection_when_trailers_already_present]
421+
plugins/dso/hooks/dispatchers/post-skill.sh:tests/hooks/test-post-skill-attribution.sh
416422
plugins/dso/hooks/post-commit-override-cleanup.sh:tests/hooks/test-post-commit-override-cleanup.sh [test_cleanup_removes_token],tests/scripts/test-reinstall-hooks.sh
417423
plugins/dso/scripts/commit-step.sh: tests/scripts/test-commit-step.sh [test_writes_result_artifact],tests/scripts/test-commit-step-worktree.sh [test_cwd_mismatch_fails_fast] [test_harvest_mode_bypasses_cwd_check],tests/scripts/test-commit-step-truncation.sh [test_pipe_to_tail_artifact_intact],tests/hooks/test-verifier-baseline.sh
418424
plugins/dso/hooks/pre-commit-compliance-verifier.sh: tests/hooks/test-pre-commit-compliance-verifier.sh [test_blocks_missing_artifact],tests/scripts/test-reinstall-hooks.sh [test_compliance_verifier_registered],tests/hooks/test-verifier-worktree-context.sh [test_verifier_reads_correct_worktree],tests/hooks/test-overlay-verification.sh [test_security_overlay_flagged_missing],tests/hooks/test-verifier-baseline.sh [test_validate_ci_clean],tests/hooks/test-skip-markers.sh [test_skipped_marker_accepted]

plugins/dso/docs/CONFIGURATION-REFERENCE.md

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,10 @@ Security, performance, and test-quality overlays are dispatched automatically wh
227227

228228
| | |
229229
|---|---|
230-
| **Description** | Overrides the DSO plugin version used in CI LLM review. This is **Tier 2** of the 3-tier version resolution chain implemented by `${CLAUDE_PLUGIN_ROOT}/scripts/resolve-dso-version.sh`: **Tier 1** is `~/.claude/plugins/installed_plugins.json` (the Claude per-project plugin tracking file; key `dso@digital-service-orchestra`) — overridable via the `PLUGIN_TRACKING_FILE` env var; **Tier 2** is this config key in `.claude/dso-config.conf` (project-level override without editing the workflow) — overridable via the `DSO_CONFIG_FILE` env var; **Tier 3** is the `dso` (stable) channel in `.claude-plugin/marketplace.json` — overridable via the `MARKETPLACE_JSON` env var. The resolver does **not** fall back to the `dso-dev` channel; if Tier 3 is reached and the stable `dso` channel is absent or malformed, the resolver exits non-zero. When all three tiers fail, the workflow exits with a diagnostic indicating which tier(s) failed. |
230+
| **Description** | Overrides the DSO plugin version used in CI LLM review. This is **Tier 2** of the 3-tier version resolution chain implemented by `${CLAUDE_PLUGIN_ROOT}/scripts/resolve-dso-version.sh`: **Tier 1** is `~/.claude/plugins/installed_plugins.json` (the Claude per-project plugin tracking file; key `dso@digital-service-orchestra`) — overridable via the `PLUGIN_TRACKING_FILE` env var; **Tier 2** is this config key in `.claude/dso-config.conf` (project-level override without editing the workflow) — overridable via the `DSO_CONFIG_FILE` env var; **Tier 3** is the `dso` (stable) channel in `.claude-plugin/marketplace.json` — overridable via the `MARKETPLACE_JSON` env var. The resolver does **not** fall back to the `dso-dev` channel; if Tier 3 is reached and the stable `dso` channel is absent or malformed, the resolver exits non-zero. When all three tiers fail, the workflow exits with a diagnostic indicating which tier(s) failed. | # shim-exempt: internal implementation references in config documentation
231231
| **Accepted values** | A version tag (e.g., `v1.13.0`) or a marketplace channel name resolvable in `marketplace.json` |
232232
| **Default** | Absent — Tier 1 (`installed_plugins.json`) or Tier 3 (`marketplace.json` `dso` channel) is used |
233-
| **Used by** | `${CLAUDE_PLUGIN_ROOT}/scripts/resolve-dso-version.sh` (called by `ci-dso-fetch.sh` in the generated host-project CI workflow) |
233+
| **Used by** | `${CLAUDE_PLUGIN_ROOT}/scripts/resolve-dso-version.sh` (called by `ci-dso-fetch.sh` in the generated host-project CI workflow) | # shim-exempt: internal implementation references in config documentation
234234

235235
---
236236

@@ -1137,7 +1137,7 @@ After each resolution of an AMBIGUITY or CONFLICT cross-epic signal, brainstorm
11371137

11381138
## Ruleset Provisioning Knobs
11391139

1140-
The following are CLI flags accepted by `${CLAUDE_PLUGIN_ROOT}/scripts/onboarding/provision-ruleset.sh`. They are NOT `dso-config.conf` keys — they are passed as command-line arguments when provisioning the GitHub Ruleset. Each flag has a default that matches the prior hardcoded behavior so existing invocations remain backward compatible.
1140+
The following are CLI flags accepted by `${CLAUDE_PLUGIN_ROOT}/scripts/onboarding/provision-ruleset.sh`. They are NOT `dso-config.conf` keys — they are passed as command-line arguments when provisioning the GitHub Ruleset. Each flag has a default that matches the prior hardcoded behavior so existing invocations remain backward compatible. # shim-exempt: internal implementation references in config documentation
11411141

11421142
### `--bypass-actor-policy`
11431143

@@ -1146,7 +1146,7 @@ The following are CLI flags accepted by `${CLAUDE_PLUGIN_ROOT}/scripts/onboardin
11461146
| **Description** | Bypass mode applied to the `bypass_actors` entry on the Ruleset. `always` allows admins to bypass at any time; `pull_request_only` restricts the bypass window to pull-request flows. Setting a non-default value requires an admin token (script exits 1 if `gh auth status` does not report admin scope). |
11471147
| **Accepted values** | `always` \| `pull_request_only` |
11481148
| **Default** | `always` |
1149-
| **Used by** | `${CLAUDE_PLUGIN_ROOT}/scripts/onboarding/provision-ruleset.sh` |
1149+
| **Used by** | `${CLAUDE_PLUGIN_ROOT}/scripts/onboarding/provision-ruleset.sh` | # shim-exempt: internal implementation references in config documentation
11501150

11511151
**Example**: `provision-ruleset.sh --bypass-actor-policy=pull_request_only`
11521152

@@ -1159,7 +1159,7 @@ The following are CLI flags accepted by `${CLAUDE_PLUGIN_ROOT}/scripts/onboardin
11591159
| **Description** | Maps to the `required_review_thread_resolution` parameter on the Ruleset's `pull_request` rule. When `true`, GitHub requires every PR review thread to be marked resolved before merge. |
11601160
| **Accepted values** | `true` \| `false` (also `1`/`0`, `yes`/`no`) |
11611161
| **Default** | `false` |
1162-
| **Used by** | `${CLAUDE_PLUGIN_ROOT}/scripts/onboarding/provision-ruleset.sh` |
1162+
| **Used by** | `${CLAUDE_PLUGIN_ROOT}/scripts/onboarding/provision-ruleset.sh` | # shim-exempt: internal implementation references in config documentation
11631163

11641164
**Example**: `provision-ruleset.sh --require-conversation-resolution=true`
11651165

@@ -1172,7 +1172,7 @@ The following are CLI flags accepted by `${CLAUDE_PLUGIN_ROOT}/scripts/onboardin
11721172
| **Description** | When `true`, the script records the preference in dry-run output and the success summary. GitHub does not currently expose Copilot code review configuration via the Rulesets API — the flag is a documented placeholder for future automation when API support lands. |
11731173
| **Accepted values** | `true` \| `false` |
11741174
| **Default** | `false` |
1175-
| **Used by** | `${CLAUDE_PLUGIN_ROOT}/scripts/onboarding/provision-ruleset.sh` |
1175+
| **Used by** | `${CLAUDE_PLUGIN_ROOT}/scripts/onboarding/provision-ruleset.sh` | # shim-exempt: internal implementation references in config documentation
11761176

11771177
**Example**: `provision-ruleset.sh --request-copilot-review=true`
11781178

@@ -1185,7 +1185,7 @@ The following are CLI flags accepted by `${CLAUDE_PLUGIN_ROOT}/scripts/onboardin
11851185
| **Description** | Maps to the `dismiss_stale_reviews_on_push` parameter on the Ruleset's `pull_request` rule. When `true`, GitHub dismisses prior approvals when new commits are pushed to the PR branch. |
11861186
| **Accepted values** | `true` \| `false` |
11871187
| **Default** | `false` |
1188-
| **Used by** | `${CLAUDE_PLUGIN_ROOT}/scripts/onboarding/provision-ruleset.sh` |
1188+
| **Used by** | `${CLAUDE_PLUGIN_ROOT}/scripts/onboarding/provision-ruleset.sh` | # shim-exempt: internal implementation references in config documentation
11891189

11901190
**Example**: `provision-ruleset.sh --dismiss-stale-approvals-on-push=true`
11911191

@@ -1198,7 +1198,7 @@ The following are CLI flags accepted by `${CLAUDE_PLUGIN_ROOT}/scripts/onboardin
11981198
| **Description** | Maps to the `required_approving_review_count` parameter on the Ruleset's `pull_request` rule. Sets the minimum number of approving reviews required before a PR can be merged. |
11991199
| **Accepted values** | Non-negative integer |
12001200
| **Default** | `1` |
1201-
| **Used by** | `${CLAUDE_PLUGIN_ROOT}/scripts/onboarding/provision-ruleset.sh` |
1201+
| **Used by** | `${CLAUDE_PLUGIN_ROOT}/scripts/onboarding/provision-ruleset.sh` | # shim-exempt: internal implementation references in config documentation
12021202

12031203
**Example**: `provision-ruleset.sh --required-approvals=2`
12041204

@@ -1719,6 +1719,18 @@ and [`model.deep`](#modeldeep) above.
17191719

17201720
---
17211721

1722+
## attribution
1723+
1724+
| Key | Type | Default | Description |
1725+
|-----|------|---------|-------------|
1726+
| `attribution.enabled` | boolean | `false` | When `true`, `/dso:commit` appends DSO-Agent, DSO-Skill, DSO-Model, DSO-Task, DSO-Story, DSO-Epic, Jira-Ticket, and DSO-Review-Score trailers to commit messages via `git interpret-trailers`. Requires git ≥ 2.6. |
1727+
1728+
**Notes:**
1729+
- Setting `attribution.enabled=false` after enabling stops future writes to `attribution-contributors.jsonl` but does not purge existing entries; the file can be safely deleted manually.
1730+
- Canonical query for attributed commits: `git log --format="%(trailers:key=DSO-Agent,valueonly)" -- <path>` returns agent names per commit.
1731+
1732+
---
1733+
17221734
## Section 2 — Environment Variables
17231735

17241736
These variables are consumed by DSO hooks, scripts, and skills at runtime. They supplement or override `dso-config.conf` values.

plugins/dso/docs/workflows/COMMIT-WORKFLOW.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,23 @@ Files are already staged from Step 5. The diff stat summary is already in contex
181181

182182
Create a single git commit following the repository's commit message conventions visible in the recent commits from Step 1.
183183

184+
### Attribution Pre-Commit (skip if attribution.enabled ≠ true)
185+
186+
**SKIP ENTIRELY when `attribution.enabled` is absent or not `true` in `dso-config.conf`.**
187+
188+
If `attribution.enabled=true`:
189+
- Run `.claude/scripts/dso apply-attribution-trailers.sh "$COMMIT_MSG_FILE"` with `ARTIFACTS_DIR` set to the session artifacts dir and `DSO_TASK_ID` exported in the environment (the script reads it from the environment, not as a positional argument)
190+
- If the script exits non-zero: emit a warning to stderr and continue — this step is **non-blocking**
191+
192+
```bash
193+
ATTRIBUTION_ENABLED=$(grep -m1 '^attribution\.enabled=' "$REPO_ROOT/.claude/dso-config.conf" 2>/dev/null | cut -d= -f2-)
194+
if [[ "${ATTRIBUTION_ENABLED:-}" == "true" ]]; then
195+
DSO_TASK_ID="${DSO_TASK_ID:-}" \
196+
.claude/scripts/dso apply-attribution-trailers.sh "$COMMIT_MSG_FILE" || \
197+
echo "WARNING: apply-attribution-trailers.sh failed (non-blocking)" >&2
198+
fi
199+
```
200+
184201
```bash
185202
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) step-6-commit" >> "$ARTIFACTS_DIR/commit-breadcrumbs.log"
186203
```
@@ -198,6 +215,21 @@ REPO_ROOT=$(git rev-parse --show-toplevel)
198215
".claude/scripts/dso" emit-commit-workflow-event.sh --phase=end --success=true
199216
```
200217

218+
### Attribution Post-Commit Truncate (skip if attribution.enabled ≠ true)
219+
220+
**SKIP ENTIRELY when `attribution.enabled` is absent or not `true` in `dso-config.conf`.**
221+
222+
If `attribution.enabled=true` and the commit exited 0:
223+
- Run `.claude/scripts/dso apply-attribution-trailers.sh --truncate` with `ARTIFACTS_DIR` set to the session artifacts dir
224+
- If truncation exits non-zero: emit a warning to stderr but do **not** fail
225+
226+
```bash
227+
if [[ "${ATTRIBUTION_ENABLED:-}" == "true" ]]; then
228+
.claude/scripts/dso apply-attribution-trailers.sh --truncate || \
229+
echo "WARNING: apply-attribution-trailers.sh --truncate failed (non-blocking)" >&2
230+
fi
231+
```
232+
201233
After committing, report the SHA and **immediately return control to the caller** — do NOT wait for user input. Resume the calling workflow at the step after this commit invocation. If you were executing `/dso:debug-everything`, continue at the step after this commit invocation (Phase F Step 5 for auto-fix commits, or Phase H Step 11 for post-batch commits). If you were executing `/dso:sprint`, continue at Phase F Step 17 (Commit & Push) or the step that invoked this workflow. Do NOT output any text that implies the session is complete.
202234

203235
<!-- Future work: Jira-Ticket and DSO-Task git trailers (story cab1-600f) are planned

plugins/dso/hooks/dispatchers/post-agent.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#
55
# Hook execution order:
66
# 1. hook_extract_agent_suggestion — extract first SUGGESTION: line and call suggestion-record.sh
7+
# 2. hook_record_agent_attribution — append agent/model entry to attribution-contributors.jsonl
78
#
89
# PostToolUse hooks always exit 0 (non-blocking).
910
# Always emits at least '{}' on stdout per Claude Code bug #10463 workaround.
@@ -54,6 +55,7 @@ _post_agent_dispatch() {
5455
INPUT=$(cat)
5556

5657
_run_post_fn hook_extract_agent_suggestion "$INPUT"
58+
_run_post_fn hook_record_agent_attribution "$INPUT"
5759
}
5860

5961
# Only execute dispatch logic when run as a script (not sourced).
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#!/usr/bin/env bash
2+
# hooks/dispatchers/post-skill.sh
3+
# PostToolUse Skill dispatcher: records skill attribution data.
4+
#
5+
# Hook execution order:
6+
# 1. hook_record_skill_attribution — append skill entry to attribution-contributors.jsonl
7+
#
8+
# PostToolUse hooks always exit 0 (non-blocking).
9+
# Always emits at least '{}' on stdout per Claude Code bug #10463 workaround.
10+
11+
# DEFENSE-IN-DEPTH: Guarantee exit 0 and non-empty stdout on any unexpected failure.
12+
_HOOK_HAS_OUTPUT=""
13+
trap 'if [[ -z "$_HOOK_HAS_OUTPUT" ]]; then printf "{}"; fi; exit 0' EXIT
14+
# Resolve dispatcher directory (CLAUDE_PLUGIN_ROOT if set, else relative)
15+
if [[ -z "${CLAUDE_PLUGIN_ROOT:-}" || ! -d "${CLAUDE_PLUGIN_ROOT:-}/hooks/lib" ]]; then
16+
CLAUDE_PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
17+
fi
18+
19+
HOOKS_LIB_DIR="$CLAUDE_PLUGIN_ROOT/hooks/lib"
20+
21+
# Source shared ERR handler (fail-open: if missing, keep original silent trap behavior)
22+
if [[ -f "${HOOKS_LIB_DIR}/hook-error-handler.sh" ]]; then
23+
# shellcheck source=/dev/null
24+
source "${HOOKS_LIB_DIR}/hook-error-handler.sh" 2>/dev/null || true
25+
_dso_register_hook_err_handler "post-skill.sh"
26+
else
27+
trap 'exit 0' ERR
28+
fi
29+
30+
# Source the dispatcher framework (provides run_hooks)
31+
source "$HOOKS_LIB_DIR/dispatcher.sh"
32+
33+
# Source all post hook functions (includes hook_record_skill_attribution)
34+
source "$HOOKS_LIB_DIR/post-functions.sh"
35+
36+
# Run all Skill post-hook functions sequentially.
37+
# PostToolUse hooks are non-blocking (always return 0).
38+
_run_post_fn() {
39+
local fn_name="$1"
40+
local json_input="$2"
41+
local _fn_out=""
42+
_fn_out=$("$fn_name" "$json_input") || true
43+
if [[ -n "$_fn_out" ]] && [[ "$_fn_out" != "{}" ]]; then
44+
_HOOK_HAS_OUTPUT=1
45+
printf '%s\n' "$_fn_out"
46+
fi
47+
}
48+
49+
_post_skill_dispatch() {
50+
# Read hook input from stdin
51+
local INPUT
52+
INPUT=$(cat)
53+
54+
_run_post_fn hook_record_skill_attribution "$INPUT"
55+
}
56+
57+
# Only execute dispatch logic when run as a script (not sourced).
58+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
59+
_post_skill_dispatch
60+
exit 0
61+
fi

0 commit comments

Comments
 (0)