forgedock 1.0.22 → 1.1.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.
- package/README.md +255 -211
- package/bin/batch-runner.mjs +339 -0
- package/bin/cinema.mjs +320 -0
- package/bin/demo.mjs +92 -7
- package/bin/engine/phases.mjs +168 -0
- package/bin/engine/projector.mjs +32 -0
- package/bin/engine/reconcile.mjs +11 -0
- package/bin/engine/runlog.mjs +79 -0
- package/bin/engine/state.mjs +32 -0
- package/bin/engine-cli.mjs +222 -0
- package/bin/engine.mjs +138 -0
- package/bin/forgedock.mjs +896 -1540
- package/bin/hooks/interactive-engine.mjs +509 -0
- package/bin/hooks/pre-tool-use.mjs +498 -0
- package/bin/hooks/subagent-stop-enforce.mjs +197 -0
- package/bin/journey.mjs +841 -0
- package/bin/registry.mjs +139 -0
- package/bin/report.mjs +447 -0
- package/bin/runner.mjs +136 -20
- package/bin/settings-hook.mjs +346 -0
- package/bin/tui.mjs +6 -3
- package/commands/adopt.md +4 -1
- package/commands/analytics.md +4 -1
- package/commands/audit-agents.md +110 -27
- package/commands/audit.md +20 -1
- package/commands/autopilot.md +84 -12
- package/commands/changelog.md +2 -1
- package/commands/ci-audit.md +2 -1
- package/commands/cleanup.md +161 -1
- package/commands/compat-audit.md +2 -1
- package/commands/deploy-info.md +63 -4
- package/commands/design-bench.md +6 -1
- package/commands/design-render-critique-loop.md +2 -1
- package/commands/design.md +1 -0
- package/commands/diagnose.md +2 -1
- package/commands/explain.md +2 -1
- package/commands/forge-stats.md +122 -2
- package/commands/forgedock-init.md +1 -1
- package/commands/geo-audit.md +3 -0
- package/commands/incident-response.md +2 -1
- package/commands/issue.md +24 -3
- package/commands/milestone.md +77 -12
- package/commands/optimize.md +2 -1
- package/commands/orchestrate/config.md +67 -0
- package/commands/orchestrate/phase-1-resolve.md +159 -0
- package/commands/orchestrate/phase-2-triage.md +126 -0
- package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
- package/commands/orchestrate/phase-3-dependency.md +591 -0
- package/commands/orchestrate/phase-4-execution.md +962 -0
- package/commands/orchestrate/phase-5-cleanup.md +47 -0
- package/commands/orchestrate/phase-6-report.md +198 -0
- package/commands/orchestrate/safety.md +62 -0
- package/commands/orchestrate.md +25 -2027
- package/commands/pipeline-health/config.md +34 -0
- package/commands/pipeline-health/phase-1-context.md +180 -0
- package/commands/pipeline-health/phase-2-metrics.md +1729 -0
- package/commands/pipeline-health/phase-3-analyze.md +204 -0
- package/commands/pipeline-health/phase-4-proposals.md +117 -0
- package/commands/pipeline-health/phase-5-report.md +284 -0
- package/commands/pipeline-health/phase-6-summary.md +52 -0
- package/commands/pipeline-health.md +25 -2401
- package/commands/pipeline-resume.md +24 -1
- package/commands/pipeline-status.md +65 -0
- package/commands/qa-sweep.md +4 -1
- package/commands/quality-gate.md +326 -42
- package/commands/replay.md +2 -1
- package/commands/review-pr-agents/api.md +135 -0
- package/commands/review-pr-agents/auth.md +137 -0
- package/commands/review-pr-agents/billing.md +101 -0
- package/commands/review-pr-agents/concurrency.md +145 -0
- package/commands/review-pr-agents/database.md +103 -0
- package/commands/review-pr-agents/frontend.md +89 -0
- package/commands/review-pr-agents/infra.md +385 -0
- package/commands/review-pr-agents/protocols.md +185 -0
- package/commands/review-pr-agents/scraper.md +185 -0
- package/commands/review-pr-agents/security.md +406 -0
- package/commands/review-pr-agents.md +45 -1722
- package/commands/review-pr-staging.md +196 -40
- package/commands/review-pr.md +612 -56
- package/commands/rollback.md +2 -1
- package/commands/scope.md +2 -1
- package/commands/security-audit.md +2 -1
- package/commands/signal-planner.md +623 -0
- package/commands/sync-ecosystem.md +1 -0
- package/commands/test-gate.md +17 -1
- package/commands/validate.md +2 -1
- package/commands/work-on/build/architect.md +28 -0
- package/commands/work-on/build/context.md +2 -0
- package/commands/work-on/build/implement.md +19 -5
- package/commands/work-on/build/validate.md +62 -6
- package/commands/work-on/build.md +187 -30
- package/commands/work-on/close.md +132 -5
- package/commands/work-on/decompose.md +19 -1
- package/commands/work-on/investigate.md +117 -2
- package/commands/work-on/review.md +48 -4
- package/commands/work-on-monolithic.md +8 -2
- package/commands/work-on.md +247 -7
- package/docs/BRAND.md +16 -5
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +36 -1
- package/docs/CONFIG.md +44 -42
- package/docs/FORGE-PROTOCOL.md +15 -1064
- package/docs/anthropic-devrel-outreach.md +2 -2
- package/docs/articles/command-decomposition-token-savings.md +214 -0
- package/docs/articles/model-release-playbook.md +131 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
- package/docs/claude-breakpoints.json +75 -5
- package/docs/design/abc-benchmark.md +1 -1
- package/docs/design/design-spec-schema.md +1 -1
- package/docs/design/divergent-generation.md +1 -1
- package/docs/design/eval-gate-runbook.md +178 -0
- package/docs/design/show-hn-draft.md +26 -1
- package/docs/design/token-cost-baseline-1298.md +59 -0
- package/docs/eval/README.md +117 -0
- package/docs/eval/corpus/seed-v1.json +6 -0
- package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
- package/docs/site/annotations-explained.md +24 -0
- package/docs/site/command-learning-path.md +51 -14
- package/docs/site/command-reference.md +339 -113
- package/docs/site/for-companies.md +95 -0
- package/docs/site/forge-annotation-protocol.md +12 -256
- package/docs/site/getting-started.md +58 -12
- package/docs/site/github-is-the-memory.md +113 -0
- package/docs/site/how-it-works.md +3 -13
- package/docs/site/index.md +10 -2
- package/docs/spec/eval-run-result.md +147 -0
- package/docs/spec/forge-protocol-v1.md +441 -18
- package/docs/spec/label-state-machine.md +55 -0
- package/docs/spec/review-protocol.md +158 -0
- package/docs/spec-graph-schema.md +28 -0
- package/docs/superpowers/plans/2026-07-03-cinematic-onboarding-tui.md +2339 -0
- package/docs/superpowers/plans/2026-07-03-durable-execution-engine.md +1223 -0
- package/docs/superpowers/specs/2026-07-03-cinematic-onboarding-tui-design.md +157 -0
- package/docs/superpowers/specs/2026-07-03-durable-execution-engine-design.md +194 -0
- package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
- package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
- package/examples/forgedock-demo/README.md +41 -22
- package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
- package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
- package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
- package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
- package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
- package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
- package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
- package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
- package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
- package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
- package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
- package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
- package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
- package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
- package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
- package/examples/forgedock-demo/scripts/smoke.js +18 -1
- package/examples/forgedock-demo/src/db.js +36 -11
- package/examples/forgedock-demo/src/routes/notes.js +106 -9
- package/examples/forgedock-demo/src/server.js +4 -1
- package/package.json +12 -4
- package/packages/protocol/LICENSE +21 -0
- package/packages/protocol/src/cli.js +135 -0
- package/packages/protocol/src/emit.js +146 -0
- package/packages/protocol/src/index.js +43 -0
- package/packages/protocol/src/parse.js +229 -0
- package/packages/protocol/src/types.js +171 -0
- package/packages/protocol/src/validate.js +122 -0
- package/scripts/bench-topology-cost.mjs +372 -0
- package/scripts/check-command-docs-drift.sh +198 -0
- package/scripts/check-protocol-restatements.sh +176 -0
- package/scripts/check-registry/README.md +72 -0
- package/scripts/check-registry/manifest.json +6 -0
- package/scripts/code-index.sh +454 -0
- package/scripts/conformance-check.mjs +168 -0
- package/scripts/doctor-pipeline-state.sh +484 -0
- package/scripts/eval-gate-baseline.json +17 -0
- package/scripts/eval-gate-scorecard.mjs +242 -0
- package/scripts/eval-scorecard-aggregator.mjs +252 -0
- package/scripts/flaky-quarantine.sh +241 -0
- package/scripts/forge-annotation.sh +567 -0
- package/scripts/issue-dedup.sh +236 -0
- package/scripts/validate-annotation-node.mjs +233 -0
- package/scripts/verify-env-vars.sh +18 -2
- package/scripts/verify-host-headers.sh +4 -4
- package/scripts/verify-route-registration.sh +2 -2
- package/scripts/worktree-lifecycle.sh +202 -0
- package/templates/devdocs/agent/using-forgedock.md +14 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
#
|
|
5
|
+
# forge-annotation.sh — Deterministic FORGE annotation read/write/validate engine
|
|
6
|
+
#
|
|
7
|
+
# Every `commands/*.md` phase boundary posts or reads a machine-readable
|
|
8
|
+
# `<!-- FORGE:MARKER -->` HTML-comment annotation on a GitHub issue/PR. Until
|
|
9
|
+
# this script existed, each command spec re-derived the annotation body via
|
|
10
|
+
# inline heredoc/prose and re-derived the read/grep query independently, so
|
|
11
|
+
# annotation format drift between specs was possible and undetected. This
|
|
12
|
+
# script is the single source of truth for the annotation schema: it knows
|
|
13
|
+
# every marker's required fields and completion terminator, and exposes a
|
|
14
|
+
# stable write/read/validate CLI so pipeline phases stop hand-rolling both.
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# forge-annotation.sh write <MARKER> [--field KEY=VALUE]... [--field-file KEY=FILE]...
|
|
18
|
+
# forge-annotation.sh read <ISSUE_NUMBER> [GH_FLAG...] <MARKER> [--all]
|
|
19
|
+
# forge-annotation.sh validate <MARKER> [FILE]
|
|
20
|
+
# forge-annotation.sh list-markers
|
|
21
|
+
# forge-annotation.sh -h | --help
|
|
22
|
+
#
|
|
23
|
+
# Subcommands:
|
|
24
|
+
# write Renders a well-formed `<!-- FORGE:MARKER -->` comment body to
|
|
25
|
+
# stdout, ready to be piped into `gh issue comment --body-file -`
|
|
26
|
+
# (or captured into a variable for `--body`). Fails (exit 1) if
|
|
27
|
+
# a required field for MARKER is missing.
|
|
28
|
+
# read Fetches the latest comment matching MARKER on ISSUE_NUMBER via
|
|
29
|
+
# `gh api` and prints its raw body to stdout. Prints nothing and
|
|
30
|
+
# exits 0 if no matching comment exists (mirrors the existing
|
|
31
|
+
# `| last | .body // ""` prose convention — "absent" is not an
|
|
32
|
+
# error). GH_FLAG tokens (e.g. `-R owner/repo`) may appear
|
|
33
|
+
# between ISSUE_NUMBER and MARKER, matching transition-label.sh's
|
|
34
|
+
# argument convention. Pass `--all` to print every matching
|
|
35
|
+
# comment body as a JSON array (newest last) instead of just the
|
|
36
|
+
# latest raw body.
|
|
37
|
+
# validate Reads a candidate comment body (from FILE, or stdin if FILE is
|
|
38
|
+
# omitted) and checks it has the correct `<!-- FORGE:MARKER -->`
|
|
39
|
+
# header and, if MARKER requires one, its completion terminator
|
|
40
|
+
# (e.g. `<!-- INVESTIGATION:COMPLETE -->` for INVESTIGATOR).
|
|
41
|
+
# Exits 1 with a clear message on any malformed body.
|
|
42
|
+
# list-markers Prints all known markers and their completion-terminator
|
|
43
|
+
# requirement (or "none").
|
|
44
|
+
#
|
|
45
|
+
# Exit codes: 0 = success, 1 = error (bad args, missing field, malformed body,
|
|
46
|
+
# gh/api failure, unknown marker).
|
|
47
|
+
#
|
|
48
|
+
# This is a UNIVERSAL-tier script (ships with the npm package). See
|
|
49
|
+
# devdocs/project/architecture.md → Script Precedence for the 4-tier
|
|
50
|
+
# resolution hierarchy this script participates in.
|
|
51
|
+
|
|
52
|
+
set -euo pipefail
|
|
53
|
+
|
|
54
|
+
SCRIPT_NAME="forge-annotation.sh"
|
|
55
|
+
|
|
56
|
+
# NOTE: this script does not export FORGEDOCK_SCRIPTS/FORGEDOCK_HOME. Those
|
|
57
|
+
# exports are meaningless when a script is invoked via `bash script.sh`
|
|
58
|
+
# (a subprocess) rather than sourced — the exported vars never reach the
|
|
59
|
+
# caller's shell. This exact dead-code pattern was confirmed and removed from
|
|
60
|
+
# classify-lane.sh in #852/#854; do not reintroduce it here.
|
|
61
|
+
|
|
62
|
+
usage() {
|
|
63
|
+
cat >&2 <<EOF
|
|
64
|
+
Usage:
|
|
65
|
+
$SCRIPT_NAME write <MARKER> [--field KEY=VALUE]... [--field-file KEY=FILE]...
|
|
66
|
+
$SCRIPT_NAME read <ISSUE_NUMBER> [GH_FLAG...] <MARKER> [--all]
|
|
67
|
+
$SCRIPT_NAME validate <MARKER> [FILE]
|
|
68
|
+
$SCRIPT_NAME list-markers
|
|
69
|
+
$SCRIPT_NAME -h | --help
|
|
70
|
+
|
|
71
|
+
Known markers: ${!COMPLETION_MARKER[*]} ${KNOWN_NO_TERMINATOR_MARKERS[*]}
|
|
72
|
+
EOF
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# Marker registry
|
|
77
|
+
#
|
|
78
|
+
# COMPLETION_MARKER[MARKER] = literal completion-terminator string that MUST
|
|
79
|
+
# appear in the body for the marker to be
|
|
80
|
+
# considered COMPLETE. Absence from this map
|
|
81
|
+
# means the marker has no required terminator
|
|
82
|
+
# (it is a single-shot, always-complete post).
|
|
83
|
+
# PARTIAL_MARKER[MARKER] = literal alternate terminator accepted in place
|
|
84
|
+
# of the COMPLETE one when a phase intentionally
|
|
85
|
+
# posts a partial result under a time budget.
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
declare -A COMPLETION_MARKER=(
|
|
88
|
+
[INVESTIGATOR]="INVESTIGATION:COMPLETE"
|
|
89
|
+
[CONTEXT]="FORGE:CONTEXT:COMPLETE"
|
|
90
|
+
[ARCHITECT]="FORGE:ARCHITECT:COMPLETE"
|
|
91
|
+
[BUILDER]="FORGE:BUILDER:COMPLETE"
|
|
92
|
+
[DECOMPOSED]="FORGE:DECOMPOSED:COMPLETE"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
declare -A PARTIAL_MARKER=(
|
|
96
|
+
[CONTEXT]="FORGE:CONTEXT:PARTIAL"
|
|
97
|
+
[ARCHITECT]="FORGE:ARCHITECT:PARTIAL"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Markers with no required completion terminator (single-shot posts).
|
|
101
|
+
KNOWN_NO_TERMINATOR_MARKERS=(
|
|
102
|
+
CONTRACT CHECKPOINT FAST_PATH HEARTBEAT LEARNED TRAJECTORY
|
|
103
|
+
DECISION_RECORD REVIEW_STARTED
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
is_known_marker() {
|
|
107
|
+
local m="$1"
|
|
108
|
+
[[ -n "${COMPLETION_MARKER[$m]+x}" ]] && return 0
|
|
109
|
+
local known
|
|
110
|
+
for known in "${KNOWN_NO_TERMINATOR_MARKERS[@]}"; do
|
|
111
|
+
[[ "$known" == "$m" ]] && return 0
|
|
112
|
+
done
|
|
113
|
+
return 1
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
normalize_marker() {
|
|
117
|
+
# Accepts INVESTIGATOR, FORGE:INVESTIGATOR, or lowercase variants.
|
|
118
|
+
local raw="${1^^}"
|
|
119
|
+
raw="${raw#FORGE:}"
|
|
120
|
+
echo "$raw"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# Template registry — one function per marker, using {{FIELD}} placeholders.
|
|
125
|
+
# Substitution is plain bash parameter-expansion string replacement (handles
|
|
126
|
+
# multi-line replacement values fine, no sed/awk needed).
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
render_template() {
|
|
130
|
+
local marker="$1"
|
|
131
|
+
local body=""
|
|
132
|
+
|
|
133
|
+
case "$marker" in
|
|
134
|
+
INVESTIGATOR)
|
|
135
|
+
body='<!-- FORGE:INVESTIGATOR -->
|
|
136
|
+
## Investigation Report
|
|
137
|
+
|
|
138
|
+
**Verdict**: {{VERDICT}}
|
|
139
|
+
**Confidence**: {{CONFIDENCE}}
|
|
140
|
+
**Severity**: {{SEVERITY}}
|
|
141
|
+
**Task Type**: {{TASK_TYPE}}
|
|
142
|
+
|
|
143
|
+
### What Was Claimed
|
|
144
|
+
{{WHAT_CLAIMED}}
|
|
145
|
+
|
|
146
|
+
### What We Found
|
|
147
|
+
{{WHAT_FOUND}}
|
|
148
|
+
|
|
149
|
+
### Root Cause
|
|
150
|
+
{{ROOT_CAUSE}}
|
|
151
|
+
|
|
152
|
+
### Affected Files
|
|
153
|
+
{{AFFECTED_FILES}}
|
|
154
|
+
|
|
155
|
+
### Evidence
|
|
156
|
+
{{EVIDENCE}}
|
|
157
|
+
|
|
158
|
+
### Recommendation
|
|
159
|
+
{{RECOMMENDATION}}
|
|
160
|
+
|
|
161
|
+
### Related Issues
|
|
162
|
+
{{RELATED_ISSUES}}
|
|
163
|
+
|
|
164
|
+
### Decomposition Assessment
|
|
165
|
+
**{{DECOMPOSE}}** — {{DECOMPOSE_REASON}}
|
|
166
|
+
{{DECOMPOSE_DETAILS}}
|
|
167
|
+
|
|
168
|
+
<!-- INVESTIGATION:COMPLETE -->'
|
|
169
|
+
;;
|
|
170
|
+
CONTRACT)
|
|
171
|
+
body='<!-- FORGE:CONTRACT -->
|
|
172
|
+
## Builder Contract
|
|
173
|
+
|
|
174
|
+
**Task type**: {{TASK_TYPE}}
|
|
175
|
+
|
|
176
|
+
### Proposed Approach
|
|
177
|
+
{{APPROACH}}
|
|
178
|
+
|
|
179
|
+
### Deliverables
|
|
180
|
+
| File | Change | Why |
|
|
181
|
+
|------|--------|-----|
|
|
182
|
+
{{DELIVERABLES_ROWS}}
|
|
183
|
+
|
|
184
|
+
### Acceptance Criteria
|
|
185
|
+
{{ACCEPTANCE_CRITERIA}}
|
|
186
|
+
|
|
187
|
+
### Quality Considerations
|
|
188
|
+
{{QUALITY_CONSIDERATIONS}}
|
|
189
|
+
|
|
190
|
+
### Out of Scope
|
|
191
|
+
{{OUT_OF_SCOPE}}'
|
|
192
|
+
;;
|
|
193
|
+
BUILDER)
|
|
194
|
+
body='<!-- FORGE:BUILDER -->
|
|
195
|
+
## Implementation Complete
|
|
196
|
+
|
|
197
|
+
**Branch**: `{{BRANCH}}`
|
|
198
|
+
**Commits**: {{COMMITS}}
|
|
199
|
+
**Files changed**: {{FILES_CHANGED}}
|
|
200
|
+
|
|
201
|
+
### Approach
|
|
202
|
+
{{APPROACH}}
|
|
203
|
+
|
|
204
|
+
### Changes
|
|
205
|
+
{{CHANGES}}
|
|
206
|
+
|
|
207
|
+
### Acceptance Criteria Status
|
|
208
|
+
{{ACCEPTANCE_STATUS}}
|
|
209
|
+
|
|
210
|
+
### Testing Checklist
|
|
211
|
+
{{TESTING_CHECKLIST}}
|
|
212
|
+
|
|
213
|
+
<!-- FORGE:BUILDER:COMPLETE -->'
|
|
214
|
+
;;
|
|
215
|
+
CHECKPOINT)
|
|
216
|
+
body='<!-- FORGE:CHECKPOINT -->
|
|
217
|
+
```json
|
|
218
|
+
{"phase": "{{PHASE}}", "status": "{{STATUS}}", "next_phase": "{{NEXT_PHASE}}", "timestamp": "{{TIMESTAMP}}"}
|
|
219
|
+
```'
|
|
220
|
+
;;
|
|
221
|
+
*)
|
|
222
|
+
echo "ERROR: no write template registered for marker '$marker' (known write templates: INVESTIGATOR, CONTRACT, BUILDER, CHECKPOINT)" >&2
|
|
223
|
+
return 1
|
|
224
|
+
;;
|
|
225
|
+
esac
|
|
226
|
+
|
|
227
|
+
echo "$body"
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
required_fields_for() {
|
|
231
|
+
case "$1" in
|
|
232
|
+
INVESTIGATOR) echo "VERDICT CONFIDENCE SEVERITY TASK_TYPE WHAT_CLAIMED WHAT_FOUND ROOT_CAUSE AFFECTED_FILES EVIDENCE RECOMMENDATION DECOMPOSE DECOMPOSE_REASON" ;;
|
|
233
|
+
CONTRACT) echo "TASK_TYPE APPROACH DELIVERABLES_ROWS ACCEPTANCE_CRITERIA" ;;
|
|
234
|
+
BUILDER) echo "BRANCH COMMITS FILES_CHANGED APPROACH CHANGES ACCEPTANCE_STATUS TESTING_CHECKLIST" ;;
|
|
235
|
+
CHECKPOINT) echo "PHASE STATUS NEXT_PHASE" ;;
|
|
236
|
+
*) echo "" ;;
|
|
237
|
+
esac
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
default_for_field() {
|
|
241
|
+
# Optional-field defaults, applied only when the field was not supplied.
|
|
242
|
+
local marker="$1" key="$2"
|
|
243
|
+
case "$marker:$key" in
|
|
244
|
+
INVESTIGATOR:RELATED_ISSUES) echo "None found." ;;
|
|
245
|
+
INVESTIGATOR:DECOMPOSE_DETAILS) echo "" ;;
|
|
246
|
+
CONTRACT:QUALITY_CONSIDERATIONS) echo "None." ;;
|
|
247
|
+
CONTRACT:OUT_OF_SCOPE) echo "None." ;;
|
|
248
|
+
CHECKPOINT:TIMESTAMP) date -u +"%Y-%m-%dT%H:%M:%SZ" ;;
|
|
249
|
+
*) echo "" ;;
|
|
250
|
+
esac
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# ---------------------------------------------------------------------------
|
|
254
|
+
# write
|
|
255
|
+
# ---------------------------------------------------------------------------
|
|
256
|
+
cmd_write() {
|
|
257
|
+
if [ $# -lt 1 ]; then
|
|
258
|
+
echo "ERROR: write requires a MARKER argument" >&2
|
|
259
|
+
usage
|
|
260
|
+
exit 1
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
local marker
|
|
264
|
+
marker="$(normalize_marker "$1")"
|
|
265
|
+
shift
|
|
266
|
+
|
|
267
|
+
case "$marker" in
|
|
268
|
+
INVESTIGATOR|CONTRACT|BUILDER|CHECKPOINT) ;;
|
|
269
|
+
*)
|
|
270
|
+
echo "ERROR: '$marker' has no registered write template. Supported: INVESTIGATOR, CONTRACT, BUILDER, CHECKPOINT" >&2
|
|
271
|
+
exit 1
|
|
272
|
+
;;
|
|
273
|
+
esac
|
|
274
|
+
|
|
275
|
+
declare -A FIELDS=()
|
|
276
|
+
|
|
277
|
+
while [ $# -gt 0 ]; do
|
|
278
|
+
case "$1" in
|
|
279
|
+
--field)
|
|
280
|
+
if [ $# -lt 2 ] || [[ "$2" != *=* ]]; then
|
|
281
|
+
echo "ERROR: --field requires KEY=VALUE" >&2
|
|
282
|
+
exit 1
|
|
283
|
+
fi
|
|
284
|
+
local kv="$2" key val
|
|
285
|
+
key="${kv%%=*}"
|
|
286
|
+
val="${kv#*=}"
|
|
287
|
+
FIELDS["$key"]="$val"
|
|
288
|
+
shift 2
|
|
289
|
+
;;
|
|
290
|
+
--field-file)
|
|
291
|
+
if [ $# -lt 2 ] || [[ "$2" != *=* ]]; then
|
|
292
|
+
echo "ERROR: --field-file requires KEY=FILE" >&2
|
|
293
|
+
exit 1
|
|
294
|
+
fi
|
|
295
|
+
local kvf="$2" fkey ffile
|
|
296
|
+
fkey="${kvf%%=*}"
|
|
297
|
+
ffile="${kvf#*=}"
|
|
298
|
+
if [ ! -f "$ffile" ]; then
|
|
299
|
+
echo "ERROR: --field-file: file not found: $ffile" >&2
|
|
300
|
+
exit 1
|
|
301
|
+
fi
|
|
302
|
+
FIELDS["$fkey"]="$(cat "$ffile")"
|
|
303
|
+
shift 2
|
|
304
|
+
;;
|
|
305
|
+
*)
|
|
306
|
+
echo "ERROR: unknown write argument: $1" >&2
|
|
307
|
+
exit 1
|
|
308
|
+
;;
|
|
309
|
+
esac
|
|
310
|
+
done
|
|
311
|
+
|
|
312
|
+
# Fill optional-field defaults for anything not explicitly supplied.
|
|
313
|
+
case "$marker" in
|
|
314
|
+
INVESTIGATOR)
|
|
315
|
+
for k in RELATED_ISSUES DECOMPOSE_DETAILS; do
|
|
316
|
+
[[ -n "${FIELDS[$k]+x}" ]] || FIELDS["$k"]="$(default_for_field "$marker" "$k")"
|
|
317
|
+
done
|
|
318
|
+
;;
|
|
319
|
+
CONTRACT)
|
|
320
|
+
for k in QUALITY_CONSIDERATIONS OUT_OF_SCOPE; do
|
|
321
|
+
[[ -n "${FIELDS[$k]+x}" ]] || FIELDS["$k"]="$(default_for_field "$marker" "$k")"
|
|
322
|
+
done
|
|
323
|
+
;;
|
|
324
|
+
CHECKPOINT)
|
|
325
|
+
[[ -n "${FIELDS[TIMESTAMP]+x}" ]] || FIELDS["TIMESTAMP"]="$(default_for_field "$marker" "TIMESTAMP")"
|
|
326
|
+
;;
|
|
327
|
+
esac
|
|
328
|
+
|
|
329
|
+
# Validate required fields are present (and non-empty).
|
|
330
|
+
local missing="" f
|
|
331
|
+
for f in $(required_fields_for "$marker"); do
|
|
332
|
+
if [[ -z "${FIELDS[$f]+x}" ]] || [[ -z "${FIELDS[$f]}" ]]; then
|
|
333
|
+
missing="$missing $f"
|
|
334
|
+
fi
|
|
335
|
+
done
|
|
336
|
+
if [ -n "$missing" ]; then
|
|
337
|
+
echo "ERROR: missing required field(s) for MARKER '$marker':$missing" >&2
|
|
338
|
+
echo " Required fields: $(required_fields_for "$marker")" >&2
|
|
339
|
+
exit 1
|
|
340
|
+
fi
|
|
341
|
+
|
|
342
|
+
local body
|
|
343
|
+
body="$(render_template "$marker")" || exit 1
|
|
344
|
+
|
|
345
|
+
local key
|
|
346
|
+
for key in "${!FIELDS[@]}"; do
|
|
347
|
+
body="${body//\{\{$key\}\}/${FIELDS[$key]}}"
|
|
348
|
+
done
|
|
349
|
+
|
|
350
|
+
# Fail loudly if any placeholder was left unsubstituted — this catches
|
|
351
|
+
# template/field-name drift between this script and the callers of it.
|
|
352
|
+
if [[ "$body" == *"{{"*"}}"* ]]; then
|
|
353
|
+
echo "ERROR: unsubstituted placeholder(s) remain in rendered '$marker' body — check field names:" >&2
|
|
354
|
+
grep -oE '\{\{[A-Z_]+\}\}' <<<"$body" | sort -u >&2 || true
|
|
355
|
+
exit 1
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
echo "$body"
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# ---------------------------------------------------------------------------
|
|
362
|
+
# read
|
|
363
|
+
# ---------------------------------------------------------------------------
|
|
364
|
+
cmd_read() {
|
|
365
|
+
if [ $# -lt 2 ]; then
|
|
366
|
+
echo "ERROR: Usage: $SCRIPT_NAME read <ISSUE_NUMBER> [GH_FLAG...] <MARKER> [--all]" >&2
|
|
367
|
+
exit 1
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
local issue_number="$1"
|
|
371
|
+
shift
|
|
372
|
+
|
|
373
|
+
if ! [[ "$issue_number" =~ ^[0-9]+$ ]]; then
|
|
374
|
+
echo "ERROR: ISSUE_NUMBER must be a positive integer, got: '$issue_number'" >&2
|
|
375
|
+
exit 1
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
local all_flag=0
|
|
379
|
+
local args=("$@")
|
|
380
|
+
local filtered=()
|
|
381
|
+
local i
|
|
382
|
+
for ((i = 0; i < ${#args[@]}; i++)); do
|
|
383
|
+
if [ "${args[$i]}" = "--all" ]; then
|
|
384
|
+
all_flag=1
|
|
385
|
+
else
|
|
386
|
+
filtered+=("${args[$i]}")
|
|
387
|
+
fi
|
|
388
|
+
done
|
|
389
|
+
|
|
390
|
+
if [ ${#filtered[@]} -lt 1 ]; then
|
|
391
|
+
echo "ERROR: MARKER is required" >&2
|
|
392
|
+
exit 1
|
|
393
|
+
fi
|
|
394
|
+
|
|
395
|
+
local last_index=$(( ${#filtered[@]} - 1 ))
|
|
396
|
+
local marker
|
|
397
|
+
marker="$(normalize_marker "${filtered[$last_index]}")"
|
|
398
|
+
local gh_args=("${filtered[@]:0:$last_index}")
|
|
399
|
+
|
|
400
|
+
if ! is_known_marker "$marker"; then
|
|
401
|
+
echo "WARNING: '$marker' is not a known registered marker — reading anyway (unknown markers are not rejected, only unvalidated)." >&2
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
# Resolve owner/repo for the gh api REST path. gh_args may contain `-R owner/repo`.
|
|
405
|
+
local repo=""
|
|
406
|
+
for ((i = 0; i < ${#gh_args[@]}; i++)); do
|
|
407
|
+
if [ "${gh_args[$i]}" = "-R" ]; then
|
|
408
|
+
if [ $((i + 1)) -ge ${#gh_args[@]} ]; then
|
|
409
|
+
echo "ERROR: -R requires a value <owner/repo>" >&2
|
|
410
|
+
echo "Usage: $SCRIPT_NAME read <ISSUE_NUMBER> [GH_FLAG...] <MARKER> [--all]" >&2
|
|
411
|
+
exit 1
|
|
412
|
+
fi
|
|
413
|
+
repo="${gh_args[$((i + 1))]}"
|
|
414
|
+
break
|
|
415
|
+
fi
|
|
416
|
+
done
|
|
417
|
+
|
|
418
|
+
if [ -z "$repo" ]; then
|
|
419
|
+
repo="$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || true)"
|
|
420
|
+
if [ -z "$repo" ]; then
|
|
421
|
+
echo "ERROR: could not resolve owner/repo — pass -R <owner/repo> or run inside a repo with gh context" >&2
|
|
422
|
+
exit 1
|
|
423
|
+
fi
|
|
424
|
+
fi
|
|
425
|
+
|
|
426
|
+
if ! [[ "$repo" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
|
|
427
|
+
echo "ERROR: resolved repo is not owner/repo format: '$repo'" >&2
|
|
428
|
+
exit 1
|
|
429
|
+
fi
|
|
430
|
+
|
|
431
|
+
local needle="FORGE:${marker}"
|
|
432
|
+
local comments_json
|
|
433
|
+
if ! comments_json="$(gh api "repos/${repo}/issues/${issue_number}/comments" --paginate 2>&1)"; then
|
|
434
|
+
echo "ERROR: gh api call failed: $comments_json" >&2
|
|
435
|
+
exit 1
|
|
436
|
+
fi
|
|
437
|
+
|
|
438
|
+
# `gh api --paginate` writes one JSON array PER PAGE to stdout, concatenated
|
|
439
|
+
# (not merged into a single top-level array). Issues with >100 comments span
|
|
440
|
+
# multiple pages. Feeding that multi-document stream directly into a jq filter
|
|
441
|
+
# written for one document makes jq re-run the filter once per page — the
|
|
442
|
+
# default/latest mode below would print one "latest per page" body per page
|
|
443
|
+
# instead of a single global latest, and --all mode would emit N separate
|
|
444
|
+
# JSON arrays concatenated instead of one valid array. Aggregate all pages
|
|
445
|
+
# into a single array first so behavior is correct regardless of page count;
|
|
446
|
+
# this is a no-op for the common single-page case.
|
|
447
|
+
local aggregated_json
|
|
448
|
+
if ! aggregated_json="$(jq -s 'add' <<<"$comments_json" 2>&1)"; then
|
|
449
|
+
echo "ERROR: failed to aggregate paginated comments: $aggregated_json" >&2
|
|
450
|
+
exit 1
|
|
451
|
+
fi
|
|
452
|
+
comments_json="$aggregated_json"
|
|
453
|
+
|
|
454
|
+
if [ "$all_flag" -eq 1 ]; then
|
|
455
|
+
jq --arg needle "$needle" '[.[] | select(.body | contains($needle)) | .body]' <<<"$comments_json"
|
|
456
|
+
else
|
|
457
|
+
jq -r --arg needle "$needle" '[.[] | select(.body | contains($needle))] | last | .body // ""' <<<"$comments_json"
|
|
458
|
+
fi
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
# ---------------------------------------------------------------------------
|
|
462
|
+
# validate
|
|
463
|
+
# ---------------------------------------------------------------------------
|
|
464
|
+
cmd_validate() {
|
|
465
|
+
if [ $# -lt 1 ]; then
|
|
466
|
+
echo "ERROR: Usage: $SCRIPT_NAME validate <MARKER> [FILE]" >&2
|
|
467
|
+
exit 1
|
|
468
|
+
fi
|
|
469
|
+
|
|
470
|
+
local marker
|
|
471
|
+
marker="$(normalize_marker "$1")"
|
|
472
|
+
shift
|
|
473
|
+
|
|
474
|
+
local body
|
|
475
|
+
if [ $# -ge 1 ]; then
|
|
476
|
+
if [ ! -f "$1" ]; then
|
|
477
|
+
echo "ERROR: file not found: $1" >&2
|
|
478
|
+
exit 1
|
|
479
|
+
fi
|
|
480
|
+
body="$(cat "$1")"
|
|
481
|
+
else
|
|
482
|
+
body="$(cat -)"
|
|
483
|
+
fi
|
|
484
|
+
|
|
485
|
+
if [ -z "$body" ]; then
|
|
486
|
+
echo "ERROR: empty body — nothing to validate" >&2
|
|
487
|
+
exit 1
|
|
488
|
+
fi
|
|
489
|
+
|
|
490
|
+
if ! is_known_marker "$marker"; then
|
|
491
|
+
echo "ERROR: unknown marker '$marker' — run 'list-markers' for the known set" >&2
|
|
492
|
+
exit 1
|
|
493
|
+
fi
|
|
494
|
+
|
|
495
|
+
local header="FORGE:${marker}"
|
|
496
|
+
if [[ "$body" != *"<!-- ${header} -->"* ]]; then
|
|
497
|
+
echo "ERROR: body is missing required header '<!-- ${header} -->'" >&2
|
|
498
|
+
exit 1
|
|
499
|
+
fi
|
|
500
|
+
|
|
501
|
+
if [[ -n "${COMPLETION_MARKER[$marker]+x}" ]]; then
|
|
502
|
+
local complete_marker="${COMPLETION_MARKER[$marker]}"
|
|
503
|
+
local partial_marker="${PARTIAL_MARKER[$marker]:-}"
|
|
504
|
+
if [[ "$body" == *"$complete_marker"* ]]; then
|
|
505
|
+
: # complete — OK
|
|
506
|
+
elif [[ -n "$partial_marker" ]] && [[ "$body" == *"$partial_marker"* ]]; then
|
|
507
|
+
echo "OK: '${marker}' annotation is well-formed (PARTIAL — terminator '${partial_marker}' present)"
|
|
508
|
+
return 0
|
|
509
|
+
else
|
|
510
|
+
echo "ERROR: '${marker}' annotation is missing its completion marker. Expected to find: <!-- ${complete_marker} -->" >&2
|
|
511
|
+
exit 1
|
|
512
|
+
fi
|
|
513
|
+
fi
|
|
514
|
+
|
|
515
|
+
if [ "$marker" = "CHECKPOINT" ]; then
|
|
516
|
+
local json_block
|
|
517
|
+
json_block="$(sed -n '/```json/,/```/p' <<<"$body" | sed '1d;$d')"
|
|
518
|
+
if [ -z "$json_block" ]; then
|
|
519
|
+
echo "ERROR: CHECKPOINT annotation is missing its \`\`\`json ... \`\`\` block" >&2
|
|
520
|
+
exit 1
|
|
521
|
+
fi
|
|
522
|
+
if ! echo "$json_block" | jq -e '.phase and .status and .next_phase and .timestamp' >/dev/null 2>&1; then
|
|
523
|
+
echo "ERROR: CHECKPOINT JSON block is missing one of the required keys: phase, status, next_phase, timestamp" >&2
|
|
524
|
+
exit 1
|
|
525
|
+
fi
|
|
526
|
+
fi
|
|
527
|
+
|
|
528
|
+
echo "OK: '${marker}' annotation is well-formed"
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
# ---------------------------------------------------------------------------
|
|
532
|
+
# list-markers
|
|
533
|
+
# ---------------------------------------------------------------------------
|
|
534
|
+
cmd_list_markers() {
|
|
535
|
+
echo "MARKER COMPLETION_TERMINATOR"
|
|
536
|
+
local m
|
|
537
|
+
for m in "${!COMPLETION_MARKER[@]}"; do
|
|
538
|
+
printf '%-20s %s\n' "$m" "${COMPLETION_MARKER[$m]}"
|
|
539
|
+
done | sort
|
|
540
|
+
for m in "${KNOWN_NO_TERMINATOR_MARKERS[@]}"; do
|
|
541
|
+
printf '%-20s %s\n' "$m" "none"
|
|
542
|
+
done | sort
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
# ---------------------------------------------------------------------------
|
|
546
|
+
# Entry point
|
|
547
|
+
# ---------------------------------------------------------------------------
|
|
548
|
+
if [ $# -lt 1 ]; then
|
|
549
|
+
usage
|
|
550
|
+
exit 1
|
|
551
|
+
fi
|
|
552
|
+
|
|
553
|
+
SUBCOMMAND="$1"
|
|
554
|
+
shift
|
|
555
|
+
|
|
556
|
+
case "$SUBCOMMAND" in
|
|
557
|
+
write) cmd_write "$@" ;;
|
|
558
|
+
read) cmd_read "$@" ;;
|
|
559
|
+
validate) cmd_validate "$@" ;;
|
|
560
|
+
list-markers) cmd_list_markers ;;
|
|
561
|
+
-h|--help) usage; exit 0 ;;
|
|
562
|
+
*)
|
|
563
|
+
echo "ERROR: unknown subcommand: $SUBCOMMAND" >&2
|
|
564
|
+
usage
|
|
565
|
+
exit 1
|
|
566
|
+
;;
|
|
567
|
+
esac
|