repo-harness 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +1 -1
  3. package/README.md +62 -8
  4. package/README.zh-CN.md +34 -7
  5. package/SKILL.md +22 -0
  6. package/assets/hooks/lib/workflow-state.sh +55 -12
  7. package/assets/hooks/post-bash.sh +116 -2
  8. package/assets/hooks/prompt-guard.sh +451 -135
  9. package/assets/initializer-question-pack.v4.json +207 -1
  10. package/assets/initializer-question-pack.v4.schema.json +40 -3
  11. package/assets/partials/04-project-structure.partial.md +10 -0
  12. package/assets/partials/07-footer.partial.md +3 -3
  13. package/assets/partials/08-orchestration.partial.md +1 -1
  14. package/assets/partials-agents/04-task-protocol.partial.md +4 -4
  15. package/assets/partials-agents/06-quality-safety.partial.md +2 -2
  16. package/assets/plan-map.json +44 -0
  17. package/assets/project-structures/ai-native-product-copilot.txt +21 -0
  18. package/assets/project-structures/ai-native-runtime-console.txt +25 -0
  19. package/assets/project-structures/ai-native-sidecar-kernel.txt +19 -0
  20. package/assets/reference-configs/evaluator-rubric.md +1 -1
  21. package/assets/reference-configs/external-tooling.md +14 -0
  22. package/assets/reference-configs/harness-overview.md +7 -7
  23. package/assets/reference-configs/release-deploy.md +8 -0
  24. package/assets/reference-configs/sprint-contracts.md +2 -2
  25. package/assets/skill-commands/repo-harness-scaffold/SKILL.md +13 -3
  26. package/assets/templates/contract.template.md +8 -8
  27. package/assets/templates/helpers/archive-workflow.sh +5 -1
  28. package/assets/templates/helpers/capture-plan.sh +14 -13
  29. package/assets/templates/helpers/check-deploy-sql-order.sh +12 -0
  30. package/assets/templates/helpers/codex-handoff-resume.sh +40 -8
  31. package/assets/templates/helpers/contract-worktree.sh +25 -1
  32. package/assets/templates/helpers/ensure-task-workflow.sh +19 -19
  33. package/assets/templates/helpers/new-plan.sh +20 -12
  34. package/assets/templates/helpers/plan-to-todo.sh +49 -17
  35. package/assets/templates/helpers/refresh-current-status.sh +6 -2
  36. package/assets/templates/plan.template.md +11 -11
  37. package/assets/templates/tech-stack.template.md +14 -0
  38. package/docs/reference-configs/evaluator-rubric.md +1 -1
  39. package/docs/reference-configs/external-tooling.md +14 -0
  40. package/docs/reference-configs/harness-overview.md +7 -7
  41. package/docs/reference-configs/hook-operations.md +9 -0
  42. package/docs/reference-configs/release-deploy.md +8 -0
  43. package/docs/reference-configs/sprint-contracts.md +2 -2
  44. package/package.json +1 -1
  45. package/scripts/archive-workflow.sh +5 -1
  46. package/scripts/assemble-template.ts +140 -0
  47. package/scripts/capture-plan.sh +14 -13
  48. package/scripts/check-deploy-sql-order.sh +12 -0
  49. package/scripts/codex-handoff-resume.sh +40 -8
  50. package/scripts/contract-worktree.sh +25 -1
  51. package/scripts/ensure-task-workflow.sh +19 -19
  52. package/scripts/initializer-question-pack.ts +28 -0
  53. package/scripts/lib/project-init-lib.sh +19 -19
  54. package/scripts/new-plan.sh +20 -12
  55. package/scripts/plan-to-todo.sh +49 -17
  56. package/scripts/refresh-current-status.sh +6 -2
  57. package/src/cli/commands/prompt-guard-decision.ts +88 -0
  58. package/src/cli/commands/status.ts +1 -1
  59. package/src/cli/hook/prompt-guard-decision.ts +238 -0
  60. package/src/cli/hook-entry.ts +8 -1
  61. package/src/cli/index.ts +11 -1
