forgedock 1.26.0 → 1.28.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 (137) hide show
  1. package/README.md +4 -1
  2. package/bin/bootstrap.mjs +55 -0
  3. package/bin/engine/outcome-codes.mjs +13 -0
  4. package/bin/engine/phases.mjs +38 -2
  5. package/bin/engine/telemetry-seam.mjs +41 -6
  6. package/bin/engine/telemetry-seam.test.mjs +35 -0
  7. package/bin/engine-cli.mjs +616 -54
  8. package/bin/engine.mjs +13 -5
  9. package/bin/forgedock.mjs +169 -39
  10. package/bin/gc.mjs +580 -0
  11. package/bin/init-detect.mjs +68 -3
  12. package/bin/init-enrich.mjs +3 -0
  13. package/bin/init-repo.mjs +213 -0
  14. package/bin/journey.mjs +278 -43
  15. package/bin/lean/admission.mjs +33 -3
  16. package/bin/lean/batch.mjs +24 -5
  17. package/bin/lean/batch.test.mjs +54 -4
  18. package/bin/lean/checks.mjs +199 -0
  19. package/bin/lean/checks.test.mjs +183 -0
  20. package/bin/lean/conformance.mjs +235 -37
  21. package/bin/lean/conformance.test.mjs +143 -5
  22. package/bin/lean/credentials.mjs +29 -0
  23. package/bin/lean/depcache.mjs +312 -0
  24. package/bin/lean/depcache.test.mjs +298 -0
  25. package/bin/lean/drift.mjs +198 -0
  26. package/bin/lean/drift.test.mjs +223 -0
  27. package/bin/lean/effects.mjs +178 -16
  28. package/bin/lean/execution.e2e.test.mjs +81 -0
  29. package/bin/lean/execution.mjs +431 -0
  30. package/bin/lean/execution.test.mjs +583 -0
  31. package/bin/lean/heavy.mjs +289 -0
  32. package/bin/lean/heavy.test.mjs +207 -0
  33. package/bin/lean/lean.mjs +877 -151
  34. package/bin/lean/lean.test.mjs +1798 -28
  35. package/bin/lean/ledger.mjs +92 -0
  36. package/bin/lean/ledger.test.mjs +100 -0
  37. package/bin/lean/limits.mjs +58 -0
  38. package/bin/lean/limits.test.mjs +150 -0
  39. package/bin/lean/orchestrate.mjs +321 -19
  40. package/bin/lean/prompts.mjs +125 -17
  41. package/bin/lean/refactor.mjs +276 -0
  42. package/bin/lean/refactor.test.mjs +359 -0
  43. package/bin/lean/render.mjs +25 -6
  44. package/bin/lean/review-trigger.mjs +134 -0
  45. package/bin/lean/review-trigger.test.mjs +104 -0
  46. package/bin/lean/sandbox-provider.contract.test.mjs +257 -0
  47. package/bin/lean/sandbox-remote.mjs +142 -0
  48. package/bin/lean/sched.mjs +221 -0
  49. package/bin/lean/sched.test.mjs +310 -0
  50. package/bin/lean/schedd.mjs +247 -0
  51. package/bin/lean/schedd.test.mjs +180 -0
  52. package/bin/lean/toolchain.mjs +228 -0
  53. package/bin/lean/toolchain.test.mjs +154 -0
  54. package/bin/lean/verify.mjs +124 -0
  55. package/bin/lean/verify.test.mjs +118 -0
  56. package/bin/lean/worktree.mjs +69 -0
  57. package/bin/lean/worktree.test.mjs +141 -0
  58. package/bin/runner.mjs +53 -10
  59. package/bin/stats/github-source.mjs +1 -1
  60. package/bin/stats/index.mjs +14 -2
  61. package/bin/stats/query.mjs +72 -0
  62. package/bin/stats/stats.test.mjs +54 -0
  63. package/commands/adopt.md +3 -0
  64. package/commands/audit.md +5 -8
  65. package/commands/autopilot.md +2 -0
  66. package/commands/bootstrap.md +35 -0
  67. package/commands/cleanup.md +7 -22
  68. package/commands/forge-stats.md +2 -1
  69. package/commands/forgedock-init.md +125 -6
  70. package/commands/issue.md +89 -830
  71. package/commands/milestone.md +55 -6
  72. package/commands/orchestrate.md +10 -2
  73. package/commands/review-pr.md +2 -2
  74. package/commands/upgrade-deps.md +3 -3
  75. package/commands/work-on/build/validate.md +4 -0
  76. package/commands/work-on/close.md +2 -0
  77. package/commands/work-on/decompose.md +24 -6
  78. package/docs/CI.md +3 -3
  79. package/docs/CONFIG.md +227 -3
  80. package/docs/OUTCOME-CODES.md +11 -0
  81. package/docs/TELEMETRY.md +1 -1
  82. package/docs/site/command-reference.md +22 -0
  83. package/docs/site/getting-started.md +2 -0
  84. package/docs/site/start-a-new-project.md +327 -0
  85. package/package.json +1 -1
  86. package/scripts/affected-files-check.sh +91 -0
  87. package/scripts/affected-files-check.test.sh +80 -0
  88. package/scripts/bootstrap-create.mjs +393 -0
  89. package/scripts/bootstrap-validate.mjs +321 -0
  90. package/scripts/calibration.mjs +116 -0
  91. package/scripts/check-devdocs-freshness.sh +125 -0
  92. package/scripts/check-devdocs-freshness.test.sh +130 -0
  93. package/scripts/check-spec-markers.sh +3 -0
  94. package/scripts/check-spec-size.sh +29 -11
  95. package/scripts/check-spec-size.test.sh +27 -4
  96. package/scripts/ci-local/lib.mjs +8 -1
  97. package/scripts/ci-local/parallelism.test.mjs +27 -0
  98. package/scripts/ci-local/run.mjs +16 -5
  99. package/scripts/ci-local/scoping.mjs +82 -0
  100. package/scripts/ci-local.test.sh +14 -2
  101. package/scripts/fixtures/symbol-index/other/sample.rb +3 -0
  102. package/scripts/fixtures/symbol-index/py/pkg/__init__.py +1 -0
  103. package/scripts/fixtures/symbol-index/py/pkg/a.py +5 -0
  104. package/scripts/fixtures/symbol-index/py/pkg/b.py +14 -0
  105. package/scripts/fixtures/symbol-index/py/pkg/c.py +1 -0
  106. package/scripts/fixtures/symbol-index/py/sample.py +41 -0
  107. package/scripts/fixtures/symbol-index/ts/sample.ts +13 -0
  108. package/scripts/fixtures/symbol-move/gen_big.py +201 -0
  109. package/scripts/fixtures/symbol-move-ts/gen_big.mjs +56 -0
  110. package/scripts/forge-root.test.sh +1 -1
  111. package/scripts/hotspots.sh +100 -0
  112. package/scripts/hotspots.test.sh +101 -0
  113. package/scripts/issue-create.sh +197 -0
  114. package/scripts/issue-create.test.sh +166 -0
  115. package/scripts/maintenance-scan.sh +185 -0
  116. package/scripts/maintenance-scan.test.sh +89 -0
  117. package/scripts/milestone-drift.sh +206 -0
  118. package/scripts/milestone-drift.test.sh +120 -0
  119. package/scripts/northstar-docs.test.sh +68 -0
  120. package/scripts/refactor-verify.sh +368 -0
  121. package/scripts/refactor-verify.test.sh +202 -0
  122. package/scripts/review-prepare.sh +11 -9
  123. package/scripts/review-prepare.test.sh +14 -0
  124. package/scripts/structure-delta.sh +155 -0
  125. package/scripts/structure-delta.test.sh +136 -0
  126. package/scripts/symbol-index.mjs +253 -0
  127. package/scripts/symbol-index.py +354 -0
  128. package/scripts/symbol-index.test.sh +119 -0
  129. package/scripts/symbol-move-ts.test.sh +161 -0
  130. package/scripts/symbol-move.mjs +379 -0
  131. package/scripts/symbol-move.py +383 -0
  132. package/scripts/symbol-move.test.sh +168 -0
  133. package/templates/bootstrap/interview.md +34 -0
  134. package/templates/bootstrap/issue-body.md +42 -0
  135. package/templates/bootstrap/needs-human-issue.md +45 -0
  136. package/templates/bootstrap/stack-profiles.md +34 -0
  137. package/templates/bootstrap/technical-spike.md +75 -0
