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.
- package/README.md +1 -1
- package/bin/cli-spawn-shared.mjs +25 -0
- package/bin/engine-cli.mjs +22 -1
- package/bin/engine.mjs +46 -4
- package/bin/forgedock.mjs +166 -20
- package/bin/hooks/pre-tool-use.mjs +331 -10
- package/bin/init-detect.mjs +1 -1
- package/bin/init-enrich-api.mjs +12 -4
- package/bin/init-enrich-cli.mjs +328 -0
- package/bin/init-enrich.mjs +137 -0
- package/bin/journey.mjs +328 -50
- package/bin/runner.mjs +653 -10
- package/commands/adopt.md +1 -1
- package/commands/analytics.md +10 -7
- package/commands/audit-agents.md +29 -6
- package/commands/audit.md +9 -20
- package/commands/autopilot.md +52 -78
- package/commands/changelog.md +1 -1
- package/commands/ci-audit.md +19 -10
- package/commands/cleanup.md +1 -1
- package/commands/compat-audit.md +1 -1
- package/commands/deploy-info.md +1 -1
- package/commands/deploy-pr.md +1 -1
- package/commands/design-bench.md +1 -1
- package/commands/design-render-critique-loop.md +1 -1
- package/commands/diagnose.md +1 -1
- package/commands/forge-stats.md +1 -1
- package/commands/forgedock-init.md +1 -1
- package/commands/geo-audit.md +9 -6
- package/commands/incident-response.md +15 -5
- package/commands/issue.md +298 -15
- package/commands/milestone.md +45 -18
- package/commands/optimize.md +1 -1
- package/commands/orchestrate/phase-1-resolve.md +20 -5
- package/commands/orchestrate/phase-4-execution.md +6 -1
- package/commands/orchestrate/phase-5-cleanup.md +43 -2
- package/commands/orchestrate/phase-6-report.md +1 -0
- package/commands/orchestrate.md +1 -1
- package/commands/pipeline-health/phase-4-proposals.md +40 -10
- package/commands/pipeline-health/phase-5-report.md +140 -13
- package/commands/pipeline-health.md +1 -1
- package/commands/pipeline-resume.md +1 -1
- package/commands/pipeline-status.md +1 -1
- package/commands/qa-sweep.md +10 -4
- package/commands/recover-orphans.md +1 -1
- package/commands/review-pr-agents/protocols.md +4 -0
- package/commands/review-pr-agents.md +7 -4
- package/commands/review-pr-staging.md +44 -15
- package/commands/review-pr.md +73 -28
- package/commands/rollback.md +15 -5
- package/commands/security-audit.md +14 -11
- package/commands/signal-planner.md +32 -12
- package/commands/spec-doctor.md +1 -1
- package/commands/sync-ecosystem.md +54 -11
- package/commands/test-gate.md +49 -11
- package/commands/validate.md +28 -9
- package/commands/work-on/build/architect.md +1 -1
- package/commands/work-on/build/context.md +1 -1
- package/commands/work-on/build/implement.md +2 -2
- package/commands/work-on/build/validate.md +3 -1
- package/commands/work-on/build.md +1 -1
- package/commands/work-on/close.md +1 -1
- package/commands/work-on/decompose.md +27 -24
- package/commands/work-on/investigate.md +2 -2
- package/commands/work-on/remediate.md +11 -3
- package/commands/work-on/review.md +5 -3
- package/commands/work-on-monolithic.md +1 -1
- package/commands/work-on.md +38 -8
- package/docs/CI.md +18 -0
- package/docs/CONFIG.md +5 -2
- package/docs/site/annotations-explained.md +2 -2
- package/docs/site/command-reference.md +21 -0
- package/docs/spec/review-protocol.md +6 -0
- package/package.json +1 -1
- package/scripts/danger-zones.mjs +3 -1
- 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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 };
|