forgedock 1.1.10 → 1.2.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 (76) hide show
  1. package/README.md +1 -1
  2. package/bin/cli-spawn-shared.mjs +25 -0
  3. package/bin/engine-cli.mjs +22 -1
  4. package/bin/engine.mjs +46 -4
  5. package/bin/forgedock.mjs +166 -20
  6. package/bin/hooks/pre-tool-use.mjs +331 -10
  7. package/bin/init-detect.mjs +1 -1
  8. package/bin/init-enrich-api.mjs +12 -4
  9. package/bin/init-enrich-cli.mjs +328 -0
  10. package/bin/init-enrich.mjs +137 -0
  11. package/bin/journey.mjs +328 -50
  12. package/bin/runner.mjs +653 -10
  13. package/commands/adopt.md +1 -1
  14. package/commands/analytics.md +10 -7
  15. package/commands/audit-agents.md +29 -6
  16. package/commands/audit.md +9 -20
  17. package/commands/autopilot.md +52 -78
  18. package/commands/changelog.md +1 -1
  19. package/commands/ci-audit.md +19 -10
  20. package/commands/cleanup.md +1 -1
  21. package/commands/compat-audit.md +1 -1
  22. package/commands/deploy-info.md +1 -1
  23. package/commands/deploy-pr.md +1 -1
  24. package/commands/design-bench.md +1 -1
  25. package/commands/design-render-critique-loop.md +1 -1
  26. package/commands/diagnose.md +1 -1
  27. package/commands/forge-stats.md +1 -1
  28. package/commands/forgedock-init.md +1 -1
  29. package/commands/geo-audit.md +9 -6
  30. package/commands/incident-response.md +15 -5
  31. package/commands/issue.md +298 -15
  32. package/commands/milestone.md +45 -18
  33. package/commands/optimize.md +1 -1
  34. package/commands/orchestrate/phase-1-resolve.md +20 -5
  35. package/commands/orchestrate/phase-4-execution.md +6 -1
  36. package/commands/orchestrate/phase-5-cleanup.md +43 -2
  37. package/commands/orchestrate/phase-6-report.md +1 -0
  38. package/commands/orchestrate.md +1 -1
  39. package/commands/pipeline-health/phase-4-proposals.md +40 -10
  40. package/commands/pipeline-health/phase-5-report.md +140 -13
  41. package/commands/pipeline-health.md +1 -1
  42. package/commands/pipeline-resume.md +1 -1
  43. package/commands/pipeline-status.md +1 -1
  44. package/commands/qa-sweep.md +10 -4
  45. package/commands/recover-orphans.md +1 -1
  46. package/commands/review-pr-agents/protocols.md +4 -0
  47. package/commands/review-pr-agents.md +7 -4
  48. package/commands/review-pr-staging.md +44 -15
  49. package/commands/review-pr.md +73 -28
  50. package/commands/rollback.md +15 -5
  51. package/commands/security-audit.md +14 -11
  52. package/commands/signal-planner.md +32 -12
  53. package/commands/spec-doctor.md +1 -1
  54. package/commands/sync-ecosystem.md +54 -11
  55. package/commands/test-gate.md +49 -11
  56. package/commands/validate.md +28 -9
  57. package/commands/work-on/build/architect.md +1 -1
  58. package/commands/work-on/build/context.md +1 -1
  59. package/commands/work-on/build/implement.md +2 -2
  60. package/commands/work-on/build/validate.md +3 -1
  61. package/commands/work-on/build.md +1 -1
  62. package/commands/work-on/close.md +1 -1
  63. package/commands/work-on/decompose.md +27 -24
  64. package/commands/work-on/investigate.md +2 -2
  65. package/commands/work-on/remediate.md +11 -3
  66. package/commands/work-on/review.md +5 -3
  67. package/commands/work-on-monolithic.md +1 -1
  68. package/commands/work-on.md +38 -8
  69. package/docs/CI.md +18 -0
  70. package/docs/CONFIG.md +5 -2
  71. package/docs/site/annotations-explained.md +2 -2
  72. package/docs/site/command-reference.md +21 -0
  73. package/docs/spec/review-protocol.md +6 -0
  74. package/package.json +1 -1
  75. package/scripts/danger-zones.mjs +3 -1
  76. package/scripts/transition-label.sh +80 -28