package/AGENTS.md CHANGED
@@ -21,7 +21,7 @@ This repository self-hosts the `repo-harness` contract, formerly `repo-harness-s
21
21
  ## Operating Rules
22
22
 
23
23
  - Sync `tasks/` whenever substantive repo changes are made.
24
- - Use `tasks/notes/<slug>.notes.md` only for non-obvious slice decisions, deviations, tradeoffs, and open questions; do not use notes as durable memory or a task log, and archive/promote them deliberately when the slice closes.
24
+ - Use `tasks/notes/<plan-stem>.notes.md` only for non-obvious slice decisions, deviations, tradeoffs, and open questions; `<plan-stem>` is the active plan filename without `plan-` and `.md` (for example `20260531-0045-governance-workflow`). Do not use notes as durable memory or a task log, and archive/promote them deliberately when the slice closes.
25
25
  - Treat `.ai/hooks/` as the shared repo-local hook implementation; user-level `~/.claude/settings.json` and `~/.codex/hooks.json` are the host adapters.
26
26
  - Keep the umbrella hierarchy explicit: architecture owns stable truth, capability contracts own local agent context, `tasks/workstreams/<domain>/<capability>/` owns durable progress, and `tasks/todo.md` owns only deferred medium/long-term goals with tradeoff and revisit trigger.
27
27
  - Treat `.ai/context/capabilities.json` as the source of truth for capability prefixes; `agent-context-blocks.txt` and nested agent files are compatibility inputs only.
package/CLAUDE.md CHANGED
@@ -21,7 +21,7 @@ This repository self-hosts the `repo-harness` contract, formerly `repo-harness-s
21
21
  ## Operating Rules
22
22
 
23
23
  - Sync `tasks/` whenever substantive repo changes are made.
24
- - Use `tasks/notes/<slug>.notes.md` only for non-obvious slice decisions, deviations, tradeoffs, and open questions; do not use notes as durable memory or a task log, and archive/promote them deliberately when the slice closes.
24
+ - Use `tasks/notes/<plan-stem>.notes.md` only for non-obvious slice decisions, deviations, tradeoffs, and open questions; `<plan-stem>` is the active plan filename without `plan-` and `.md` (for example `20260531-0045-governance-workflow`). Do not use notes as durable memory or a task log, and archive/promote them deliberately when the slice closes.
25
25
  - Treat `.ai/hooks/` as the shared repo-local hook implementation; user-level `~/.claude/settings.json` and `~/.codex/hooks.json` are the host adapters.
26
26
  - Keep the umbrella hierarchy explicit: architecture owns stable truth, capability contracts own local agent context, `tasks/workstreams/<domain>/<capability>/` owns durable progress, and `tasks/todo.md` owns only deferred medium/long-term goals with tradeoff and revisit trigger.
27
27
  - Treat `.ai/context/capabilities.json` as the source of truth for capability prefixes; `agent-context-blocks.txt` and nested agent files are compatibility inputs only.
package/README.md CHANGED
@@ -45,6 +45,16 @@ The design has three layers:
45
45
  the current repo's `.ai/hooks/*` scripts only when
46
46
  `.ai/harness/workflow-contract.json` exists.
47
47
 
48
+ For `UserPromptSubmit`, the public adapter contract stays
49
+ `repo-harness-hook UserPromptSubmit --route default`. The CLI route registry
50
+ dispatches that route to `.ai/hooks/prompt-guard.sh`. The shell hook remains the
51
+ repo-local adapter for host JSON parsing, workflow file reads, capture side
52
+ effects, quality gate rendering, and host-safe stdout/stderr. The prompt intent
53
+ and workflow-state decision is handled by the TypeScript decision engine behind
54
+ `repo-harness-hook prompt-guard-decide`, which returns one action enum from an
55
+ explicit decision table. That split keeps host configuration stable while moving
56
+ the brittle classifier/state-machine layer out of shell conditionals.
57
+
48
58
  The core invariant is that durable truth lives in the repo, not in a chat
49
59
  thread. Hooks are accelerators and guardrails; the authority remains the
50
60
  file-backed plan, contract, review, checks, and handoff artifacts.
@@ -67,9 +77,9 @@ flowchart TD
67
77
 
68
78
  Approve --> Project["Project plan into execution<br/>capture-plan.sh --execute<br/>or plan-to-todo.sh --plan"]
69
79
  Project --> Active["Active markers<br/>.ai/harness/active-plan<br/>.ai/harness/active-worktree"]
70
- Project --> Contract["Sprint contract<br/>tasks/contracts/task-slug.contract.md"]
71
- Project --> ReviewFile["Review file<br/>tasks/reviews/task-slug.review.md"]
72
- Project --> Notes["Task notes<br/>tasks/notes/task-slug.notes.md"]
80
+ Project --> Contract["Sprint contract<br/>tasks/contracts/YYYYMMDD-HHMM-task-slug.contract.md"]
81
+ Project --> ReviewFile["Review file<br/>tasks/reviews/YYYYMMDD-HHMM-task-slug.review.md"]
82
+ Project --> Notes["Task notes<br/>tasks/notes/YYYYMMDD-HHMM-task-slug.notes.md"]
73
83
 