@@ -0,0 +1,327 @@
1
+ ---
2
+ title: "Start a New Project with ForgeDock"
3
+ description: "Go from a one-line idea to a validated, orchestrate-ready GitHub backlog with /bootstrap: interview, spike doc, dry-run preview with estimated cost, creation and the /orchestrate hand-off. Includes a worked example."
4
+ keywords: ["forgedock bootstrap", "new project backlog", "claude code new project", "ai project setup", "orchestrate"]
5
+ ---
6
+
7
+ # Start a New Project with ForgeDock
8
+
9
+ [Getting Started](./getting-started.md) assumes a repo that already has issues. This guide covers a brand-new project. You start with an idea, `/bootstrap` turns it into a backlog of GitHub issues in build order, and `/orchestrate` builds that backlog one milestone at a time.
10
+
11
+ ```text
12
+ idea ──▶ /bootstrap ──▶ spike doc + plan ──▶ dry-run preview ──▶ backlog on GitHub ──▶ /orchestrate per milestone
13
+ ```
14
+
15
+ All the GitHub writes happen in one deterministic script (`scripts/bootstrap-create.mjs`). The agent does the judgment work: the interview, the defaults and the plan. The script validates the plan and creates it. Flags are listed in the [command reference](./command-reference.md#bootstrap).
16
+
17
+ ---
18
+
19
+ ## Prerequisites
20
+
21
+ - A GitHub repo for the new project (it can be empty). Set it in `forge.yaml` (`project.owner` / `project.repo`, from `npx forgedock init`) or pass `--repo OWNER/NAME`.
22
+ - GitHub CLI (`gh`) authenticated (`gh auth login`).
23
+ - Node.js 18 or higher.
24
+ - Claude Code with ForgeDock installed for `/bootstrap`, or just `npx forgedock` for the headless path.
25
+
26
+ ## 1. From idea to `/bootstrap`
27
+
28
+ Start from a free-text idea, or from docs you already have:
29
+
30
+ ```bash
31
+ /bootstrap "a shared grocery list for households" # in a Claude Code session
32
+ /bootstrap docs/idea.md # start from existing docs
33
+ /bootstrap "<idea>" --dry-run # preview only, no GitHub writes
34
+ ```
35
+
36
+ Without an interactive session:
37
+
38
+ ```bash
39
+ npx forgedock bootstrap "a shared grocery list for households" --dry-run
40
+ ```
41
+
42
+ Headless mode can't ask you questions. It takes the recommended option for each one and lists it under "Here's what I assumed", so check that list before you create anything.
43
+
44
+ ## 2. What `/bootstrap` does
45
+
46
+ 1. **Interview.** It covers the aspects in `templates/bootstrap/interview.md` (users, core jobs, scope, platforms, stack, auth, data and tenancy, integrations, payments, compliance, deployment, budget and more) and skips anything your idea already answers.
47
+ 2. **Fills the blanks.** Every unanswered aspect gets a sensible default with a one-line rationale. All of them appear in one editable "Here's what I assumed" list.
48
+ 3. **Asks only genuine doubts.** These are choices that change architecture or cost, such as native vs PWA, data residency or self-hosted vs managed DB, plus the product name and repo owner, which are always asked. It asks at most 4 multiple-choice questions per batch, each with a recommended option.
49
+ 4. **Writes the spike doc** to `docs/engineering/technical-spike.md` (or the path in `--spike <path>`). It must have the sections Stack, Repo layout, Runtime topology, Data model, Security and privacy, Design system and quality gates, Route/API map, Credentials and Milestone plan. Each section needs a `**Rationale**:` line.
50
+ 5. **Writes a plan JSON** with milestones in build order (foundation first), a `SPIKE` parent issue, and sub-issues in the pipeline issue template. Each sub-issue lists planned files and acceptance criteria with `[type:*]` tags.
51
+ - Every credential, account or external input gets its own **needs-human** issue. Code issues may mention it, but they never depend on it. A mock keeps dev and CI green until the real thing exists.
52
+ 6. **Validates** the plan with `scripts/bootstrap-validate.mjs` and fixes it until it passes. Nothing is ever created from an invalid plan.
53
+
54
+ ## 3. Preview the backlog (dry run)
55
+
56
+ If you already have a plan file, you can preview it without an LLM call or any GitHub access:
57
+
58
+ ```bash
59
+ npx forgedock bootstrap --plan plan.json --dry-run
60
+ ```
61
+
62
+ The preview lists each milestone with its `milestone/<slug>` branch, the issues in build order and their dependencies, a DAG summary, and an **estimated agent cost**. The estimate is a flat rate per build issue: `$3` by default, or `costPerIssueUsd` in the plan, or `--cost-per-issue <usd>`. The spike and needs-human issues are excluded. It's a planning estimate, not a measured figure.
63
+
64
+ The spike doc has to exist on disk even for a dry run. If it's missing a section or a rationale, the script prints the error and writes nothing.
65
+
66
+ ## 4. Create the backlog
67
+
68
+ ```bash
69
+ npx forgedock bootstrap --plan plan.json
70
+ ```
71
+
72
+ This creates:
73
+
74
+ - the spike parent issue
75
+ - the milestones, plus a `milestone/<slug>` branch for each
76
+ - every sub-issue, with a `**Parent**` link to the spike, the plan's guardrails and domain rules added to its Context, and <code v-pre>#{{KEY}}</code> placeholders resolved to real issue numbers
77
+ - a board item for each issue (Status=Todo, Priority, Component)
78
+ - `FORGE:BOOTSTRAP_PLAN` and `FORGE:DECOMPOSED` comments on the spike, which is then labelled `workflow:decomposed`
79
+
80
+ Exit codes: `0` created (or previewed), `1` invalid plan or spike doc (nothing written), `2` usage error, `3` GitHub error.
81
+
82
+ **Resume by re-running.** GitHub is the state. Issues are reused by exact title plus their `**Parent**` link, milestones by title, branches if they already exist, and comments if their marker is already there. After a network or rate-limit failure (exit `3`), run the same command again.
83
+
84
+ ## 5. The hand-off
85
+
86
+ Creation ends with a hand-off in three parts:
87
+
88
+ - **Created**: links to the build issues.
89
+ - **Needs a human**: links to the credential and account issues. Do these when you can. They block nothing, because the code issues use mocks until then.
90
+ - **Next commands**: one `/orchestrate milestone "<title>"` per milestone, in build order.
91
+
92
+ Run the orchestrate commands in that order. Each one builds its milestone's issues through the normal `/work-on` pipeline.
93
+
94
+ ---
95
+
96
+ ## Worked example: Shelf, a shared grocery list
97
+
98
+ The idea: *"Shelf, a shared grocery list for households. Members invite each other by email and see items checked off live."*
99
+
100
+ ### Assumptions and doubts
101
+
102
+ Headless, `/bootstrap` takes the recommended options and lists them:
103
+
104
+ ```text
105
+ Here's what I assumed
106
+ - Platform: responsive web app (PWA later): a household list is used on phones, and a PWA avoids app-store review.
107
+ - Stack: Next.js + TypeScript + Postgres (Prisma): the closest preset for a small web app.
108
+ - Auth: email magic link: members already arrive through an email invite.
109
+ - Email: Resend, behind a mock mailer in dev and CI.
110
+ - Hosting: one web service plus managed Postgres.
111
+ ```
112
+
113
+ ### The spike doc
114
+
115
+ `docs/engineering/technical-spike.md` has every required section, each with a rationale:
116
+
117
+ ```markdown
118
+ # Shelf — technical spike
119
+
120
+ ## Stack
121
+ Next.js (App Router) + TypeScript, Postgres via Prisma, pnpm.
122
+ **Rationale**: one language end to end; the closest stack preset for a small web app.
123
+
124
+ ## Repo layout
125
+ Single app: `app/`, `lib/`, `prisma/`.
126
+ **Rationale**: one deployable; no monorepo until a second app exists.
127
+
128
+ ## Runtime topology
129
+ One web service plus managed Postgres.
130
+ **Rationale**: the cheapest setup that still gives live updates for a few households.
131
+
132
+ ## Data model
133
+ `Household`, `Member`, `List`, `Item`; every row carries `householdId`.
134
+ **Rationale**: the household is the sharing and access boundary.
135
+
136
+ ## Security and privacy
137
+ Members only ever read their own household's rows; invites expire after 7 days.
138
+ **Rationale**: grocery lists are personal data.
139
+
140
+ ## Design system and quality gates
141
+ shadcn/ui; `pnpm build`, lint and unit tests in CI.
142
+ **Rationale**: accessible defaults and fast feedback.
143
+
144
+ ## Route/API map
145
+ `/api/lists`, `/api/invites`.
146
+ **Rationale**: one route per milestone feature.
147
+
148
+ ## Credentials
149
+ Resend API key (needs-human); the mock mailer is used until it exists.
150
+ **Rationale**: agents cannot create third-party accounts.
151
+
152
+ ## Milestone plan
153
+ 1. Foundation: scaffold, schema. 2. Sharing: list API, invites.
154
+ **Rationale**: sharing needs the schema first.
155
+ ```
156
+
157
+ ### The plan
158
+
159
+ Two milestones, a spike, four build issues and one needs-human issue. `INVITES` mentions <code v-pre>#{{RESEND_KEY}}</code> but doesn't depend on it, so it can be built and tested against the mock mailer.
160
+
161
+ ```json
162
+ {
163
+ "labels": [
164
+ "frontend",
165
+ "backend",
166
+ "infra",
167
+ "needs-human"
168
+ ],
169
+ "milestones": [
170
+ {
171
+ "key": "foundation",
172
+ "title": "Foundation"
173
+ },
174
+ {
175
+ "key": "sharing",
176
+ "title": "Sharing"
177
+ }
178
+ ],
179
+ "spikeDoc": "docs/engineering/technical-spike.md",
180
+ "guardrails": [
181
+ "no email is sent without an explicit invite"
182
+ ],
183
+ "domainRules": [
184
+ "one household per list"
185
+ ],
186
+ "issues": [
187
+ {
188
+ "key": "SPIKE",
189
+ "title": "spike: Shelf stack, layout and milestone plan",
190
+ "milestone": "foundation",
191
+ "labels": [
192
+ "infra"
193
+ ],
194
+ "deps": [],
195
+ "body": "## Problem\n\nDecide Shelf's stack, repo layout and milestone plan.\n\n## Root Cause (if known)\n\nGreenfield: not built yet.\n\n## Affected Files\n\n1. `docs/engineering/technical-spike.md` — new\n\n## Acceptance Criteria\n\n- [ ] Spike doc covers every section with a rationale [type:manual]\n\n## Context\n\n**Parent**: none (spike)\n\n## Dependencies\n\nNone.\n"
196
+ },
197
+ {
198
+ "key": "SCAFFOLD",
199
+ "title": "scaffold: Next.js app with pnpm and CI",
200
+ "milestone": "foundation",
201
+ "labels": [
202
+ "frontend",
203
+ "infra"
204
+ ],
205
+ "deps": [
206
+ "SPIKE"
207
+ ],
208
+ "priority": "p0",
209
+ "body": "## Problem\n\nScaffold the Next.js app with pnpm and a CI workflow.\n\n## Root Cause (if known)\n\nGreenfield: not built yet.\n\n## Affected Files\n\n1. `package.json` — new\n2. `app/layout.tsx` — new\n3. `.github/workflows/ci.yml` — new\n\n## Acceptance Criteria\n\n- [ ] `pnpm build` succeeds [type:unit]\n\n## Context\n\n**Parent**: #{{SPIKE}}\n\n## Dependencies\n\nDepends on #{{SPIKE}} — needs the stack decision\n"
210
+ },
211
+ {
212
+ "key": "DB",
213
+ "title": "db: Postgres schema for households, lists and items",
214
+ "milestone": "foundation",
215
+ "labels": [
216
+ "backend"
217
+ ],
218
+ "deps": [
219
+ "SCAFFOLD"
220
+ ],
221
+ "priority": "p0",
222
+ "body": "## Problem\n\nPostgres schema for households, lists and items via Prisma.\n\n## Root Cause (if known)\n\nGreenfield: not built yet.\n\n## Affected Files\n\n1. `prisma/schema.prisma` — new\n\n## Acceptance Criteria\n\n- [ ] A member reads only their own household's lists [type:unit]\n\n## Context\n\n**Parent**: #{{SPIKE}}\n\n## Dependencies\n\nDepends on #{{SCAFFOLD}} — needs the app and package setup\n"
223
+ },
224
+ {
225
+ "key": "LISTS_API",
226
+ "title": "lists: Shared list API with live updates",
227
+ "milestone": "sharing",
228
+ "labels": [
229
+ "backend"
230
+ ],
231
+ "deps": [
232
+ "DB"
233
+ ],
234
+ "priority": "p1",
235
+ "body": "## Problem\n\nAPI to add, check off and remove items, with live updates for everyone in the household.\n\n## Root Cause (if known)\n\nGreenfield: not built yet.\n\n## Affected Files\n\n1. `app/api/lists/route.ts` — new\n\n## Acceptance Criteria\n\n- [ ] Checking an item off shows up for a second member [type:api]\n\n## Context\n\n**Parent**: #{{SPIKE}}\n\n## Dependencies\n\nDepends on #{{DB}} — needs the schema\n"
236
+ },
237
+ {
238
+ "key": "INVITES",
239
+ "title": "invites: Email invites to join a household",
240
+ "milestone": "sharing",
241
+ "labels": [
242
+ "backend"
243
+ ],
244
+ "deps": [
245
+ "LISTS_API"
246
+ ],
247
+ "priority": "p1",
248
+ "body": "## Problem\n\nInvite a member by email. Sending uses Resend; the key comes from #{{RESEND_KEY}}, and until it exists dev and CI use the mock mailer.\n\n## Root Cause (if known)\n\nGreenfield: not built yet.\n\n## Affected Files\n\n1. `app/api/invites/route.ts` — new\n2. `lib/mailer.ts` — new\n\n## Acceptance Criteria\n\n- [ ] An invite link adds the member to the household [type:api]\n\n## Context\n\n**Parent**: #{{SPIKE}}\n\n## Dependencies\n\nDepends on #{{LISTS_API}} — needs households and lists\n"
249
+ },
250
+ {
251
+ "key": "RESEND_KEY",
252
+ "title": "resend: Create the Resend API key and store it",
253
+ "milestone": "sharing",
254
+ "labels": [
255
+ "needs-human"
256
+ ],
257
+ "deps": [],
258
+ "needsHuman": true,
259
+ "body": "## Problem\n\nCreate the Resend API key that #{{INVITES}} uses to send email.\n\n### Setup steps\n\n1. Create an API key at https://resend.com/api-keys with sending access only.\n2. Store it as the `RESEND_API_KEY` deploy secret.\n\n### Environment variables\n\n- `RESEND_API_KEY` — Resend sending key\n\n### Secret handling\n\nNever commit the key or paste it into issues. Until it exists, dev and CI use the mock mailer.\n\n## Root Cause (if known)\n\nGreenfield: not built yet.\n\n## Affected Files\n\n1. `docs/setup/resend.md` — new\n\n## Acceptance Criteria\n\n- [ ] Key stored as a deploy secret [type:manual]\n- [ ] The real mailer replaces the mock in staging [type:manual]\n\n## Context\n\n**Parent**: #{{SPIKE}}\n\n## Dependencies\n\nNone.\n"
260
+ }
261
+ ]
262
+ }
263
+ ```
264
+
265
+ `node scripts/bootstrap-validate.mjs plan.json` prints `bootstrap-validate: OK (6 issues, 2 milestones)`.
266
+
267
+ ### The dry-run preview
268
+
269
+ `npx forgedock bootstrap --plan plan.json --dry-run --repo acme/shelf` prints:
270
+
271
+ ```text
272
+ ## Bootstrap plan preview (dry run: nothing written to GitHub)
273
+
274
+ Spike: spike: Shelf stack, layout and milestone plan — doc `docs/engineering/technical-spike.md`
275
+
276
+ ### 1. Foundation (`milestone/foundation`, 2 issues)
277
+ - scaffold: Next.js app with pnpm and CI
278
+ - db: Postgres schema for households, lists and items — depends on SCAFFOLD
279
+
280
+ ### 2. Sharing (`milestone/sharing`, 3 issues)
281
+ - lists: Shared list API with live updates — depends on DB
282
+ - invites: Email invites to join a household — depends on LISTS_API
283
+ - resend: Create the Resend API key and store it [needs-human]
284
+
285
+ DAG: 5 sub-issues (4 build, 1 needs-human), 3 dependency edges, 2 milestones.
286
+ Estimated agent cost: ~$12.00 (4 build issues × $3/issue; needs-human and spike excluded).
287
+ ```
288
+
289
+ ### Create and hand off
290
+
291
+ `npx forgedock bootstrap --plan plan.json --repo acme/shelf` creates the backlog and prints the hand-off. The issue numbers below are illustrative:
292
+
293
+ ```text
294
+ ## Bootstrap hand-off
295
+
296
+ Spike: #1 spike: Shelf stack, layout and milestone plan — https://github.com/acme/shelf/issues/1
297
+
298
+ ### Created
299
+ - #2 scaffold: Next.js app with pnpm and CI — https://github.com/acme/shelf/issues/2
300
+ - #3 db: Postgres schema for households, lists and items — https://github.com/acme/shelf/issues/3
301
+ - #4 lists: Shared list API with live updates — https://github.com/acme/shelf/issues/4
302
+ - #5 invites: Email invites to join a household — https://github.com/acme/shelf/issues/5
303
+
304
+ ### Needs a human
305
+ - #6 resend: Create the Resend API key and store it — https://github.com/acme/shelf/issues/6
306
+
307
+ ### Next commands (in build order)
308
+ 1. /orchestrate milestone "Foundation"
309
+ 2. /orchestrate milestone "Sharing"
310
+ ```
311
+
312
+ Then:
313
+
314
+ ```bash
315
+ /orchestrate milestone "Foundation" # scaffold, then the schema
316
+ /orchestrate milestone "Sharing" # list API, then invites (against the mock mailer until #6 is done)
317
+ ```
318
+
319
+ If creation fails halfway, for example at issue #4, re-run `npx forgedock bootstrap --plan plan.json --repo acme/shelf`. Issues #1–#3 are reused and creation carries on from #4.
320
+
321
+ ---
322
+
323
+ ## Next
324
+
325
+ - [Command Reference: `/bootstrap`](./command-reference.md#bootstrap)
326
+ - [How It Works](./how-it-works.md): what `/orchestrate` and `/work-on` do with each issue
327
+ - [Troubleshooting & Recovery](./troubleshooting.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgedock",
3
- "version": "1.26.0",
3
+ "version": "1.28.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"
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env bash
2
+ # SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+ #
5
+ # affected-files-check.sh — /issue Phase 3E rule 2: are the draft's Affected Files real? (forge#4172)
6
+ #
7
+ # Usage:
8
+ # affected-files-check.sh <body-file> [--ref <git-ref>]... [--spike <spike-doc>]
9
+ #
10
+ # <body-file> issue body (markdown); paths are the backticked tokens containing '/' or '.'
11
+ # inside its `## Affected Files` section (up to the next `## ` heading).
12
+ # --ref git ref to check existence against (repeatable). Default: origin/staging then
13
+ # origin/main; refs that do not resolve are skipped. Run from the target repo.
14
+ # --spike greenfield mode: a path that exists at no ref still passes as PLANNED when it
15
+ # equals an entry of the spike doc's repo-layout section, or starts with an entry
16
+ # ending in '/'. The section is the first heading matching /repo layout/i; its
17
+ # entries are backticked paths plus the lines of fenced blocks (tree glyphs and
18
+ # `#` comments stripped). Entries are full repo-relative paths: indentation-based
19
+ # nesting is not reconstructed.
20
+ #
21
+ # Output: one `OK <path>` | `PLANNED <path>` | `MISSING <path>` line per path.
22
+ # Exit: 0 no MISSING | 1 any MISSING, or no paths found | 2 usage error (no body file,
23
+ # --spike file absent, spike doc without a repo-layout section or entries).
24
+ # Deterministic, no LLM. bash 3.2 portable.
25
+
26
+ set -u
27
+ BODY_FILE=""
28
+ SPIKE=""
29
+ REFS=""
30
+ while [ $# -gt 0 ]; do
31
+ case "$1" in
32
+ --ref) [ -n "${2:-}" ] || { echo "affected-files-check: --ref requires a value" >&2; exit 2; }
33
+ REFS="$REFS
34
+ $2"; shift 2 ;;
35
+ --spike) [ -n "${2:-}" ] || { echo "affected-files-check: --spike requires a value" >&2; exit 2; }
36
+ SPIKE="$2"; shift 2 ;;
37
+ --*) echo "affected-files-check: unknown argument: $1" >&2; exit 2 ;;
38
+ *) [ -z "$BODY_FILE" ] || { echo "affected-files-check: more than one body file" >&2; exit 2; }
39
+ BODY_FILE="$1"; shift ;;
40
+ esac
41
+ done
42
+ [ -n "$BODY_FILE" ] && [ -r "$BODY_FILE" ] || { echo "usage: affected-files-check.sh <body-file> [--ref <git-ref>]... [--spike <spike-doc>]" >&2; exit 2; }
43
+ [ -n "$REFS" ] || REFS="
44
+ origin/staging
45
+ origin/main"
46
+
47
+ # Backticked path-like tokens from the `## Affected Files` section.
48
+ PATHS=$(awk '/^## /{ins=($0 ~ /^## Affected Files[[:space:]]*$/); next} ins' "$BODY_FILE" \
49
+ | grep -oE '`[^` ]+`' | tr -d '`' | grep -E '[/.]' | sed 's|^\./||; s|:[0-9][0-9,-]*$||' | awk '!seen[$0]++' || true)
50
+
51
+ LAYOUT=""
52
+ if [ -n "$SPIKE" ]; then
53
+ [ -r "$SPIKE" ] || { echo "affected-files-check: spike doc not found: $SPIKE" >&2; exit 2; }
54
+ grep -qiE '^#+[[:space:]].*repo layout' "$SPIKE" || { echo "affected-files-check: spike doc has no 'Repo layout' section: $SPIKE" >&2; exit 2; }
55
+ LAYOUT=$(awk '
56
+ /^[[:space:]]*```/ { fence=!fence; next }
57
+ !fence && /^#+[[:space:]]/ { if (ins) exit; if (tolower($0) ~ /repo layout/) ins=1; next }
58
+ !ins { next }
59
+ fence { sub(/#.*/, ""); gsub(/├──|└──|│|─/, ""); gsub(/^[[:space:]]+|[[:space:]]+$/, ""); if ($0 != "") print; next }
60
+ { while (match($0, /`[^` ]+`/)) { print substr($0, RSTART+1, RLENGTH-2); $0 = substr($0, RSTART+RLENGTH) } }
61
+ ' "$SPIKE" | sed 's|^\./||' | grep -E '[/.]' | awk '!seen[$0]++' || true)
62
+ [ -n "$LAYOUT" ] || { echo "affected-files-check: spike doc 'Repo layout' section declares no paths: $SPIKE" >&2; exit 2; }
63
+ fi
64
+
65
+ [ -n "$PATHS" ] || { echo "MISSING (no backticked paths in ## Affected Files)"; exit 1; }
66
+
67
+ exists_at_ref() { # path -> 0 when it exists at any resolvable ref
68
+ printf '%s\n' "$REFS" | while IFS= read -r r; do
69
+ [ -n "$r" ] || continue
70
+ git cat-file -e "$r:$1" 2>/dev/null && { echo y; break; }
71
+ done | grep -q y
72
+ }
73
+ in_layout() { # path -> 0 when it equals a declared entry or sits under a declared directory
74
+ printf '%s\n' "$LAYOUT" | while IFS= read -r e; do
75
+ [ -n "$e" ] || continue
76
+ if [ "$1" = "$e" ]; then echo y; break; fi
77
+ case "$e" in */) case "$1" in "$e"*) echo y; break ;; esac ;; esac
78
+ done | grep -q y
79
+ }
80
+
81
+ FAIL=0
82
+ while IFS= read -r p; do
83
+ [ -n "$p" ] || continue
84
+ if exists_at_ref "$p"; then echo "OK $p"
85
+ elif [ -n "$LAYOUT" ] && in_layout "$p"; then echo "PLANNED $p"
86
+ else echo "MISSING $p"; FAIL=1
87
+ fi
88
+ done <<EOF
89
+ $PATHS
90
+ EOF
91
+ exit $FAIL
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ # affected-files-check.test.sh — tests for scripts/affected-files-check.sh (forge#4172)
3
+ #
4
+ # Builds a throwaway git repo under $TMPDIR; no network or GitHub access.
5
+ # Usage: bash scripts/affected-files-check.test.sh
6
+ # Exit code: 0 if all assertions pass, 1 if any fail.
7
+ #
8
+ # SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
9
+ # SPDX-License-Identifier: AGPL-3.0-or-later
10
+
11
+ set -uo pipefail
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ CHECK="$SCRIPT_DIR/affected-files-check.sh"
15
+ T=$(mktemp -d "${TMPDIR:-/tmp}/afc-test.XXXXXX")
16
+ trap 'rm -rf "$T"' EXIT
17
+
18
+ PASS=0; FAIL=0
19
+ expect() { # label expected actual
20
+ if [ "$2" = "$3" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); printf 'FAIL %s\n expected: %s\n actual: %s\n' "$1" "$2" "$3"; fi
21
+ }
22
+ run() { # args... -> "<exit>|<stdout lines joined by ;>"
23
+ local out rc
24
+ out=$(cd "$T/repo" && bash "$CHECK" "$@" 2>/dev/null); rc=$?
25
+ printf '%s|%s' "$rc" "$(printf '%s' "$out" | tr '\n' ';')"
26
+ }
27
+ body() { # name lines... -> body file path
28
+ local f="$T/$1.md"; shift
29
+ { printf '## Problem\n\nx `not/listed.ts`\n\n## Affected Files\n\n'; printf '%s\n' "$@"; printf '\n## Acceptance Criteria\n\n- [ ] y\n'; } > "$f"
30
+ printf '%s' "$f"
31
+ }
32
+
33
+ mkdir -p "$T/repo/src"
34
+ git -C "$T/repo" init -q -b main
35
+ printf 'x\n' > "$T/repo/src/existing.ts"
36
+ git -C "$T/repo" add -A && git -C "$T/repo" -c user.name=t -c user.email=t@t commit -qm init
37
+
38
+ cat > "$T/spike.md" <<'EOF'
39
+ # Technical spike
40
+
41
+ ## Repo layout
42
+
43
+ Apps live in `apps/web/` and the shared schema is `packages/db/schema.sql`.
44
+
45
+ ```
46
+ apps/api/ # backend
47
+ ├── apps/api/src/main.ts
48
+ └── docs/README.md
49
+ ```
50
+
51
+ ## Data model
52
+
53
+ `packages/billing/` is mentioned outside the layout section.
54
+ EOF
55
+ printf '# Spike\n\n## Stack\n\n`apps/web/`\n' > "$T/nolayout.md"
56
+
57
+ R="--ref main"
58
+
59
+ # Brownfield (no --spike): unchanged semantics — existing OK, missing MISSING.
60
+ expect "brownfield existing path" "0|OK src/existing.ts" "$(run "$(body b1 '1. `src/existing.ts`: the file')" $R)"
61
+ expect "brownfield missing path" "1|MISSING apps/web/page.tsx" "$(run "$(body b2 '1. `apps/web/page.tsx`: planned')" $R)"
62
+ expect "brownfield ignores paths outside the section" "0|OK src/existing.ts" "$(run "$(body b3 '- `src/existing.ts`')" $R)"
63
+ expect "brownfield unresolvable default refs -> MISSING" "1|MISSING src/existing.ts" "$(run "$(body b4 '- `src/existing.ts`')")"
64
+
65
+ # Greenfield (--spike): planned paths validated against the declared layout.
66
+ expect "greenfield path under declared dir" "0|PLANNED apps/web/app/page.tsx" "$(run "$(body g1 '- `apps/web/app/page.tsx`')" $R --spike "$T/spike.md")"
67
+ expect "greenfield path equal to declared file" "0|PLANNED packages/db/schema.sql" "$(run "$(body g2 '- `packages/db/schema.sql`')" $R --spike "$T/spike.md")"
68
+ expect "greenfield path from fenced tree" "0|PLANNED apps/api/src/main.ts;PLANNED apps/api/routes.ts" "$(run "$(body g3 '- `apps/api/src/main.ts`' '- `apps/api/routes.ts`')" $R --spike "$T/spike.md")"
69
+ expect "greenfield path outside layout" "1|PLANNED apps/web/x.ts;MISSING packages/billing/x.ts" "$(run "$(body g4 '- `apps/web/x.ts`' '- `packages/billing/x.ts`')" $R --spike "$T/spike.md")"
70
+ expect "greenfield existing path still OK" "0|OK src/existing.ts" "$(run "$(body g5 '- `src/existing.ts`')" $R --spike "$T/spike.md")"
71
+ expect "greenfield file prefix is not a directory" "1|MISSING packages/db/schema.sql.bak" "$(run "$(body g6 '- `packages/db/schema.sql.bak`')" $R --spike "$T/spike.md")"
72
+
73
+ # Usage errors.
74
+ expect "spike doc missing" "2|" "$(run "$(body u1 '- `apps/web/x.ts`')" $R --spike "$T/absent.md")"
75
+ expect "spike doc without repo layout" "2|" "$(run "$(body u2 '- `apps/web/x.ts`')" $R --spike "$T/nolayout.md")"
76
+ expect "no body file" "2|" "$(run)"
77
+ expect "body without Affected Files paths" "1|MISSING (no backticked paths in ## Affected Files)" "$(run "$(body e1 'Files to be identified during investigation.')" $R)"
78
+
79
+ echo "affected-files-check.test.sh: $PASS passed, $FAIL failed"
80
+ [ "$FAIL" -eq 0 ]