@moretea-labs/forge 1.7.0 → 1.7.1
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.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +2 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +2 -0
- package/assets/plugin-registry.v1.json +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +17 -0
- package/assets/templates/helpers/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- package/assets/templates/helpers/workflow-contract.ts +5 -1
- package/docs/architecture/CURRENT.md +12 -4
- package/docs/forge-plugin-management.md +1 -1
- package/docs/operations/controller-performance-and-502.md +19 -0
- package/docs/operations/homebrew.md +2 -2
- package/package.json +5 -2
- package/scripts/architecture-event.ts +2 -820
- package/scripts/architecture-queue.sh +2 -645
- package/scripts/archive-architecture-request.sh +2 -242
- package/scripts/archive-workflow.sh +1 -291
- package/scripts/benchmark-work-efficiency.ts +0 -0
- package/scripts/capability-config.ts +2 -392
- package/scripts/capability-resolver.ts +2 -503
- package/scripts/capture-plan.sh +1 -332
- package/scripts/check-agent-tooling.sh +2 -1531
- package/scripts/check-architecture-sync.sh +2 -302
- package/scripts/check-brain-manifest.sh +2 -253
- package/scripts/check-context-files.sh +2 -47
- package/scripts/check-deploy-sql-order.sh +2 -115
- package/scripts/check-runtime-architecture.mjs +243 -16
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +0 -0
- package/scripts/check-task-sync.sh +2 -63
- package/scripts/check-task-workflow.sh +2 -1000
- package/scripts/codex-handoff-resume.sh +2 -267
- package/scripts/context-contract-sync.sh +2 -514
- package/scripts/contract-run.ts +2 -463
- package/scripts/contract-worktree.sh +1 -702
- package/scripts/ensure-task-workflow.sh +1 -1192
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/lib/project-init-lib.sh +7 -2
- package/scripts/lib/run-forge-helper.sh +19 -0
- package/scripts/lib/run-forge-helper.ts +26 -0
- package/scripts/migrate-project-template.sh +1 -33
- package/scripts/migrate-workflow-docs.ts +4 -412
- package/scripts/new-plan.sh +1 -180
- package/scripts/new-spec.sh +1 -47
- package/scripts/new-sprint.sh +1 -46
- package/scripts/plan-to-todo.sh +1 -771
- package/scripts/prepare-codex-handoff.sh +2 -201
- package/scripts/prepare-handoff.sh +1 -78
- package/scripts/refresh-current-status.sh +2 -467
- package/scripts/run-governed-gate.ts +3 -0
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -531
- package/scripts/sprint-backlog.sh +1 -834
- package/scripts/stage-runtime-release.ts +2 -1
- package/scripts/summarize-failures.sh +2 -123
- package/scripts/switch-plan.sh +1 -150
- package/scripts/sync-brain-docs.sh +2 -379
- package/scripts/sync-generated-authority.mjs +175 -0
- package/scripts/verify-contract.sh +2 -593
- package/scripts/verify-sprint.sh +1 -500
- package/scripts/workflow-contract.ts +4 -201
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/plugin.ts +12 -3
- package/src/cli/controller/check-runner.ts +7 -2
- package/src/cli/controller/context-pack.ts +46 -21
- package/src/cli/github/contracts.ts +16 -0
- package/src/cli/github/github.ts +2 -16
- package/src/cli/github/session.ts +2 -1
- package/src/cli/local-bridge/facade-api.ts +37 -47
- package/src/cli/local-bridge/server.ts +2 -2
- package/src/cli/mcp/legacy-tool-service.ts +10 -33
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +85 -89
- package/src/cli/mcp/server.ts +57 -13
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset.ts +2 -2
- package/src/cli/mcp/transports/http.ts +23 -0
- package/src/cli/repositories/command-classifier.ts +2 -2
- package/src/cli/repositories/command-executor.ts +24 -6
- package/src/cli/repositories/repository-snapshot.ts +5 -2
- package/src/cli/repositories/structured-git.ts +57 -3
- package/src/cli/repository/inspector.ts +11 -3
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +9 -61
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +18 -25
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
- package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
- package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
- package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
- package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
- package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
- package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
- package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
- package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
- package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
- package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
- package/src/runtime/control-plane/facade/index.ts +2 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
- package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
- package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
- package/src/runtime/control-plane/facade/types.ts +68 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
- package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +18 -6
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- package/src/runtime/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- package/src/runtime/execution/process-runtime/check-result.ts +53 -18
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
- package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
- package/src/runtime/execution/process-runtime/runtime.ts +10 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -12
- package/src/runtime/execution/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -13
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/router.ts +2 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +805 -1009
- package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
- package/src/runtime/plugins/browser-adapter.ts +256 -1083
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +18 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
- package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
- package/src/runtime/plugins/browser-native-session-service.ts +222 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +10 -0
- package/src/runtime/plugins/browser-session-store.ts +121 -0
- package/src/runtime/plugins/browser-session-types.ts +98 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
- package/src/runtime/plugins/external-adapter.ts +92 -58
- package/src/runtime/plugins/macos-capability-broker.ts +0 -8
- package/src/runtime/plugins/store.ts +66 -9
- package/src/runtime/plugins/types.ts +3 -1
- package/src/runtime/recovery/maintenance-executor.ts +61 -25
- package/src/runtime/root/package-connector-service.ts +46 -4
- package/src/runtime/root/package-runtime-release.ts +1 -1
- package/src/runtime/root/package-runtime-service.ts +33 -2
- package/src/runtime/root/release-manifest.ts +137 -16
- package/src/runtime/root/release-materialize.ts +109 -35
- package/src/runtime/root/types.ts +10 -0
- package/src/runtime/standalone-recovery/core.ts +425 -194
- package/src/runtime/standalone-recovery/entry.ts +1 -1
- package/src/runtime/standalone-recovery/installer.ts +1 -1
- package/src/runtime/workflow/schedules/engine.ts +113 -87
- package/src/runtime/workflow/schedules/settlement.ts +26 -23
- package/src/runtime/workflow/schedules/store.ts +79 -10
|
@@ -1,1002 +1,4 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
|
|
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" "$@"
|