74
84
  Contract --> WorktreePolicy{"Contract worktree required?"}
75
85
  WorktreePolicy -->|yes| Checkout["Checkout isolated worktree<br/>contract-worktree.sh start --plan<br/>branch codex/task-slug"]
@@ -111,11 +121,12 @@ npx -y repo-harness init
111
121
  ```
112
122
 
113
123
  The npm package release line is `0.1.x`; generated workflow compatibility is
114
- tracked separately as the `5.x` model line. The `0.1.2` package publishes the
124
+ tracked separately as the `5.x` model line. The `0.1.4` package publishes the
115
125
  renamed `repo-harness` CLI, user-level Claude/Codex hook adapter bootstrap,
116
- Waza runtime skill sync, `diagram-design` sync, and the release gate used by
117
- maintainers before npm publish. When working from a source checkout instead of
118
- npm, run:
126
+ AI-native scaffold overlays, the typed prompt-guard decision engine, plan-stem
127
+ task artifact naming, Waza runtime skill sync, `diagram-design` sync, and the
128
+ release gate used by maintainers before npm publish. When working from a source
129
+ checkout instead of npm, run:
119
130
 
120
131
  ```bash
121
132
  git clone https://github.com/Ancienttwo/repo-harness.git ~/Projects/repo-harness
@@ -194,6 +205,23 @@ before applying anything.
194
205
  - Codex must mark `~/.codex/hooks.json` as trusted in Codex Settings before those hooks run.
195
206
  - Debug in this order: user-level adapter config -> `repo-harness-hook` (or fallback `repo-harness hook`) -> route registry -> `.ai/hooks/*`.
196
207
 
208
+ Prompt guard has one extra internal step:
209
+
210
+ ```mermaid
211
+ flowchart LR
212
+ Host["Claude/Codex UserPromptSubmit"] --> Adapter["user-level adapter"]
213
+ Adapter --> CLI["repo-harness-hook UserPromptSubmit --route default"]
214
+ CLI --> Route["route registry"]
215
+ Route --> Shell[".ai/hooks/prompt-guard.sh"]
216
+ Shell --> Decision["repo-harness-hook prompt-guard-decide<br/>TypeScript decision table"]
217
+ Decision --> Action["single action enum"]
218
+ Action --> Shell
219
+ Shell --> HostOutput["host-safe allow, advice, block, or done gate output"]
220
+ ```
221
+
222
+ The shell layer still owns filesystem authority and side effects. TypeScript owns
223
+ only the classifier plus `intent x plan state` decision table.
224
+
197
225
  ## Hook Failure Playbook
198
226
 
199
227
  When a hook blocks work, start with the structured output in the terminal. The core
@@ -222,7 +250,7 @@ Most common guards:
222
250
 
223
251
  ## Current Release
224
252
 
225
- - npm package: `repo-harness@0.1.2`
253
+ - npm package: `repo-harness@0.1.4`
226
254
  - Generated workflow compatibility: `5.2.3`
227
255
  - GitHub repository: `Ancienttwo/repo-harness`
228
256
  - Release history: [`docs/CHANGELOG.md`](docs/CHANGELOG.md)
@@ -270,6 +298,23 @@ Most common guards:
270
298
  - durable capability progress -> `tasks/workstreams/`
271
299
  - release history -> `docs/CHANGELOG.md`
272
300
 
301
+ ## Acknowledgements and Tooling Dependencies
302
+
303
+ `repo-harness` is built around a small set of external skills and repos that
304
+ proved useful while this project was being designed, debugged, and released.
305
+ They are acknowledged here because they shaped the workflow contract, but they
306
+ are not all bundled product dependencies.
307
+
308
+ | Tool or repo | Used for | Dependency shape |
309
+ | --- | --- | --- |
310
+ | gstack skills, including `document-release`, `office-hours`, `plan-eng-review`, and `plan-design-review` | Product discovery, plan review, design review, and post-ship documentation hygiene | External operator workflow; advisory by default |
311
+ | Waza skills, including `think`, `hunt`, `check`, `health`, `design`, `learn`, `read`, and `write` | Daily planning, bug hunts, verification, health checks, and Codex-first skill sync | Installed through the skills CLI into host skill roots |
312
+ | `diagram-design` | Human-readable architecture and system-flow diagrams when Mermaid is not enough | Runtime-referenced skill, not vendored into generated repos |
313
+ | `gbrain` | Knowledge sync, handoff retrieval, and long-form repo memory | Optional external CLI and index |
314
+ | CodeGraph (`@colbymchenry/codegraph`) | Symbol-aware navigation, impact tracing, and readiness checks for this self-host repo | Dev dependency in this repo; generated repos stay global-MCP-first unless policy opts in |
315
+ | Bun | Source checkout execution, tests, template assembly, and release checks | Required local runtime for maintainers |
316
+ | `commander` | `repo-harness` CLI command parsing | Runtime npm dependency |
317
+
273
318
  ## Action Command Skills