package/bin/journey.mjs CHANGED
@@ -23,6 +23,242 @@ function todo(key, low) {
23
23
  return low.includes(key) ? ` # TODO(forgedock:${key}) — verify this value` : "";
24
24
  }
25
25
 
26
+ /**
27
+ * Ordered list of the 16 optional forge.yaml sections. Each entry is a
28
+ * self-contained, fully-commented stub block (banner + example content).
29
+ * This is the single source of truth for optional-section text, shared by
30
+ * writeForgeYaml() (full-file generation for `npx forgedock init`) and
31
+ * backfillForgeYaml() (append-only migration for existing files, #1982) —
32
+ * keeping the two paths from drifting apart the same way full-generation
33
+ * itself drifted from forge.yaml.example before #1983.
34
+ *
35
+ * `key` is the top-level YAML key the section introduces (used by
36
+ * backfillForgeYaml() to detect whether a section is already present).
37
+ * `block(v)` renders the stub text; blocks never end with a trailing
38
+ * newline of their own — callers join them with `"\n\n"` to reproduce the
39
+ * blank-line separation between sections.
40
+ * @type {Array<{ key: string, block: (v: object) => string }>}
41
+ */
42
+ const OPTIONAL_SECTIONS = [
43
+ {
44
+ key: "agents",
45
+ block: () => `# =============================================================================
46
+ # AGENTS (OPTIONAL) — model overrides for pipeline agents.
47
+ # Commands: all commands with an "Agent model policy" line
48
+ # =============================================================================
49
+
50
+ # agents:
51
+ # default_model: "sonnet" # main orchestrator/agent model: "sonnet" | "opus" | "haiku"
52
+ # subagent_model: "sonnet" # model for child sub-agents (orchestrate, review-pr, ...)`,
53
+ },
54
+ {
55
+ key: "repos",
56
+ block: (v) => `# =============================================================================
57
+ # REPOS (OPTIONAL) — multi-repo configuration. Remove the # to enable.
58
+ # =============================================================================
59
+
60
+ # repos:
61
+ # default:
62
+ # repo: "${esc(v.owner)}/${esc(v.repo)}"
63
+ # staging_branch: "${esc(v.stagingBranch)}"
64
+ # satellites:
65
+ # - prefix: "mcp"
66
+ # repo: "${esc(v.owner)}/your-satellite-repo"
67
+ # staging_branch: "main"`,
68
+ },
69
+ {
70
+ key: "project_board",
71
+ block: (v) => `# =============================================================================
72
+ # PROJECT BOARD (OPTIONAL) — GitHub Projects v2 integration.
73
+ # To find IDs: gh project list --owner ${esc(v.owner)}
74
+ # =============================================================================
75
+
76
+ # project_board:
77
+ # owner: "${esc(v.owner)}"
78
+ # project_number: 1
79
+ # project_id: "PVT_kwHOxxxxxxxxxxxxxxxx"
80
+ # field_ids:
81
+ # status: "PVTSSF_xxxxxxxxxxxxxxxxxxxxxxxx"`,
82
+ },
83
+ {
84
+ key: "pipeline",
85
+ block: () => `# =============================================================================
86
+ # PIPELINE (OPTIONAL) — tuning knobs for the /orchestrate batch engine.
87
+ # Commands: orchestrate
88
+ # =============================================================================
89
+
90
+ # pipeline:
91
+ # stall_timeout_minutes: 15 # minutes an agent may sit idle before auto-resume
92
+ # token_budget_per_batch: 900000 # per-batch token ceiling for the review-finding cascade
93
+ # token_estimate_per_finding: 150000
94
+ # narration: "terse" # "terse" | "verbose"`,
95
+ },
96
+ {
97
+ key: "services",
98
+ block: () => `# =============================================================================
99
+ # SERVICES (OPTIONAL) — external service URLs for analytics/monitoring/GEO audit.
100
+ # Commands: analytics, geo-audit, autopilot (analytics snapshot)
101
+ # =============================================================================
102
+
103
+ # services:
104
+ # domain: "acme.io"
105
+ # gsc_property: "https://acme.io"
106
+ # app_url: "https://acme.io"
107
+ # api_url: "https://api.acme.io"
108
+ # analytics:
109
+ # umami:
110
+ # url: "https://umami.acme.io"
111
+ # website_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
112
+ # clarity:
113
+ # project_id: "xxxxxxxxxx"
114
+ # ga4:
115
+ # property_id: "000000000"`,
116
+ },
117
+ {
118
+ key: "review",
119
+ block: () => `# =============================================================================
120
+ # REVIEW (OPTIONAL) — context injected into review agent prompts.
121
+ # =============================================================================
122
+
123
+ # review:
124
+ # tech_stack: "Node.js, TypeScript, PostgreSQL"
125
+ # context: |
126
+ # Describe your repo structure and any unusual conventions here.`,
127
+ },
128
+ {
129
+ key: "verification",
130
+ block: () => `# =============================================================================
131
+ # VERIFICATION (OPTIONAL) — health checks for quality gate / validate.
132
+ # =============================================================================
133
+
134
+ # verification:
135
+ # health_endpoint: "https://api.example.com/health"
136
+ # health_patterns:
137
+ # - '"status": "ok"'`,
138
+ },
139
+ {
140
+ key: "deploy",
141
+ block: () => `# =============================================================================
142
+ # DEPLOY (OPTIONAL) — deployment model configuration.
143
+ # Commands: deploy-info, incident-response, rollback, work-on (Phase 3J)
144
+ # =============================================================================
145
+
146
+ # deploy:
147
+ # workflow: "deploy.yml" # GitHub Actions workflow filename used to trigger deploys
148
+ # workflow_inputs:
149
+ # services: "services"
150
+ # reason: "reason"
151
+ # secrets_backend: "sops" # sops | aws-sm | vault | ci-env | none`,
152
+ },
153
+ {
154
+ key: "autopilot",
155
+ block: () => `# =============================================================================
156
+ # AUTOPILOT (OPTIONAL) — configuration for the /autopilot autonomous deploy loop.
157
+ # Commands: autopilot
158
+ # =============================================================================
159
+
160
+ # autopilot:
161
+ # ops_issue_label: "autopilot-ops"
162
+ # headless: false # opt-in to unattended fixing; default: false
163
+ # approve:
164
+ # p0: needs-human
165
+ # p1: needs-human
166
+ # p2: auto
167
+ # p3: auto
168
+ # budget:
169
+ # per_cycle_fixes: 3
170
+ # per_cycle_tokens: null`,
171
+ },
172
+ {
173
+ key: "billing",
174
+ block: () => `# =============================================================================
175
+ # BILLING (OPTIONAL) — financial integrity checks in /security-audit.
176
+ # Commands: security-audit (Phase 4 — Financial Integrity)
177
+ # =============================================================================
178
+
179
+ # billing:
180
+ # enabled: false`,
181
+ },
182
+ {
183
+ key: "devdocs",
184
+ block: () => `# =============================================================================
185
+ # DEVDOCS (OPTIONAL) — path to the devdocs knowledge tree.
186
+ # Commands: docs init
187
+ # =============================================================================
188
+
189
+ # devdocs:
190
+ # path: "devdocs"`,
191
+ },
192
+ {
193
+ key: "adaptive_scripts",
194
+ block: () => `# =============================================================================
195
+ # ADAPTIVE_SCRIPTS (OPTIONAL) — per-repo scripts ForgeDock learns and updates.
196
+ # Commands: work-on (script discovery), optimize (script generation)
197
+ # =============================================================================
198
+
199
+ # adaptive_scripts:
200
+ # enabled: true
201
+ # directory: ".forgedock/scripts"
202
+ # commit: false`,
203
+ },
204
+ {
205
+ key: "learned",
206
+ block: () => `# =============================================================================
207
+ # LEARNED (OPTIONAL) — agent-writable patterns captured across sessions.
208
+ # Commands: work-on (Phase 0B reads; Phase 1D writes)
209
+ # =============================================================================
210
+
211
+ # learned:
212
+ # branch_targets:
213
+ # staging: "develop"
214
+ # test_commands:
215
+ # - "pnpm typecheck"
216
+ # label_map:
217
+ # "workflow:investigating": "needs-triage"
218
+ # commit_style: "conventional-with-scope"`,
219
+ },
220
+ {
221
+ key: "index",
222
+ block: () => `# =============================================================================
223
+ # INDEX (OPTIONAL) — per-commit code index for scripts/code-index.sh.
224
+ # Commands: work-on:investigate, work-on/build/architect, review-pr
225
+ # =============================================================================
226
+
227
+ # index:
228
+ # languages: "Python,JavaScript,TypeScript,Go"
229
+ # cache_dir: ".forge/index"
230
+ # enabled: true`,
231
+ },
232
+ {
233
+ key: "attribution",
234
+ block: () => `# =============================================================================
235
+ # ATTRIBUTION (OPTIONAL) — opt-in growth features (PR/annotation footer links).
236
+ # Commands: work-on/review, review-pr
237
+ # =============================================================================
238
+
239
+ # attribution:
240
+ # pr_footer: true
241
+ # annotation_link: true`,
242
+ },
243
+ {
244
+ key: "pattern_feeds",
245
+ block: () => `# =============================================================================
246
+ # PATTERN_FEEDS (OPTIONAL) — subscribe to external pattern card repositories.
247
+ # Commands: scripts/build-knowledge-index.mjs, quality-gate, optimize
248
+ # =============================================================================
249
+
250
+ # pattern_feeds:
251
+ # feeds:
252
+ # - slug: "forge-core"
253
+ # repo: "RapierCraftStudios/forge-patterns"
254
+ # ref: "abc1234def5678..." # REQUIRED: pinned commit SHA — never a branch name
255
+ # path: "cards"
256
+ # stacks: ["node", "bash", "gha"]
257
+ # priority: "LOW"
258
+ # enabled: true`,
259
+ },
260
+ ];
261
+
26
262
  /**
27
263
  * Write forge.yaml from reviewed values.
28
264
  * @param {{owner:string,repo:string,name:string,description:string,root:string,
@@ -34,13 +270,15 @@ function todo(key, low) {
34
270
  */
