forgedock 1.12.0 → 1.13.0
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/bin/engine/phases-args.test.mjs +2 -1
- package/bin/engine/phases.mjs +349 -90
- package/bin/engine/runlog.mjs +25 -2
- package/bin/engine-cli.mjs +4 -1
- package/bin/engine.mjs +72 -19
- package/bin/hooks/interactive-engine.mjs +135 -209
- package/bin/hooks/pre-tool-use.mjs +16 -4
- package/bin/labels.json +10 -0
- package/bin/opencode-adapter.mjs +1 -1
- package/bin/orchestrate-preflight.mjs +1 -1
- package/bin/runner.mjs +258 -23
- package/bin/watch-utils.mjs +1 -0
- package/commands/autopilot.md +6 -6
- package/commands/cleanup.md +8 -5
- package/commands/orchestrate/phase-1-resolve.md +1 -1
- package/commands/orchestrate/phase-3-dependency.md +14 -19
- package/commands/orchestrate/phase-4-execution.md +80 -53
- package/commands/orchestrate/phase-6-report.md +6 -0
- package/commands/orchestrate/safety.md +1 -1
- package/commands/pipeline-health/phase-2-metrics.md +39 -0
- package/commands/pipeline-health/phase-5-report.md +1 -0
- package/commands/pipeline-resume.md +1 -1
- package/commands/pipeline-status.md +2 -2
- package/commands/recover-orphans.md +5 -5
- package/commands/review-pr-agents/api.md +1 -1
- package/commands/review-pr-agents/auth.md +1 -1
- package/commands/review-pr-agents/infra.md +3 -3
- package/commands/review-pr-agents/protocols.md +15 -6
- package/commands/review-pr-agents/scraper.md +1 -1
- package/commands/review-pr-agents/security.md +5 -5
- package/commands/review-pr-agents.md +12 -12
- package/commands/review-pr-staging.md +226 -8
- package/commands/review-pr.md +394 -53
- package/commands/work-on/build/architect.md +36 -0
- package/commands/work-on/build/implement.md +1 -1
- package/commands/work-on/build/validate.md +88 -4
- package/commands/work-on/build.md +106 -7
- package/commands/work-on/decompose.md +2 -2
- package/commands/work-on/investigate.md +14 -4
- package/commands/work-on/remediate.md +137 -25
- package/commands/work-on/review.md +25 -9
- package/commands/work-on.md +15 -15
- package/docs/CONFIG.md +29 -0
- package/docs/RELEASE-NOTES.md +45 -0
- package/docs/spec/forge-protocol-v1.md +22 -1
- package/docs/spec/label-state-machine.md +6 -1
- package/docs/spec/review-protocol.md +20 -5
- package/package.json +2 -1
- package/packages/protocol/src/phases.js +21 -0
- package/scripts/check-blast-radius.sh +164 -0
- package/scripts/check-blast-radius.test.sh +183 -0
- package/scripts/check-contract-scope.sh +137 -0
- package/scripts/check-contract-scope.test.sh +83 -0
- package/scripts/check-spec-bash.sh +60 -3
- package/scripts/check-spec-bash.test.sh +56 -0
- package/scripts/check-spec-markers.sh +29 -0
- package/scripts/check-test-wiring.sh +57 -0
- package/scripts/check-test-wiring.test.sh +70 -0
- package/scripts/classify-finding.sh +72 -8
- package/scripts/classify-finding.test.sh +77 -8
- package/scripts/close-dossier-no-main-commit.test.sh +12 -12
- package/scripts/diff-size.test.sh +3 -2
- package/scripts/doctor-pipeline-state.sh +35 -0
- package/scripts/forge-annotation.sh +3 -0
- package/scripts/forge-root.test.sh +103 -13
- package/scripts/forge-run.sh +2 -2
- package/scripts/recovery-claim-live.test.sh +10 -7
- package/scripts/remediate-basesync.test.sh +103 -0
- package/scripts/review-delta.sh +206 -0
- package/scripts/review-delta.test.sh +213 -0
- package/scripts/review-pr-domain-map.test.sh +87 -0
- package/scripts/review-pr-gate2.test.sh +84 -0
- package/scripts/review-pr-staging-scope.test.sh +99 -0
- package/scripts/run-shell-tests.sh +115 -0
- package/scripts/select-fix-targets.sh +17 -5
- package/scripts/transition-label.sh +3 -1
package/docs/RELEASE-NOTES.md
CHANGED
|
@@ -2,6 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
Operator-facing notes for behavior changes that need action or awareness. Newest first.
|
|
4
4
|
|
|
5
|
+
## Review damper: LOW needs keyword + CONFIRMED; a missing classifier blocks the review (#3639)
|
|
6
|
+
|
|
7
|
+
`scripts/classify-finding.sh` is stricter for LOW findings. A safety keyword now files a LOW finding only when it is also CONFIRMED (LOW LIKELY/POSSIBLE with a keyword is a NOTE), and a dedicated domain agent (Auth, Billing, Concurrency, Database) rescues MEDIUM findings only, no longer LOW CONFIRMED. MEDIUM behaviour is unchanged.
|
|
8
|
+
|
|
9
|
+
`/review-pr` §6B.5 and `/review-pr-staging` §7B.5 no longer let the model hand-classify (`classifier=manual`) when the script cannot be resolved. They return `REVIEW_RESULT: status: BLOCKED` with blocker `classifier unavailable (classify-finding.sh not resolvable through the trusted resolver)`, file nothing and do not merge or approve a deploy. The staging per-finding runtime-failure fallback is renamed `ISSUE classifier-error`. The `classifier=` marker stays and is always `script`.
|
|
10
|
+
|
|
11
|
+
Action: if reviews now report `classifier unavailable`, fix the ForgeDock install so the trusted resolver finds `scripts/classify-finding.sh`, then re-run the review.
|
|
12
|
+
|
|
13
|
+
## Introduced-line gate no longer demotes CRITICAL/HIGH (#3597, #3598)
|
|
14
|
+
|
|
15
|
+
`/review-pr` §6B.4 gate 2 never routes a CRITICAL/HIGH finding (any confidence) as `pre-existing`: a new caller of an unchanged unsafe helper is an interaction defect (`protocols.md` §5), so it stays a blocking `review-finding` under §7B criterion 2, including with `--auto-merge`. The pre-existing route now covers MEDIUM and lower only. A file with no `patch` in the PR files API (large or truncated diff) is treated as UNKNOWN and its findings are kept, never routed as pre-existing. Expect slightly more blocking findings on PRs that touch unsafe code through unchanged helpers.
|
|
16
|
+
|
|
17
|
+
## Contract out-of-scope items now typed; review demotes matching findings (#3447)
|
|
18
|
+
|
|
19
|
+
The `### Out of Scope` section of `FORGE:CONTRACT` is no longer free text. It is `None.` or bullets that each name a backticked path and one disposition: `deferred → #N`, `not-affected: <evidence>` or `accepted-risk: <reason>`. A contract that keeps a known-flawed design must list it as `deferred → #N`.
|
|
20
|
+
|
|
21
|
+
- **Build** (B2.0) files a follow-up issue for each `deferred` item before the contract is posted (idempotent through a `FORGE:DEFERRED_FROM: #N` body marker) and validates the section with the new `scripts/check-contract-scope.sh`. An untyped item blocks the build after one retry.
|
|
22
|
+
- **Review** (`/review-pr` §6B.5) reads the latest trusted contract and passes it to `scripts/classify-finding.sh --contract-scope`. A finding on a `deferred` path (follow-up still open) or an `accepted-risk` path becomes a note instead of a new `review-finding`; deferred matches are commented on the existing issue. `not-affected` items, HIGH/CRITICAL findings, safety-exempt `accepted-risk` findings and files the PR itself changed are never demoted. `FORGE:NOTE_DISPOSITION` gains `contract_demoted=N`.
|
|
23
|
+
- The review scope-creep check now reads only the contract's `### Deliverables`, so backticked out-of-scope paths no longer count as in-scope files.
|
|
24
|
+
|
|
25
|
+
Action: none. Older contracts with free-text Out of Scope sections are not parsed and demote nothing.
|
|
26
|
+
|
|
27
|
+
## Review provenance gates and noise share (#3452)
|
|
28
|
+
|
|
29
|
+
`/review-pr` §6B.4 now checks each finding before it can become a `review-finding` issue: the cited line must exist at the reviewed head SHA (else dropped as stale), must be added or changed by the PR diff (else routed as `pre-existing`), and must carry a concrete `**Failure scenario**` (else demoted to a note; CRITICAL/HIGH, any confidence, are exempt). Pre-existing defects are never counted as this PR's findings or in the amplification ratio; only MEDIUM or lower ones are routed this way, and the safety-domain ones are filed once with the new `pre-existing` label. The `FORGE:NOTE_DISPOSITION` record gains `findings_dropped_stale`, `findings_preexisting`, `notes_demoted_no_scenario`, and `/pipeline-health` reports a noise share (2E.5).
|
|
30
|
+
|
|
31
|
+
Action: run `npx forgedock labels setup` to create the `pre-existing` label (the review also creates it on demand).
|
|
32
|
+
|
|
33
|
+
## Diff-size gate in `/work-on` build (#3450)
|
|
34
|
+
|
|
35
|
+
`/work-on` build now measures the changed lines (added plus deleted) after implement stages its changes and before validate commits them. The gate is **on by default at 1000 lines**. Review findings per PR rise steeply with diff size, so an oversized build is split instead of validated.
|
|
36
|
+
|
|
37
|
+
- **Over the threshold**: the build posts a `FORGE:DIFF_SIZE` comment with a split proposal and exits `NEEDS_DECOMPOSE`; the router then runs `work-on:decompose`.
|
|
38
|
+
- **Tune or disable**: set `build.diff_size.threshold` in `forge.yaml` (`0` disables the gate) and add exclusions with `build.diff_size.exclude_globs`. Lockfiles, `*.min.*`, `*.snap` and vendored/generated directories are excluded by default.
|
|
39
|
+
- **Per-issue bypass**: post a `FORGE:SIZE_OVERRIDE` comment with a non-empty justification on the line after the marker. Only comments from trusted authors count.
|
|
40
|
+
- **No re-decomposition**: an issue that is already a decomposed child, or has `FORGE:DECOMPOSED`, blocks until an override is posted instead of being split again.
|
|
41
|
+
|
|
42
|
+
Action: if your builds routinely exceed 1000 changed lines, raise `build.diff_size.threshold` or set it to `0`. See the `build` section of [CONFIG.md](CONFIG.md).
|
|
43
|
+
|
|
44
|
+
## Stricter `review-pr` / `review-pr-staging` argument rejection (#3466)
|
|
45
|
+
|
|
46
|
+
`/review-pr` and `/review-pr-staging` now reject an argument string as a whole if it contains a quote, backtick, `$`, backslash, newline or tab. Nothing is parsed from a rejected string, so no PR number, repo, merge value or flag survives, and `--auto-merge` is not honoured. `/review-pr-staging` stops with nothing reviewed and nothing posted.
|
|
47
|
+
|
|
48
|
+
Action: callers must pass `--gh-flag -R owner/repo` unquoted, and must not forward untrusted text (issue titles, comment bodies) into the argument string.
|
|
49
|
+
|
|
5
50
|
## Spec bash no longer corrupted by Claude Code argument substitution
|
|
6
51
|
|
|
7
52
|
Claude Code replaces `$0`..`$9` in a skill body with the invocation's arguments (0-based), so spec bash loaded via `Skill(...)` with args was silently rewritten: `awk '{print $2}'` became `awk '{print --issue}'`, `local AGENT="$1"` became `local AGENT="--auto-merge"`, which broke `/review-pr` agent selection and the CI/deploy comparisons with no error. `${N}`, `$(N)`, `$NF`, `$@`, `$#` and `$10`+ are not substituted.
|
|
@@ -251,9 +251,30 @@ findings and what will actually be built.
|
|
|
251
251
|
{auth model, new configuration, data-safety, security surface, migration risk}
|
|
252
252
|
|
|
253
253
|
### Out of Scope
|
|
254
|
-
|
|
254
|
+
None.
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
+
`### Out of Scope` is either the single line `None.` or a list of typed bullets. Each bullet names at
|
|
258
|
+
least one backticked path or symbol followed by exactly one disposition:
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
- `path` — deferred → #N: <why>
|
|
262
|
+
- `path` — not-affected: <evidence>
|
|
263
|
+
- `path` — accepted-risk: <reason>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
- `deferred → #N`: a follow-up issue `#N` was filed before the contract was posted (`->` is accepted
|
|
267
|
+
for `→`). A contract that keeps a known-flawed design must list the flaw as a `deferred` item.
|
|
268
|
+
- `not-affected: <evidence>`: the path was checked and is not reached by the change.
|
|
269
|
+
- `accepted-risk: <reason>`: a known gap that is knowingly kept.
|
|
270
|
+
- Free-text items, a `deferred` item without an issue number, or a missing/empty section are invalid.
|
|
271
|
+
`scripts/check-contract-scope.sh validate|list` is the reference parser (TSV
|
|
272
|
+
`disposition<TAB>path<TAB>issue`).
|
|
273
|
+
- Review demotion: a finding on a `deferred` path (follow-up issue still open) or an `accepted-risk`
|
|
274
|
+
path becomes a note instead of a new `review-finding`; `not-affected` items, HIGH/CRITICAL findings,
|
|
275
|
+
safety-exempt `accepted-risk` findings and files the PR changed are never demoted. Only a trusted
|
|
276
|
+
author's contract is read. The count is recorded as `contract_demoted` in `FORGE:NOTE_DISPOSITION`.
|
|
277
|
+
|
|
257
278
|
#### `FORGE:CONTEXT`
|
|
258
279
|
|
|
259
280
|
- **Written by**: a context-gathering agent
|
|
@@ -14,6 +14,7 @@ See also: [FORGE Annotation Protocol §6](forge-protocol-v1.md#6-label-state-mac
|
|
|
14
14
|
| `workflow:ready-to-build` | Investigation complete, build not started | Investigator agent |
|
|
15
15
|
| `workflow:building` | Build phase active | Builder agent |
|
|
16
16
|
| `workflow:in-review` | PR created, review active | Orchestrator agent |
|
|
17
|
+
| `workflow:remediating` | Autonomous remediation pending: review handed a ci-gate refusal, an in-PR fix request or a pure base conflict to `work-on:remediate`, which fixes it with no human action. NOT a human gate, NOT terminal; orchestrate classifies it IN_PROGRESS (never GATED) and `work-on:remediate` clears it on every exit | Review phase (`work-on/review.md` R4); cleared by `work-on/remediate.md` |
|
|
17
18
|
| `workflow:awaiting-merge` | Remediated + re-reviewed, awaiting a human merge decision | Review-pr Phase 8 (auto-merge guard) |
|
|
18
19
|
| `workflow:merged` | PR merged, issue closed | Close phase agent |
|
|
19
20
|
| `workflow:invalid` | Issue closed as invalid | Investigator agent |
|
|
@@ -21,6 +22,8 @@ See also: [FORGE Annotation Protocol §6](forge-protocol-v1.md#6-label-state-mac
|
|
|
21
22
|
| `needs-human` | Pipeline blocked, human intervention required | Any agent on error |
|
|
22
23
|
| `workflow:engine-error` | Pipeline blocked by the engine/tool itself breaking (CLI crash, missing SDK/API key, exhausted retries with no attempt ever reaching a real outcome) — not a genuine content-level judgment call | `terminate()` in `bin/engine.mjs` |
|
|
23
24
|
|
|
25
|
+
`workflow:remediating` vs `needs-human` (forge#3541): `needs-human` is reserved for genuine human decisions and external actions (an exhausted remediation bound, an unresolvable conflict, a policy gate, spend, real-environment validation). Work the pipeline fixes by itself (a pure base-sync, a red CI gate, an in-PR finding round) is marked `workflow:remediating` instead, so operators are not alerted and orchestrate does not hold DAG dependents as GATED. `work-on:remediate` accepts either label (a bare `needs-human` from an older run still works), swaps it for `workflow:in-review` on a FIXABLE run, and on every BLOCKED/UNFIXABLE exit clears `workflow:remediating` and asserts `needs-human`.
|
|
26
|
+
|
|
24
27
|
`workflow:awaiting-merge` vs `needs-human`: both are terminal (the pipeline stops advancing the issue automatically), but they mean different things. `needs-human` means the pipeline hit a condition it cannot resolve on its own (genuinely blocked — conflicting PR, failed verdict, calibration/trust escalation, etc.) and a human must diagnose and act. `workflow:awaiting-merge` means the opposite: the PR was previously escalated to `needs-human`, has since been remediated and re-reviewed to a clean `APPROVED` verdict with no mergeability blockers, but does not yet meet the automated auto-land bar (see forge#1809 Q1) — a human only needs to click merge, not diagnose a problem. `scripts/transition-label.sh` only clears `needs-human` (best-effort) when the target state is `workflow:awaiting-merge` — every other forward transition leaves a pre-existing `needs-human` label untouched, preserving its sticky/terminal semantics.
|
|
25
28
|
|
|
26
29
|
`workflow:engine-error` vs `needs-human` (taxonomy split — commit `90376f5` / #2261): before this commit, `terminate()` wrote `needs-human` for *every* exhausted-retry blocked outcome, including cases where the engine/CLI tool itself was breaking (crash, missing API key, no SDK) rather than a genuine human-judgment call. As of `90376f5`, `runPhaseWithRetry()` tags an outcome `engine-error` when every exhausted attempt failed via a thrown runner exception (never reached `detectOutcome()`) — the engine/tool is broken, not the issue content — and `terminate()` writes `workflow:engine-error` instead of `needs-human` for that case. A genuine content-level block (unmerged PR, unresolved branch, a fixed-point zero-commits case) still writes `needs-human` unchanged. **Consequence for downstream consumers**: any dashboard, query, or health metric that counts `needs-human` issues computed a different, larger population before `90376f5` than after — a subset of what used to be `needs-human` now shows up as `workflow:engine-error` instead. This is a one-time taxonomy discontinuity, not a real change in pipeline reliability; consumers tracking `needs-human` counts over time should also track `workflow:engine-error` from this commit forward to get a like-for-like comparison. `bin/labels.json` already includes `workflow:engine-error` in this repo; syncing it to consumer/satellite repos is tracked separately (see forge#2346).
|
|
@@ -33,6 +36,8 @@ See also: [FORGE Annotation Protocol §6](forge-protocol-v1.md#6-label-state-mac
|
|
|
33
36
|
→ workflow:ready-to-build [Phase 2: Investigation complete]
|
|
34
37
|
→ workflow:building [Phase 3: Build started]
|
|
35
38
|
→ workflow:in-review [Phase 4: PR created]
|
|
39
|
+
→ workflow:remediating [Review R4: autonomous ci-gate / in-pr-fix / base-sync handoff; no human needed]
|
|
40
|
+
→ workflow:in-review [Remediate M1: fix under way] (workflow:in-review → workflow:remediating → workflow:in-review)
|
|
36
41
|
→ workflow:awaiting-merge [TERMINAL] [Phase 5/review-pr: re-reviewed after needs-human, awaiting human merge]
|
|
37
42
|
→ workflow:merged [TERMINAL] [Phase 5: PR merged]
|
|
38
43
|
→ workflow:invalid [TERMINAL] [Any phase: closed as invalid]
|
|
@@ -63,7 +68,7 @@ At most one `workflow:*` label should be active on an issue at any time. When tr
|
|
|
63
68
|
```bash
|
|
64
69
|
gh issue edit {NUMBER} -R {REPO} \
|
|
65
70
|
--add-label "workflow:building" \
|
|
66
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid,workflow:decomposed"
|
|
71
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:in-review,workflow:remediating,workflow:awaiting-merge,workflow:merged,workflow:invalid,workflow:decomposed"
|
|
67
72
|
```
|
|
68
73
|
|
|
69
74
|
This is enforced by `scripts/transition-label.sh` — use `resolve_script 'transition-label'` rather than calling `gh issue edit` directly when possible.
|
|
@@ -25,6 +25,9 @@ Pipeline agents MUST NOT use `find` (unbounded or filesystem-wide) to locate pro
|
|
|
25
25
|
Every agent MUST follow this protocol:
|
|
26
26
|
|
|
27
27
|
### 1. Start From the PR Diff
|
|
28
|
+
|
|
29
|
+
**Input scoping**: You have been given a pre-computed diff slice containing only the files relevant to your domain (`[DOMAIN_DIFF_SLICE]`). Do NOT re-fetch `gh pr diff [PR_NUMBER]` in full — use the slice provided. This is capped at ~100K chars, mirroring the runner's tool-result limit.
|
|
30
|
+
|
|
28
31
|
```bash
|
|
29
32
|
# Verify review is still current before reading diff
|
|
30
33
|
CURRENT_SHA=$(gh pr view [PR_NUMBER] --json headRefOid --jq '.headRefOid')
|
|
@@ -34,10 +37,19 @@ if [ "$CURRENT_SHA" != "[REVIEW_SHA]" ]; then
|
|
|
34
37
|
echo "Current HEAD: $(echo $CURRENT_SHA | cut -c1-7)"
|
|
35
38
|
fi
|
|
36
39
|
|
|
40
|
+
# List files in your domain slice
|
|
37
41
|
gh pr diff [PR_NUMBER] --name-only
|
|
38
|
-
|
|
42
|
+
|
|
43
|
+
# Materialize the pre-computed domain diff slice supplied by the orchestrator.
|
|
44
|
+
# Persona greps below read "$SLICE_FILE" -- they never re-fetch the full diff.
|
|
45
|
+
SLICE_FILE=$(mktemp)
|
|
46
|
+
cat > "$SLICE_FILE" <<'FORGE_SLICE_EOF'
|
|
47
|
+
[DOMAIN_DIFF_SLICE]
|
|
48
|
+
FORGE_SLICE_EOF
|
|
39
49
|
```
|
|
40
50
|
|
|
51
|
+
**Empty or truncated slice**: if the slice is empty or ends mid-hunk, read the specific changed files with bounded reads (`sed -n 'X,Yp'`, `head -N`) — never fall back to fetching the full PR diff.
|
|
52
|
+
|
|
41
53
|
**Hot-spot prior**:
|
|
42
54
|
[CHURN_CONTEXT]
|
|
43
55
|
|
|
@@ -82,7 +94,7 @@ If a file you are reviewing is listed above as a hot-spot, apply deeper scrutiny
|
|
|
82
94
|
3. Will this cause degraded performance? → **MEDIUM**
|
|
83
95
|
4. Is it genuinely cosmetic with no runtime impact after tracing all consumers? → **LOW**
|
|
84
96
|
|
|
85
|
-
|
|
97
|
+
**Precision first.** Report defects that are likely to be real in the code this PR changes and the code it reaches. If you cannot verify a runtime impact after tracing the path, report the finding at **POSSIBLE** confidence — do not inflate its severity or confidence to be safe. A false positive costs reviewer trust and triggers a cascade of follow-up issues and re-reviews; an unverified suspicion belongs at POSSIBLE, where it stays a non-blocking note, not at an inflated severity.
|
|
86
98
|
|
|
87
99
|
### 5. INTERACTION ANALYSIS — "Pre-existing" Is Not "Safe"
|
|
88
100
|
|
|
@@ -95,6 +107,8 @@ A redundant import, an unused variable, or a duplicated constant may be harmless
|
|
|
95
107
|
2. For each reference, ask: "Does the pre-existing construct cause this new line to fail at runtime?"
|
|
96
108
|
3. If yes → CONFIRMED finding, not a dismissal
|
|
97
109
|
|
|
110
|
+
**Severity rule**: CRITICAL/HIGH findings, any confidence, are never routed as pre-existing by the `/review-pr` introduced-line gate (§6B.4): they stay blocking `review-finding` issues. The `**Scope**` tag does not gate routing.
|
|
111
|
+
|
|
98
112
|
### 6. FALSE POSITIVE PREVENTION
|
|
99
113
|
|
|
100
114
|
**Before claiming variable scope issues:** Read the FULL function, count indentation levels, check if/else structure.
|
|
@@ -120,7 +134,7 @@ Every finding must include:
|
|
|
120
134
|
|
|
121
135
|
## Structured Findings Protocol
|
|
122
136
|
|
|
123
|
-
**All review agents MUST include a machine-readable findings block at the end of their PR comment.** This is NON-OPTIONAL. Without structured findings, the review system cannot
|
|
137
|
+
**All review agents MUST include a machine-readable findings block at the end of their PR comment.** This is NON-OPTIONAL. Without structured findings, the review system cannot classify, file, or track what you found. The orchestrator (`/review-pr` §6B.4/§6B.5) decides disposition: CONFIRMED/LIKELY findings that pass its gates become issues, while LOW severity and POSSIBLE confidence findings are non-blocking NOTEs that are not filed as standalone issues.
|
|
124
138
|
|
|
125
139
|
### Persist Before Post
|
|
126
140
|
|
|
@@ -138,7 +152,7 @@ Append this block at the very end of your comment (after the `---` footer line,
|
|
|
138
152
|
|
|
139
153
|
### Rules
|
|
140
154
|
|
|
141
|
-
1. **Include
|
|
155
|
+
1. **Include every finding you can support at CONFIRMED, LIKELY, or POSSIBLE confidence in the block** — the orchestrator needs them all to classify. Inclusion does not mean a GitHub issue is filed: `/review-pr` §6B.4/§6B.5 decide filing. **POSSIBLE confidence and LOW severity findings are non-blocking NOTEs** — reported in the review, never filed as standalone issues, never block merge. CONFIRMED and LIKELY findings are blocking at P1/P2 respectively. Do not pad the block with unverified or speculative items; precision over recall.
|
|
142
156
|
2. **One line per finding** — sequential numbering (PREFIX-1, PREFIX-2, ...)
|
|
143
157
|
3. **Confidence**: `CONFIRMED`, `LIKELY`, or `POSSIBLE`
|
|
144
158
|
4. **Severity**: `CRITICAL`, `HIGH`, `MEDIUM`, or `LOW`
|
|
@@ -146,8 +160,9 @@ Append this block at the very end of your comment (after the `---` footer line,
|
|
|
146
160
|
6. **Summary**: Concise one-line description (no pipe `|` characters in summary)
|
|
147
161
|
7. **Empty block**: If no findings at all, include just the START/END markers
|
|
148
162
|
8. **HTML comments**: The block is invisible in rendered markdown but parseable by the review system
|
|
149
|
-
9. **Agent marker**: Include exactly one `<!-- FORGE:REVIEW-AGENT:{domain} -->` marker in the persisted body, where `{domain}` is the
|
|
163
|
+
9. **Agent marker**: Include exactly one `<!-- FORGE:REVIEW-AGENT:{domain} -->` marker in the persisted body, where `{domain}` is the marker domain the orchestrator passes you (the canonical value from the roster-to-marker table in `review-pr.md`, e.g. `infra` and `scraper`; never a lowercased display name such as `infrastructure` or `scraping`).
|
|
150
164
|
10. **Reviewed SHA**: Include exactly one line `Reviewed-SHA: [REVIEW_SHA]` (the full 40-hex head you were pinned to) in the persisted body, outside the marker. `/review-pr` scopes its completion wait and idempotent re-entry to this line, so a comment without the current head does not count as a completed review. Do NOT put the SHA inside the `<!-- FORGE:REVIEW-AGENT:{domain} -->` marker.
|
|
165
|
+
11. **Failure scenario and scope (provenance inputs)**: every finding's body section (the prose above the findings block) MUST carry a `**Failure scenario**` line — concrete inputs leading to a wrong output or behaviour — and a `**Scope**: changed` or `**Scope**: pre-existing` line (`changed` = the cited line is added or modified by this PR; `pre-existing` = it was already there). `/review-pr` §6B.4 reads these: a finding without a failure scenario is demoted to a note (unless CRITICAL/HIGH, any confidence), and a MEDIUM or lower finding whose cited line is not in the PR diff is routed as pre-existing (CRITICAL/HIGH never are; see §5). The `**Scope**` tag is informational and never gates routing. Speculative hardening with no failure scenario is a note, not a finding. The one-line `<!-- FINDING:... -->` grammar above is unchanged. <!-- Added: forge#3452 -->
|
|
151
166
|
|
|
152
167
|
### Domain Prefixes
|
|
153
168
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgedock",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Autonomous development pipeline for coding agents. GitHub as institutional memory, with native Claude Code and OpenCode entrypoints.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"forgedock": "bin/forgedock.mjs"
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
],
|
|
49
49
|
"scripts": {
|
|
50
50
|
"test": "node --test \"bin/tests/**/*.test.mjs\" \"bin/engine/**/*.test.mjs\" \"packages/protocol/test/**/*.test.mjs\"",
|
|
51
|
+
"test:sh": "bash scripts/run-shell-tests.sh",
|
|
51
52
|
"docs:dev": "vitepress dev docs/site",
|
|
52
53
|
"docs:build": "vitepress build docs/site",
|
|
53
54
|
"docs:preview": "vitepress preview docs/site"
|
|
@@ -54,10 +54,19 @@ export const PHASE_IDS = ['investigate', 'decompose', 'build', 'review', 'remedi
|
|
|
54
54
|
* whose presence means this phase has committed.
|
|
55
55
|
* @property {string} [invalidMarker] - (investigate only) marks the issue INVALID.
|
|
56
56
|
* @property {string} [decomposedMarker] - (investigate only) marks the issue DECOMPOSED.
|
|
57
|
+
* investigate.md emits it as the HTML comment `<!-- DECOMPOSE:YES -->`.
|
|
58
|
+
* @property {string} [notDecomposedMarker] - (investigate only) the explicit not-decomposed
|
|
59
|
+
* verdict, emitted as `<!-- DECOMPOSE:NO -->`.
|
|
60
|
+
* @property {string} [sizeGateMarker] - (build only) bare annotation-opener of the diff-size gate record.
|
|
61
|
+
* @property {string} [sizeOverrideMarker] - (build only) bare annotation-opener of the size-gate override.
|
|
57
62
|
* @property {string} [partialMarker] - sentinel for an interrupted/partial annotation.
|
|
58
63
|
* @property {string} [presenceMarker] - bare annotation-opener substring, used only by
|
|
59
64
|
* phases whose completion is non-critical (see `context` below) — presence alone
|
|
60
65
|
* (without `:COMPLETE`) is enough to report a soft/visible skip rather than a hard fail.
|
|
66
|
+
* @property {string} [header] - the typed-comment header (without `<!-- ` / ` -->`) a genuine
|
|
67
|
+
* phase comment must START with (e.g. `FORGE:INVESTIGATOR`). The engine matches the
|
|
68
|
+
* sentinel only inside a trusted comment that leads with this header (forge#3542);
|
|
69
|
+
* additive, the bare marker strings above are unchanged.
|
|
61
70
|
* @property {string} [completionLabel] - a GitHub issue *label* (not a comment marker)
|
|
62
71
|
* whose presence means this phase has committed. Only `close` uses this form.
|
|
63
72
|
*/
|
|
@@ -66,17 +75,21 @@ export const PHASE_IDS = ['investigate', 'decompose', 'build', 'review', 'remedi
|
|
|
66
75
|
export const PHASE_MARKERS = {
|
|
67
76
|
investigate: {
|
|
68
77
|
completionMarker: RESERVED_TYPES.INVESTIGATOR.completionSentinel, // 'INVESTIGATION:COMPLETE'
|
|
78
|
+
header: 'FORGE:INVESTIGATOR',
|
|
69
79
|
invalidMarker: 'INVESTIGATION:INVALID',
|
|
70
80
|
decomposedMarker: 'DECOMPOSE:YES',
|
|
81
|
+
notDecomposedMarker: 'DECOMPOSE:NO',
|
|
71
82
|
},
|
|
72
83
|
// forge#2379: `decompose` actually runs work-on/decompose (sub-issue
|
|
73
84
|
// fan-out) once `investigate` hands off on `DECOMPOSE:YES` — see
|
|
74
85
|
// bin/engine/phases.mjs's `decompose` phase entry for the handoff mechanics.
|
|
75
86
|
decompose: {
|
|
76
87
|
completionMarker: RESERVED_TYPES.DECOMPOSED.completionSentinel, // 'FORGE:DECOMPOSED:COMPLETE'
|
|
88
|
+
header: 'FORGE:DECOMPOSED',
|
|
77
89
|
},
|
|
78
90
|
context: {
|
|
79
91
|
completionMarker: RESERVED_TYPES.CONTEXT.completionSentinel, // 'FORGE:CONTEXT:COMPLETE'
|
|
92
|
+
header: 'FORGE:CONTEXT',
|
|
80
93
|
partialMarker: RESERVED_TYPES.CONTEXT.partialSentinel, // 'FORGE:CONTEXT:PARTIAL'
|
|
81
94
|
// Bare presence marker: context is documented as non-critical (spec §7) — a
|
|
82
95
|
// missing completion marker is a visible skip, not a hard fail. Only
|
|
@@ -87,10 +100,17 @@ export const PHASE_MARKERS = {
|
|
|
87
100
|
},
|
|
88
101
|
architect: {
|
|
89
102
|
completionMarker: RESERVED_TYPES.ARCHITECT.completionSentinel, // 'FORGE:ARCHITECT:COMPLETE'
|
|
103
|
+
header: 'FORGE:ARCHITECT',
|
|
90
104
|
partialMarker: RESERVED_TYPES.ARCHITECT.partialSentinel, // 'FORGE:ARCHITECT:PARTIAL'
|
|
91
105
|
},
|
|
92
106
|
build: {
|
|
93
107
|
completionMarker: RESERVED_TYPES.BUILDER.completionSentinel, // 'FORGE:BUILDER:COMPLETE'
|
|
108
|
+
header: 'FORGE:BUILDER',
|
|
109
|
+
// Size-gate (B5.5) records. Neither is a completion sentinel: the gate's
|
|
110
|
+
// NEEDS_DECOMPOSE exit posts no FORGE:BUILDER:COMPLETE, so the engine reads
|
|
111
|
+
// these to route an oversized build to decompose instead of retrying it.
|
|
112
|
+
sizeGateMarker: `FORGE:${RESERVED_TYPES.DIFF_SIZE.type}`, // 'FORGE:DIFF_SIZE'
|
|
113
|
+
sizeOverrideMarker: `FORGE:${RESERVED_TYPES.SIZE_OVERRIDE.type}`, // 'FORGE:SIZE_OVERRIDE'
|
|
94
114
|
},
|
|
95
115
|
review: {
|
|
96
116
|
// No RESERVED_TYPES entry defines this sentinel (REVIEWER has no
|
|
@@ -102,6 +122,7 @@ export const PHASE_MARKERS = {
|
|
|
102
122
|
// `remediate` phase reads it off the issue, consistent with every other phase here.
|
|
103
123
|
remediate: {
|
|
104
124
|
completionMarker: RESERVED_TYPES.REMEDIATION.completionSentinel, // 'FORGE:REMEDIATION:COMPLETE'
|
|
125
|
+
header: 'FORGE:REMEDIATION',
|
|
105
126
|
},
|
|
106
127
|
close: {
|
|
107
128
|
// A GitHub label, not a comment marker — see `completionLabel` above.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
#
|
|
5
|
+
# check-blast-radius.sh — Enforce the FORGE:ARCHITECT blast-radius manifest against the final tree.
|
|
6
|
+
#
|
|
7
|
+
# Usage: check-blast-radius.sh --manifest <file> --base <ref> [--repo <path>]
|
|
8
|
+
#
|
|
9
|
+
# <file> is either the extracted manifest block or a whole FORGE:ARCHITECT comment body. Only the text between
|
|
10
|
+
# <!-- FORGE:BLAST_RADIUS:BEGIN --> and <!-- FORGE:BLAST_RADIUS:END --> is parsed (all such regions, concatenated).
|
|
11
|
+
# Grammar, one record per line (blank lines and markdown code-fence lines are the only other lines allowed):
|
|
12
|
+
# SYMBOL: id=<sN> kind=function|field|flag|schema|contract name=<symbol> query="<fixed-string>"
|
|
13
|
+
# HIT: symbol=<sN> file=<path> role=producer|consumer|sibling disposition=change
|
|
14
|
+
# HIT: symbol=<sN> file=<path> role=producer|consumer|sibling disposition=verified-unaffected reason=<one-token>
|
|
15
|
+
# Values contain no spaces (use hyphens in reason). query must match ^[A-Za-z0-9_.:/-]{3,80}$ and not start with "-".
|
|
16
|
+
#
|
|
17
|
+
# For every SYMBOL the query is re-run as a fixed-string search over the final tree (working tree, tracked plus
|
|
18
|
+
# untracked files, so it works before and after the commit). A file that matches must be in the change set
|
|
19
|
+
# (base...HEAD plus anything staged, modified or untracked) or carry a verified-unaffected row.
|
|
20
|
+
# A "change" row whose file is not in the change set is a planned-but-not-done item.
|
|
21
|
+
#
|
|
22
|
+
# Exit codes:
|
|
23
|
+
# 0 covered, or no manifest ("SKIP: no manifest")
|
|
24
|
+
# 1 UNLISTED: <symbol> <file> and/or NOT_DONE: <symbol> <file>
|
|
25
|
+
# 2 malformed manifest, invalid query, unresolvable base/repo, or a git failure — NEVER a pass; callers report DEGRADED
|
|
26
|
+
#
|
|
27
|
+
# Offline: no network, no gh. Portable to bash 3.2: no mapfile, no associative arrays, no sort -V.
|
|
28
|
+
|
|
29
|
+
set -uo pipefail
|
|
30
|
+
|
|
31
|
+
MAX_HITS=200
|
|
32
|
+
MANIFEST=""; BASE=""; REPO="."
|
|
33
|
+
die2() { echo "check-blast-radius: $*" >&2; exit 2; }
|
|
34
|
+
|
|
35
|
+
while [ "$#" -gt 0 ]; do
|
|
36
|
+
case "$1" in
|
|
37
|
+
--manifest) [ "$#" -ge 2 ] || die2 "--manifest needs a value"; MANIFEST="$2"; shift 2 ;;
|
|
38
|
+
--base) [ "$#" -ge 2 ] || die2 "--base needs a value"; BASE="$2"; shift 2 ;;
|
|
39
|
+
--repo) [ "$#" -ge 2 ] || die2 "--repo needs a value"; REPO="$2"; shift 2 ;;
|
|
40
|
+
*) die2 "usage: check-blast-radius.sh --manifest <file> --base <ref> [--repo <path>]" ;;
|
|
41
|
+
esac
|
|
42
|
+
done
|
|
43
|
+
[ -n "$MANIFEST" ] || die2 "usage: check-blast-radius.sh --manifest <file> --base <ref> [--repo <path>]"
|
|
44
|
+
[ -f "$MANIFEST" ] || die2 "manifest file not found: $MANIFEST"
|
|
45
|
+
|
|
46
|
+
TMP=$(mktemp -d "${TMPDIR:-/tmp}/check-blast-radius.XXXXXX") || die2 "mktemp failed"
|
|
47
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
48
|
+
|
|
49
|
+
# ---- extract BEGIN..END regions -------------------------------------------------------------------------
|
|
50
|
+
awk -v out="$TMP/region" '
|
|
51
|
+
index($0, "<!-- FORGE:BLAST_RADIUS:BEGIN -->") { if (inb) { bad = 1 } inb = 1; seen = 1; next }
|
|
52
|
+
index($0, "<!-- FORGE:BLAST_RADIUS:END -->") { if (!inb) { bad = 1 } inb = 0; next }
|
|
53
|
+
inb { print > out }
|
|
54
|
+
END { if (inb || bad) exit 3; if (!seen) exit 4 }
|
|
55
|
+
' "$MANIFEST"
|
|
56
|
+
case $? in
|
|
57
|
+
0) ;;
|
|
58
|
+
4) echo "SKIP: no manifest"; exit 0 ;;
|
|
59
|
+
*) die2 "unbalanced FORGE:BLAST_RADIUS:BEGIN/END markers" ;;
|
|
60
|
+
esac
|
|
61
|
+
[ -f "$TMP/region" ] || { echo "SKIP: no manifest"; exit 0; }
|
|
62
|
+
|
|
63
|
+
# has_symbol <id>: awk over the file (no grep -q in a pipeline: SIGPIPE under pipefail misreports the status)
|
|
64
|
+
has_symbol() { awk -F'\t' -v s="$1" '$1==s {ok=1} END{exit ok?0:1}' "$TMP/symbols"; }
|
|
65
|
+
|
|
66
|
+
# ---- parse ----------------------------------------------------------------------------------------------
|
|
67
|
+
: > "$TMP/symbols" # id<TAB>name<TAB>query
|
|
68
|
+
: > "$TMP/hits" # symbol<TAB>file<TAB>disposition
|
|
69
|
+
QRE='^[A-Za-z0-9_.:/-]{3,80}$'
|
|
70
|
+
FRE='^[A-Za-z0-9_./@+=:,-]+$'
|
|
71
|
+
LN=0
|
|
72
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
73
|
+
LN=$((LN+1))
|
|
74
|
+
line="${line%$'\r'}"
|
|
75
|
+
trimmed="${line#"${line%%[![:space:]]*}"}"
|
|
76
|
+
case "$trimmed" in
|
|
77
|
+
""|'```'*) continue ;;
|
|
78
|
+
"SYMBOL: "*) kind_rec=SYMBOL; rest="${trimmed#SYMBOL: }" ;;
|
|
79
|
+
"HIT: "*) kind_rec=HIT; rest="${trimmed#HIT: }" ;;
|
|
80
|
+
*) die2 "line $LN: unrecognised record: $trimmed" ;;
|
|
81
|
+
esac
|
|
82
|
+
id=""; kind=""; name=""; query=""; symbol=""; file=""; role=""; disp=""; reason=""
|
|
83
|
+
set -f
|
|
84
|
+
for tok in $rest; do
|
|
85
|
+
case "$tok" in
|
|
86
|
+
*=*) k="${tok%%=*}"; v="${tok#*=}" ;;
|
|
87
|
+
*) set +f; die2 "line $LN: token without key=value: $tok" ;;
|
|
88
|
+
esac
|
|
89
|
+
case "$k" in
|
|
90
|
+
id) id="$v" ;; kind) kind="$v" ;; name) name="$v" ;;
|
|
91
|
+
query) v="${v#\"}"; v="${v%\"}"; query="$v" ;;
|
|
92
|
+
symbol) symbol="$v" ;; file) file="$v" ;; role) role="$v" ;;
|
|
93
|
+
disposition) disp="$v" ;; reason) reason="$v" ;;
|
|
94
|
+
*) set +f; die2 "line $LN: unknown key: $k" ;;
|
|
95
|
+
esac
|
|
96
|
+
done
|
|
97
|
+
set +f
|
|
98
|
+
if [ "$kind_rec" = SYMBOL ]; then
|
|
99
|
+
case "$id" in s[0-9]*) ;; *) die2 "line $LN: SYMBOL id must look like s1: '$id'" ;; esac
|
|
100
|
+
case "$id" in *[!A-Za-z0-9]*) die2 "line $LN: invalid SYMBOL id: '$id'" ;; esac
|
|
101
|
+
case "$kind" in function|field|flag|schema|contract) ;; *) die2 "line $LN: invalid kind: '$kind'" ;; esac
|
|
102
|
+
[ -n "$name" ] || die2 "line $LN: SYMBOL without name"
|
|
103
|
+
[ -n "$query" ] || die2 "line $LN: empty query"
|
|
104
|
+
case "$query" in -*) die2 "line $LN: query must not start with '-'" ;; esac
|
|
105
|
+
[[ "$query" =~ $QRE ]] || die2 "line $LN: invalid query (want $QRE): $query"
|
|
106
|
+
has_symbol "$id" && die2 "line $LN: duplicate SYMBOL id: $id"
|
|
107
|
+
printf '%s\t%s\t%s\n' "$id" "$name" "$query" >> "$TMP/symbols"
|
|
108
|
+
else
|
|
109
|
+
[ -n "$symbol" ] || die2 "line $LN: HIT without symbol"
|
|
110
|
+
has_symbol "$symbol" || die2 "line $LN: HIT references unknown symbol: $symbol"
|
|
111
|
+
[ -n "$file" ] || die2 "line $LN: HIT without file"
|
|
112
|
+
case "$file" in -*) die2 "line $LN: file must not start with '-'" ;; esac
|
|
113
|
+
[[ "$file" =~ $FRE ]] || die2 "line $LN: invalid file path: $file"
|
|
114
|
+
case "$role" in producer|consumer|sibling) ;; *) die2 "line $LN: invalid role: '$role'" ;; esac
|
|
115
|
+
case "$disp" in
|
|
116
|
+
change) ;;
|
|
117
|
+
verified-unaffected) [ -n "$reason" ] || die2 "line $LN: verified-unaffected requires reason=<token>" ;;
|
|
118
|
+
*) die2 "line $LN: invalid disposition: '$disp'" ;;
|
|
119
|
+
esac
|
|
120
|
+
printf '%s\t%s\t%s\n' "$symbol" "$file" "$disp" >> "$TMP/hits"
|
|
121
|
+
fi
|
|
122
|
+
done < "$TMP/region"
|
|
123
|
+
|
|
124
|
+
if [ ! -s "$TMP/symbols" ]; then
|
|
125
|
+
if [ -s "$TMP/hits" ]; then die2 "HIT records without any SYMBOL"; fi
|
|
126
|
+
echo "SKIP: no manifest"; exit 0
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# ---- repo, base, change set -----------------------------------------------------------------------------
|
|
130
|
+
case "$BASE" in ""|-*) die2 "invalid --base: '$BASE'" ;; esac
|
|
131
|
+
git -C "$REPO" rev-parse --is-inside-work-tree >/dev/null 2>&1 || die2 "not a git work tree: $REPO"
|
|
132
|
+
git -C "$REPO" rev-parse --verify --quiet "${BASE}^{commit}" >/dev/null 2>&1 || die2 "cannot resolve base ref '$BASE'"
|
|
133
|
+
|
|
134
|
+
: > "$TMP/changed"
|
|
135
|
+
git -C "$REPO" diff -z --name-only "${BASE}...HEAD" -- > "$TMP/c1" 2>/dev/null || die2 "git diff ${BASE}...HEAD failed"
|
|
136
|
+
git -C "$REPO" diff -z --name-only HEAD -- > "$TMP/c2" 2>/dev/null || die2 "git diff HEAD failed"
|
|
137
|
+
git -C "$REPO" ls-files -z --others --exclude-standard > "$TMP/c3" 2>/dev/null || die2 "git ls-files failed"
|
|
138
|
+
cat "$TMP/c1" "$TMP/c2" "$TMP/c3" | tr '\0' '\n' | sed '/^$/d' | sort -u > "$TMP/changed"
|
|
139
|
+
|
|
140
|
+
# ---- check ----------------------------------------------------------------------------------------------
|
|
141
|
+
RC=0
|
|
142
|
+
while IFS="$(printf '\t')" read -r sid sname squery; do
|
|
143
|
+
git -C "$REPO" grep -z --untracked -l -F -e "$squery" -- > "$TMP/g" 2>"$TMP/gerr"
|
|
144
|
+
grc=$?
|
|
145
|
+
if [ "$grc" -gt 1 ]; then die2 "git grep failed for symbol $sid (rc=$grc): $(head -n 1 "$TMP/gerr")"; fi
|
|
146
|
+
tr '\0' '\n' < "$TMP/g" | sed '/^$/d' | sort -u > "$TMP/found"
|
|
147
|
+
n=$(wc -l < "$TMP/found" | tr -d ' ')
|
|
148
|
+
[ "$n" -le "$MAX_HITS" ] || die2 "query too broad for symbol $sid ($n files > $MAX_HITS): use a more distinctive query"
|
|
149
|
+
while IFS= read -r f; do
|
|
150
|
+
[ -n "$f" ] || continue
|
|
151
|
+
if grep -Fxq -- "$f" "$TMP/changed"; then continue; fi
|
|
152
|
+
if awk -F'\t' -v s="$sid" -v f="$f" '$1==s && $2==f && $3=="verified-unaffected" {ok=1} END{exit ok?0:1}' "$TMP/hits"; then continue; fi
|
|
153
|
+
echo "UNLISTED: $sid $f"; RC=1
|
|
154
|
+
done < "$TMP/found"
|
|
155
|
+
# planned-but-not-done: change rows for this symbol whose file is not in the change set
|
|
156
|
+
while IFS="$(printf '\t')" read -r hs hf hd; do
|
|
157
|
+
[ "$hs" = "$sid" ] && [ "$hd" = change ] || continue
|
|
158
|
+
grep -Fxq -- "$hf" "$TMP/changed" || { echo "NOT_DONE: $sid $hf"; RC=1; }
|
|
159
|
+
done < "$TMP/hits"
|
|
160
|
+
done < "$TMP/symbols"
|
|
161
|
+
|
|
162
|
+
# HIT rows whose symbol exists were validated at parse time; a manifest that covers every hit passes.
|
|
163
|
+
[ "$RC" -eq 0 ] && echo "OK: blast-radius manifest covered ($(wc -l < "$TMP/symbols" | tr -d ' ') symbol(s))"
|
|
164
|
+
exit "$RC"
|