274
319
 
275
320
  Source-owned command skill facades live in `assets/skill-commands/`. They keep
@@ -284,6 +329,15 @@ and tests:
284
329
  project or module scaffold. `hooks-init`, `docs-init`, and `create-project-dirs`
285
330
  are internal steps, not public commands.
286
331
 
332
+ `repo-harness-scaffold` keeps the A-K plan catalog as the project-type authority
333
+ and adds AI-native app structure through an optional `ai_native_profile` overlay.
334
+ The default profile is `none`, so existing scaffold output remains unchanged.
335
+ When selected, profiles such as `runtime-console`, `product-copilot`, and
336
+ `sidecar-kernel` document the AG-UI event boundary, assistant-ui or CopilotKit
337
+ UI runtime, Bun/Hono gateway, shared contracts, observability, and MCP/HTTP
338
+ sidecar rules without installing model providers or making Python, Go, Rust, or
339
+ A2UI mandatory defaults.
340
+
287
341
  Use `repo-harness-capability` when the harness already exists and only selected
288
342
  capability boundaries should be added. It updates `.ai/context/capabilities.json`,
289
343
  syncs the requested local `AGENTS.md` / `CLAUDE.md` contract files, and validates
package/README.zh-CN.md CHANGED
@@ -40,6 +40,15 @@ repo-local hooks,然后验证这些 workflow surfaces 仍然一致。
40
40
  repo 是否存在 `.ai/harness/workflow-contract.json`;没有 opt in 就静默退出,有 opt in
41
41
  才进入当前仓库的 `.ai/hooks/*`。
42
42
 
43
+ 对 `UserPromptSubmit` 来说,公开 adapter contract 仍然是
44
+ `repo-harness-hook UserPromptSubmit --route default`。CLI route registry 会把这个
45
+ route dispatch 到 `.ai/hooks/prompt-guard.sh`。Shell hook 继续负责 host JSON 解析、
46
+ workflow 文件读取、plan capture 副作用、quality gate 渲染,以及 host-safe
47
+ stdout/stderr。Prompt intent 和 workflow state 的决策交给
48
+ `repo-harness-hook prompt-guard-decide` 背后的 TypeScript decision engine;它从显式
49
+ decision table 里返回一个 action enum。这样 host 配置不变,但最容易出错的
50
+ classifier/state-machine 层不再散落在 shell 条件分支里。
51
+
43
52
  核心不变量:持久事实在仓库里,不在聊天窗口里。Hooks 只是加速器和 guardrail;
44
53
  真正的 authority 是 plan、contract、review、checks 和 handoff 这些文件。
45
54
 
@@ -60,9 +69,9 @@ flowchart TD
60
69
 
61
70
  Approve --> Project["投射到执行面<br/>capture-plan.sh --execute<br/>或 plan-to-todo.sh --plan"]
62
71
  Project --> Active["Active markers<br/>.ai/harness/active-plan<br/>.ai/harness/active-worktree"]
63
- Project --> Contract["Sprint contract<br/>tasks/contracts/task-slug.contract.md"]
64
- Project --> ReviewFile["Review file<br/>tasks/reviews/task-slug.review.md"]
65
- Project --> Notes["Task notes<br/>tasks/notes/task-slug.notes.md"]
72
+ Project --> Contract["Sprint contract<br/>tasks/contracts/YYYYMMDD-HHMM-task-slug.contract.md"]
73
+ Project --> ReviewFile["Review file<br/>tasks/reviews/YYYYMMDD-HHMM-task-slug.review.md"]
74
+ Project --> Notes["Task notes<br/>tasks/notes/YYYYMMDD-HHMM-task-slug.notes.md"]
66
75
 
67
76
  Contract --> WorktreePolicy{"是否需要 contract worktree?"}
68
77
  WorktreePolicy -->|是| Checkout["Checkout 隔离 worktree<br/>contract-worktree.sh start --plan<br/>branch codex/task-slug"]
