@momentiq/dark-factory-cli 2.0.0 → 2.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/dist/adapters/static-schema-lint.js +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +18 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/onboard.d.ts +69 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +323 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/cycle-doc-validator/validate_cycle_doc.py +131 -60
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +20 -0
- package/dist/doctor.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +2 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/onboard.d.ts +6 -0
- package/dist/mcp/tools/onboard.d.ts.map +1 -0
- package/dist/mcp/tools/onboard.js +115 -0
- package/dist/mcp/tools/onboard.js.map +1 -0
- package/dist/onboard/analyze.d.ts +7 -0
- package/dist/onboard/analyze.d.ts.map +1 -0
- package/dist/onboard/analyze.js +86 -0
- package/dist/onboard/analyze.js.map +1 -0
- package/dist/onboard/analyzer.d.ts +15 -0
- package/dist/onboard/analyzer.d.ts.map +1 -0
- package/dist/onboard/analyzer.js +52 -0
- package/dist/onboard/analyzer.js.map +1 -0
- package/dist/onboard/analyzers/ci.d.ts +3 -0
- package/dist/onboard/analyzers/ci.d.ts.map +1 -0
- package/dist/onboard/analyzers/ci.js +112 -0
- package/dist/onboard/analyzers/ci.js.map +1 -0
- package/dist/onboard/analyzers/docs.d.ts +3 -0
- package/dist/onboard/analyzers/docs.d.ts.map +1 -0
- package/dist/onboard/analyzers/docs.js +151 -0
- package/dist/onboard/analyzers/docs.js.map +1 -0
- package/dist/onboard/analyzers/git.d.ts +3 -0
- package/dist/onboard/analyzers/git.d.ts.map +1 -0
- package/dist/onboard/analyzers/git.js +87 -0
- package/dist/onboard/analyzers/git.js.map +1 -0
- package/dist/onboard/analyzers/lockfile.d.ts +3 -0
- package/dist/onboard/analyzers/lockfile.d.ts.map +1 -0
- package/dist/onboard/analyzers/lockfile.js +285 -0
- package/dist/onboard/analyzers/lockfile.js.map +1 -0
- package/dist/onboard/analyzers/manifest.d.ts +3 -0
- package/dist/onboard/analyzers/manifest.d.ts.map +1 -0
- package/dist/onboard/analyzers/manifest.js +228 -0
- package/dist/onboard/analyzers/manifest.js.map +1 -0
- package/dist/onboard/analyzers/tree.d.ts +3 -0
- package/dist/onboard/analyzers/tree.d.ts.map +1 -0
- package/dist/onboard/analyzers/tree.js +196 -0
- package/dist/onboard/analyzers/tree.js.map +1 -0
- package/dist/onboard/apply-plan.d.ts +29 -0
- package/dist/onboard/apply-plan.d.ts.map +1 -0
- package/dist/onboard/apply-plan.js +80 -0
- package/dist/onboard/apply-plan.js.map +1 -0
- package/dist/onboard/auto-profile.d.ts +3 -0
- package/dist/onboard/auto-profile.d.ts.map +1 -0
- package/dist/onboard/auto-profile.js +20 -0
- package/dist/onboard/auto-profile.js.map +1 -0
- package/dist/onboard/fixtures/replay-git-history.d.ts +20 -0
- package/dist/onboard/fixtures/replay-git-history.d.ts.map +1 -0
- package/dist/onboard/fixtures/replay-git-history.js +91 -0
- package/dist/onboard/fixtures/replay-git-history.js.map +1 -0
- package/dist/onboard/generate-plan.d.ts +18 -0
- package/dist/onboard/generate-plan.d.ts.map +1 -0
- package/dist/onboard/generate-plan.js +68 -0
- package/dist/onboard/generate-plan.js.map +1 -0
- package/dist/onboard/llm-client.d.ts +45 -0
- package/dist/onboard/llm-client.d.ts.map +1 -0
- package/dist/onboard/llm-client.js +84 -0
- package/dist/onboard/llm-client.js.map +1 -0
- package/dist/onboard/prompts/scaffold.md +93 -0
- package/dist/onboard/prompts.d.ts +13 -0
- package/dist/onboard/prompts.d.ts.map +1 -0
- package/dist/onboard/prompts.js +54 -0
- package/dist/onboard/prompts.js.map +1 -0
- package/dist/onboard/scaffold-schema.d.ts +194 -0
- package/dist/onboard/scaffold-schema.d.ts.map +1 -0
- package/dist/onboard/scaffold-schema.js +73 -0
- package/dist/onboard/scaffold-schema.js.map +1 -0
- package/dist/onboard/schema.d.ts +545 -0
- package/dist/onboard/schema.d.ts.map +1 -0
- package/dist/onboard/schema.js +148 -0
- package/dist/onboard/schema.js.map +1 -0
- package/dist/onboard/seeders/adr.d.ts +3 -0
- package/dist/onboard/seeders/adr.d.ts.map +1 -0
- package/dist/onboard/seeders/adr.js +212 -0
- package/dist/onboard/seeders/adr.js.map +1 -0
- package/dist/onboard/seeders/agent-review-config/cloud.canonical.json +22 -0
- package/dist/onboard/seeders/agent-review-config/local.canonical.json +21 -0
- package/dist/onboard/seeders/agent-review-config.d.ts +3 -0
- package/dist/onboard/seeders/agent-review-config.d.ts.map +1 -0
- package/dist/onboard/seeders/agent-review-config.js +38 -0
- package/dist/onboard/seeders/agent-review-config.js.map +1 -0
- package/dist/onboard/seeders/cycle1-bootstrap.d.ts +3 -0
- package/dist/onboard/seeders/cycle1-bootstrap.d.ts.map +1 -0
- package/dist/onboard/seeders/cycle1-bootstrap.js +76 -0
- package/dist/onboard/seeders/cycle1-bootstrap.js.map +1 -0
- package/dist/onboard/seeders/index.d.ts +47 -0
- package/dist/onboard/seeders/index.d.ts.map +1 -0
- package/dist/onboard/seeders/index.js +37 -0
- package/dist/onboard/seeders/index.js.map +1 -0
- package/dist/onboard/seeders/runbook.d.ts +13 -0
- package/dist/onboard/seeders/runbook.d.ts.map +1 -0
- package/dist/onboard/seeders/runbook.js +85 -0
- package/dist/onboard/seeders/runbook.js.map +1 -0
- package/dist/onboard/seeders/templates/adr.md.tmpl +26 -0
- package/dist/onboard/seeders/templates/cycle1-bootstrap.md.tmpl +64 -0
- package/dist/onboard/seeders/templates/runbook.md.tmpl +26 -0
- package/dist/onboard/template-loader.d.ts +20 -0
- package/dist/onboard/template-loader.d.ts.map +1 -0
- package/dist/onboard/template-loader.js +143 -0
- package/dist/onboard/template-loader.js.map +1 -0
- package/dist/onboard/template-ref.d.ts +15 -0
- package/dist/onboard/template-ref.d.ts.map +1 -0
- package/dist/onboard/template-ref.js +45 -0
- package/dist/onboard/template-ref.js.map +1 -0
- package/dist/onboard/validate.d.ts +19 -0
- package/dist/onboard/validate.d.ts.map +1 -0
- package/dist/onboard/validate.js +127 -0
- package/dist/onboard/validate.js.map +1 -0
- package/dist/onboard/writers/dry-run-renderer.d.ts +6 -0
- package/dist/onboard/writers/dry-run-renderer.d.ts.map +1 -0
- package/dist/onboard/writers/dry-run-renderer.js +88 -0
- package/dist/onboard/writers/dry-run-renderer.js.map +1 -0
- package/dist/onboard/writers/emit.d.ts +6 -0
- package/dist/onboard/writers/emit.d.ts.map +1 -0
- package/dist/onboard/writers/emit.js +29 -0
- package/dist/onboard/writers/emit.js.map +1 -0
- package/dist/onboard/writers/merge.d.ts +16 -0
- package/dist/onboard/writers/merge.d.ts.map +1 -0
- package/dist/onboard/writers/merge.js +142 -0
- package/dist/onboard/writers/merge.js.map +1 -0
- package/dist/onboard/writers/pr-writer.d.ts +44 -0
- package/dist/onboard/writers/pr-writer.d.ts.map +1 -0
- package/dist/onboard/writers/pr-writer.js +104 -0
- package/dist/onboard/writers/pr-writer.js.map +1 -0
- package/dist/onboard/writers/skip.d.ts +9 -0
- package/dist/onboard/writers/skip.d.ts.map +1 -0
- package/dist/onboard/writers/skip.js +9 -0
- package/dist/onboard/writers/skip.js.map +1 -0
- package/package.json +8 -1
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
// `df onboard` — cycles 15 Phase A (analysis) + Phase B (LLM tailoring) +
|
|
2
|
+
// Phase C (deterministic seeders, merged via `cmdOnboard`).
|
|
3
|
+
//
|
|
4
|
+
// Two exported entry points:
|
|
5
|
+
//
|
|
6
|
+
// - **`cmdOnboard(opts)`** — the orchestrator. Runs the pipeline
|
|
7
|
+
// `analyze → generatePlan (Phase B) → runSeeders (Phase C) → mergeScaffoldPlan`
|
|
8
|
+
// and returns a typed result. The MCP `df_onboard` tool (Task 6) and the
|
|
9
|
+
// sage3c reproduction harness (Task 5) call this directly.
|
|
10
|
+
// - **`cmdOnboardCli(rest, io)`** — the CLI shim. Parses argv, validates
|
|
11
|
+
// credentials at the surface, delegates to `cmdOnboard`, then formats the
|
|
12
|
+
// structured result for stdout/stderr per --json / --analysis-only / etc.
|
|
13
|
+
//
|
|
14
|
+
// Phase A surface (preserved): `df onboard --analysis-only [--json] [target]`.
|
|
15
|
+
// Phase B/C surface: `df onboard [--dry-run | --apply | --pr]
|
|
16
|
+
// [--template <ref>] [--api-key <k>] [--model <id>] [--profile local|cloud]
|
|
17
|
+
// [--include-runtime-infra] [--force] [--json] [target]`.
|
|
18
|
+
//
|
|
19
|
+
// Mode flags are mutually exclusive; default is --dry-run when none set.
|
|
20
|
+
// --include-runtime-infra is a loud rejection (deferred to v2).
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
22
|
+
import { readdir } from "node:fs/promises";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { analyze } from "../onboard/analyze.js";
|
|
25
|
+
import { loadTemplate } from "../onboard/template-loader.js";
|
|
26
|
+
import { generatePlan } from "../onboard/generate-plan.js";
|
|
27
|
+
import { applyPlan, ScaffoldApplyError } from "../onboard/apply-plan.js";
|
|
28
|
+
import { runPrMode, preflightPr, defaultRunner } from "../onboard/writers/pr-writer.js";
|
|
29
|
+
import { autoProfile } from "../onboard/auto-profile.js";
|
|
30
|
+
import { ALL_SEEDERS_DEFAULT, runSeeders } from "../onboard/seeders/index.js";
|
|
31
|
+
import { ScaffoldPlanSchema, } from "../onboard/scaffold-schema.js";
|
|
32
|
+
const DEFAULT_TEMPLATE = "gh:momentiq-ai/sage-blueprint@latest";
|
|
33
|
+
const DEFAULT_MODEL = "claude-3-7-sonnet-latest";
|
|
34
|
+
const HELP = [
|
|
35
|
+
"df onboard — Cycle 15: deterministic analyzer (Phase A) + LLM-tailored scaffolding (Phase B) + deterministic seeders (Phase C).",
|
|
36
|
+
"",
|
|
37
|
+
"Usage:",
|
|
38
|
+
" df onboard [--analysis-only | --dry-run | --apply | --pr] [target-dir]",
|
|
39
|
+
" [--template <ref>] [--api-key <k>] [--model <id>]",
|
|
40
|
+
" [--profile local|cloud] [--force] [--json]",
|
|
41
|
+
"",
|
|
42
|
+
"Modes (mutually exclusive; default --dry-run):",
|
|
43
|
+
" --analysis-only Phase A only — runs the Stage A scanner, emits RepoAnalysis.",
|
|
44
|
+
" --dry-run Render the tailored ScaffoldPlan as a diff preview (no writes).",
|
|
45
|
+
" --apply Write the tailored scaffold to the target directory.",
|
|
46
|
+
" --pr Branch + commit + open a PR via gh CLI.",
|
|
47
|
+
"",
|
|
48
|
+
"Stage B/C flags (apply / pr / dry-run):",
|
|
49
|
+
" --template <ref> Template source. Default: " + DEFAULT_TEMPLATE,
|
|
50
|
+
" Ref shape: gh:<owner>/<repo>@<sha|tag|branch|latest>",
|
|
51
|
+
" file:///<abs-path>@<ref>",
|
|
52
|
+
" --api-key <key> Anthropic API key. Defaults to ANTHROPIC_API_KEY env.",
|
|
53
|
+
" --model <id> LLM model id. Default: " + DEFAULT_MODEL,
|
|
54
|
+
" --profile local|cloud Critic profile for the generated .agent-review/config.json.",
|
|
55
|
+
" Default: auto-detect (cloud if DF gate already present).",
|
|
56
|
+
" --force On --apply, overwrite existing emit-targets without prompting;",
|
|
57
|
+
" on --pr, recreate the df/onboard-<sha8> branch (-C vs -c).",
|
|
58
|
+
" --json JSON output. With --analysis-only: RepoAnalysis.",
|
|
59
|
+
" With --dry-run: merged ScaffoldPlan (Phase B + Phase C seeders).",
|
|
60
|
+
"",
|
|
61
|
+
"Rejected:",
|
|
62
|
+
" --include-runtime-infra Deferred to v2 (Cycle 15 D5: outside the agent-context-set bar).",
|
|
63
|
+
"",
|
|
64
|
+
"Exit codes:",
|
|
65
|
+
" 0 success",
|
|
66
|
+
" 1 runtime failure (missing API key, write error, gh-auth missing, etc.)",
|
|
67
|
+
" 2 usage error",
|
|
68
|
+
"",
|
|
69
|
+
].join("\n");
|
|
70
|
+
function parseOnboardArgs(rest) {
|
|
71
|
+
let analysisOnly = false, dryRun = false, apply = false, pr = false;
|
|
72
|
+
let json = false, force = false, includeRuntimeInfra = false;
|
|
73
|
+
let template = DEFAULT_TEMPLATE;
|
|
74
|
+
let apiKey;
|
|
75
|
+
let modelId = DEFAULT_MODEL;
|
|
76
|
+
let profile;
|
|
77
|
+
let targetDir = null;
|
|
78
|
+
for (let i = 0; i < rest.length; i++) {
|
|
79
|
+
const a = rest[i] ?? "";
|
|
80
|
+
if (a === "--analysis-only") {
|
|
81
|
+
analysisOnly = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (a === "--dry-run") {
|
|
85
|
+
dryRun = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (a === "--apply") {
|
|
89
|
+
apply = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (a === "--pr") {
|
|
93
|
+
pr = true;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (a === "--json") {
|
|
97
|
+
json = true;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (a === "--force") {
|
|
101
|
+
force = true;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (a === "--include-runtime-infra") {
|
|
105
|
+
includeRuntimeInfra = true;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (a === "--template") {
|
|
109
|
+
template = rest[++i] ?? "";
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (a === "--api-key") {
|
|
113
|
+
apiKey = rest[++i];
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (a === "--model") {
|
|
117
|
+
modelId = rest[++i] ?? DEFAULT_MODEL;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (a === "--profile") {
|
|
121
|
+
const v = rest[++i];
|
|
122
|
+
if (v !== "local" && v !== "cloud")
|
|
123
|
+
return { error: `--profile must be local|cloud, got "${v}"` };
|
|
124
|
+
profile = v;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (a.startsWith("--"))
|
|
128
|
+
return { error: `unknown flag: ${a}` };
|
|
129
|
+
if (targetDir !== null)
|
|
130
|
+
return { error: `unexpected positional arg: ${a}` };
|
|
131
|
+
targetDir = a;
|
|
132
|
+
}
|
|
133
|
+
if (includeRuntimeInfra) {
|
|
134
|
+
return {
|
|
135
|
+
error: "--include-runtime-infra is deferred to v2 (Cycle 15 D5: " +
|
|
136
|
+
"Generating runtime infrastructure (Terraform, Helm) is outside the agent-context-set bar).",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const modes = [analysisOnly, dryRun, apply, pr].filter(Boolean).length;
|
|
140
|
+
if (modes > 1) {
|
|
141
|
+
return { error: "mode flags are mutually exclusive (pass one of --analysis-only / --dry-run / --apply / --pr)" };
|
|
142
|
+
}
|
|
143
|
+
const mode = analysisOnly ? "analysis-only" :
|
|
144
|
+
apply ? "apply" :
|
|
145
|
+
pr ? "pr" :
|
|
146
|
+
"dry-run";
|
|
147
|
+
const resolvedApiKey = apiKey ?? process.env["ANTHROPIC_API_KEY"];
|
|
148
|
+
return {
|
|
149
|
+
mode,
|
|
150
|
+
template,
|
|
151
|
+
...(resolvedApiKey !== undefined ? { apiKey: resolvedApiKey } : {}),
|
|
152
|
+
modelId,
|
|
153
|
+
...(profile !== undefined ? { profile } : {}),
|
|
154
|
+
force,
|
|
155
|
+
json,
|
|
156
|
+
targetDir: targetDir ?? process.cwd(),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function sha8(input) {
|
|
160
|
+
return createHash("sha256").update(input).digest("hex").slice(0, 8);
|
|
161
|
+
}
|
|
162
|
+
async function listExistingAdrs(target) {
|
|
163
|
+
try {
|
|
164
|
+
return (await readdir(join(target, "docs", "ADR"))).filter((n) => n.endsWith(".md"));
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Merge Phase B's LLM-emitted `ScaffoldPlan` with Phase C's seeder-emitted
|
|
172
|
+
* `FilePlan[]`. Phase B wins on path collision — the LLM tailoring is
|
|
173
|
+
* authoritative for any file the LLM chose to emit. The result is re-parsed
|
|
174
|
+
* through `ScaffoldPlanSchema` as the structural canary (per-file 16KB cap,
|
|
175
|
+
* 100-file cap, discriminated-union constraints) so caps applied to the
|
|
176
|
+
* Phase B half don't silently leak.
|
|
177
|
+
*/
|
|
178
|
+
export function mergeScaffoldPlan(phaseBPlan, seederFiles) {
|
|
179
|
+
const phaseBPaths = new Set(phaseBPlan.files.map((f) => f.path));
|
|
180
|
+
const nonColliding = seederFiles.filter((s) => !phaseBPaths.has(s.path));
|
|
181
|
+
const merged = {
|
|
182
|
+
...phaseBPlan,
|
|
183
|
+
files: [...phaseBPlan.files, ...nonColliding],
|
|
184
|
+
};
|
|
185
|
+
return ScaffoldPlanSchema.parse(merged);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The orchestrator. Runs analyze → generatePlan → runSeeders →
|
|
189
|
+
* mergeScaffoldPlan, then optionally writes to the working tree (apply) or
|
|
190
|
+
* opens a PR (pr). Returns a structured result the CLI shim and MCP tool
|
|
191
|
+
* both consume.
|
|
192
|
+
*
|
|
193
|
+
* Analysis-only mode skips Phase B + Phase C; it returns `{ analysis }`
|
|
194
|
+
* alone for the no-LLM preview path.
|
|
195
|
+
*/
|
|
196
|
+
export async function cmdOnboard(opts) {
|
|
197
|
+
const analysis = await analyze(opts.target);
|
|
198
|
+
if (opts.mode === "analysis-only") {
|
|
199
|
+
return { analysis };
|
|
200
|
+
}
|
|
201
|
+
const profile = opts.profile ?? autoProfile(analysis);
|
|
202
|
+
const template = await loadTemplate(opts.templateRef ?? DEFAULT_TEMPLATE);
|
|
203
|
+
const phaseBPlan = await generatePlan(analysis, template, {
|
|
204
|
+
modelId: opts.modelId ?? DEFAULT_MODEL,
|
|
205
|
+
profile,
|
|
206
|
+
...(opts.apiKey !== undefined ? { apiKey: opts.apiKey } : {}),
|
|
207
|
+
});
|
|
208
|
+
const existingAdrs = await listExistingAdrs(opts.target);
|
|
209
|
+
const seederFiles = await runSeeders({ analysis, existingAdrs, now: new Date(), profile }, ALL_SEEDERS_DEFAULT);
|
|
210
|
+
const plan = mergeScaffoldPlan(phaseBPlan, seederFiles);
|
|
211
|
+
let branchName = null;
|
|
212
|
+
let applied = false;
|
|
213
|
+
const force = opts.force ?? false;
|
|
214
|
+
if (opts.mode === "apply") {
|
|
215
|
+
await applyPlan(opts.target, plan, { mode: "apply", force });
|
|
216
|
+
applied = true;
|
|
217
|
+
}
|
|
218
|
+
else if (opts.mode === "pr") {
|
|
219
|
+
const analysisSha8 = sha8(JSON.stringify(analysis));
|
|
220
|
+
const r = await runPrMode(opts.target, plan, analysisSha8, {
|
|
221
|
+
canonicalName: analysis.canonicalName || "unknown/unknown",
|
|
222
|
+
defaultBranch: analysis.git.defaultBranch,
|
|
223
|
+
force,
|
|
224
|
+
});
|
|
225
|
+
branchName = r.branch;
|
|
226
|
+
applied = true;
|
|
227
|
+
}
|
|
228
|
+
// dry-run: orchestrator returns the merged plan; the CLI shim renders it.
|
|
229
|
+
return { analysis, plan, branchName, applied };
|
|
230
|
+
}
|
|
231
|
+
export async function cmdOnboardCli(rest, io) {
|
|
232
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
233
|
+
io.stdout(HELP);
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
const parsed = parseOnboardArgs(rest);
|
|
237
|
+
if ("error" in parsed) {
|
|
238
|
+
io.stderr(`df onboard: ${parsed.error}\nRun \`df onboard --help\` for usage.\n`);
|
|
239
|
+
return 2;
|
|
240
|
+
}
|
|
241
|
+
if (parsed.mode === "analysis-only") {
|
|
242
|
+
try {
|
|
243
|
+
const out = await cmdOnboard({ target: parsed.targetDir, mode: "analysis-only" });
|
|
244
|
+
const analysisResult = out;
|
|
245
|
+
io.stdout(parsed.json
|
|
246
|
+
? JSON.stringify(analysisResult.analysis) + "\n"
|
|
247
|
+
: renderAnalysisSummary(analysisResult.analysis));
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
catch (e) {
|
|
251
|
+
io.stderr(`df onboard: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
252
|
+
return 1;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (!parsed.apiKey || !parsed.apiKey.trim()) {
|
|
256
|
+
io.stderr("df onboard: ANTHROPIC_API_KEY is required for Phase B (--dry-run, --apply, --pr). " +
|
|
257
|
+
"Set the env var (or pass --api-key) and re-run. Use --analysis-only for a no-LLM preview.\n");
|
|
258
|
+
return 1;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
if (parsed.mode === "pr") {
|
|
262
|
+
await preflightPr(defaultRunner, parsed.targetDir);
|
|
263
|
+
}
|
|
264
|
+
const out = await cmdOnboard({
|
|
265
|
+
target: parsed.targetDir,
|
|
266
|
+
mode: parsed.mode,
|
|
267
|
+
templateRef: parsed.template,
|
|
268
|
+
apiKey: parsed.apiKey,
|
|
269
|
+
modelId: parsed.modelId,
|
|
270
|
+
...(parsed.profile !== undefined ? { profile: parsed.profile } : {}),
|
|
271
|
+
force: parsed.force,
|
|
272
|
+
});
|
|
273
|
+
const planResult = out;
|
|
274
|
+
const plan = planResult.plan;
|
|
275
|
+
if (parsed.mode === "dry-run") {
|
|
276
|
+
if (parsed.json) {
|
|
277
|
+
io.stdout(JSON.stringify(plan) + "\n");
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
const r = await applyPlan(parsed.targetDir, plan, { mode: "dry-run" });
|
|
281
|
+
if (r.rendered)
|
|
282
|
+
io.stdout(r.rendered);
|
|
283
|
+
}
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
if (parsed.mode === "apply") {
|
|
287
|
+
const profile = parsed.profile ?? autoProfile(planResult.analysis);
|
|
288
|
+
io.stdout(`df onboard: applied ${plan.files.length} files (profile=${profile}).\n`);
|
|
289
|
+
return 0;
|
|
290
|
+
}
|
|
291
|
+
// pr mode
|
|
292
|
+
io.stdout(`df onboard: opened PR on branch ${planResult.branchName ?? "(unknown)"}\n`);
|
|
293
|
+
return 0;
|
|
294
|
+
}
|
|
295
|
+
catch (e) {
|
|
296
|
+
if (e instanceof ScaffoldApplyError) {
|
|
297
|
+
io.stderr(`df onboard: ${e.message}\n`);
|
|
298
|
+
return 1;
|
|
299
|
+
}
|
|
300
|
+
io.stderr(`df onboard: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function renderAnalysisSummary(a) {
|
|
305
|
+
const lines = [];
|
|
306
|
+
lines.push(`Repo: ${a.canonicalName || "(unknown)"} at ${a.repoRoot}`);
|
|
307
|
+
lines.push(`Stacks: ${a.stacks.map((s) => `${s.language}@${s.versionPin ?? "?"}`).join(", ") || "(none detected)"}`);
|
|
308
|
+
lines.push(`Services: ${a.services.length}`);
|
|
309
|
+
lines.push(`Workflows: ${a.ci.workflows.length}; deploy: ${a.ci.deployStory?.target ?? "(none)"}`);
|
|
310
|
+
const hooks = a.dfPresence.hooks ? "v" : "x";
|
|
311
|
+
const config = a.dfPresence.configJson ? "v" : "x";
|
|
312
|
+
const workflow = a.dfPresence.prWorkflow ? "v" : "x";
|
|
313
|
+
lines.push(`DF gate: ${hooks}husky ${config}config ${workflow}workflow` +
|
|
314
|
+
(a.dfPresence.cliPin ? ` (cli pin: ${a.dfPresence.cliPin})` : ""));
|
|
315
|
+
lines.push(`Agent context set: ${a.docs.agentContextSetPresent ? "present" : "MISSING (df onboard target)"}`);
|
|
316
|
+
if (a.analyzerErrors.length > 0) {
|
|
317
|
+
lines.push(`Analyzer errors: ${a.analyzerErrors.length}`);
|
|
318
|
+
for (const e of a.analyzerErrors)
|
|
319
|
+
lines.push(` - ${e.name}: ${e.error}`);
|
|
320
|
+
}
|
|
321
|
+
return lines.join("\n") + "\n";
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=onboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"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"}
|
|
@@ -134,7 +134,17 @@ def _resolve_repo_root() -> Path:
|
|
|
134
134
|
|
|
135
135
|
|
|
136
136
|
REPO_ROOT = _resolve_repo_root()
|
|
137
|
+
# Cycle-doc lookup paths in priority order. Legacy `docs/roadmap/cycles/` is
|
|
138
|
+
# tried first for backward compatibility with sage3c / dark-factory-platform;
|
|
139
|
+
# `docs/cycles/` is the newer dark-factory-dashboard convention (PR #105
|
|
140
|
+
# moved it there to align with the eventual upstream convention). Consumers
|
|
141
|
+
# that have moved to the new path get a working validator without
|
|
142
|
+
# configuration; consumers still on the legacy path are unchanged.
|
|
143
|
+
CYCLE_DOC_DIRS: tuple[str, ...] = ("docs/roadmap/cycles", "docs/cycles")
|
|
137
144
|
CYCLES_DIR = REPO_ROOT / "docs" / "roadmap" / "cycles"
|
|
145
|
+
# Glob and the singular `CYCLES_DIR` retained for backward compatibility with
|
|
146
|
+
# downstream tooling that imports them. New code should prefer `CYCLE_DOC_DIRS`
|
|
147
|
+
# + `_is_cycle_doc_path()` which check both layouts.
|
|
138
148
|
CYCLE_DOC_GLOB = "docs/roadmap/cycles/cycle*.md"
|
|
139
149
|
|
|
140
150
|
# Trailers we recognize. ``git-interpret-trailers`` defines a trailer as
|
|
@@ -238,14 +248,27 @@ def normalize_cycle_id(raw: str) -> str | None:
|
|
|
238
248
|
|
|
239
249
|
|
|
240
250
|
def find_cycle_doc(cycle_id: str) -> CycleDoc | None:
|
|
241
|
-
"""Locate
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
)
|
|
251
|
+
"""Locate ``<cycle_dir>/cycle<id>*.md`` and read its status.
|
|
252
|
+
|
|
253
|
+
Searches every candidate dir in ``CYCLE_DOC_DIRS`` (legacy
|
|
254
|
+
``docs/roadmap/cycles/`` first, then ``docs/cycles/``) so consumers
|
|
255
|
+
that have moved get a working lookup without configuration. If the
|
|
256
|
+
same cycle exists in both dirs (transitional state), prefers the
|
|
257
|
+
longer-named match across all hits — same disambiguation rule as
|
|
258
|
+
before, just over a wider candidate set.
|
|
259
|
+
"""
|
|
260
|
+
matches: list[Path] = []
|
|
261
|
+
for cycle_dir in CYCLE_DOC_DIRS:
|
|
262
|
+
dir_path = REPO_ROOT / cycle_dir
|
|
263
|
+
if not dir_path.exists():
|
|
264
|
+
continue
|
|
265
|
+
matches.extend(sorted(dir_path.glob(f"cycle{cycle_id}-*.md")))
|
|
266
|
+
matches.extend(sorted(dir_path.glob(f"cycle{cycle_id}.md")))
|
|
245
267
|
if not matches:
|
|
246
268
|
return None
|
|
247
|
-
# If multiple matches exist (legacy ``cycle318.md`` + new ``cycle318-foo.md
|
|
248
|
-
# prefer the one with the longer name (more
|
|
269
|
+
# If multiple matches exist (legacy ``cycle318.md`` + new ``cycle318-foo.md``,
|
|
270
|
+
# or one in each layout), prefer the one with the longer name (more
|
|
271
|
+
# descriptive slug).
|
|
249
272
|
path = max(matches, key=lambda p: len(p.name))
|
|
250
273
|
status, superseded_by = read_cycle_frontmatter(path)
|
|
251
274
|
return CycleDoc(path=path, status=status, superseded_by=superseded_by)
|
|
@@ -332,50 +355,89 @@ def base_cycle_doc(
|
|
|
332
355
|
if gh_token:
|
|
333
356
|
env["GH_TOKEN"] = gh_token
|
|
334
357
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
358
|
+
# Collect candidates across EVERY candidate cycle-doc dir on the base
|
|
359
|
+
# ref — DON'T break on the first successful listing. A consumer can
|
|
360
|
+
# have BOTH layouts present (transitional state — e.g. dark-factory-
|
|
361
|
+
# dashboard pre-#105 had cycle1-5 at legacy + cycle6 at the new path
|
|
362
|
+
# for one PR's worth of overlap), and a legacy dir that exists but
|
|
363
|
+
# lacks the cited cycle (sage3c with cycle331.x at legacy + a new
|
|
364
|
+
# cycle 6 at the new path) must fall through to the next dir. Per-dir
|
|
365
|
+
# 404 is the normal "consumer has not moved (or has moved fully) to
|
|
366
|
+
# this layout" signal; only fail if EVERY dir 404s or any dir hits a
|
|
367
|
+
# non-404 error.
|
|
368
|
+
pattern = re.compile(rf"^cycle{re.escape(cycle_id)}(?:-.+)?\.md$")
|
|
369
|
+
candidates: list[dict] = []
|
|
370
|
+
dirs_listed: list[str] = []
|
|
371
|
+
last_404: BaseCycleDocFetchError | None = None
|
|
372
|
+
for cycle_dir in CYCLE_DOC_DIRS:
|
|
373
|
+
try:
|
|
374
|
+
listing_result = subprocess.run(
|
|
375
|
+
[
|
|
376
|
+
"gh",
|
|
377
|
+
"api",
|
|
378
|
+
f"repos/{repo}/contents/{cycle_dir}?ref={base_ref}",
|
|
379
|
+
],
|
|
380
|
+
check=True,
|
|
381
|
+
capture_output=True,
|
|
382
|
+
text=True,
|
|
383
|
+
env=env,
|
|
384
|
+
timeout=60,
|
|
385
|
+
)
|
|
386
|
+
except FileNotFoundError as exc:
|
|
387
|
+
# gh CLI itself missing — environmental, fail loud regardless
|
|
388
|
+
# of which cycle_dir we were probing.
|
|
389
|
+
raise BaseCycleDocFetchError(
|
|
390
|
+
"gh CLI not on PATH; cannot read cycle docs from base ref."
|
|
391
|
+
) from exc
|
|
392
|
+
except subprocess.CalledProcessError as exc:
|
|
393
|
+
# Treat 404 (or "not found") as "this dir doesn't exist on
|
|
394
|
+
# base ref; keep trying the others". Any other non-zero exit
|
|
395
|
+
# (auth, rate-limit, server error) escapes immediately —
|
|
396
|
+
# don't silently swallow a real failure as a missing-dir.
|
|
397
|
+
if _is_gh_not_found_error(exc):
|
|
398
|
+
last_404 = BaseCycleDocFetchError(
|
|
399
|
+
f"gh api repos/{repo}/contents/{cycle_dir} returned 404 "
|
|
400
|
+
f"(exit {exc.returncode}): {(exc.stderr or '').strip()}"
|
|
401
|
+
)
|
|
402
|
+
continue
|
|
403
|
+
raise BaseCycleDocFetchError(
|
|
404
|
+
f"gh api repos/{repo}/contents/{cycle_dir} failed "
|
|
405
|
+
f"(exit {exc.returncode}): {(exc.stderr or '').strip()}"
|
|
406
|
+
) from exc
|
|
407
|
+
except subprocess.TimeoutExpired as exc:
|
|
408
|
+
raise BaseCycleDocFetchError(
|
|
409
|
+
f"gh api repos/{repo}/contents/{cycle_dir} timed out after {exc.timeout}s"
|
|
410
|
+
) from exc
|
|
411
|
+
|
|
412
|
+
dirs_listed.append(cycle_dir)
|
|
413
|
+
try:
|
|
414
|
+
entries = json.loads(listing_result.stdout)
|
|
415
|
+
except json.JSONDecodeError as exc:
|
|
416
|
+
raise BaseCycleDocFetchError(
|
|
417
|
+
f"GitHub contents API returned malformed JSON for {cycle_dir} listing."
|
|
418
|
+
) from exc
|
|
419
|
+
|
|
420
|
+
candidates.extend(
|
|
421
|
+
entry
|
|
422
|
+
for entry in entries
|
|
423
|
+
if isinstance(entry, dict)
|
|
424
|
+
and entry.get("type") == "file"
|
|
425
|
+
and isinstance(entry.get("name"), str)
|
|
426
|
+
and pattern.match(entry["name"])
|
|
347
427
|
)
|
|
348
|
-
except FileNotFoundError as exc:
|
|
349
|
-
raise BaseCycleDocFetchError(
|
|
350
|
-
"gh CLI not on PATH; cannot read cycle docs from base ref."
|
|
351
|
-
) from exc
|
|
352
|
-
except subprocess.CalledProcessError as exc:
|
|
353
|
-
raise BaseCycleDocFetchError(
|
|
354
|
-
f"gh api repos/{repo}/contents/docs/roadmap/cycles failed "
|
|
355
|
-
f"(exit {exc.returncode}): {exc.stderr.strip()}"
|
|
356
|
-
) from exc
|
|
357
|
-
except subprocess.TimeoutExpired as exc:
|
|
358
|
-
raise BaseCycleDocFetchError(
|
|
359
|
-
f"gh api repos/{repo}/contents/docs/roadmap/cycles timed out after {exc.timeout}s"
|
|
360
|
-
) from exc
|
|
361
428
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
429
|
+
if not dirs_listed:
|
|
430
|
+
# EVERY candidate dir 404'd — no cycle docs anywhere on base ref.
|
|
431
|
+
# Surface the last 404 so consumers see a concrete actionable error
|
|
432
|
+
# naming what was tried.
|
|
433
|
+
raise last_404 or BaseCycleDocFetchError(
|
|
434
|
+
"no candidate cycle-doc directory found on base ref "
|
|
435
|
+
f"(tried: {', '.join(CYCLE_DOC_DIRS)})"
|
|
436
|
+
)
|
|
368
437
|
|
|
369
|
-
pattern = re.compile(rf"^cycle{re.escape(cycle_id)}(?:-.+)?\.md$")
|
|
370
|
-
candidates = [
|
|
371
|
-
entry
|
|
372
|
-
for entry in entries
|
|
373
|
-
if isinstance(entry, dict)
|
|
374
|
-
and entry.get("type") == "file"
|
|
375
|
-
and isinstance(entry.get("name"), str)
|
|
376
|
-
and pattern.match(entry["name"])
|
|
377
|
-
]
|
|
378
438
|
if not candidates:
|
|
439
|
+
# At least one dir existed but doesn't hold the cited cycle —
|
|
440
|
+
# treat as "new cycle, OK to proceed" (the doc lands in this PR).
|
|
379
441
|
return None
|
|
380
442
|
|
|
381
443
|
chosen = max(candidates, key=lambda entry: len(entry["name"]))
|
|
@@ -657,18 +719,22 @@ def _parse_paginated_commit_messages(raw: str) -> str:
|
|
|
657
719
|
|
|
658
720
|
|
|
659
721
|
def _is_cycle_doc_path(path: str) -> bool:
|
|
660
|
-
"""Match
|
|
722
|
+
"""Match ``<cycle_dir>/cycle<id>(-<slug>)?.md`` under any layout.
|
|
661
723
|
|
|
662
|
-
|
|
663
|
-
``
|
|
664
|
-
``
|
|
665
|
-
|
|
724
|
+
Accepts both legacy ``docs/roadmap/cycles/`` and the newer
|
|
725
|
+
``docs/cycles/`` per ``CYCLE_DOC_DIRS``. The classifier counts only
|
|
726
|
+
files whose basename starts with ``cycle`` followed by an id
|
|
727
|
+
character. Sibling files like ``<cycle_dir>/README.md`` are
|
|
728
|
+
index/meta content and do NOT trigger the plan-PR contract.
|
|
666
729
|
"""
|
|
667
|
-
|
|
668
|
-
if not path.startswith(prefix) or not path.endswith(".md"):
|
|
730
|
+
if not path.endswith(".md"):
|
|
669
731
|
return False
|
|
670
|
-
|
|
671
|
-
|
|
732
|
+
for cycle_dir in CYCLE_DOC_DIRS:
|
|
733
|
+
prefix = f"{cycle_dir}/cycle"
|
|
734
|
+
if path.startswith(prefix):
|
|
735
|
+
next_char = path[len(prefix) : len(prefix) + 1]
|
|
736
|
+
return next_char.isdigit()
|
|
737
|
+
return False
|
|
672
738
|
|
|
673
739
|
|
|
674
740
|
def is_plan_pr(
|
|
@@ -731,7 +797,9 @@ def status_completion_in_diff(diff: str) -> list[str]:
|
|
|
731
797
|
in_frontmatter = False
|
|
732
798
|
seen_completed_in_frontmatter = False
|
|
733
799
|
continue
|
|
734
|
-
if not current_file or not
|
|
800
|
+
if not current_file or not any(
|
|
801
|
+
current_file.startswith(f"{d}/") for d in CYCLE_DOC_DIRS
|
|
802
|
+
):
|
|
735
803
|
continue
|
|
736
804
|
# Naive frontmatter tracking inside the diff hunk: ``---`` delimiters
|
|
737
805
|
# toggle the flag. False positives are acceptable here — the goal
|
|
@@ -789,7 +857,7 @@ def cycle_docs_transitioned_to_terminal(
|
|
|
789
857
|
cycle_paths = [
|
|
790
858
|
p
|
|
791
859
|
for p in changed_files
|
|
792
|
-
if p.startswith("
|
|
860
|
+
if any(p.startswith(f"{d}/cycle") for d in CYCLE_DOC_DIRS) and p.endswith(".md")
|
|
793
861
|
]
|
|
794
862
|
if not cycle_paths:
|
|
795
863
|
return []
|
|
@@ -1030,8 +1098,9 @@ def validate(
|
|
|
1030
1098
|
if doc is None:
|
|
1031
1099
|
errors.append(
|
|
1032
1100
|
f"[cycle-doc] FAIL ({pr_type}): cycle `{cycle_id}` not found in "
|
|
1033
|
-
f"
|
|
1034
|
-
"PR that creates the
|
|
1101
|
+
f"any of: {', '.join(f'`{d}/`' for d in CYCLE_DOC_DIRS)}. "
|
|
1102
|
+
"Either fix the trailer or open a plan PR that creates the "
|
|
1103
|
+
"cycle doc first."
|
|
1035
1104
|
)
|
|
1036
1105
|
return errors
|
|
1037
1106
|
|
|
@@ -1056,7 +1125,9 @@ def validate(
|
|
|
1056
1125
|
# PR that doesn't touch its cycle's doc is mis-labelled.
|
|
1057
1126
|
assert doc is not None # plan PR without cycle_id already returned
|
|
1058
1127
|
cycle_paths = [
|
|
1059
|
-
f
|
|
1128
|
+
f
|
|
1129
|
+
for f in changed_files
|
|
1130
|
+
if any(f.startswith(f"{d}/") for d in CYCLE_DOC_DIRS)
|
|
1060
1131
|
]
|
|
1061
1132
|
doc_relative = doc.path.relative_to(REPO_ROOT).as_posix()
|
|
1062
1133
|
if doc_relative not in cycle_paths:
|
package/dist/doctor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAO9D,OAAO,KAAK,EACV,iBAAiB,EACjB,WAAW,EACZ,MAAM,gCAAgC,CAAC;AAIxC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,eAAe,CAAC;IAK1B,SAAS,CAAC,EAAE,eAAe,CAAC;IAO5B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAkN9E;AAuBD,eAAO,MAAM,iBAAiB,qFAKpB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED,wBAAgB,cAAc,CAC5B,OAAO,GAAE,qBAA0B,GAClC,iBAAiB,CAYnB;AAiPD,MAAM,MAAM,iBAAiB,GACzB,gBAAgB,GAChB,cAAc,GACd,IAAI,CAAC;AAET,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;;OAKG;IACH,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC;;;;;;OAMG;IACH,eAAe,EAAE,SAAS,WAAW,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,iBAAiB,CAAC;IACzB;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,GAClC,kBAAkB,CAiDpB;AA0BD,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,CAwDjE;AAoED,eAAO,MAAM,2BAA2B,QACQ,CAAC;AA8GjD,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA6F3E"}
|
package/dist/doctor.js
CHANGED
|
@@ -30,6 +30,7 @@ import { spawn } from "node:child_process";
|
|
|
30
30
|
import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, } from "node:fs";
|
|
31
31
|
import { resolve } from "node:path";
|
|
32
32
|
import { gitCommonDir, gitDir } from "./git.js";
|
|
33
|
+
import { checkAgentContextSet } from "./onboard/validate.js";
|
|
33
34
|
import { applyProfileAuth, resolveProfileWithConfig, } from "./policy/profile.js";
|
|
34
35
|
const HOOK_FILES = ["post-commit", "pre-push"];
|
|
35
36
|
export async function runDoctor(options) {
|
|
@@ -158,6 +159,25 @@ export async function runDoctor(options) {
|
|
|
158
159
|
// envs via Doppler / env vars.
|
|
159
160
|
const cloudEnv = detectCloudEnv();
|
|
160
161
|
checks.push(cloudEnvCheck(cloudEnv));
|
|
162
|
+
// 7b. Agent-context-set validation (cycle 15 Phase C). The required-files
|
|
163
|
+
// walk (cycle 15 D3 floor: CLAUDE.md, AGENTS.md, .claude/settings.json,
|
|
164
|
+
// docs/PRINCIPLES.md, docs/roadmap/cycles/cycle1-*.md,
|
|
165
|
+
// .agent-review/config.json) runs UNCONDITIONALLY — if any of those is
|
|
166
|
+
// missing, doctor fails loudly. The `context.guidanceFiles` field gates
|
|
167
|
+
// ONLY the additional per-path walk (which is opt-in; the required-files
|
|
168
|
+
// set is not opt-in). Per Decision #7 round-1 revision.
|
|
169
|
+
//
|
|
170
|
+
// Note: the `context.guidanceFiles` field may not yet exist on the
|
|
171
|
+
// AgentReviewConfig Zod schema in @momentiq/dark-factory-schemas — the
|
|
172
|
+
// schemas-package PR is a follow-up. Until then, we read the field via a
|
|
173
|
+
// runtime cast so this doctor patch lands first (additive-field pattern
|
|
174
|
+
// precedent: cycle 5 / cycle 13 cloud-env detection).
|
|
175
|
+
const guidanceFiles = loaded.config.context?.guidanceFiles;
|
|
176
|
+
const agentContextChecks = await checkAgentContextSet({
|
|
177
|
+
repoRoot: root,
|
|
178
|
+
guidanceFiles,
|
|
179
|
+
});
|
|
180
|
+
checks.push(...agentContextChecks);
|
|
161
181
|
// 8. per-adapter doctor() — this is where subscription-auth verification
|
|
162
182
|
// actually lives. Each adapter's doctor() checks ITS own credentials
|
|
163
183
|
// (env var, ~/.cursor auth, ~/.codex auth, etc.). When a profile is
|