35
271
  export function writeForgeYaml(v, lowConfidenceKeys, outputPath) {
36
272
  const low = lowConfidenceKeys;
37
- const content = `# forge.yaml — ForgeDock Configuration
273
+ const header = `# forge.yaml — ForgeDock Configuration
38
274
  #
39
275
  # Auto-generated by: npx forgedock
40
276
  # Fields marked with TODO comments below were guessed — verify them.
41
277
  #
42
278
  # Required sections: project, paths, branches
43
- # Optional sections: repos, project_board, services, review, verification
279
+ # Optional sections: agents, repos, project_board, pipeline, services, review,
280
+ # verification, deploy, autopilot, billing, devdocs, adaptive_scripts,
281
+ # learned, index, attribution, pattern_feeds
44
282
  #
45
283
  # See docs/CONFIG.md for full reference.
46
284
 
@@ -69,51 +307,9 @@ paths:
69
307
  branches:
70
308
  default: "${esc(v.defaultBranch)}"${todo("defaultBranch", low)}
71
309
  staging: "${esc(v.stagingBranch)}"${todo("stagingBranch", low)}
72
- feature_pattern: "milestone/{slug}"
73
-
74
- # =============================================================================
75
- # REPOS (OPTIONAL) — multi-repo configuration. Remove the # to enable.
76
- # =============================================================================
77
-
78
- # repos:
79
- # default:
80
- # repo: "${esc(v.owner)}/${esc(v.repo)}"
81
- # staging_branch: "${esc(v.stagingBranch)}"
82
- # satellites:
83
- # - prefix: "mcp"
84
- # repo: "${esc(v.owner)}/your-satellite-repo"
85
- # staging_branch: "main"
86
-
87
- # =============================================================================
88
- # PROJECT BOARD (OPTIONAL) — GitHub Projects v2 integration.
89
- # To find IDs: gh project list --owner ${esc(v.owner)}
90
- # =============================================================================
91
-
92
- # project_board:
93
- # owner: "${esc(v.owner)}"
94
- # project_number: 1
95
- # project_id: "PVT_kwHOxxxxxxxxxxxxxxxx"
96
- # field_ids:
97
- # status: "PVTSSF_xxxxxxxxxxxxxxxxxxxxxxxx"
98
-
99
- # =============================================================================
100
- # REVIEW (OPTIONAL) — context injected into review agent prompts.
101
- # =============================================================================
102
-
103
- # review:
104
- # tech_stack: "Node.js, TypeScript, PostgreSQL"
105
- # context: |
106
- # Describe your repo structure and any unusual conventions here.
107
-
108
- # =============================================================================
109
- # VERIFICATION (OPTIONAL) — health checks for quality gate / validate.
110
- # =============================================================================
310
+ feature_pattern: "milestone/{slug}"`;
111
311
 
112
- # verification:
113
- # health_endpoint: "https://api.example.com/health"
114
- # health_patterns:
115
- # - '"status": "ok"'
116
- `;
312
+ const content = [header, ...OPTIONAL_SECTIONS.map((s) => s.block(v))].join("\n\n") + "\n";
117
313
  // Atomic write: write to a temp file first, then rename into place.
118
314
  // If the write fails (e.g. ENOSPC), the original file is untouched and
119
315
  // any partial .tmp is cleaned up — no corrupt or missing forge.yaml.
@@ -129,6 +325,79 @@ branches:
129
325
  return { todoCount };
130
326
  }