@@ -103,9 +112,10 @@ npx -y repo-harness init
103
112
  ```
104
113
 
105
114
  npm package release line 是 `0.1.x`;生成的 workflow compatibility model line
106
- 单独以 `5.x` 追踪。`repo-harness@0.1.2` 发布的是改名后的 CLI、Claude/Codex
107
- user-level hook adapter bootstrap、Waza runtime skill sync、`diagram-design` sync,
108
- 以及 maintainer 发布 npm 前使用的 release gate。
115
+ 单独以 `5.x` 追踪。`repo-harness@0.1.4` 发布的是改名后的 CLI、Claude/Codex
116
+ user-level hook adapter bootstrap、AI-native scaffold overlays、typed prompt-guard
117
+ decision engine、plan-stem task artifact 命名、Waza runtime skill sync、
118
+ `diagram-design` sync,以及 maintainer 发布 npm 前使用的 release gate。
109
119
 
110
120
  如果从源码 checkout 工作:
111
121
 
@@ -180,6 +190,23 @@ bun test
180
190
  - Codex 必须在 Settings 里信任 `~/.codex/hooks.json`,hooks 才会执行。
181
191
  - 调试顺序:user-level adapter config -> `repo-harness-hook` 或 fallback `repo-harness hook` -> route registry -> `.ai/hooks/*`。
182
192
 
193
+ Prompt guard 多一个内部步骤:
194
+
195
+ ```mermaid
196
+ flowchart LR
197
+ Host["Claude/Codex UserPromptSubmit"] --> Adapter["user-level adapter"]
198
+ Adapter --> CLI["repo-harness-hook UserPromptSubmit --route default"]
199
+ CLI --> Route["route registry"]
200
+ Route --> Shell[".ai/hooks/prompt-guard.sh"]
201
+ Shell --> Decision["repo-harness-hook prompt-guard-decide<br/>TypeScript decision table"]
202
+ Decision --> Action["single action enum"]
203
+ Action --> Shell
204
+ Shell --> HostOutput["host-safe allow, advice, block, or done gate output"]
205
+ ```
206
+
207
+ Shell 层仍然拥有文件系统 authority 和副作用。TypeScript 只拥有 classifier 加
208
+ `intent x plan state` decision table。
209
+
183
210
  ## Hook Failure Playbook
184
211
 
185
212
  hook block 工作时,先看 terminal 里的结构化输出。核心字段是
@@ -208,7 +235,7 @@ hook block 工作时,先看 terminal 里的结构化输出。核心字段是
208
235
 
209
236
  ## 当前 Release
210
237
 
211
- - npm package:`repo-harness@0.1.2`
238
+ - npm package:`repo-harness@0.1.4`
212
239
  - Generated workflow compatibility:`5.2.3`
213
240
  - GitHub repository:`Ancienttwo/repo-harness`
214
241
  - Release history:[`docs/CHANGELOG.md`](docs/CHANGELOG.md)
package/SKILL.md CHANGED
@@ -138,6 +138,28 @@ Custom Presets (G-K):
138
138
  - Plan J: AI coding agent / TUI
139
139
  - Plan K: Fully custom configuration
140
140
 
141
+ ## AI-native scaffold overlay
142
+
143
+ `repo-harness-scaffold` keeps A-K as the project-type catalog and uses
144
+ `ai_native_profile` as a separate overlay axis. The default profile is `none`,
145
+ so existing generated output stays on the selected A-K plan. Use an overlay only
146
+ when the generated app needs agent runtime, UI protocol, tool, sidecar, state,
147
+ or observability boundaries.
148
+
149
+ Supported profile IDs live in `assets/initializer-question-pack.v4.json`.
150
+ Generated structure overlays currently exist for:
151
+
152
+ - `runtime-console`: Vite 8 + assistant-ui, AG-UI event transport, Bun/Hono
153
+ agent gateway, run store, contracts, approvals, artifacts, and telemetry
154
+ - `product-copilot`: in-product copilot panel, AG-UI app-domain events, product
155
+ context loaders, business action tools, authorization and approval policies
156
+ - `sidecar-kernel`: Bun/Hono app gateway with Python, Go, or Rust kernels behind
157
+ MCP tools or narrow HTTP jobs
158
+
159
+ Do not turn the AI-native overlay into another lettered plan. Do not make A2UI,
160
+ Python, Go, Rust, Redis, ClickHouse, Temporal, object storage, vector DBs, model
161
+ providers, or tracing vendors mandatory defaults.
162
+
141
163
  ## Migration Rules
142
164
 
143
165
  For legacy repos, migrate old document surfaces before refreshing templates.
@@ -275,7 +275,7 @@ get_todo_source_plan() {
275
275
  awk -F': ' '/^\> \*\*Source Plan\*\*:/ {print $2; exit}' tasks/todo.md | xargs
276
276
  }
277
277
 
278
- derive_contract_path() {
278
+ workflow_plan_slug_from_path() {
279
279
  local plan_file="$1"
280
280
  local base slug
281
281
 
@@ -286,7 +286,43 @@ derive_contract_path() {
286
286
  return 1
287
287
  fi
288
288
 
289
- printf 'tasks/contracts/%s.contract.md' "$slug"
289
+ printf '%s' "$slug"
290
+ }
291
+
292
+ workflow_plan_artifact_stem_from_path() {
293
+ local plan_file="$1"
294
+ local base stem
295
+
296
+ base="$(basename "$plan_file")"
297
+ stem="$(printf '%s' "$base" | sed -E 's/^plan-//; s/\.md$//')"
298
+ if [[ "$stem" =~ ^[0-9]{8}-[0-9]{4}-.+ ]]; then
299
+ printf '%s' "$stem"
300
+ return 0
301
+ fi
302
+
303
+ workflow_plan_slug_from_path "$plan_file"
304
+ }
305
+
306
+ workflow_preferred_or_legacy_path() {
307
+ local preferred="$1"
308
+ local legacy="$2"
309
+
310
+ if [[ -f "$preferred" ]] || [[ ! -f "$legacy" ]]; then
311
+ printf '%s' "$preferred"
312
+ else
313
+ printf '%s' "$legacy"
314
+ fi
315
+ }
316
+
317
+ derive_contract_path() {
318
+ local plan_file="$1"
319
+ local stem slug
320
+
321
+ stem="$(workflow_plan_artifact_stem_from_path "$plan_file" || true)"
322
+ slug="$(workflow_plan_slug_from_path "$plan_file" || true)"
323
+ [[ -n "$stem" && -n "$slug" ]] || return 1
324
+
325
+ workflow_preferred_or_legacy_path "tasks/contracts/${stem}.contract.md" "tasks/contracts/${slug}.contract.md"
290
326
  }
291
327
 
292
328
  workflow_plan_slug() {
@@ -296,7 +332,7 @@ workflow_plan_slug() {
296
332
  return 1
297
333
  fi
298
334
 
299
- slug="$(basename "$active_plan" | sed -E 's/^plan-[0-9]{8}-[0-9]{4}-//; s/\.md$//')"
335
+ slug="$(workflow_plan_slug_from_path "$active_plan" || true)"
300
336
  if [[ -n "$slug" ]]; then
301
337
  printf '%s' "$slug"
302
338
  return 0
@@ -930,7 +966,7 @@ workflow_contract_slug() {
930
966
  local active_plan slug
931
967
  active_plan="$(get_active_plan || true)"
932
968
  [[ -n "$active_plan" ]] || return 1
933
- slug="$(basename "$active_plan" | sed -E 's/^plan-[0-9]{8}-[0-9]{4}-//; s/\.md$//')"
969
+ slug="$(workflow_plan_slug_from_path "$active_plan" || true)"
934
970
  [[ -n "$slug" ]] || return 1
935
971
  printf '%s' "$slug"
936
972
  }
@@ -945,18 +981,25 @@ workflow_active_contract() {
945
981
  }
946
982
 
947
983
  workflow_active_review() {
948
- local slug
949
- slug="$(workflow_contract_slug || true)"
950
- [[ -n "$slug" ]] || return 1
951
- printf 'tasks/reviews/%s.review.md' "$slug"
984
+ local active_plan stem slug reviews_dir
985
+ active_plan="$(get_active_plan || true)"
986
+ [[ -n "$active_plan" ]] || return 1
987
+ stem="$(workflow_plan_artifact_stem_from_path "$active_plan" || true)"
988
+ slug="$(workflow_plan_slug_from_path "$active_plan" || true)"
989
+ [[ -n "$stem" && -n "$slug" ]] || return 1
990
+ reviews_dir="$(workflow_repo_relative_path "$(workflow_policy_get '.tasks.reviews_dir' 'tasks/reviews')" 'tasks/reviews' 'tasks/')"
991
+ workflow_preferred_or_legacy_path "${reviews_dir}/${stem}.review.md" "${reviews_dir}/${slug}.review.md"
952
992
  }
953
993
 
954
994
  workflow_active_notes() {
955
- local slug notes_dir
956
- slug="$(workflow_contract_slug || true)"
957
- [[ -n "$slug" ]] || return 1
995
+ local active_plan stem slug notes_dir
996
+ active_plan="$(get_active_plan || true)"
997
+ [[ -n "$active_plan" ]] || return 1
998
+ stem="$(workflow_plan_artifact_stem_from_path "$active_plan" || true)"
999
+ slug="$(workflow_plan_slug_from_path "$active_plan" || true)"
1000
+ [[ -n "$stem" && -n "$slug" ]] || return 1
958
1001
  notes_dir="$(workflow_repo_relative_path "$(workflow_policy_get '.tasks.notes_dir' 'tasks/notes')" 'tasks/notes' 'tasks/')"
959
- printf '%s/%s.notes.md' "$notes_dir" "$slug"
1002
+ workflow_preferred_or_legacy_path "${notes_dir}/${stem}.notes.md" "${notes_dir}/${slug}.notes.md"
960
1003
  }
961
1004
 
962
1005
  workflow_checks_file() {
@@ -10,12 +10,44 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
10
  # shellcheck source=/dev/null
11
11
  . "$SCRIPT_DIR/lib/workflow-state.sh"
12
12
 
13
+ post_bash_set_tool_output_from_stdin() {
14
+ local parsed tmp
15
+
16
+ hook_read_stdin_once
17
+ [[ -n "${HOOK_STDIN_JSON:-}" ]] || return 1
18
+
19
+ if command -v jq >/dev/null 2>&1; then
20
+ if printf '%s' "$HOOK_STDIN_JSON" | jq -e 'has("tool_output") and .tool_output != null' >/dev/null 2>&1; then
21
+ IFS= read -r -d '' parsed < <(printf '%s' "$HOOK_STDIN_JSON" | jq -j '.tool_output' 2>/dev/null; printf '\0')
22
+ TOOL_OUTPUT="$parsed"
23
+ return 0
24
+ fi
25
+ fi
26
+
27
+ command -v bun >/dev/null 2>&1 || return 1
28
+ tmp="$(mktemp "${TMPDIR:-/tmp}/post-bash-tool-output.XXXXXX")" || return 1
29
+ if JSON_INPUT="$HOOK_STDIN_JSON" bun -e '
30
+ const raw = process.env.JSON_INPUT ?? "";
31
+ const value = JSON.parse(raw).tool_output;
32
+ if (value == null) process.exit(1);
33
+ if (typeof value === "object") process.stdout.write(JSON.stringify(value));
34
+ else process.stdout.write(String(value));
35
+ ' > "$tmp" 2>/dev/null; then
36
+ IFS= read -r -d '' parsed < <(cat "$tmp"; printf '\0')
37
+ TOOL_OUTPUT="$parsed"
38
+ rm -f "$tmp"
39
+ return 0
40
+ fi
41
+ rm -f "$tmp"
42
+ return 1
43
+ }
44
+
13
45
  TOOL_OUTPUT="${1:-${TOOL_OUTPUT:-}}"
14
46
  EXIT_CODE="${2:-${EXIT_CODE:-}}"
15
47
  COMMAND_TEXT="$(hook_json_get '.tool_input.command' '')"
16
48
 
17
49
  if [[ -z "$TOOL_OUTPUT" ]]; then
18
- TOOL_OUTPUT="$(hook_json_get '.tool_output' '')"
50
+ post_bash_set_tool_output_from_stdin || TOOL_OUTPUT="$(hook_json_get '.tool_output' '')"
19
51
  fi
20
52
  if [[ -z "$EXIT_CODE" ]]; then
21
53
  EXIT_CODE="$(hook_json_get '.exit_code' '')"
@@ -30,6 +62,35 @@ post_bash_output_line_count() {
30
62
  printf '%s' "$output" | awk 'END { print NR }'
31
63
  }
32
64
 
65
+ post_bash_output_byte_count() {
66
+ local output="$1"
67
+ printf '%s' "$output" | wc -c | tr -d '[:space:]'
68
+ }
69
+
70
+ post_bash_sha256() {
71
+ local output="$1"
72
+ if command -v shasum >/dev/null 2>&1; then
73
+ printf '%s' "$output" | shasum -a 256 | awk '{ print $1 }'
74
+ return
75
+ fi
76
+ if command -v sha256sum >/dev/null 2>&1; then
77
+ printf '%s' "$output" | sha256sum | awk '{ print $1 }'
78
+ return
79
+ fi
80
+ printf ''
81
+ }
82
+
83
+ post_bash_failure_signal() {
84
+ local output="$1"
85
+ [[ -n "$output" ]] || return 1
86
+ printf '%s\n' "$output" | grep -qEi "(^|[[:space:]])(FAIL|FAILED|failed)([[:space:]:,]|$)|Traceback|panic:|fatal:|error.*test"
87
+ }
88
+
89
+ post_bash_exit_failed() {
90
+ local exit_code="$1"
91
+ [[ -n "$exit_code" && "$exit_code" != "0" ]]
92
+ }
93
+
33
94
  post_bash_broad_command() {
34
95
  local command_text="$1"
35
96
  local trimmed
@@ -63,9 +124,45 @@ if post_bash_broad_command "$COMMAND_TEXT"; then
63
124
  recommended_next_tool="codegraph_context"
64
125
  fi
65
126
  output_line_count="$(post_bash_output_line_count "$TOOL_OUTPUT")"
127
+ raw_output_bytes="$(post_bash_output_byte_count "$TOOL_OUTPUT")"
128
+ failure_signal=false
129
+ if post_bash_failure_signal "$TOOL_OUTPUT"; then
130
+ failure_signal=true
131
+ fi
132
+ rtk_available=false
133
+ if command -v rtk >/dev/null 2>&1; then
134
+ rtk_available=true
135
+ fi
136
+
137
+ LONG_OUTPUT_LINES=200
138
+ LONG_OUTPUT_BYTES=32768
139
+ verbosity_class="inline"
140
+ suggested_runner="inline"
141
+ raw_output_path=""
142
+ raw_output_sha256=""
143
+
144
+ if post_bash_exit_failed "$EXIT_CODE"; then
145
+ verbosity_class="failure"
146
+ suggested_runner="raw"
147
+ elif (( output_line_count >= LONG_OUTPUT_LINES || raw_output_bytes >= LONG_OUTPUT_BYTES )); then
148
+ verbosity_class="long"
149
+ if [[ "$broad_command" == "true" && "$rtk_available" == "true" ]]; then
150
+ suggested_runner="rtk"
151
+ else
152
+ suggested_runner="raw"
153
+ fi
154
+ fi
155
+
156
+ if [[ "$verbosity_class" != "inline" ]]; then
157
+ output_dir="$(workflow_runs_dir)/bash-output"
158
+ mkdir -p "$output_dir"
159
+ raw_output_sha256="$(post_bash_sha256 "$TOOL_OUTPUT")"
160
+ raw_output_path="${output_dir}/post-bash-$(date '+%Y%m%dT%H%M%S')-$$-${raw_output_sha256:0:12}.log"
161
+ printf '%s' "$TOOL_OUTPUT" > "$raw_output_path"
162
+ fi
66
163
 
67
164
  if [[ "$EXIT_CODE" != "0" ]]; then
68
- if echo "$TOOL_OUTPUT" | grep -qEi "(FAIL|failed|error.*test)"; then
165
+ if [[ "$failure_signal" == "true" ]]; then
69
166
  echo "[PostBash] Tests failed. Reminder: failure = rewrite module, not patching."
70
167
  fi
71
168
  fi
@@ -78,6 +175,16 @@ if [[ -f "$checks_file" ]] && grep -Eq '"source"[[:space:]]*:[[:space:]]*"verify
78
175
  fi
79
176
 
80
177
  mkdir -p "$(dirname "$target_checks_file")"
178
+ if [[ -n "$raw_output_path" ]]; then
179
+ raw_output_path_json="\"$(hook_json_escape "$raw_output_path")\""
180
+ else
181
+ raw_output_path_json="null"
182
+ fi
183
+ if [[ -n "$raw_output_sha256" ]]; then
184
+ raw_output_sha256_json="\"$(hook_json_escape "$raw_output_sha256")\""
185
+ else
186
+ raw_output_sha256_json="null"
187
+ fi
81
188
  cat > "$target_checks_file" <<EOF_CHECKS
82
189
  {
83
190
  "source": "post-bash",
@@ -86,6 +193,13 @@ cat > "$target_checks_file" <<EOF_CHECKS
86
193
  "status": "$([[ "${EXIT_CODE:-0}" = "0" ]] && echo pass || echo fail)",
87
194
  "broad_command": ${broad_command},
88
195
  "output_line_count": ${output_line_count:-0},
196
+ "verbosity_class": "$(hook_json_escape "$verbosity_class")",
197
+ "suggested_runner": "$(hook_json_escape "$suggested_runner")",
198
+ "raw_output_path": ${raw_output_path_json},
199
+ "raw_output_bytes": ${raw_output_bytes:-0},
200
+ "raw_output_sha256": ${raw_output_sha256_json},
201
+ "failure_signal": ${failure_signal},
202
+ "rtk_available": ${rtk_available},
89
203
  "recommended_next_tool": "$(hook_json_escape "$recommended_next_tool")",
90
204
  "generated_at": "$(date '+%Y-%m-%dT%H:%M:%S%z')"
91
205
  }