forgedock 1.1.11 → 1.3.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.
Files changed (109) hide show
  1. package/README.md +9 -3
  2. package/bin/cli-spawn-shared.mjs +25 -0
  3. package/bin/engine/phases.mjs +178 -28
  4. package/bin/engine/state.mjs +39 -7
  5. package/bin/engine-cli.mjs +85 -3
  6. package/bin/engine.mjs +71 -8
  7. package/bin/forgedock.mjs +322 -20
  8. package/bin/hooks/pre-tool-use.mjs +345 -10
  9. package/bin/init-enrich-api.mjs +12 -4
  10. package/bin/init-enrich-cli.mjs +328 -0
  11. package/bin/init-enrich.mjs +137 -0
  12. package/bin/journey.mjs +116 -15
  13. package/bin/runner.mjs +653 -10
  14. package/commands/adopt.md +1 -1
  15. package/commands/analytics.md +10 -7
  16. package/commands/audit-agents.md +29 -6
  17. package/commands/audit.md +9 -20
  18. package/commands/autopilot.md +52 -78
  19. package/commands/changelog.md +1 -1
  20. package/commands/ci-audit.md +19 -10
  21. package/commands/cleanup.md +1 -1
  22. package/commands/compat-audit.md +1 -1
  23. package/commands/deploy-info.md +1 -1
  24. package/commands/deploy-pr.md +1 -1
  25. package/commands/design-bench.md +1 -1
  26. package/commands/design-render-critique-loop.md +1 -1
  27. package/commands/diagnose.md +1 -1
  28. package/commands/forge-stats.md +6 -5
  29. package/commands/forgedock-init.md +1 -1
  30. package/commands/geo-audit.md +9 -6
  31. package/commands/incident-response.md +15 -5
  32. package/commands/issue.md +302 -15
  33. package/commands/milestone.md +45 -18
  34. package/commands/optimize.md +1 -1
  35. package/commands/orchestrate/phase-1-resolve.md +20 -5
  36. package/commands/orchestrate/phase-4-execution.md +8 -1
  37. package/commands/orchestrate/phase-5-cleanup.md +43 -2
  38. package/commands/orchestrate/phase-6-report.md +1 -0
  39. package/commands/orchestrate.md +1 -1
  40. package/commands/pipeline-health/phase-4-proposals.md +40 -10
  41. package/commands/pipeline-health/phase-5-report.md +140 -13
  42. package/commands/pipeline-health.md +1 -1
  43. package/commands/pipeline-resume.md +1 -1
  44. package/commands/pipeline-status.md +1 -1
  45. package/commands/qa-sweep.md +27 -4
  46. package/commands/recover-orphans.md +1 -1
  47. package/commands/replay.md +16 -2
  48. package/commands/review-pr-agents/protocols.md +4 -0
  49. package/commands/review-pr-agents/spec-cli.md +179 -0
  50. package/commands/review-pr-agents.md +7 -4
  51. package/commands/review-pr-staging.md +44 -15
  52. package/commands/review-pr.md +122 -28
  53. package/commands/rollback.md +15 -5
  54. package/commands/security-audit.md +14 -11
  55. package/commands/signal-planner.md +32 -12
  56. package/commands/spec-doctor.md +1 -1
  57. package/commands/sync-ecosystem.md +54 -11
  58. package/commands/test-gate.md +49 -11
  59. package/commands/upgrade-deps.md +518 -0
  60. package/commands/validate.md +16 -5
  61. package/commands/work-on/build/architect.md +1 -1
  62. package/commands/work-on/build/context.md +1 -1
  63. package/commands/work-on/build/implement.md +2 -2
  64. package/commands/work-on/build/validate.md +64 -2
  65. package/commands/work-on/build.md +10 -2
  66. package/commands/work-on/close.md +1 -1
  67. package/commands/work-on/decompose.md +35 -25
  68. package/commands/work-on/investigate.md +2 -2
  69. package/commands/work-on/remediate.md +1 -1
  70. package/commands/work-on/review.md +5 -3
  71. package/commands/work-on-monolithic.md +1 -1
  72. package/commands/work-on.md +28 -7
  73. package/docs/AIDER.md +264 -0
  74. package/docs/AUTOPILOT-CRON.md +130 -0
  75. package/docs/CI.md +18 -0
  76. package/docs/CONFIG.md +41 -2
  77. package/docs/CURSOR.md +135 -0
  78. package/docs/OPENCODE.md +312 -0
  79. package/docs/demo.gif +0 -0
  80. package/docs/eval/README.md +4 -4
  81. package/docs/launch/show-hn.md +3 -3
  82. package/docs/site/annotations-explained.md +2 -2
  83. package/docs/site/architecture.md +180 -0
  84. package/docs/site/command-reference.md +38 -0
  85. package/docs/site/configuration.md +243 -0
  86. package/docs/site/for-companies.md +4 -4
  87. package/docs/site/forge-annotation-protocol.md +1 -1
  88. package/docs/site/github-is-the-memory.md +2 -2
  89. package/docs/site/how-it-works.md +1 -1
  90. package/docs/site/public/llms-full.txt +380 -0
  91. package/docs/site/public/llms.txt +35 -0
  92. package/docs/spec/review-protocol.md +6 -0
  93. package/docs/spec-graph-schema.md +3 -3
  94. package/package.json +2 -2
  95. package/packages/protocol/README.md +75 -0
  96. package/packages/protocol/src/card.js +114 -0
  97. package/packages/protocol/src/cli.js +4 -77
  98. package/packages/protocol/src/emit.js +14 -3
  99. package/packages/protocol/src/index.js +2 -1
  100. package/packages/protocol/src/parse.js +17 -8
  101. package/packages/protocol/src/types.js +1 -1
  102. package/packages/protocol/src/validate.js +11 -0
  103. package/scripts/check-command-side-effects.sh +261 -0
  104. package/scripts/check-spec-markers.sh +273 -0
  105. package/scripts/classify-lane.sh +29 -7
  106. package/scripts/forge-run.sh +322 -0
  107. package/templates/cross-repo-trigger.yml +282 -0
  108. package/templates/cursor/forge.mdc +148 -0
  109. package/templates/github-actions/forgedock-autopilot.yml +113 -0