131
327
 
328
+ /**
329
+ * Backfill any of the 16 optional forge.yaml sections missing from an
330
+ * existing config file (#1982). Additive only — never edits, reorders, or
331
+ * removes existing content. Required sections, already-present optional
332
+ * sections, and any hand-edited content are left byte-for-byte untouched;
333
+ * only trailing whitespace at the very end of the file is trimmed before
334
+ * appending. Follows the same atomic temp+rename write pattern as
335
+ * writeForgeYaml() (ref: #1396 — a non-atomic write here could corrupt the
336
+ * user's forge.yaml on disk-full).
337
+ *
338
+ * A section counts as "present" if its top-level key appears either active
339
+ * (`key:` at column 0 — the user enabled it) or as the standard commented
340
+ * stub (`# key:`) anywhere in the file. Detection is intentionally strict
341
+ * about indentation (no arbitrary leading whitespace) so a same-named key
342
+ * nested under an unrelated section can never produce a false "present".
343
+ *
344
+ * @param {string} cwd - directory containing forge.yaml
345
+ * @returns {{ present: boolean, added: string[], alreadyPresent: string[] }}
346
+ * `present: false` means no forge.yaml exists at cwd — added/alreadyPresent
347
+ * are both empty; the caller should direct the user to `npx forgedock init`
348
+ * instead of migrate.
349
+ */
350
+ export function backfillForgeYaml(cwd) {
351
+ const outputPath = join(cwd, "forge.yaml");
352
+ if (!existsSync(outputPath)) {
353
+ return { present: false, added: [], alreadyPresent: [] };
354
+ }
355
+ const raw = readFileSync(outputPath, "utf-8");
356
+
357
+ // Best-effort values for the blocks that interpolate project identity
358
+ // (repos/project_board examples) — read from the existing file's REQUIRED
359
+ // sections so backfilled examples reference this project rather than a
360
+ // generic placeholder. Falls back to placeholders if unset/unparseable;
361
+ // these only ever land inside commented-out example text.
362
+ const v = {
363
+ owner: raw.match(/^\s*owner:\s*"([^"]*)"/m)?.[1] || "your-org",
364
+ repo: raw.match(/^\s*repo:\s*"([^"]*)"/m)?.[1] || "your-repo",
365
+ stagingBranch: raw.match(/^\s*staging:\s*"([^"]*)"/m)?.[1] || "staging",
366
+ };
367
+
368
+ const added = [];
369
+ const alreadyPresent = [];
370
+ const newBlocks = [];
371
+ for (const section of OPTIONAL_SECTIONS) {
372
+ const isActive = new RegExp(`^${section.key}:`, "m").test(raw);
373
+ const isStub = new RegExp(`^#\\s?${section.key}:`, "m").test(raw);
374
+ if (isActive || isStub) {
375
+ alreadyPresent.push(section.key);
376
+ } else {
377
+ added.push(section.key);
378
+ newBlocks.push(section.block(v));
379
+ }
380
+ }
381
+
382
+ if (added.length === 0) {
383
+ return { present: true, added, alreadyPresent };
384
+ }
385
+
386
+ const trimmedRaw = raw.replace(/\s+$/, "");
387
+ const content = `${trimmedRaw}\n\n${newBlocks.join("\n\n")}\n`;
388
+
389
+ const tmpPath = outputPath + ".tmp";
390
+ try {
391
+ writeFileSync(tmpPath, content, "utf-8");
392
+ renameSync(tmpPath, outputPath);
393
+ } catch (err) {
394
+ try { unlinkSync(tmpPath); } catch { /* best-effort cleanup */ }
395
+ throw err;
396
+ }
397
+
398
+ return { present: true, added, alreadyPresent };
399
+ }
400
+
132
401
  /** Maps manual-flow value keys to their location in a ConfigDraft. */
