@moretea-labs/forge 1.7.0 → 1.7.2

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 (175) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/THIRD_PARTY_NOTICES.md +1 -1
  5. package/agents/openai.yaml +1 -1
  6. package/assets/partials-agents/02-operating-mode.partial.md +2 -0
  7. package/assets/plugin-registry.v1.json +3 -3
  8. package/assets/skill-version.json +2 -2
  9. package/assets/templates/helpers/check-architecture-sync.sh +17 -0
  10. package/assets/templates/helpers/inspect-project-state.ts +8 -6
  11. package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
  12. package/assets/templates/helpers/workflow-contract.ts +5 -1
  13. package/docs/architecture/CURRENT.md +12 -4
  14. package/docs/forge-plugin-management.md +1 -1
  15. package/docs/operations/controller-performance-and-502.md +19 -0
  16. package/docs/operations/homebrew.md +2 -2
  17. package/package.json +5 -2
  18. package/scripts/architecture-event.ts +2 -820
  19. package/scripts/architecture-queue.sh +2 -645
  20. package/scripts/archive-architecture-request.sh +2 -242
  21. package/scripts/archive-workflow.sh +1 -291
  22. package/scripts/benchmark-work-efficiency.ts +0 -0
  23. package/scripts/capability-config.ts +2 -392
  24. package/scripts/capability-resolver.ts +2 -503
  25. package/scripts/capture-plan.sh +1 -332
  26. package/scripts/check-agent-tooling.sh +2 -1531
  27. package/scripts/check-architecture-sync.sh +2 -302
  28. package/scripts/check-brain-manifest.sh +2 -253
  29. package/scripts/check-context-files.sh +2 -47
  30. package/scripts/check-deploy-sql-order.sh +2 -115
  31. package/scripts/check-runtime-architecture.mjs +243 -16
  32. package/scripts/check-skill-version.ts +3 -191
  33. package/scripts/check-source-duplication.mjs +61 -0
  34. package/scripts/check-stable-baseline.ts +0 -0
  35. package/scripts/check-task-sync.sh +2 -63
  36. package/scripts/check-task-workflow.sh +2 -1000
  37. package/scripts/codex-handoff-resume.sh +2 -267
  38. package/scripts/context-contract-sync.sh +2 -514
  39. package/scripts/contract-run.ts +2 -463
  40. package/scripts/contract-worktree.sh +1 -702
  41. package/scripts/ensure-task-workflow.sh +1 -1192
  42. package/scripts/inspect-project-state.ts +4 -279
  43. package/scripts/lib/project-init-lib.sh +7 -2
  44. package/scripts/lib/run-forge-helper.sh +19 -0
  45. package/scripts/lib/run-forge-helper.ts +26 -0
  46. package/scripts/migrate-project-template.sh +1 -33
  47. package/scripts/migrate-workflow-docs.ts +4 -412
  48. package/scripts/new-plan.sh +1 -180
  49. package/scripts/new-spec.sh +1 -47
  50. package/scripts/new-sprint.sh +1 -46
  51. package/scripts/plan-to-todo.sh +1 -771
  52. package/scripts/prepare-codex-handoff.sh +2 -201
  53. package/scripts/prepare-handoff.sh +1 -78
  54. package/scripts/refresh-current-status.sh +2 -467
  55. package/scripts/run-governed-gate.ts +3 -0
  56. package/scripts/select-agent-context-blocks.sh +2 -64
  57. package/scripts/ship-worktrees.sh +1 -531
  58. package/scripts/sprint-backlog.sh +1 -834
  59. package/scripts/stage-runtime-release.ts +2 -1
  60. package/scripts/summarize-failures.sh +2 -123
  61. package/scripts/switch-plan.sh +1 -150
  62. package/scripts/sync-brain-docs.sh +2 -379
  63. package/scripts/sync-generated-authority.mjs +175 -0
  64. package/scripts/verify-contract.sh +2 -593
  65. package/scripts/verify-sprint.sh +1 -500
  66. package/scripts/workflow-contract.ts +4 -201
  67. package/scripts/workstream-sync.sh +2 -261
  68. package/src/cli/agent-jobs/executor-health.ts +1 -1
  69. package/src/cli/commands/plugin.ts +12 -3
  70. package/src/cli/controller/check-runner.ts +7 -2
  71. package/src/cli/controller/context-pack.ts +46 -21
  72. package/src/cli/github/contracts.ts +16 -0
  73. package/src/cli/github/github.ts +2 -16
  74. package/src/cli/github/session.ts +2 -1
  75. package/src/cli/local-bridge/facade-api.ts +37 -47
  76. package/src/cli/local-bridge/server.ts +2 -2
  77. package/src/cli/mcp/legacy-tool-service.ts +10 -33
  78. package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
  79. package/src/cli/mcp/repository-tools.ts +125 -101
  80. package/src/cli/mcp/server.ts +57 -13
  81. package/src/cli/mcp/tool-contract.ts +14 -0
  82. package/src/cli/mcp/toolset.ts +2 -2
  83. package/src/cli/mcp/transports/http.ts +23 -0
  84. package/src/cli/repositories/command-classifier.ts +2 -2
  85. package/src/cli/repositories/command-executor.ts +24 -6
  86. package/src/cli/repositories/repository-snapshot.ts +5 -2
  87. package/src/cli/repositories/structured-git.ts +57 -3
  88. package/src/cli/repository/inspector.ts +11 -3
  89. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  90. package/src/runtime/context/semantic-navigation-contract.ts +61 -0
  91. package/src/runtime/context/semantic-navigation.ts +9 -61
  92. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +69 -29
  93. package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
  94. package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
  95. package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
  96. package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
  97. package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
  98. package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
  99. package/src/runtime/control-plane/execution/work-handle-authority.ts +150 -0
  100. package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
  101. package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
  102. package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
  103. package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
  104. package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
  105. package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
  106. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
  107. package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
  108. package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
  109. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
  110. package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
  111. package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
  112. package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
  113. package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
  114. package/src/runtime/control-plane/facade/index.ts +2 -0
  115. package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
  116. package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
  117. package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
  118. package/src/runtime/control-plane/facade/types.ts +68 -5
  119. package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
  120. package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
  121. package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
  122. package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
  123. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
  124. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
  125. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
  126. package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
  127. package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
  128. package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
  129. package/src/runtime/control-plane/routing/route-policy.ts +18 -6
  130. package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
  131. package/src/runtime/evidence/process-check-execution.ts +18 -0
  132. package/src/runtime/execution/jobs/child-reference.ts +6 -2
  133. package/src/runtime/execution/process-runtime/check-result.ts +53 -18
  134. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
  135. package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
  136. package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
  137. package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
  138. package/src/runtime/execution/process-runtime/runtime.ts +10 -0
  139. package/src/runtime/execution/process-runtime/types.ts +2 -12
  140. package/src/runtime/execution/work-activity.ts +21 -0
  141. package/src/runtime/execution/workers/executor.ts +18 -13
  142. package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
  143. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
  144. package/src/runtime/gateway/mcp/router.ts +2 -2
  145. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
  146. package/src/runtime/gateway/mcp/runtime-tools.ts +815 -1015
  147. package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
  148. package/src/runtime/plugins/browser-adapter.ts +256 -1083
  149. package/src/runtime/plugins/browser-handoff-host.ts +2 -25
  150. package/src/runtime/plugins/browser-handoff.ts +18 -3
  151. package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
  152. package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
  153. package/src/runtime/plugins/browser-native-session-service.ts +222 -0
  154. package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
  155. package/src/runtime/plugins/browser-runtime.ts +10 -0
  156. package/src/runtime/plugins/browser-session-store.ts +121 -0
  157. package/src/runtime/plugins/browser-session-types.ts +98 -0
  158. package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
  159. package/src/runtime/plugins/external-adapter.ts +92 -58
  160. package/src/runtime/plugins/macos-capability-broker.ts +0 -8
  161. package/src/runtime/plugins/store.ts +66 -9
  162. package/src/runtime/plugins/types.ts +3 -1
  163. package/src/runtime/recovery/maintenance-executor.ts +61 -25
  164. package/src/runtime/root/package-connector-service.ts +46 -4
  165. package/src/runtime/root/package-runtime-release.ts +3 -2
  166. package/src/runtime/root/package-runtime-service.ts +48 -3
  167. package/src/runtime/root/release-manifest.ts +137 -16
  168. package/src/runtime/root/release-materialize.ts +109 -35
  169. package/src/runtime/root/types.ts +10 -0
  170. package/src/runtime/standalone-recovery/core.ts +425 -194
  171. package/src/runtime/standalone-recovery/entry.ts +1 -1
  172. package/src/runtime/standalone-recovery/installer.ts +1 -1
  173. package/src/runtime/workflow/schedules/engine.ts +113 -87
  174. package/src/runtime/workflow/schedules/settlement.ts +26 -23
  175. package/src/runtime/workflow/schedules/store.ts +79 -10