@@ -0,0 +1,179 @@
1
+ ---
2
+ install: internal
3
+ ---
4
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
5
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
6
+
7
+ # Agent: Spec/CLI Auditor
8
+
9
+ > Read `review-pr-agents/protocols.md` for the Evidence-Based Review Protocol, Structured Findings Protocol, Per-Agent Input Scoping rules, and Tool-Result Truncation Discipline that all agents must follow.
10
+
11
+
12
+
13
+ **Trigger**: SPEC_CLI domain detected (PR touches `commands/**` or `bin/**`)
14
+ **Type**: `security-exploit-auditor` | **Model**: `sonnet`
15
+
16
+ **Prompt template:**
17
+ ```
18
+ You are auditing PR #[PR_NUMBER] for spec-as-code and CLI correctness in [PROJECT_NAME].
19
+
20
+ ## Context
21
+ - PR title: [TITLE]
22
+ - Files changed: [FILE_LIST]
23
+ [PROJECT_CONTEXT]
24
+
25
+ ## Spec/CLI Conventions
26
+
27
+ [DOMAIN_CONTEXT]
28
+
29
+ If no spec/CLI context is configured above, derive conventions from the changed files: read sibling `.mjs` files for the established exec pattern and sibling `commands/*.md` files for the established side-effect gating pattern.
30
+
31
+ ## Why This Agent Exists
32
+ `commands/**` (the pipeline's spec-as-code prompts) and `bin/**` (the Node CLI) ARE the product — every `commands/*.md` file is executed as instructions by an AI agent on every customer repo, and every `bin/*.mjs` file runs with the user's local `gh` credentials. The General Security agent's checklist is Python/FastAPI/Docker-shaped and does not cover exec/spawn injection in `.mjs`, `gh` write/egress side effects, or command-spec side-effect changes — this agent exists to close exactly that gap (ref: forge#1586, forge#1587, both of which reached staging via PR #1575 uncaught by any automated persona).
33
+
34
+ ## Step 1: Read the Diff
35
+
36
+ The diff is pre-supplied as `[DOMAIN_DIFF_SLICE]` and is capped at ~100K chars. Do NOT re-fetch the full diff — use what was provided.
37
+
38
+ ```bash
39
+ gh pr diff [PR_NUMBER] --name-only
40
+ # Use the pre-supplied diff: [DOMAIN_DIFF_SLICE]
41
+ ```
42
+
43
+ Build the two file subsets this agent scans (note: `gh pr diff` does NOT accept path arguments — always filter the name-only list with grep):
44
+
45
+ ```bash
46
+ CLI_FILES=$(gh pr diff [PR_NUMBER] --name-only | grep -E '^bin/.*\.(mjs|js|ts)$')
47
+ SPEC_FILES=$(gh pr diff [PR_NUMBER] --name-only | grep -E '^commands/.*\.md$')
48
+ ```
49
+
50
+ ## Step 2: Shell-String Exec/Spawn Interpolation (CLI files)
51
+
52
+ Node's `child_process.exec()`/`execSync()` run their argument through `/bin/sh -c` (or `cmd.exe` on Windows) — any interpolated string becomes shell-interpretable. `execFile()`/`execFileSync()`/`spawn()` with an args array never invoke a shell and are safe by construction.
53
+
54
+ Scan the added lines of the pre-supplied diff for exec calls with template-string or concatenated arguments (attribute each hit to its file via the nearest preceding `diff --git` header):
55
+
56
+ ```bash
57
+ # Added lines with exec/execSync taking a template string or concatenation
58
+ echo "[DOMAIN_DIFF_SLICE]" | grep -E '^\+' | grep -E '\bexecSync?\(`|\bexecSync?\([^)]*\+' | grep -v '^\+\+\+'
59
+ ```
60
+
61
+ For each hit, read the surrounding file (from `$CLI_FILES`) with the Read tool to trace the interpolated variable to its source. Classify:
62
+ - `execSync(\`gh ... ${variable} ...\`)` or `execSync("cmd " + variable)`: **CONFIRMED HIGH** if `variable` derives from ANY external/config-controlled source — a repo-local `forge.yaml` field, CLI argv, an environment variable, or a `gh api` response body. This is the exact shape of forge#1586 (`ghJson()` in `bin/report.mjs` interpolated `forge.yaml`-derived `owner`/`repo` into an `execSync` string parsed by a hand-rolled YAML reader that did not reject `$(...)`, backticks, `;`, or `&`).
63
+ - `execFileSync("gh", [...args])` or `spawn(cmd, [args], {shell: false})`: **OK** — this is the safe pattern already used elsewhere in this codebase (e.g. `watch()` in `bin/forgedock.mjs`).
64
+ - `execSync`/`exec` called with a **fully static string literal** (no interpolation, no concatenation): **OK** — no injectable surface.
65
+
66
+ **Do not stop at the first call site** — scan every `exec(`/`execSync(` occurrence in the diff, not just the one nearest an obviously-tainted variable. A file can fix one call site and leave a sibling one unconverted.
67
+
68
+ ## Step 3: `gh` Write/Egress Side Effects (all files)
69
+
70
+ Any new or modified `gh` invocation that **writes** (mutates GitHub state) or **publishes** (makes something world-readable) is a side-effect change and must be justified by the PR's stated purpose.
71
+
72
+ ```bash
73
+ # gh write/publish operations in added lines
74
+ echo "[DOMAIN_DIFF_SLICE]" | grep -E '^\+' | grep -E \
75
+ 'gh (gist create|pr merge|pr close|issue create|issue close|issue comment|repo edit|label create|release create|workflow run)' | \
76
+ grep -v '^\+\+\+'
77
+
78
+ # Newly-added --public / visibility flags
79
+ echo "[DOMAIN_DIFF_SLICE]" | grep -E '^\+' | grep -E '\-\-public\b|visibility.*public|make.*public' | grep -v '^\+\+\+'
80
+ ```
81
+
82
+ For each hit:
83
+ - A new `gh gist create ... --public` (or any new default-to-public visibility change) that writes issue/PR content, root-cause analysis, or file paths: **CONFIRMED HIGH** — this is the exact shape of forge#1587 (`commands/work-on/close.md` published the pipeline's per-issue memory index — titles, root causes, affected file paths — to a world-readable Gist; a secret gist is sufficient since the read side already authenticates via `gh`).
84
+ - A new `gh pr merge`, `gh issue close`, or `gh label` mutation added to a command spec (`commands/*.md`) that was not previously present: **LIKELY MEDIUM** — verify the new destructive/mutating step is gated behind the same preconditions (auto-merge flag, terminal-state check, etc.) as sibling mutations in the same file. An ungated new mutation can fire on a code path the spec author didn't intend.
85
+ - Read-only `gh` calls (`gh pr view`, `gh issue list`, `gh api ... GET`) or a write call gated behind an explicit `--auto-merge`/confirmation flag already present in the spec: **OK**.
86
+
87
+ ## Step 4: Command-Spec Side-Effect Changes (spec files)
88
+
89
+ `commands/*.md` files are prompts, but they are executable specifications — they instruct an autonomous agent to run shell/`gh` commands with real side effects on every repo that installs ForgeDock. Review diffs to these files the same way you would review a code diff to a function that performs the same operations.
90
+
91
+ ```bash
92
+ # Added lines in the pre-supplied diff that introduce/alter gh, git-destructive, delete, or network commands
93
+ echo "[DOMAIN_DIFF_SLICE]" | grep -E '^\+' | grep -E '\bgh \w|\bgit (push|reset|clean|rm)|\brm -rf|\bcurl\b|\bwget\b' | grep -v '^\+\+\+'
94
+ ```
95
+
96
+ Attribute each hit to its `commands/*.md` file via the nearest preceding `diff --git` header, then ask: "Does this new/changed line cause a write, delete, publish, or network egress that the PREVIOUS version of this spec did not perform, or performed with a narrower scope (e.g. `gist create` without `--public` → with `--public`; `git push` → `git push --force`)?" If yes: **CONFIRMED** finding — severity HIGH if the new side effect is destructive or publishes data, MEDIUM if it is a scope-widening of an existing side effect (e.g. a new label added to an existing bulk-edit loop). <!-- allowlist:check-command-side-effects — the `git push` above is a cited example of a scope-widening diff this auditor looks for, not a command this spec runs -->
97
+
98
+
99
+ **Do NOT treat `commands/*.md` diffs as "just docs".** A one-line change to a `gh` command embedded in a code fence has the exact same blast radius as the equivalent change in a `.py`/`.mjs` file — it runs autonomously, unattended, across every customer repo using the pipeline.
100
+
101
+ ## Step 5: Secret Handling (CLI files)
102
+
103
+ ```bash
104
+ # Added lines touching secret-like identifiers
105
+ echo "[DOMAIN_DIFF_SLICE]" | grep -E '^\+' | grep -iE 'token|secret|api[_-]?key|password|credential' | grep -v '^\+\+\+'
106
+ ```
107
+
108
+ For each hit attributed to a file in `$CLI_FILES`:
109
+ - A token/secret/key passed as a CLI argument to a child process (visible to any other process via `ps`/Task Manager) rather than via an env var or stdin: **CONFIRMED HIGH**.
110
+ - A token/secret logged via `console.log`/`console.error`, written to a non-gitignored file, or included in an error message that could be posted back to a public GitHub comment: **CONFIRMED HIGH**.
111
+ - A token/secret read from `process.env` and passed through `env:` to a child process without being logged or echoed: **OK**.
112
+
113
+ ## Step 6: Network/Publish Calls (CLI files)
114
+
115
+ ```bash
116
+ # Added lines with network or publish calls
117
+ echo "[DOMAIN_DIFF_SLICE]" | grep -E '^\+' | grep -iE '\bfetch\(|\bhttps?\.request\(|\baxios\.|npm publish|npm.*publish' | grep -v '^\+\+\+'
118
+ ```
119
+
120
+ For each hit: identify the destination host. A call to `api.github.com`/`github.com` via the already-authenticated `gh` CLI wrapper is expected. A call to any OTHER host (telemetry endpoint, analytics service, third-party API) introduced without being named in the PR title/description: **LIKELY MEDIUM** — undisclosed egress from a CLI tool that runs with the user's local credentials is a trust-surface concern even when not directly exploitable.
121
+
122
+ ## Step 7: Post Findings
123
+ ```bash
124
+ gh pr comment [PR_NUMBER] --body "$(cat <<'EOF'
125
+ ## Spec/CLI Audit
126
+
127
+ ### Risk Level: [LOW/MEDIUM/HIGH/CRITICAL]
128
+
129
+ ### Shell/Exec Injection Findings
130
+ | File:Line | Pattern | Tainted Source | Confidence | Evidence |
131
+ |-----------|---------|-----------------|------------|----------|
132
+ | ... | execSync template string | forge.yaml field / argv / env | CONFIRMED | [code path] |
133
+
134
+ ### gh Write/Egress Side Effects
135
+ | File:Line | Operation | New or Widened? | Confidence | Evidence |
136
+ |-----------|-----------|------------------|------------|----------|
137
+ | ... | gh gist create --public | New | CONFIRMED | [explanation] |
138
+
139
+ ### Command-Spec Side-Effect Changes
140
+ | File:Line | Old Behavior | New Behavior | Confidence |
141
+ |-----------|--------------|--------------|------------|
142
+ | ... | ... | ... | ... |
143
+
144
+ ### Secret Handling
145
+ [Any secrets passed as argv, logged, or written to disk — or "None found"]
146
+
147
+ ### Network/Publish Calls
148
+ [Any new external network destinations — or "None found — all egress via gh CLI to github.com"]
149
+
150
+ ### Files Reviewed
151
+ [List all files checked]
152
+
153
+ ---
154
+ *Spec/CLI audit*
155
+
156
+ <!-- REVIEW-FINDINGS-START -->
157
+ <!-- FINDING:SPEC-1|CONFIDENCE|SEVERITY|file.mjs:line|Summary -->
158
+ <!-- (add one FINDING line per issue found — include ALL confidence levels) -->
159
+ <!-- REVIEW-FINDINGS-END -->
160
+ EOF
161
+ )"
162
+ ```
163
+
164
+ **Structured Findings**: Include the structured findings block above at the end of your comment. Your prefix: `SPEC`. See the Structured Findings Protocol in `protocols.md` for format rules.
165
+ ```
166
+
167
+ ### Coverage Matrix — SPEC Agent
168
+
169
+ | Defect Category | Check Item(s) | Status | Ref |
170
+ |----------------|---------------|--------|-----|
171
+ | Shell-string exec/spawn interpolation in `.mjs` | Step 2 | COVERED | forge#1586 |
172
+ | `gh` write/egress side effects (gist --public, pr merge, issue create) | Step 3 | COVERED | forge#1587 |
173
+ | Command-spec side-effect changes in `commands/*.md` | Step 4 | COVERED | |
174
+ | Secret handling in `.mjs` (argv exposure, logging) | Step 5 | COVERED | |
175
+ | Undisclosed network/publish calls | Step 6 | COVERED | |
176
+ | Prototype pollution / unsafe `eval`/`new Function` in `.mjs` | — | GAP | |
177
+ | Path traversal via CLI-supplied file paths | — | GAP | |
178
+
179
+ ---
@@ -11,10 +11,13 @@ to avoid loading the full catalog on every invocation. Protocols live in `docs/s
11
11
  Do not modify this file without also updating `review-pr.md`. To update either protocol, edit
12
12
  `docs/spec/review-protocol.md` and sync the copy to `commands/review-pr-agents/protocols.md`.
13
13
 
14
- **NEVER use the Agent tool.** The orchestrator dispatches review agents via `Task` only. `Agent` spawns
15
- opaque subprocesses that bypass the `allowed-tools` constraint, cannot post structured findings to the PR,
16
- and are not tracked by the review pipeline. Every agent template is intended to be passed as the `prompt`
17
- argument to a `Task(...)` call.
14
+ **Dispatch tool: `Task` preferred, `Agent` is the documented fallback.** The orchestrator (`/review-pr` or
15
+ `/review-pr-staging`) resolves the dispatch tool once per invocation per its **Sub-Agent Dispatch Tool
16
+ Resolution** rule: `Task` when available, `Agent` when `Task` is absent from the environment — never inline
17
+ self-review as a substitute for either. Every agent template in this catalog is intended to be passed as
18
+ the `prompt` argument to whichever tool the orchestrator resolved (`Task(...)` or `Agent(...)`), with the
19
+ same per-agent isolation and the same requirement that each agent posts structured findings to the PR
20
+ directly (`gh pr comment`) rather than relaying them through the orchestrator's own context.
18
21
 
19
22
  <!-- FORGE:PROTOCOL_SOURCE — canonical definition lives in docs/spec/review-protocol.md -->
20
23
 
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  description: Staging review mode — comprehensive review of staging branch before deploy to main
3
- argument-hint: [PR number or "staging"]
4
- allowed-tools: Task, Bash, Read, Grep, Glob, WebFetch
5
- install: extras
3
+ argument-hint: "[PR number or \"staging\"]"
4
+ allowed-tools: Task, Agent, Bash, Read, Grep, Glob, WebFetch
5
+ install: core
6
6
  ---
7
7
  <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
8
8
  <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
@@ -15,20 +15,33 @@ Performs comprehensive review of `staging` before merging to `main`. Handles lar
15
15
 
16
16
  **Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
17
17
  **NEVER use plan mode (EnterPlanMode).**
18
- **NEVER use the Agent tool** — this spec uses `Task` for domain agent dispatch. The Agent tool bypasses the allowed-tools constraint declared in this spec's frontmatter and produces opaque output that cannot be structured into the review verdict.
18
+ **Sub-agent dispatch tool: `Task` preferred, `Agent` is the documented fallback.** This spec dispatches domain review agents via a sub-agent-spawning tool. Resolve which one is available ONCE per invocation, before Phase 3, per the **Sub-Agent Dispatch Tool Resolution** rule below — do not halt to ask the operator which tool to use. Never fall back to reviewing inline in the orchestrator's own context; that is a strictly weaker substitute for an isolated fresh-context reviewer and is not a permitted fallback.
19
19
 
20
20
  <!-- FORGE:SPEC_LOADED — review-pr-staging.md loaded and active. Agent is bound by this spec. -->
21
21
 
22
+ ## Sub-Agent Dispatch Tool Resolution (MANDATORY — run once, before Phase 3)
23
+
24
+ This spec dispatches Bug Hunter and domain review agents via a sub-agent-spawning tool. Different runtimes expose different tools for this — resolve deterministically, once per invocation, and use the same tool for every dispatch call in this run:
25
+
26
+ 1. **If `Task` is available in the current environment**: set `{DISPATCH_TOOL} = Task`. This is the preferred tool — tightest `allowed-tools` scoping. (Identical resolution logic to `/review-pr` Phase 3C — do not diverge.)
27
+ 2. **Else if `Agent` is available**: set `{DISPATCH_TOOL} = Agent`. This is the documented fallback, not a degraded path — use it exactly as you would `Task`: one call per selected agent (Bug Hunters in Phase 3, Code Quality in Phase 4, domain agents in Phase 5), same prompt template, `subagent_type: "general-purpose"` (or the closest equivalent the environment offers), same requirement that each agent posts its own findings directly to the PR via `gh pr comment`. Isolation and fresh-context review are preserved either way.
28
+ 3. **Neither tool is available**: this is a genuine setup defect, not a routing decision — HARD STOP, post a PR/issue comment explaining that no sub-agent dispatch tool is available, add `needs-human`, and exit without posting a verdict. Do NOT fall back to reviewing inline in the orchestrator's own context.
29
+
30
+ **Do not halt to ask the operator which tool to use.** Steps 1–2 are deterministic and fully resolve the common case; only step 3 (both absent) requires a stop, and even then the action is HARD STOP + `needs-human`, not a question back to the operator.
31
+
32
+ Everywhere this file says `Task(...)`, read it as `{DISPATCH_TOOL}(...)` using the value resolved here.
33
+
22
34
  ## Forbidden Tools Self-Check
23
35
 
24
- **Before executing any phase**, verify you are NOT using any of these tools:
36
+ **Before executing any phase**, verify you are NOT using any of these:
25
37
 
26
- | Tool | Status | Reason |
38
+ | Tool/Pattern | Status | Reason |
27
39
  |------|--------|--------|
28
- | `Agent` | **FORBIDDEN** | Bypasses allowed-tools constraint; produces opaque output that cannot be structured into the review verdict |
40
+ | `Agent`, when `Task` is available | **FORBIDDEN** | `Task` is preferred whenever present — `Agent` is only the fallback for when `Task` is absent, not a free substitute |
41
+ | Inline self-review (no sub-agent spawn at all) | **FORBIDDEN** | Bypasses isolated fresh-context review entirely — always spawn via the resolved `{DISPATCH_TOOL}`, never review directly in the orchestrator's own context |
29
42
  | `EnterPlanMode` | **FORBIDDEN** | Breaks execution context; phases must be executed, not planned |
30
43
 
31
- If you find yourself about to call `Agent(...)`, stop and use `Task(...)` instead.
44
+ If you find yourself about to call `Agent(...)` while `Task` is available, stop and use `Task(...)` instead. If neither `Task` nor `Agent` is available, do not fall through to inline review — follow step 3 of Sub-Agent Dispatch Tool Resolution above.
32
45
 
33
46
  ---
34
47
 
@@ -677,13 +690,16 @@ Check for open review-finding issues at same file:line → skip. Closed issues a
677
690
  ### 7F: Create Issues
678
691
  Sequential creation. Title: `Staging Review: {summary} (staging → main)`. Labels: review-finding, needs-validation, staging-review, priority:P1/priority:P2/priority:P3. Body includes: source branch context (`staging`), code context, evidence, validation checklist.
679
692
 
680
- **For each finding** (that passes dedup), create issue:
693
+ **For each finding** (that passes dedup), create issue through the `/issue` create-hook's programmatic invocation contract (see `commands/issue.md` § "Programmatic Invocation Contract") instead of calling the raw issue-creation command directly:
681
694
  ```bash
682
- ISSUE_NUM=$(gh issue create \
683
- -R {GH_REPO} \
684
- --title "chore: [summary] (staging review — PR #${PR_NUMBER})" \
685
- --label "review-finding,needs-validation,staging-review,{priority}" \
686
- --body "$(cat <<'ISSUE_EOF'
695
+ STAGING_FINDING_TITLE="chore: [summary] (staging review — PR #${PR_NUMBER})"
696
+ # Defense-in-depth: /issue's arg tokenizer (commands/issue.md, forge#2094) uses
697
+ # an xargs-based tokenizer that never expands backtick/$(...) substitution, so
698
+ # this is no longer required for safety — but strip it anyway so the raw title
699
+ # stays readable if it round-trips through any other eval-based consumer.
700
+ STAGING_FINDING_TITLE=$(printf '%s' "$STAGING_FINDING_TITLE" | tr '`' "'" | sed 's/\$(/$ (/g')
701
+ STAGING_FINDING_BODY_FILE=$(mktemp)
702
+ cat <<'ISSUE_EOF' > "$STAGING_FINDING_BODY_FILE"
687
703
  ## Problem
688
704
 
689
705
  [One sentence: what bug or issue was found. Where it occurs (`file:line`) and what it causes.]
@@ -716,7 +732,20 @@ Files that need changes:
716
732
  - [ ] Finding validated: VALIDATED / FALSE_POSITIVE / INCONCLUSIVE
717
733
  - [ ] If VALIDATED: fix implemented and tested on correct branch
718
734
  ISSUE_EOF
719
- )" --json number --jq '.number')
735
+
736
+ # --label is repeatable (not comma-joined) per the /issue programmatic contract.
737
+ Skill(skill="issue", args="--title \"$STAGING_FINDING_TITLE\" --body-file \"$STAGING_FINDING_BODY_FILE\" --label review-finding --label needs-validation --label staging-review --label \"{priority}\"")
738
+ rm -f "$STAGING_FINDING_BODY_FILE"
739
+
740
+ # /issue has no machine-readable return contract — resolve the created issue's number by
741
+ # exact-title search immediately after the call (title embeds ${PR_NUMBER} + summary, unique
742
+ # enough for a reliable single-match lookup). Retry to absorb GitHub Search API indexing lag.
743
+ ISSUE_NUM=""
744
+ for _resolve_attempt in 1 2 3; do
745
+ ISSUE_NUM=$(gh issue list -R {GH_REPO} --search "in:title \"${STAGING_FINDING_TITLE}\"" --state open --limit 1 --json number --jq '.[0].number // empty')
746
+ [ -n "$ISSUE_NUM" ] && break
747
+ sleep 2
748
+ done
720
749
  ```
721
750
 
722
751
  Labels: `review-finding` + `needs-validation` + `staging-review` + priority (`priority:P1` CONFIRMED, `priority:P2` LIKELY, `priority:P3` POSSIBLE).
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Context-aware PR review — analyzes what the PR touches, spawns domain-specific agents with project conventions. Supports staging reviews.
3
- argument-hint: [PR number, URL, "open", or "staging" for feature→main review]
4
- allowed-tools: Task, Bash, Read, Grep, Glob, WebFetch, Skill
3
+ argument-hint: "[PR number, URL, \"open\", or \"staging\" for feature→main review]"
4
+ allowed-tools: Task, Agent, Bash, Read, Grep, Glob, WebFetch, Skill
5
5
  ---
6
6
  <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
7
7
  <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
@@ -11,7 +11,7 @@ allowed-tools: Task, Bash, Read, Grep, Glob, WebFetch, Skill
11
11
  **Input**: $ARGUMENTS
12
12
 
13
13
  **NEVER use plan mode (EnterPlanMode)** during review — it breaks execution context.
14
- **NEVER use the Agent tool** — review-pr dispatches domain agents via `Task` tool only. `Agent` spawns opaque subprocesses that bypass the allowed-tools constraint and cannot post structured findings to the PR. Always use `Task(...)` for review agent launch (Phase 3C).
14
+ **Sub-agent dispatch tool: `Task` preferred, `Agent` is the documented fallback.** review-pr dispatches domain review agents via a sub-agent-spawning tool. Resolve which one is available ONCE per invocation, before Phase 3C, per the **Sub-Agent Dispatch Tool Resolution** rule below — do not halt to ask the operator which tool to use. Never fall back to reviewing inline in the orchestrator's own context; inline self-review is a strictly weaker substitute for an isolated fresh-context reviewer and is not a permitted fallback.
15
15
 
16
16
  **Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier); the General Security & Quality reviewer spawned as always-runs Task uses `effort: xhigh` (deep tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`. Pass model and effort explicitly in every `Task` tool call. Feature gate: pass `effort` only on Claude Code >= 2.1.154. **The domain-review Task agents this file dispatches** (per `commands/review-pr-agents/*.md`) resolve via `model: "{SUBAGENT_MODEL}"` — forge.yaml `agents.subagent_model`, else `agents.default_model`, else `"sonnet"` — not `{DEFAULT_MODEL}` directly.
17
17
 
@@ -19,7 +19,7 @@ allowed-tools: Task, Bash, Read, Grep, Glob, WebFetch, Skill
19
19
 
20
20
  ## HARD RULES — READ BEFORE ANYTHING ELSE
21
21
 
22
- 1. **Use `Task(...)` for ALL domain agent launches.** Never substitute `Agent(...)`. Task agents run in a constrained context, post findings to the PR via `gh pr comment`, and their output is structured. Agent spawns opaque subprocesses outside allowed-tools.
22
+ 1. **Use the resolved `{DISPATCH_TOOL}(...)` for ALL domain agent launches.** Resolve `{DISPATCH_TOOL}` once, per the **Sub-Agent Dispatch Tool Resolution** section below: `Task` when available, `Agent` as the documented fallback when `Task` is absent from the environment. Every dispatch call in this run uses the same resolved tool — do not mix. Whichever tool is used, each agent MUST run in an isolated, fresh-context sub-agent (not inline in the orchestrator's own context) and post findings to the PR via `gh pr comment` in structured form.
23
23
 
24
24
  2. **Post the FORGE:REVIEW verdict regardless of finding severity.** A review that completes but posts no `<!-- FORGE:REVIEW -->` comment is invisible to the pipeline. Even a PASS verdict must be posted.
25
25
 
@@ -29,16 +29,29 @@ allowed-tools: Task, Bash, Read, Grep, Glob, WebFetch, Skill
29
29
 
30
30
  5. **`spec-evolution` PRs are NEVER auto-merged.** When a PR carries the `spec-evolution` label (created by `/spec-doctor`), Phase -1 MUST set `AUTO_MERGE=false` and add `needs-human` before any other processing. This cannot be overridden by the caller — the eval gate plus human review are the only permitted merge path. See Phase -1 `spec-evolution guard` block. <!-- Added: forge#1742 -->
31
31
 
32
+ ## Sub-Agent Dispatch Tool Resolution (MANDATORY — run once, before Phase 3C)
33
+
34
+ This spec dispatches domain review agents via a sub-agent-spawning tool. Different runtimes expose different tools for this — resolve deterministically, once per invocation, and use the same tool for every dispatch call in this run:
35
+
36
+ 1. **If `Task` is available in the current environment**: set `{DISPATCH_TOOL} = Task`. This is the preferred tool — tightest `allowed-tools` scoping.
37
+ 2. **Else if `Agent` is available**: set `{DISPATCH_TOOL} = Agent`. This is the documented fallback, not a degraded path — use it exactly as you would `Task`: one call per selected domain agent, same prompt template, `subagent_type: "general-purpose"` (or the closest equivalent the environment offers), same requirement that each agent posts its own findings directly to the PR via `gh pr comment`. Isolation and fresh-context review are preserved either way.
38
+ 3. **Neither tool is available**: this is a genuine setup defect, not a routing decision — HARD STOP, post a PR/issue comment explaining that no sub-agent dispatch tool is available, add `needs-human`, and exit without posting a verdict. Do NOT fall back to reviewing inline in the orchestrator's own context — inline self-review is strictly weaker than an isolated fresh-context reviewer and is never a substitute for a missing dispatch tool.
39
+
40
+ **Do not halt to ask the operator which tool to use.** Steps 1–2 are deterministic and fully resolve the common case; only step 3 (both absent) requires a stop, and even then the action is HARD STOP + `needs-human`, not a question back to the operator.
41
+
42
+ Everywhere this file (and `review-pr-agents.md` / the `review-pr-agents/*.md` persona files) says `Task(...)`, read it as `{DISPATCH_TOOL}(...)` using the value resolved here.
43
+
32
44
  ## Forbidden Tools Self-Check
33
45
 
34
- **Before executing any phase**, verify you are NOT using any of these tools:
46
+ **Before executing any phase**, verify you are NOT using any of these:
35
47
 
36
- | Tool | Status | Reason |
48
+ | Tool/Pattern | Status | Reason |
37
49
  |------|--------|--------|
38
- | `Agent` | **FORBIDDEN** | Spawns opaque subprocesses outside allowed-tools; bypasses spec workflow; cannot post structured findings |
50
+ | `Agent`, when `Task` is available | **FORBIDDEN** | `Task` is preferred whenever present — `Agent` is only the fallback for when `Task` is absent, not a free substitute |
51
+ | Inline self-review (no sub-agent spawn at all) | **FORBIDDEN** | Bypasses isolated fresh-context review entirely — always spawn via the resolved `{DISPATCH_TOOL}`, never review directly in the orchestrator's own context |
39
52
  | `EnterPlanMode` | **FORBIDDEN** | Breaks execution context; must run phases, not plan them |
40
53
 
41
- If you find yourself about to call `Agent(...)`, stop and use `Task(...)` instead. If you find yourself about to use `EnterPlanMode`, stop and execute the next phase directly.
54
+ If you find yourself about to call `Agent(...)` while `Task` is available, stop and use `Task(...)` instead. If neither `Task` nor `Agent` is available, do not fall through to inline review — follow step 3 of Sub-Agent Dispatch Tool Resolution above. If you find yourself about to use `EnterPlanMode`, stop and execute the next phase directly.
42
55
 
43
56
  ## Architecture — How This Command Works
44
57
 
@@ -48,11 +61,11 @@ This is the **orchestrator**. It routes to the right review mode, runs automated
48
61
 
49
62
  | File | What | How to invoke |
50
63
  |------|------|---------------|
51
- | `$FORGE_HOME/commands/review-pr-agents/protocols.md` | Shared review protocols (Evidence-Based + Structured Findings + Input Scoping) | `Read` tool during Phase 3C (always) |
52
- | `$FORGE_HOME/commands/review-pr-agents/<persona>.md` | Per-persona agent prompt templates (9 files) | `Read` tool during Phase 3C (selected agents only) |
53
- | `$FORGE_HOME/commands/review-pr-staging.md` | Full staging→main review pipeline | `Skill("review-pr-staging", ...)` during Phase 0 |
64
+ | `${FORGE_HOME:-$REPO_PATH}/commands/review-pr-agents/protocols.md` | Shared review protocols (Evidence-Based + Structured Findings + Input Scoping) | `Read` tool during Phase 3C (always) |
65
+ | `${FORGE_HOME:-$REPO_PATH}/commands/review-pr-agents/<persona>.md` | Per-persona agent prompt templates (9 files) | `Read` tool during Phase 3C (selected agents only) |
66
+ | `${FORGE_HOME:-$REPO_PATH}/commands/review-pr-staging.md` | Full staging→main review pipeline | `Skill("review-pr-staging", ...)` during Phase 0 |
54
67
 
55
- `$FORGE_HOME` defaults to `~/.claude` (the directory where `npx forgedock` symlinks commands). Override by setting `FORGE_HOME` in your environment.
68
+ `$FORGE_HOME` defaults to `~/.claude` (the directory where `npx forgedock` symlinks commands). When unset, every resolution in this file falls back to `$REPO_PATH` (the repo root, from `forge.yaml → paths.root`) rather than degrading to a bare root-anchored path — see the `TEMPLATE_BASE` tiered guard in Phase 3C and the verification-script resolution in Step 2.5B for the actual fallback chains. Never resolve a missing file via a filesystem-wide `find` — see the guardrail in `commands/review-pr-agents/protocols.md`.
56
69
 
57
70
  **Invocation flow:**
58
71
  ```
@@ -239,6 +252,14 @@ done
239
252
 
240
253
  # Resolve repo name early — used in Phases 5, 6, 8B, 9A (clean-review skip path bypasses Phase 6A) <!-- Added: forge#820 -->
241
254
  REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
255
+
256
+ # First-time contributor detection <!-- Added: forge#960 -->
257
+ # Requires REPO to be set (line above). Checks whether the PR author has any prior merged PRs in this repo.
258
+ # IS_FIRST_TIME_CONTRIBUTOR is consumed in Phase 9 to conditionally post an onboarding welcome comment.
259
+ PR_AUTHOR=$(gh pr view "$ARGUMENTS" -R "$REPO" --json author --jq '.author.login')
260
+ PRIOR_MERGED_COUNT=$(gh pr list -R "$REPO" --author "$PR_AUTHOR" --state merged --limit 1 --json number --jq 'length' 2>/dev/null || echo "1")
261
+ IS_FIRST_TIME_CONTRIBUTOR="false"
262
+ [ "$PRIOR_MERGED_COUNT" -eq 0 ] && IS_FIRST_TIME_CONTRIBUTOR="true"
242
263
  ```
243
264
 
244
265
  ### 1B: Classify
@@ -580,7 +601,7 @@ Map each changed file to its activation requirements.
580
601
 
581
602
  ### Step 2.5B: Run Verification
582
603
 
583
- For each changed file, execute the relevant checks using the standalone verification scripts in `$FORGE_HOME/scripts/`. These scripts can also be run independently outside the review context (e.g., from `/quality-gate` or `/work-on` builder steps).
604
+ For each changed file, execute the relevant checks using the standalone verification scripts in `${FORGE_HOME:-$SCRIPTS_HOME}/scripts/`. These scripts can also be run independently outside the review context (e.g., from `/quality-gate` or `/work-on` builder steps).
584
605
 
585
606
  **Platform note**: The verify-*.sh scripts require bash and standard POSIX tools. On Windows without bash (Git Bash / WSL / MSYS2), these checks are skipped with an explicit message — the review continues without them.
586
607
 
@@ -588,6 +609,20 @@ For each changed file, execute the relevant checks using the standalone verifica
588
609
  CHANGED_FILES=$(gh pr diff $ARGUMENTS --name-only)
589
610
  REPO_ROOT="." # Assumes cwd is the repo root
590
611
 
612
+ # Resolve the verify-*.sh scripts source directory with the same deterministic
613
+ # fallback as the Phase 3C TEMPLATE_BASE guard: $FORGE_HOME first (the installed
614
+ # ForgeDock location), then this repo's own root (forge.yaml -> paths.root, or
615
+ # git top-level). A bare/unset $FORGE_HOME must never be used directly in a path —
616
+ # that degrades to a root-anchored path (/scripts/verify-*.sh) and silently skips
617
+ # every check below. Never fall back to a filesystem-wide `find`.
618
+ # <!-- Added: forge#2035 -->
619
+ if [ -n "$FORGE_HOME" ] && [ -f "$FORGE_HOME/scripts/verify-route-registration.sh" ]; then
620
+ SCRIPTS_HOME="$FORGE_HOME"
621
+ else
622
+ FORGE_YAML="${FORGE_CONFIG:-$(git rev-parse --show-toplevel 2>/dev/null)/forge.yaml}"
623
+ SCRIPTS_HOME=$(yq '.paths.root' "$FORGE_YAML" 2>/dev/null || git rev-parse --show-toplevel 2>/dev/null || pwd)
624
+ fi
625
+
591
626
  # --- Platform / bash capability guard ---
592
627
  # The verify-*.sh scripts require bash. Detect availability before invoking.
593
628
  # On Windows without Git Bash/WSL, skip gracefully rather than crash.
@@ -627,11 +662,11 @@ if [ "$BASH_AVAILABLE" = "true" ]; then
627
662
  [ -n "$_API_MIDDLEWARE" ] && export FORGE_API_MIDDLEWARE_DIR="$_API_MIDDLEWARE"
628
663
  fi
629
664
  echo "=== Running: verify-route-registration.sh ==="
630
- bash "$FORGE_HOME/scripts/verify-route-registration.sh" "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
665
+ bash "$SCRIPTS_HOME/scripts/verify-route-registration.sh" "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
631
666
 
632
667
  # 2. Environment variable wiring (checks .env.example, docker-compose, env_validation, SOPS mapping)
633
668
  echo "=== Running: verify-env-vars.sh ==="
634
- bash "$FORGE_HOME/scripts/verify-env-vars.sh" "$DIFF_TMP" "$REPO_ROOT" || true
669
+ bash "$SCRIPTS_HOME/scripts/verify-env-vars.sh" "$DIFF_TMP" "$REPO_ROOT" || true
635
670
 
636
671
  # 3. Host headers in shell scripts + client-side proxy bypass check
637
672
  # Read project-specific internal service patterns from forge.yaml (if present)
@@ -645,11 +680,11 @@ if [ "$BASH_AVAILABLE" = "true" ]; then
645
680
  fi
646
681
  export FORGE_INTERNAL_PATTERNS
647
682
  echo "=== Running: verify-host-headers.sh ==="
648
- bash "$FORGE_HOME/scripts/verify-host-headers.sh" "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
683
+ bash "$SCRIPTS_HOME/scripts/verify-host-headers.sh" "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
649
684
 
650
685
  # 4. SOPS deploy chain (ENV_MAPPING consistency, deploy path drift, hotfix sync)
651
686
  echo "=== Running: verify-sops-chain.sh ==="
652
- bash "$FORGE_HOME/scripts/verify-sops-chain.sh" "$DIFF_TMP" "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
687
+ bash "$SCRIPTS_HOME/scripts/verify-sops-chain.sh" "$DIFF_TMP" "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
653
688
 
654
689
  # Cleanup temp files
655
690
  rm -f "$CHANGED_FILES_TMP" "$DIFF_TMP"
@@ -1485,9 +1520,9 @@ The `protocols.md` file contains the Evidence-Based Review Protocol, Structured
1485
1520
  6. Substitute code index slice: `[INDEX_SLICE]` → the matching `$INDEX_SLICE_{DOMAIN}` variable for this agent (e.g., `$INDEX_SLICE_AUTH` for the auth agent). Agents MUST query index data first; fall back to grep only when index slice is empty or unavailable.
1486
1521
  7. Substitute per-agent diff slice: `[DOMAIN_DIFF_SLICE]` → the matching `$DIFF_SLICE_*` variable (e.g., `$DIFF_SLICE_AUTH` for the auth agent, `$DIFF_SLICE_SECURITY` for the security agent). This replaces any `gh pr diff [PR_NUMBER]` call inside the agent template — the agent works from the pre-computed slice, not the full changeset.
1487
1522
  8. If Phase 2.5 found broken assumptions, append them to the agent's prompt as "Pre-found integration issues to verify"
1488
- 9. Launch via `Task` tool with `model: "{SUBAGENT_MODEL}"` (forge.yaml `agents.subagent_model`, else `agents.default_model`, else `"sonnet"`; fallback `"opus"` if rate-limited)
1523
+ 9. Launch via the resolved `{DISPATCH_TOOL}` (see Sub-Agent Dispatch Tool Resolution above) with `model: "{SUBAGENT_MODEL}"` (forge.yaml `agents.subagent_model`, else `agents.default_model`, else `"sonnet"`; fallback `"opus"` if rate-limited)
1489
1524
 
1490
- **CRITICAL**: Launch ALL selected agents in a SINGLE message using multiple Task tool calls. Each agent posts findings directly to the PR via `gh pr comment`.
1525
+ **CRITICAL**: Launch ALL selected agents in a SINGLE message using multiple `{DISPATCH_TOOL}` calls. Each agent posts findings directly to the PR via `gh pr comment`.
1491
1526
 
1492
1527
  #### Domain Diff Slicing
1493
1528
 
@@ -1629,7 +1664,7 @@ if [ "$DEDUP_EXIT" -eq 1 ]; then
1629
1664
  # Add a comment on the existing issue referencing this recurrence in PR #${PR_NUMBER}
1630
1665
  elif [ "$DEDUP_EXIT" -eq 2 ]; then
1631
1666
  echo "DEDUP: Usage error — $DEDUP_RESULT"
1632
- # Skip this finding — do NOT fall through to gh issue create on a usage error
1667
+ # Skip this finding — do NOT fall through to issue creation on a usage error
1633
1668
  fi
1634
1669
  ```
1635
1670
 
@@ -1688,13 +1723,17 @@ fi
1688
1723
  - Open `review-finding` issue at same file with similar title (3+ shared keywords) → **skip** (likely same finding despite line drift)
1689
1724
  - Closed `review-finding` at same file within ±5 lines → create with regression warning, elevate to `priority:P1`
1690
1725
 
1691
- **For each finding** (that passes dedup), create issue:
1726
+ **For each finding** (that passes dedup), create issue through the `/issue` create-hook's programmatic invocation contract (see `commands/issue.md` § "Programmatic Invocation Contract") — this preserves the bespoke line-range/title dedup above as a precise pre-check, while `/issue`'s own Phase 2D dedup runs as a coarser second pass:
1692
1727
  ```bash
1693
- ISSUE_NUM=$(gh issue create \
1694
- --title "fix: [summary] (review finding — PR #${PR_NUMBER})" \
1695
- --label "review-finding,needs-validation,{priority}" \
1696
- ${MILESTONE_FLAG} \
1697
- --body "$(cat <<'ISSUE_EOF'
1728
+ FINDING_ISSUE_TITLE="fix: [summary] (review finding — PR #${PR_NUMBER})"
1729
+ # Defense-in-depth: /issue's arg tokenizer (commands/issue.md Argument Parsing,
1730
+ # forge#2094) uses an xargs-based tokenizer that never expands backtick/$(...)
1731
+ # substitution, so this is no longer required for safety — but strip it anyway
1732
+ # so the raw title stays readable if it round-trips through any other
1733
+ # eval-based consumer.
1734
+ FINDING_ISSUE_TITLE=$(printf '%s' "$FINDING_ISSUE_TITLE" | tr '`' "'" | sed 's/\$(/$ (/g')
1735
+ FINDING_ISSUE_BODY_FILE=$(mktemp)
1736
+ cat <<'ISSUE_EOF' > "$FINDING_ISSUE_BODY_FILE"
1698
1737
  ## Problem
1699
1738
 
1700
1739
  [One sentence: what bug or issue was found. Where it occurs (`file:line`) and what it causes.]
@@ -1745,7 +1784,21 @@ Files that need changes:
1745
1784
  - [ ] Reproduce or construct proof-of-concept
1746
1785
  [BATCHABLE_ANNOTATION]
1747
1786
  ISSUE_EOF
1748
- )" --json number --jq '.number')
1787
+
1788
+ # --label is repeatable (not comma-joined) per the /issue programmatic contract.
1789
+ Skill(skill="issue", args="--title \"$FINDING_ISSUE_TITLE\" --body-file \"$FINDING_ISSUE_BODY_FILE\" --label review-finding --label needs-validation --label \"{priority}\" ${MILESTONE_FLAG}")
1790
+ rm -f "$FINDING_ISSUE_BODY_FILE"
1791
+
1792
+ # /issue has no machine-readable return contract (it's a user-facing command, not a work-on
1793
+ # subcommand) — resolve the created issue's number by exact-title search immediately after
1794
+ # the call. The title embeds ${PR_NUMBER} and the finding summary, making it unique enough
1795
+ # for a reliable single-match lookup. Retry to absorb GitHub Search API indexing lag.
1796
+ ISSUE_NUM=""
1797
+ for _resolve_attempt in 1 2 3; do
1798
+ ISSUE_NUM=$(gh issue list -R ${REPO} --search "in:title \"${FINDING_ISSUE_TITLE}\"" --state open --limit 1 --json number --jq '.[0].number // empty')
1799
+ [ -n "$ISSUE_NUM" ] && break
1800
+ sleep 2
1801
+ done
1749
1802
  ```
1750
1803
 
1751
1804
  Labels: `review-finding` + `needs-validation` + priority (`priority:P1` CONFIRMED, `priority:P2` LIKELY, `priority:P3` POSSIBLE).
@@ -1888,7 +1941,7 @@ ATTRIBUTION_FOOTER_LINE=""
1888
1941
  if [ "$ATTRIBUTION_PR_FOOTER" = "true" ]; then
1889
1942
  ATTRIBUTION_FOOTER_LINE="
1890
1943
  ---
1891
- > Orchestrated with [ForgeDock](https://github.com/RapierCraftStudios/ForgeDock) — state, scheduling, review, and memory on GitHub."
1944
+ > ⚒️ Orchestrated with [ForgeDock](https://github.com/RapierCraftStudios/ForgeDock) — state, scheduling, review, and memory on GitHub."
1892
1945
  fi
1893
1946
 
1894
1947
  # Build trust annotation line for verdict body — always included so the decision is auditable.
@@ -2165,6 +2218,47 @@ REVIEW_IS_STALE="false"
2165
2218
  if [ "$CURRENT_SHA" != "$REVIEW_SHA" ]; then REVIEW_IS_STALE="true"; fi
2166
2219
  ```
2167
2220
 
2221
+ ### Phase 9 — New Contributor Welcome (Conditional) <!-- Added: forge#960 -->
2222
+
2223
+ Post a welcome comment when all three conditions are true:
2224
+ 1. `IS_FIRST_TIME_CONTRIBUTOR="true"` (set in Phase 1A — no prior merged PRs from this author in this repo)
2225
+ 2. `community.welcome_new_contributors` is not `false` in `forge.yaml` (default: enabled)
2226
+ 3. This is a single-PR review (MODE 3) — not a staging→main or multi-PR review
2227
+
2228
+ ```bash
2229
+ # Read config — default enabled when key is absent
2230
+ WELCOME_ENABLED=$(yq '.community.welcome_new_contributors // true' forge.yaml 2>/dev/null || echo 'true')
2231
+
2232
+ if [ "$IS_FIRST_TIME_CONTRIBUTOR" = "true" ] && [ "$WELCOME_ENABLED" != "false" ]; then
2233
+ # Read optional custom values from forge.yaml community section
2234
+ CONTRIBUTING_URL=$(yq '.community.contributing_url // "CONTRIBUTING.md"' forge.yaml 2>/dev/null || echo 'CONTRIBUTING.md')
2235
+ CUSTOM_CONVENTIONS=$(yq '.community.conventions // ""' forge.yaml 2>/dev/null || echo '')
2236
+ PROJECT_NAME_WELCOME=$(yq '.project.name // "this project"' forge.yaml 2>/dev/null || echo 'this project')
2237
+
2238
+ gh pr comment "$ARGUMENTS" --body "## Welcome to ${PROJECT_NAME_WELCOME}! 🎉
2239
+
2240
+ Hi @${PR_AUTHOR} — this looks like your first merged contribution here. Thanks for opening a PR!
2241
+
2242
+ ### What just happened
2243
+
2244
+ The automated review pipeline (/review-pr) ran against your PR. It spawned domain-specific agents to review security, integration assumptions, and code quality. Any findings were created as separate GitHub issues — not merge blockers unless they are CONFIRMED at HIGH/CRITICAL severity.
2245
+
2246
+ ### Key conventions
2247
+
2248
+ $([ -n "\$CUSTOM_CONVENTIONS" ] && echo "\$CUSTOM_CONVENTIONS" || echo "- **Conventional commits**: prefix your commit messages with \`fix(scope):\`, \`feat(scope):\`, \`refactor(scope):\`, etc.
2249
+ - **DCO sign-off**: all commits must be signed off with \`git commit -s\` (Developer Certificate of Origin — required for the dual-license model).
2250
+ Fix unsigned commits: \`git commit --amend -s --no-edit && git push --force-with-lease\`
2251
+ - **Issues, not inline fixes**: when you spot a bug or improvement opportunity, open a GitHub issue rather than fixing it inline — so it flows through the pipeline with full traceability.")
2252
+
2253
+ ### Getting started
2254
+
2255
+ - Contributing guide: [\`${CONTRIBUTING_URL}\`](${CONTRIBUTING_URL})
2256
+ - Run \`npx forgedock\` to install the pipeline commands locally if you want to run /review-pr or /work-on yourself.
2257
+
2258
+ Welcome aboard!"
2259
+ fi
2260
+ ```
2261
+
2168
2262
  **Verifiable agent count (MANDATORY — do not report a self-asserted `Agents: [N]` figure)** <!-- Added: forge#1849 -->
2169
2263
 
2170
2264
  A degraded run that skipped Task-based agent dispatch must be visible from this summary alone, without interrogating the agent afterward. Compute the actual launched-agent count from the `<!-- FORGE:REVIEW-AGENT:{domain} -->` comments each agent is required to post (Phase 3C), rather than trusting a free-text tally:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Create a revert PR to roll back a shipped feature or fix that caused a production incident
3
- argument-hint: [PR number to revert, or "last" for most recent deploy]
3
+ argument-hint: "[PR number to revert, or \"last\" for most recent deploy]"
4
4
  install: extras
5
5
  ---
6
6
  <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
@@ -213,9 +213,10 @@ fi
213
213
  ### Step 4B: Create a follow-up issue
214
214
 
215
215
  ```bash
216
- gh issue create --title "fix: {original_title} (reverted — needs new approach)" \
217
- --label "bug,priority:P1" \
218
- --body "$(cat <<'BODY_EOF'
216
+ FOLLOWUP_TITLE="fix: {original_title} (reverted — needs new approach)"
217
+ FOLLOWUP_BODY_TMPFILE=$(mktemp)
218
+ trap 'rm -f "$FOLLOWUP_BODY_TMPFILE"' EXIT
219
+ cat > "$FOLLOWUP_BODY_TMPFILE" <<'BODY_EOF'
219
220
  ## Problem
220
221
 
221
222
  PR #{PR_NUMBER} ({original_title}) was merged and deployed but caused a production incident. It was reverted in PR #{REVERT_PR_NUMBER}. The original fix needs to be re-implemented with a different approach that avoids the production issue.
@@ -249,7 +250,16 @@ Files that need changes (same as original fix, different approach):
249
250
 
250
251
  Regression of #{ORIGINAL_ISSUE}. Should investigate why the original approach caused issues before implementing.
251
252
  BODY_EOF
252
- )"
253
+ # Route through the /issue create-hook (canonical dedup + body validation) instead of a raw
254
+ # `gh issue create`.
255
+ Skill(skill="issue", args="--title \"$FOLLOWUP_TITLE\" --body-file \"$FOLLOWUP_BODY_TMPFILE\" --label bug --label \"priority:P1\"")
256
+ rm -f "$FOLLOWUP_BODY_TMPFILE"
257
+ trap - EXIT
258
+ FOLLOWUP_ISSUE_NUMBER=$(gh issue list \
259
+ --state open \
260
+ --search "$FOLLOWUP_TITLE" \
261
+ --json number,title \
262
+ --jq --arg t "$FOLLOWUP_TITLE" '.[] | select(.title == $t) | .number' 2>/dev/null | head -1)
253
263
  ```
254
264
 
255
265
  ### Step 4C: Clean up worktree