133
402
  const DRAFT_KEY_PATHS = {
134
403
  owner: ["project", "owner"],
@@ -332,6 +601,7 @@ export function makeCtx(overrides = {}) {
332
601
  const argv = overrides.argv ?? process.argv.slice(2);
333
602
  const nodeVersion = overrides.nodeVersion ?? process.versions.node;
334
603
  const enrichFn = overrides.enrichFn ?? enrich;
604
+ const isCliAvailableFn = overrides.isCliAvailableFn ?? isClaudeCliAvailable;
335
605
  const openFn = overrides.openFn ?? openUrl;
336
606
  const confirmFn = overrides.confirmFn ?? confirm;
337
607
  const platform = overrides.platform ?? process.platform;
@@ -357,6 +627,7 @@ export function makeCtx(overrides = {}) {
357
627
  release,
358
628
  linkStrategy: "symlink",
359
629
  enrichFn,
630
+ isCliAvailableFn,
360
631
  openFn,
361
632
  confirmFn,
362
633
  exec: (cmd, args) =>
@@ -1451,7 +1722,8 @@ export async function forge(ctx) {
1451
1722
  // ---------------------------------------------------------------------------
1452
1723
 
1453
1724
  import { detectConfig } from "./init-detect.mjs";
1454
- import { enrich } from "./init-enrich-api.mjs";
1725
+ import { enrich, resolveEnrichBackend } from "./init-enrich.mjs";
1726
+ import { isClaudeCliAvailable } from "./runner.mjs";
1455
1727
  import { annotatedReviewScreen, box, confirm, getLogoTagline } from "./tui.mjs";
1456
1728
 
1457
1729
  const badgeOf = (field) => field.confidence;
@@ -1482,16 +1754,22 @@ export async function read(ctx) {
1482
1754
  ];
1483
1755
  await revealRows(rows, { mode: ctx.mode, motion: ctx.motion, writer: w });
1484
1756
 
1485
- if (ctx.env.ANTHROPIC_API_KEY) {
1757
+ const enrichBackend = resolveEnrichBackend({
1758
+ cwd: ctx.cwd,
1759
+ env: ctx.env,
1760
+ isCliAvailableFn: ctx.isCliAvailableFn,
1761
+ });
1762
+
1763
+ if (enrichBackend !== "none") {
1486
1764
  try {
1487
1765
  w.write(" " + dimLine(ctx, "✦ enriching with AI…") + "\n");
1488
- const enriched = await ctx.enrichFn(draft);
1766
+ const enriched = await ctx.enrichFn(draft, { backend: enrichBackend, cwd: ctx.cwd, env: ctx.env });
1489
1767
  if (enriched && typeof enriched === "object") draft = enriched;
1490
1768
  } catch {
1491
1769
  w.write(" " + dimLine(ctx, "✦ AI enrichment unavailable — continuing with detection only") + "\n");
1492
1770
  }
1493
1771
  } else {
1494
- w.write(" " + dimLine(ctx, "✦ no ANTHROPIC_API_KEY — skipping AI enrichment") + "\n");
1772
+ w.write(" " + dimLine(ctx, "✦ no Claude Code CLI or ANTHROPIC_API_KEY — skipping AI enrichment") + "\n");
1495
1773
  }
1496
1774
 
1497
1775
  return { draft, description };