@@ -1,1002 +1,4 @@
1
1
  #!/bin/bash
2
2
  set -euo pipefail
3
-
4
- usage() {
5
- cat <<'USAGE_EOF'
6
- Usage: scripts/check-task-workflow.sh [--strict]
7
- USAGE_EOF
8
- }
9
-
10
- strict=0
11
-
12
- while [[ $# -gt 0 ]]; do
13
- case "$1" in
14
- --strict)
15
- strict=1
16
- shift
17
- ;;
18
- --help|-h)
19
- usage
20
- exit 0
21
- ;;
22
- *)
23
- echo "Unknown argument: $1" >&2
24
- usage
25
- exit 1
26
- ;;
27
- esac
28
- done
29
-
30
- issues=0
31
- bootstrap_issues=0
32
- WORKFLOW_CONTRACT_PATH=".ai/harness/workflow-contract.json"
33
- policy_file=".ai/harness/policy.json"
34
- json_runtime=""
35
-
36
- report_issue() {
37
- local message="$1"
38
- echo "[workflow] $message"
39
- issues=$((issues + 1))
40
- }
41
-
42
- report_bootstrap() {
43
- local message="$1"
44
- bootstrap_issues=$((bootstrap_issues + 1))
45
- echo "[workflow] Bootstrap/remediation required (ignored runtime; check is read-only): $message"
46
- echo "[workflow] Supported remediation: bash scripts/migrate-project-template.sh --repo . --dry-run (inspect), then run the repository bootstrap/migration command in the owning checkout."
47
- }
48
-
49
- resolve_json_runtime() {
50
- if command -v node >/dev/null 2>&1; then
51
- printf 'node'
52
- return 0
53
- fi
54
-
55
- if command -v bun >/dev/null 2>&1; then
56
- printf 'bun'
57
- return 0
58
- fi
59
-
60
- if command -v python3 >/dev/null 2>&1; then
61
- printf 'python3'
62
- return 0
63
- fi
64
-
65
- return 1
66
- }
67
-
68
- contract_query_lines() {
69
- local selector="$1"
70
- local runtime
71
-
72
- runtime="$(resolve_json_runtime || true)"
73
- if [[ -z "$runtime" || ! -f "$WORKFLOW_CONTRACT_PATH" ]]; then
74
- return 1
75
- fi
76
-
77
- case "$runtime" in
78
- python3)
79
- "$runtime" - "$WORKFLOW_CONTRACT_PATH" "$selector" <<'PY_EOF'
80
- import json
81
- import sys
82
-
83
- path, selector = sys.argv[1], sys.argv[2]
84
- value = json.load(open(path, "r", encoding="utf-8"))
85
- for part in selector.split("."):
86
- value = value.get(part) if isinstance(value, dict) else None
87
- if isinstance(value, list):
88
- for item in value:
89
- print(item)
90
- elif value is not None:
91
- print(value)
92
- PY_EOF
93
- ;;
94
- *)
95
- "$runtime" -e '
96
- const fs = require("fs");
97
- const [, filePath, selector] = process.argv;
98
- let value = JSON.parse(fs.readFileSync(filePath, "utf8"));
99
- for (const part of selector.split(".")) {
100
- value = value && typeof value === "object" ? value[part] : undefined;
101
- }
102
- if (Array.isArray(value)) {
103
- for (const item of value) {
104
- console.log(item);
105
- }
106
- } else if (value !== undefined && value !== null) {
107
- console.log(value);
108
- }
109
- ' "$WORKFLOW_CONTRACT_PATH" "$selector"
110
- ;;
111
- esac
112
- }
113
-
114
- ACTIVE_PLAN_MARKER=".ai/harness/active-plan"
115
- LEGACY_ACTIVE_PLAN_MARKER=".claude/.active-plan"
116
- ACTIVE_WORKTREE_MARKER=".ai/harness/active-worktree"
117
-
118
- read_active_plan_marker() {
119
- local marker_file="$1"
120
- local marker_plan
121
-
122
- if [[ -f "$marker_file" ]]; then
123
- marker_plan="$(cat "$marker_file" 2>/dev/null | xargs)"
124
- if [[ -n "$marker_plan" && -f "$marker_plan" ]]; then
125
- printf '%s' "$marker_plan"
126
- return 0
127
- fi
128
- fi
129
-
130
- return 1
131
- }
132
-
133
- get_active_plan() {
134
- read_active_plan_marker "$ACTIVE_PLAN_MARKER" \
135
- || read_active_plan_marker "$LEGACY_ACTIVE_PLAN_MARKER"
136
- }
137
-
138
- extract_status() {
139
- local file="$1"
140
- # Trim with sed, not xargs: xargs aborts on unbalanced quotes in user-edited
141
- # status text, which would kill the whole check under set -e.
142
- awk '/\*\*Status\*\*:/ {sub(/^.*\*\*Status\*\*: */, ""); gsub(/\r/, ""); print; exit}' "$file" \
143
- | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//'
144
- }
145
-
146
- plan_evidence_contract_error() {
147
- local file="$1"
148
- local section=""
149
- local missing=0
150
-
151
- section="$(awk '
152
- BEGIN { in_section = 0 }
153
- /^## Evidence Contract[[:space:]]*$/ { in_section = 1; next }
154
- in_section && /^## / { exit }
155
- in_section { print }
156
- ' "$file")"
157
-
158
- if [[ -z "$(printf '%s' "$section" | tr -d '[:space:]')" ]]; then
159
- echo "missing ## Evidence Contract section"
160
- return 1
161
- fi
162
-
163
- local label line value
164
- for label in "State/progress path" "Verification evidence" "Impact review|Evaluator rubric" "Stop condition" "Rollback surface"; do
165
- line="$(printf '%s\n' "$section" | grep -Ei "^[[:space:]]*-[[:space:]]*(\\*\\*)?${label}(\\*\\*)?[[:space:]]*:" | head -1 || true)"
166
- if [[ -z "$line" ]]; then
167
- echo "missing field: ${label}"
168
- missing=1
169
- continue
170
- fi
171
-
172
- value="${line#*:}"
173
- value="$(printf '%s' "$value" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
174
- if [[ -z "$value" ]] || printf '%s' "$value" | grep -Eiq '^(tbd|todo|n/a|none|unknown|\.\.\.)$'; then
175
- echo "field has no concrete value: ${label}"
176
- missing=1
177
- fi
178
- done
179
-
180
- [[ "$missing" -eq 0 ]]
181
- }
182
-
183
- check_plan_template_evidence_contract() {
184
- local file="$1"
185
- local label
186
-
187
- grep -Eq '^## Evidence Contract[[:space:]]*$' "$file" || {
188
- report_issue "Plan template is missing ## Evidence Contract: $file"
189
- return
190
- }
191
-
192
- for label in "State/progress path" "Verification evidence" "Impact review" "Stop condition" "Rollback surface"; do
193
- if ! grep -Eiq "^[[:space:]]*-[[:space:]]*(\\*\\*)?${label}(\\*\\*)?[[:space:]]*:" "$file"; then
194
- report_issue "Plan template Evidence Contract is missing field '${label}': $file"
195
- fi
196
- done
197
- }
198
-
199
- todo_source_plan() {
200
- if [[ ! -f "${todo_file:-tasks/todos.md}" ]]; then
201
- return 1
202
- fi
203
- awk -F': ' '/^\> \*\*Source Plan\*\*:/ {print $2; exit}' "${todo_file:-tasks/todos.md}" | xargs
204
- }
205
-
206
- todo_is_deferred_ledger() {
207
- local file="${1:-${todo_file:-tasks/todos.md}}"
208
- [[ -f "$file" ]] || return 1
209
- grep -Eq '^# Deferred Goal Ledger[[:space:]]*$' "$file" \
210
- && grep -Eq '^> \*\*Status\*\*:[[:space:]]*Backlog[[:space:]]*$' "$file"
211
- }
212
-
213
- todo_deferred_ledger_error() {
214
- local file="${1:-${todo_file:-tasks/todos.md}}"
215
- local missing=0
216
-
217
- grep -Eq '^# Deferred Goal Ledger[[:space:]]*$' "$file" || {
218
- echo "missing '# Deferred Goal Ledger' heading"
219
- missing=1
220
- }
221
- grep -Eq '^> \*\*Status\*\*:[[:space:]]*Backlog[[:space:]]*$' "$file" || {
222
- echo "missing Backlog status"
223
- missing=1
224
- }
225
- grep -Eq '^## Deferred Goals[[:space:]]*$' "$file" || {
226
- echo "missing ## Deferred Goals section"
227
- missing=1
228
- }
229
- grep -Eq '\|[[:space:]]*Goal[[:space:]]*\|[[:space:]]*Why Deferred[[:space:]]*\|[[:space:]]*Tradeoff[[:space:]]*\|[[:space:]]*Revisit Trigger[[:space:]]*\|' "$file" || {
230
- echo "missing deferred-goal table with Tradeoff and Revisit Trigger"
231
- missing=1
232
- }
233
-
234
- [[ "$missing" -eq 0 ]]
235
- }
236
-
237
- sprint_known_status() {
238
- case "$1" in
239
- Draft|Approved|Executing|Done|Archived)
240
- return 0
241
- ;;
242
- esac
243
- return 1
244
- }
245
-
246
- prd_known_status() {
247
- case "$1" in
248
- Draft|Approved|Superseded)
249
- return 0
250
- ;;
251
- esac
252
- return 1
253
- }
254
-
255
- markdown_section_has_content() {
256
- local file="$1"
257
- local section="$2"
258
- awk -v section="$section" '
259
- $0 ~ "^## " section "[[:space:]]*$" { in_section = 1; next }
260
- in_section && /^## / { exit }
261
- !in_section { next }
262
- /^#/ { next }
263
- {
264
- line = $0
265
- gsub(/^[[:space:]]*[->]*[[:space:]]*/, "", line)
266
- gsub(/[[:space:]]+$/, "", line)
267
- if (line == "" || line == "..." || line ~ /^Replace /) next
268
- content = 1
269
- }
270
- END { exit content ? 0 : 1 }
271
- ' "$file"
272
- }
273
-
274
- sprint_prd_has_content() {
275
- local file="$1"
276
- markdown_section_has_content "$file" "PRD"
277
- }
278
-
279
- sprint_ready_error() {
280
- local file="$1"
281
- local missing=0
282
-
283
- if ! sprint_prd_has_content "$file"; then
284
- echo "PRD section is empty or placeholder-only"
285
- missing=1
286
- fi
287
-
288
- if ! grep -Eq '^\|[[:space:]]*#[[:space:]]*\|[[:space:]]*Status[[:space:]]*\|[[:space:]]*Task[[:space:]]*\|[[:space:]]*Mode[[:space:]]*\|[[:space:]]*Acceptance[[:space:]]*\|[[:space:]]*Plan[[:space:]]*\|' "$file"; then
289
- echo "missing backlog table header '| # | Status | Task | Mode | Acceptance | Plan |'"
290
- missing=1
291
- else
292
- local row_errors
293
- row_errors="$(awk -F '|' '
294
- /^## Backlog[[:space:]]*$/ { in_section = 1; next }
295
- in_section && /^## / { exit }
296
- !in_section { next }
297
- /^\|[[:space:]]*[0-9]+[[:space:]]*\|/ {
298
- rows++
299
- idx = $2; status = $3; task = $4; mode = $5; acceptance = $6
300
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", idx)
301
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", status)
302
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", task)
303
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", mode)
304
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", acceptance)
305
- if (status !~ /^\[[ xX]\]$/) printf "row %s has an invalid status cell (expected [ ] or [x])\n", idx
306
- if (task == "" || task == "...") printf "row %s is missing a task\n", idx
307
- if (mode != "contract" && mode != "inline") printf "row %s has an invalid mode (expected contract or inline)\n", idx
308
- if (acceptance == "" || tolower(acceptance) ~ /^(\.\.\.|tbd|todo|n\/a|none)$/) printf "row %s is missing a concrete acceptance line\n", idx
309
- if (acceptance == "Replace with a machine-checkable acceptance line") printf "row %s still has the template placeholder acceptance\n", idx
310
- seen_idx[idx]++
311
- if (task != "") seen_task[task]++
312
- }
313
- END {
314
- if (rows == 0) print "backlog table has no task rows"
315
- for (i in seen_idx) if (seen_idx[i] > 1) printf "duplicate backlog index %s\n", i
316
- for (t in seen_task) if (seen_task[t] > 1) printf "duplicate backlog task %s\n", t
317
- }
318
- ' "$file")"
319
- if [[ -n "$row_errors" ]]; then
320
- printf '%s\n' "$row_errors"
321
- missing=1
322
- fi
323
- fi
324
-
325
- [[ "$missing" -eq 0 ]]
326
- }
327
-
328
- prd_ready_error() {
329
- local file="$1"
330
- local missing=0
331
-
332
- for section in "AI Quick-Read Card" "Problem" "Acceptance Scenarios"; do
333
- if ! markdown_section_has_content "$file" "$section"; then
334
- echo "PRD section '$section' is missing or placeholder-only"
335
- missing=1
336
- fi
337
- done
338
-
339
- [[ "$missing" -eq 0 ]]
340
- }
341
-
342
- derive_slug() {
343
- basename "$1" | sed -E 's/^plan-[0-9]{8}-[0-9]{4}-//; s/\.md$//'
344
- }
345
-
346
- plan_contract_path() {
347
- local plan_file="$1" path
348
- path="$(awk '
349
- /^> \*\*Task Contract\*\*:/ {
350
- sub(/^> \*\*Task Contract\*\*:[[:space:]]*/, "")
351
- gsub(/`/, "")
352
- print
353
- exit
354
- }
355
- /^> \*\*Sprint Contract\*\*:/ {
356
- sub(/^> \*\*Sprint Contract\*\*:[[:space:]]*/, "")
357
- gsub(/`/, "")
358
- print
359
- exit
360
- }
361
- ' "$plan_file" | xargs)"
362
-
363
- case "$path" in
364
- tasks/contracts/*.contract.md)
365
- printf '%s' "$path"
366
- ;;
367
- esac
368
- }
369
-
370
- derive_contract_path() {
371
- local plan_file="$1"
372
- local explicit slug stem
373
- explicit="$(plan_contract_path "$plan_file")"
374
- if [[ -n "$explicit" ]]; then
375
- printf '%s' "$explicit"
376
- return 0
377
- fi
378
-
379
- slug="$(derive_slug "$plan_file")"
380
- stem="$(basename "$plan_file" | sed -E 's/^plan-//; s/\.md$//')"
381
- if [[ -f "tasks/contracts/${stem}.contract.md" ]] || [[ ! -f "tasks/contracts/${slug}.contract.md" ]]; then
382
- printf 'tasks/contracts/%s.contract.md' "$stem"
383
- else
384
- printf 'tasks/contracts/%s.contract.md' "$slug"
385
- fi
386
- }
387
-
388
- file_mtime() {
389
- local file="$1"
390
- local mtime
391
-
392
- mtime="$(stat -c '%Y' "$file" 2>/dev/null || true)"
393
- if [[ "$mtime" =~ ^[0-9]+$ ]]; then
394
- printf '%s' "$mtime"
395
- return 0
396
- fi
397
-
398
- mtime="$(stat -f '%m' "$file" 2>/dev/null || true)"
399
- if [[ "$mtime" =~ ^[0-9]+$ ]]; then
400
- printf '%s' "$mtime"
401
- return 0
402
- fi
403
-
404
- printf '0'
405
- }
406
-
407
- handoff_declares_no_active_plan() {
408
- local file="$1"
409
- [[ -f "$file" ]] || return 1
410
- grep -Eiq '(^|[^[:alnum:]])No active plan([^[:alnum:]]|$)|^[[:space:]]*(-[[:space:]]*)?(Active Plan|Plan):[[:space:]]*\(none\)[[:space:]]*$' "$file"
411
- }
412
-
413
- resume_references_plan() {
414
- local file="$1"
415
- [[ -f "$file" ]] || return 1
416
- grep -Eiq '^[[:space:]]*-[[:space:]]*(Active plan|Plan):[[:space:]]+`?plans/' "$file"
417
- }
418
-
419
- check_handoff_resume_pair() {
420
- local handoff_file="$1"
421
- local resume_file="$2"
422
- local handoff_mtime resume_mtime
423
-
424
- [[ -f "$handoff_file" || -f "$resume_file" ]] || return 0
425
-
426
- if [[ -f "$handoff_file" && ! -f "$resume_file" ]]; then
427
- report_bootstrap "Handoff current exists but resume packet is missing: $resume_file"
428
- return 0
429
- fi
430
-
431
- if [[ ! -f "$handoff_file" && -f "$resume_file" ]]; then
432
- report_bootstrap "Resume packet exists but handoff current is missing: $handoff_file"
433
- return 0
434
- fi
435
-
436
- handoff_mtime="$(file_mtime "$handoff_file")"
437
- resume_mtime="$(file_mtime "$resume_file")"
438
- if [[ "$resume_mtime" =~ ^[0-9]+$ && "$handoff_mtime" =~ ^[0-9]+$ && "$resume_mtime" -lt "$handoff_mtime" ]]; then
439
- report_bootstrap "Resume packet is older than handoff current: $resume_file < $handoff_file. Refresh it with scripts/prepare-handoff.sh or scripts/codex-handoff-resume.sh."
440
- fi
441
-
442
- if handoff_declares_no_active_plan "$handoff_file" && resume_references_plan "$resume_file"; then
443
- report_bootstrap "Handoff current declares no active plan but resume packet references a historical plan: $resume_file. Archive or regenerate the resume packet before continuing."
444
- fi
445
- }
446
-
447
- check_current_resume_freshness() {
448
- local current_file="$1"
449
- local resume_file="$2"
450
- local current_mtime resume_mtime
451
-
452
- [[ -f "$current_file" && -f "$resume_file" ]] || return 0
453
- current_mtime="$(file_mtime "$current_file")"
454
- resume_mtime="$(file_mtime "$resume_file")"
455
- if [[ "$current_mtime" =~ ^[0-9]+$ && "$resume_mtime" =~ ^[0-9]+$ && "$resume_mtime" -lt "$current_mtime" ]]; then
456
- report_bootstrap "Resume packet is older than current status snapshot: $resume_file < $current_file. Run scripts/prepare-handoff.sh --reason <reason> or scripts/codex-handoff-resume.sh."
457
- fi
458
- }
459
-
460
- check_required_file() {
461
- local path="$1"
462
- if [[ -f "$path" ]]; then
463
- return 0
464
- fi
465
-
466
- if [[ "$path" == .ai/harness/scripts/* ]]; then
467
- local helper_name="${path##*/}"
468
- if [[ "${helper_source:-package}" == "package" && -f "scripts/$helper_name" ]]; then
469
- return 0
470
- fi
471
- if [[ -f "assets/templates/helpers/$helper_name" && -f "scripts/$helper_name" ]]; then
472
- return 0
473
- fi
474
- fi
475
-
476
- case "$path" in
477
- .ai/context/*|.ai/harness/*)
478
- report_bootstrap "Missing generated runtime file: $path"
479
- ;;
480
- *)
481
- report_issue "Missing required file: $path"
482
- ;;
483
- esac
484
- }
485
-
486
- check_reference_config_stub() {
487
- local path="$1"
488
- [[ -f "$path" ]] || return 0
489
- grep -Fq "<!-- forge: reference-config-stub v1 -->" "$path" || return 0
490
-
491
- local name="${path##*/}"
492
- local doc_id="${name%.md}"
493
- if ! grep -Fq "> **Doc ID**: $doc_id" "$path"; then
494
- report_issue "Reference config stub has wrong or missing Doc ID: $path"
495
- fi
496
- if ! grep -Fq "forge docs path $doc_id" "$path"; then
497
- report_issue "Reference config stub is missing resolver command: $path"
498
- fi
499
- }
500
-
501
- check_reference_config_stubs() {
502
- local path
503
- if [[ ! -d "docs/reference-configs" ]]; then
504
- return 0
505
- fi
506
- for path in docs/reference-configs/*.md; do
507
- [[ -e "$path" ]] || continue
508
- check_reference_config_stub "$path"
509
- done
510
- }
511
-
512
- check_generation_surface_terminology() {
513
- local file
514
- local surfaces=(
515
- ".claude/templates/plan.template.md"
516
- ".claude/templates/contract.template.md"
517
- ".claude/templates/review.template.md"
518
- "scripts/capture-plan.sh"
519
- "scripts/new-plan.sh"
520
- "scripts/ensure-task-workflow.sh"
521
- "scripts/plan-to-todo.sh"
522
- "scripts/lib/project-init-lib.sh"
523
- )
524
-
525
- for file in "${surfaces[@]}"; do
526
- [[ -f "$file" ]] || continue
527
- if grep -Eq 'Sprint Contract|Sprint Review' "$file"; then
528
- report_issue "Legacy task artifact terminology in generation surface: $file. Use Task Contract / Task Review for new artifacts; keep verify-sprint.sh and other legacy filenames only for compatibility."
529
- fi
530
- if grep -Eq 'tasks/todo\.md|tasks/sprints/' "$file"; then
531
- report_issue "Legacy workflow path emitted by generation surface: $file. Migrate tasks/todo.md to tasks/todos.md and tasks/sprints/*.sprint.md to plans/sprints/."
532
- fi
533
- done
534
- }
535
-
536
- trace_schema_error() {
537
- local file="$1"
538
- local runtime
539
-
540
- [[ -s "$file" ]] || return 0
541
- if ! grep -q '[^[:space:]]' "$file"; then
542
- return 0
543
- fi
544
- if grep -Eq '^[[:space:]]*\{[[:space:]]*\}[[:space:]]*$' "$file"; then
545
- return 0
546
- fi
547
-
548
- runtime="$(resolve_json_runtime || true)"
549
- if [[ -z "$runtime" ]]; then
550
- echo "missing node, bun, or python3 to validate trace schema"
551
- return 1
552
- fi
553
-
554
- case "$runtime" in
555
- python3)
556
- "$runtime" - "$file" <<'PY_EOF'
557
- import json
558
- import sys
559
-
560
- path = sys.argv[1]
561
- try:
562
- data = json.load(open(path, "r", encoding="utf-8"))
563
- except Exception as exc:
564
- print(f"invalid JSON: {exc}")
565
- sys.exit(1)
566
-
567
- errors = []
568
- if data.get("schema") != "forge-run-trace.v1":
569
- errors.append("schema must be forge-run-trace.v1")
570
- for key in ["run_id", "task_profile", "active_plan", "worktree", "branch", "failure_class", "next_step"]:
571
- if key not in data:
572
- errors.append(f"missing field: {key}")
573
- for key in ["commands", "guards", "handoffs", "files_changed"]:
574
- if not isinstance(data.get(key), list):
575
- errors.append(f"field must be an array: {key}")
576
- for key in ["external_acceptance", "allowed_paths_check"]:
577
- if not isinstance(data.get(key), dict):
578
- errors.append(f"field must be an object: {key}")
579
- if data.get("status") not in ["pass", "fail"]:
580
- errors.append("status must be pass or fail")
581
- contract = data.get("contract")
582
- if not (isinstance(contract, str) or (isinstance(contract, dict) and contract.get("file"))):
583
- errors.append("contract must be a string or object with file")
584
- review = data.get("review")
585
- if not (isinstance(review, str) or (isinstance(review, dict) and "file" in review)):
586
- errors.append("review must be a string or object with file")
587
- if errors:
588
- print("; ".join(errors))
589
- sys.exit(1)
590
- PY_EOF
591
- ;;
592
- *)
593
- "$runtime" -e '
594
- const fs = require("fs");
595
- const file = process.argv[1];
596
- let data;
597
- try {
598
- data = JSON.parse(fs.readFileSync(file, "utf8"));
599
- } catch (error) {
600
- console.log(`invalid JSON: ${error.message}`);
601
- process.exit(1);
602
- }
603
- const errors = [];
604
- if (data.schema !== "forge-run-trace.v1") errors.push("schema must be forge-run-trace.v1");
605
- for (const key of ["run_id", "task_profile", "active_plan", "worktree", "branch", "failure_class", "next_step"]) {
606
- if (!(key in data)) errors.push(`missing field: ${key}`);
607
- }
608
- for (const key of ["commands", "guards", "handoffs", "files_changed"]) {
609
- if (!Array.isArray(data[key])) errors.push(`field must be an array: ${key}`);
610
- }
611
- for (const key of ["external_acceptance", "allowed_paths_check"]) {
612
- if (!data[key] || typeof data[key] !== "object" || Array.isArray(data[key])) {
613
- errors.push(`field must be an object: ${key}`);
614
- }
615
- }
616
- if (!["pass", "fail"].includes(data.status)) errors.push("status must be pass or fail");
617
- if (!(typeof data.contract === "string" || (data.contract && typeof data.contract === "object" && data.contract.file))) {
618
- errors.push("contract must be a string or object with file");
619
- }
620
- if (!(typeof data.review === "string" || (data.review && typeof data.review === "object" && "file" in data.review))) {
621
- errors.push("review must be a string or object with file");
622
- }
623
- if (errors.length) {
624
- console.log(errors.join("; "));
625
- process.exit(1);
626
- }
627
- ' "$file"
628
- ;;
629
- esac
630
- }
631
-
632
- check_required_dir() {
633
- local path="$1"
634
- if [[ -d "$path" ]]; then
635
- return 0
636
- fi
637
-
638
- if [[ "$path" == ".ai/harness/scripts" && "${helper_source:-package}" == "package" && -d "scripts" ]]; then
639
- return 0
640
- fi
641
-
642
- if [[ "$path" == ".ai/harness/scripts" && -d "assets/templates/helpers" && -d "scripts" ]]; then
643
- return 0
644
- fi
645
-
646
- case "$path" in
647
- .ai/context|.ai/context/*|.ai/harness|.ai/harness/*)
648
- report_bootstrap "Missing generated runtime directory: $path"
649
- ;;
650
- *)
651
- report_issue "Missing required directory: $path"
652
- ;;
653
- esac
654
- }
655
-
656
- check_helper_runtime_files() {
657
- local helper_names=()
658
- local helper_name
659
-
660
- if [[ -f "$WORKFLOW_CONTRACT_PATH" ]]; then
661
- while IFS= read -r helper_name; do
662
- [[ -n "$helper_name" ]] && helper_names+=("$helper_name")
663
- done < <(contract_query_lines "helpers.scripts" 2>/dev/null || true)
664
- fi
665
-
666
- if [[ "${#helper_names[@]}" -eq 0 ]]; then
667
- helper_names=(
668
- new-spec.sh new-sprint.sh new-plan.sh capture-plan.sh plan-to-todo.sh
669
- contract-run.ts contract-worktree.sh ship-worktrees.sh archive-workflow.sh
670
- refresh-current-status.sh prepare-handoff.sh verify-contract.sh summarize-failures.sh
671
- verify-sprint.sh sprint-backlog.sh check-task-sync.sh check-deploy-sql-order.sh
672
- check-architecture-sync.sh check-agent-tooling.sh check-context-files.sh
673
- check-brain-manifest.sh sync-brain-docs.sh check-skill-version.ts
674
- select-agent-context-blocks.sh ensure-task-workflow.sh check-task-workflow.sh
675
- switch-plan.sh workflow-contract.ts
676
- inspect-project-state.ts migrate-workflow-docs.ts migrate-project-template.sh
677
- capability-resolver.ts architecture-event.ts capability-config.ts architecture-queue.sh
678
- archive-architecture-request.sh context-contract-sync.sh workstream-sync.sh
679
- prepare-codex-handoff.sh codex-handoff-resume.sh
680
- )
681
- fi
682
-
683
- check_required_dir "$helper_compat_dir"
684
- if [[ "${helper_source:-package}" != "package" ]]; then
685
- check_required_dir "$helper_runtime_dir"
686
- fi
687
-
688
- for helper_name in "${helper_names[@]}"; do
689
- check_required_file "$helper_compat_dir/$helper_name"
690
- if [[ "${helper_source:-package}" != "package" && "$helper_runtime_dir" != "$helper_compat_dir" ]]; then
691
- check_required_file "$helper_runtime_dir/$helper_name"
692
- fi
693
- done
694
- }
695
-
696
- policy_get() {
697
- local jq_path="$1"
698
- local default_value="$2"
699
-
700
- if [[ -f "$policy_file" ]] && command -v jq >/dev/null 2>&1; then
701
- local value
702
- value="$(jq -r "$jq_path // empty" "$policy_file" 2>/dev/null || true)"
703
- if [[ -n "$value" ]]; then
704
- printf '%s' "$value"
705
- return 0
706
- fi
707
- fi
708
-
709
- printf '%s' "$default_value"
710
- }
711
-
712
- todo_file="$(policy_get '.tasks.todo_file' 'tasks/todos.md')"
713
- current_status_file="$(policy_get '.tasks.current_status_file' 'tasks/current.md')"
714
- lessons_file="$(policy_get '.tasks.lessons_file' 'tasks/lessons.md')"
715
- research_dir="$(policy_get '.tasks.research_dir' 'docs/researches')"
716
- contracts_dir="$(policy_get '.tasks.contracts_dir' 'tasks/contracts')"
717
- reviews_dir="$(policy_get '.tasks.reviews_dir' 'tasks/reviews')"
718
- notes_dir="$(policy_get '.tasks.notes_dir' 'tasks/notes')"
719
- workstreams_dir="$(policy_get '.tasks.workstreams_dir' 'tasks/workstreams')"
720
- runs_dir="$(policy_get '.harness.runs_dir' '.ai/harness/runs')"
721
- checks_file="$(policy_get '.harness.checks_file' '.ai/harness/checks/latest.json')"
722
- helper_runtime_dir="$(policy_get '.harness.helper_runtime_dir' '.ai/harness/scripts')"
723
- helper_compat_dir="$(policy_get '.harness.helper_compat_dir' 'scripts')"
724
- helper_source="$(policy_get '.harness.helper_source' 'package')"
725
- context_map_file="$(policy_get '.context.map_file' '.ai/context/context-map.json')"
726
- brain_manifest_file="$(policy_get '.information_lifecycle.external_knowledge.manifest_file' '.ai/harness/brain-manifest.json')"
727
- handoff_file="$(policy_get '.session.continuation_file' '.ai/harness/session/continuation.md')"
728
- resume_file="$(policy_get '.session.resume_file' '.ai/harness/session/resume.md')"
729
- sprints_dir="$(policy_get '.sprints.dir' 'plans/sprints')"
730
- sprint_marker_file="$(policy_get '.sprints.active_marker_file' '.ai/harness/sprint/active-sprint')"
731
- legacy_sprints_dir="tasks/sprints"
732
- upgrade_strategy_version=""
733
- if [[ -f "$policy_file" ]] && command -v jq >/dev/null 2>&1; then
734
- upgrade_strategy_version="$(policy_get '.upgrade.strategy_version' '')"
735
- fi
736
-
737
- check_required_dir "plans"
738
- check_required_dir "plans/archive"
739
- check_required_dir "plans/prds"
740
- check_required_dir "$sprints_dir"
741
- check_required_dir "tasks"
742
- check_required_dir "tasks/archive"
743
- check_required_dir "$contracts_dir"
744
- check_required_dir "$reviews_dir"
745
- check_required_dir "$notes_dir"
746
- check_required_dir "$workstreams_dir"
747
- check_required_dir ".claude/templates"
748
- check_required_dir ".ai/context"
749
- check_required_dir ".ai/harness"
750
- if [[ "$helper_source" != "package" ]]; then
751
- check_required_dir "$helper_runtime_dir"
752
- fi
753
- check_required_dir "$runs_dir"
754
-
755
- helper_file() {
756
- printf '%s/%s' "$helper_runtime_dir" "$1"
757
- }
758
-
759
- check_required_file "docs/spec.md"
760
- check_required_file ".claude/templates/spec.template.md"
761
- check_required_file ".claude/templates/plan.template.md"
762
- check_required_file ".claude/templates/research.template.md"
763
- check_required_file ".claude/templates/contract.template.md"
764
- check_required_file ".claude/templates/review.template.md"
765
- check_required_file ".claude/templates/implementation-notes.template.md"
766
- check_required_file ".claude/templates/prd.template.md"
767
- check_helper_runtime_files
768
- check_required_file "$todo_file"
769
- check_required_file "$current_status_file"
770
- check_required_file "$lessons_file"
771
- check_required_dir "$research_dir"
772
- check_required_file "$context_map_file"
773
- check_required_file "$policy_file"
774
- check_required_file "$brain_manifest_file"
775
-
776
- if [[ -f ".claude/templates/plan.template.md" ]]; then
777
- check_plan_template_evidence_contract ".claude/templates/plan.template.md"
778
- fi
779
-
780
- if [[ -f "$policy_file" && -z "$upgrade_strategy_version" ]] && command -v jq >/dev/null 2>&1; then
781
- report_issue "Harness policy is missing upgrade.strategy_version; rerun migration to merge the versioned upgrade strategy."
782
- fi
783
-
784
- if [[ ! -f "$WORKFLOW_CONTRACT_PATH" ]]; then
785
- report_bootstrap "Missing generated workflow contract manifest: $WORKFLOW_CONTRACT_PATH"
786
- else
787
- json_runtime="$(resolve_json_runtime || true)"
788
- if [[ -z "$json_runtime" ]]; then
789
- report_issue "Missing node, bun, or python3 to read workflow contract manifest: $WORKFLOW_CONTRACT_PATH"
790
- else
791
- while IFS= read -r rel_dir; do
792
- [[ -z "$rel_dir" ]] && continue
793
- check_required_dir "$rel_dir"
794
- done < <(contract_query_lines "artifacts.requiredDirectories")
795
-
796
- while IFS= read -r rel_file; do
797
- [[ -z "$rel_file" ]] && continue
798
- check_required_file "$rel_file"
799
- done < <(contract_query_lines "artifacts.requiredFiles")
800
- fi
801
- fi
802
-
803
- check_reference_config_stubs
804
- check_generation_surface_terminology
805
- if [[ -f "$checks_file" ]] && ! trace_error="$(trace_schema_error "$checks_file")"; then
806
- report_issue "Latest checks trace is not forge-run-trace.v1 compatible: ${trace_error//$'\n'/; }"
807
- fi
808
-
809
- if [[ -f "docs/plan.md" ]]; then
810
- report_issue "Legacy docs/plan.md detected; migrate or archive it into plans/."
811
- fi
812
-
813
- if [[ -f "docs/TODO.md" ]]; then
814
- report_issue "Legacy docs/TODO.md detected; migrate it into tasks/todos.md."
815
- fi
816
-
817
- if [[ "$todo_file" != "tasks/todo.md" && -f "tasks/todo.md" ]]; then
818
- report_issue "Legacy tasks/todo.md detected; migrate it into ${todo_file}."
819
- fi
820
-
821
- if [[ -f "scripts/check-deploy-sql-order.sh" ]]; then
822
- if ! bash "scripts/check-deploy-sql-order.sh" --quiet; then
823
- report_issue "Deploy SQL order check failed."
824
- fi
825
- fi
826
-
827
- if [[ -f "scripts/check-brain-manifest.sh" && -f "$brain_manifest_file" ]]; then
828
- if ! bash "scripts/check-brain-manifest.sh"; then
829
- report_issue "Brain manifest check failed."
830
- fi
831
- fi
832
-
833
- if [[ -f "scripts/sync-brain-docs.sh" && -f "$brain_manifest_file" ]]; then
834
- if ! bash "scripts/sync-brain-docs.sh" --check; then
835
- report_issue "Brain doc sync check failed."
836
- fi
837
- fi
838
-
839
- todo_source="$(todo_source_plan || true)"
840
- if [[ -f "$todo_file" ]]; then
841
- if grep -q '[^[:space:]]' "$todo_file"; then
842
- if ! todo_is_deferred_ledger "$todo_file"; then
843
- report_issue "Legacy ${todo_file} detected; expected a deferred-goal ledger, not an active execution checklist."
844
- elif ! ledger_error="$(todo_deferred_ledger_error "$todo_file")"; then
845
- report_issue "${todo_file} deferred ledger is incomplete: ${ledger_error//$'\n'/; }"
846
- fi
847
- fi
848
- fi
849
-
850
- if [[ -d "plans/prds" ]]; then
851
- while IFS= read -r prd_sprint_file; do
852
- [[ -n "$prd_sprint_file" ]] || continue
853
- report_issue "Sprint backlog file is in the PRD catalog; migrate ${prd_sprint_file} into ${sprints_dir}/."
854
- done < <(
855
- find plans/prds -maxdepth 1 -type f -name '*.prd.md' -print0 2>/dev/null \
856
- | xargs -0 grep -El '^(# Sprint:|## Backlog[[:space:]]*$)' 2>/dev/null || true
857
- )
858
-
859
- while IFS= read -r prd_file; do
860
- [[ -n "$prd_file" ]] || continue
861
- case "$(basename "$prd_file")" in
862
- [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]-*.prd.md)
863
- ;;
864
- *)
865
- report_issue "PRD filename must match <YYYYMMDD>-<HHMM>-<slug>.prd.md: $prd_file"
866
- continue
867
- ;;
868
- esac
869
-
870
- prd_status="$(extract_status "$prd_file")"
871
- if [[ -z "$prd_status" ]]; then
872
- report_issue "PRD is missing a '**Status**' line: $prd_file"
873
- continue
874
- fi
875
- if ! prd_known_status "$prd_status"; then
876
- report_issue "PRD has unknown status '${prd_status}': $prd_file"
877
- continue
878
- fi
879
- if [[ "$prd_status" == "Approved" ]]; then
880
- if ! prd_error="$(prd_ready_error "$prd_file")"; then
881
- report_issue "PRD $prd_file is not approval-ready: ${prd_error//$'\n'/; }"
882
- fi
883
- fi
884
- done < <(find plans/prds -maxdepth 1 -type f -name '*.prd.md' 2>/dev/null | sort)
885
- fi
886
-
887
- if [[ -d "$sprints_dir" ]]; then
888
- while IFS= read -r sprint_file; do
889
- [[ -n "$sprint_file" ]] || continue
890
- sprint_status="$(extract_status "$sprint_file")"
891
- if [[ -z "$sprint_status" ]]; then
892
- report_issue "Sprint is missing a '**Status**' line: $sprint_file"
893
- continue
894
- fi
895
- if ! sprint_known_status "$sprint_status"; then
896
- report_issue "Sprint has unknown status '${sprint_status}': $sprint_file"
897
- continue
898
- fi
899
- if [[ "$sprint_status" == "Approved" || "$sprint_status" == "Executing" ]]; then
900
- if ! sprint_error="$(sprint_ready_error "$sprint_file")"; then
901
- report_issue "Sprint $sprint_file is not execution-ready: ${sprint_error//$'\n'/; }"
902
- fi
903
- fi
904
- done < <(find "$sprints_dir" -maxdepth 1 -type f -name '*.sprint.md' 2>/dev/null | sort)
905
- fi
906
-
907
- if [[ "$sprints_dir" != "$legacy_sprints_dir" && -d "$legacy_sprints_dir" ]]; then
908
- report_issue "Legacy sprint directory detected; migrate ${legacy_sprints_dir}/*.sprint.md into ${sprints_dir}/*.sprint.md."
909
- fi
910
-
911
- if [[ -d "plans/prds" ]]; then
912
- while IFS= read -r prd_sprint_file; do
913
- [[ -n "$prd_sprint_file" ]] || continue
914
- report_issue "Sprint backlog file is in the PRD catalog; migrate ${prd_sprint_file} into ${sprints_dir}/."
915
- done < <(
916
- find plans/prds -maxdepth 1 -type f -name '*.prd.md' -print0 2>/dev/null \
917
- | xargs -0 grep -El '^(# Sprint:|## Backlog[[:space:]]*$)' 2>/dev/null || true
918
- )
919
- fi
920
-
921
- if [[ -f "$sprint_marker_file" ]]; then
922
- marker_sprint="$(sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' "$sprint_marker_file" 2>/dev/null || true)"
923
- if [[ -z "$marker_sprint" || ! -f "$marker_sprint" ]]; then
924
- report_issue "Active sprint marker does not resolve to a sprint file: ${marker_sprint:-(empty)} (marker: $sprint_marker_file)"
925
- else
926
- case "$marker_sprint" in
927
- "$sprints_dir"/*)
928
- case "$marker_sprint" in
929
- *..*)
930
- report_issue "Active sprint marker must not contain '..': $marker_sprint (marker: $sprint_marker_file)"
931
- ;;
932
- esac
933
- ;;
934
- *)
935
- report_issue "Active sprint marker points outside ${sprints_dir}: $marker_sprint (marker: $sprint_marker_file)"
936
- ;;
937
- esac
938
- fi
939
- fi
940
-
941
- if [[ -f "$current_status_file" ]]; then
942
- if ! grep -Eq '^# Current Status Snapshot[[:space:]]*$' "$current_status_file"; then
943
- report_issue "${current_status_file} is missing '# Current Status Snapshot' heading."
944
- fi
945
- if grep -Eq '^[[:space:]]*-[[:space:]]\[[ xX]\][[:space:]]+' "$current_status_file"; then
946
- report_issue "${current_status_file} must remain a read model, not a checklist."
947
- fi
948
- fi
949
-
950
- check_handoff_resume_pair "$handoff_file" "$resume_file"
951
- check_current_resume_freshness "$current_status_file" "$resume_file"
952
-
953
- active_plan="$(get_active_plan || true)"
954
- if [[ -z "$active_plan" ]]; then
955
- if [[ -f "$ACTIVE_WORKTREE_MARKER" ]]; then
956
- report_issue "$ACTIVE_WORKTREE_MARKER exists but no active plan marker resolves to a plan."
957
- fi
958
- else
959
- if [[ ! -f "$ACTIVE_WORKTREE_MARKER" ]]; then
960
- report_issue "Active plan marker exists but $ACTIVE_WORKTREE_MARKER is missing."
961
- else
962
- current_worktree="$(pwd -P)"
963
- marked_worktree="$(cat "$ACTIVE_WORKTREE_MARKER" 2>/dev/null | xargs || true)"
964
- if [[ -z "$marked_worktree" ]]; then
965
- report_issue "$ACTIVE_WORKTREE_MARKER is empty."
966
- elif [[ "$marked_worktree" != "$current_worktree" ]]; then
967
- report_issue "$ACTIVE_WORKTREE_MARKER points to $marked_worktree, expected $current_worktree."
968
- fi
969
- fi
970
-
971
- plan_status="$(extract_status "$active_plan")"
972
- if [[ -z "$plan_status" ]]; then
973
- report_issue "Active plan is missing a '**Status**' line: $active_plan"
974
- fi
975
-
976
- if [[ "$plan_status" == "Approved" || "$plan_status" == "Executing" ]]; then
977
- if ! evidence_error="$(plan_evidence_contract_error "$active_plan")"; then
978
- report_issue "Active $plan_status plan has incomplete Evidence Contract: $active_plan (${evidence_error//$'\n'/; })"
979
- fi
980
-
981
- contract_file="$(derive_contract_path "$active_plan")"
982
- if [[ ! -f "$contract_file" ]]; then
983
- report_issue "Active $plan_status plan is missing its task contract: $contract_file"
984
- elif ! grep -Eq '^> \*\*Capability ID\*\*: .+' "$contract_file"; then
985
- report_issue "Active task contract is missing a capability binding: $contract_file"
986
- fi
987
- fi
988
- fi
989
-
990
- if [[ "$issues" -eq 0 ]]; then
991
- if [[ "$bootstrap_issues" -gt 0 ]]; then
992
- echo "[workflow] OK: source/document contracts pass; $bootstrap_issues generated runtime item(s) need local bootstrap/remediation."
993
- fi
994
- echo "[workflow] OK"
995
- exit 0
996
- fi
997
-
998
- if [[ "$strict" -eq 1 ]]; then
999
- exit 1
1000
- fi
1001
-
1002
- echo "[workflow] Found $issues issue(s); rerun with --strict to fail the check."
3
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ exec "$SCRIPT_DIR/lib/run-forge-helper.sh" "check-task-workflow.sh" "$@"