@momentiq/dark-factory-cli 2.1.0 → 2.2.1

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 (110) hide show
  1. package/dist/adapters/static-schema-lint.js +1 -1
  2. package/dist/cli.js +3 -3
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/onboard.d.ts +64 -1
  5. package/dist/commands/onboard.d.ts.map +1 -1
  6. package/dist/commands/onboard.js +268 -53
  7. package/dist/commands/onboard.js.map +1 -1
  8. package/dist/doctor.d.ts.map +1 -1
  9. package/dist/doctor.js +20 -0
  10. package/dist/doctor.js.map +1 -1
  11. package/dist/mcp/server.d.ts.map +1 -1
  12. package/dist/mcp/server.js +2 -0
  13. package/dist/mcp/server.js.map +1 -1
  14. package/dist/mcp/tools/onboard.d.ts +6 -0
  15. package/dist/mcp/tools/onboard.d.ts.map +1 -0
  16. package/dist/mcp/tools/onboard.js +115 -0
  17. package/dist/mcp/tools/onboard.js.map +1 -0
  18. package/dist/onboard/analyzers/ci.d.ts.map +1 -1
  19. package/dist/onboard/analyzers/ci.js +32 -0
  20. package/dist/onboard/analyzers/ci.js.map +1 -1
  21. package/dist/onboard/analyzers/lockfile.d.ts.map +1 -1
  22. package/dist/onboard/analyzers/lockfile.js +154 -74
  23. package/dist/onboard/analyzers/lockfile.js.map +1 -1
  24. package/dist/onboard/apply-plan.d.ts +29 -0
  25. package/dist/onboard/apply-plan.d.ts.map +1 -0
  26. package/dist/onboard/apply-plan.js +80 -0
  27. package/dist/onboard/apply-plan.js.map +1 -0
  28. package/dist/onboard/auto-profile.d.ts +3 -0
  29. package/dist/onboard/auto-profile.d.ts.map +1 -0
  30. package/dist/onboard/auto-profile.js +20 -0
  31. package/dist/onboard/auto-profile.js.map +1 -0
  32. package/dist/onboard/generate-plan.d.ts +18 -0
  33. package/dist/onboard/generate-plan.d.ts.map +1 -0
  34. package/dist/onboard/generate-plan.js +72 -0
  35. package/dist/onboard/generate-plan.js.map +1 -0
  36. package/dist/onboard/llm-client.d.ts +45 -0
  37. package/dist/onboard/llm-client.d.ts.map +1 -0
  38. package/dist/onboard/llm-client.js +101 -0
  39. package/dist/onboard/llm-client.js.map +1 -0
  40. package/dist/onboard/prompts/scaffold.md +93 -0
  41. package/dist/onboard/prompts.d.ts +13 -0
  42. package/dist/onboard/prompts.d.ts.map +1 -0
  43. package/dist/onboard/prompts.js +54 -0
  44. package/dist/onboard/prompts.js.map +1 -0
  45. package/dist/onboard/scaffold-schema.d.ts +194 -0
  46. package/dist/onboard/scaffold-schema.d.ts.map +1 -0
  47. package/dist/onboard/scaffold-schema.js +73 -0
  48. package/dist/onboard/scaffold-schema.js.map +1 -0
  49. package/dist/onboard/schema.d.ts +10 -0
  50. package/dist/onboard/schema.d.ts.map +1 -1
  51. package/dist/onboard/schema.js +23 -1
  52. package/dist/onboard/schema.js.map +1 -1
  53. package/dist/onboard/seeders/adr.d.ts +3 -0
  54. package/dist/onboard/seeders/adr.d.ts.map +1 -0
  55. package/dist/onboard/seeders/adr.js +212 -0
  56. package/dist/onboard/seeders/adr.js.map +1 -0
  57. package/dist/onboard/seeders/agent-review-config/cloud.canonical.json +22 -0
  58. package/dist/onboard/seeders/agent-review-config/local.canonical.json +21 -0
  59. package/dist/onboard/seeders/agent-review-config.d.ts +3 -0
  60. package/dist/onboard/seeders/agent-review-config.d.ts.map +1 -0
  61. package/dist/onboard/seeders/agent-review-config.js +38 -0
  62. package/dist/onboard/seeders/agent-review-config.js.map +1 -0
  63. package/dist/onboard/seeders/cycle1-bootstrap.d.ts +3 -0
  64. package/dist/onboard/seeders/cycle1-bootstrap.d.ts.map +1 -0
  65. package/dist/onboard/seeders/cycle1-bootstrap.js +76 -0
  66. package/dist/onboard/seeders/cycle1-bootstrap.js.map +1 -0
  67. package/dist/onboard/seeders/index.d.ts +47 -0
  68. package/dist/onboard/seeders/index.d.ts.map +1 -0
  69. package/dist/onboard/seeders/index.js +37 -0
  70. package/dist/onboard/seeders/index.js.map +1 -0
  71. package/dist/onboard/seeders/runbook.d.ts +13 -0
  72. package/dist/onboard/seeders/runbook.d.ts.map +1 -0
  73. package/dist/onboard/seeders/runbook.js +144 -0
  74. package/dist/onboard/seeders/runbook.js.map +1 -0
  75. package/dist/onboard/seeders/templates/adr.md.tmpl +26 -0
  76. package/dist/onboard/seeders/templates/cycle1-bootstrap.md.tmpl +64 -0
  77. package/dist/onboard/seeders/templates/runbook.md.tmpl +26 -0
  78. package/dist/onboard/template-loader.d.ts +20 -0
  79. package/dist/onboard/template-loader.d.ts.map +1 -0
  80. package/dist/onboard/template-loader.js +152 -0
  81. package/dist/onboard/template-loader.js.map +1 -0
  82. package/dist/onboard/template-ref.d.ts +15 -0
  83. package/dist/onboard/template-ref.d.ts.map +1 -0
  84. package/dist/onboard/template-ref.js +45 -0
  85. package/dist/onboard/template-ref.js.map +1 -0
  86. package/dist/onboard/validate.d.ts +19 -0
  87. package/dist/onboard/validate.d.ts.map +1 -0
  88. package/dist/onboard/validate.js +127 -0
  89. package/dist/onboard/validate.js.map +1 -0
  90. package/dist/onboard/writers/dry-run-renderer.d.ts +6 -0
  91. package/dist/onboard/writers/dry-run-renderer.d.ts.map +1 -0
  92. package/dist/onboard/writers/dry-run-renderer.js +88 -0
  93. package/dist/onboard/writers/dry-run-renderer.js.map +1 -0
  94. package/dist/onboard/writers/emit.d.ts +6 -0
  95. package/dist/onboard/writers/emit.d.ts.map +1 -0
  96. package/dist/onboard/writers/emit.js +29 -0
  97. package/dist/onboard/writers/emit.js.map +1 -0
  98. package/dist/onboard/writers/merge.d.ts +16 -0
  99. package/dist/onboard/writers/merge.d.ts.map +1 -0
  100. package/dist/onboard/writers/merge.js +142 -0
  101. package/dist/onboard/writers/merge.js.map +1 -0
  102. package/dist/onboard/writers/pr-writer.d.ts +44 -0
  103. package/dist/onboard/writers/pr-writer.d.ts.map +1 -0
  104. package/dist/onboard/writers/pr-writer.js +104 -0
  105. package/dist/onboard/writers/pr-writer.js.map +1 -0
  106. package/dist/onboard/writers/skip.d.ts +9 -0
  107. package/dist/onboard/writers/skip.d.ts.map +1 -0
  108. package/dist/onboard/writers/skip.js +9 -0
  109. package/dist/onboard/writers/skip.js.map +1 -0
  110. package/package.json +4 -1
@@ -0,0 +1,80 @@
1
+ // packages/cli/src/onboard/apply-plan.ts
2
+ //
3
+ // Dispatcher over the three writers + the dry-run renderer. The PR writer
4
+ // is a separate entry point (pr-writer.ts) because it spans subprocess
5
+ // orchestration (gh auth check + branch + commit + gh pr create) that
6
+ // doesn't belong in this hot path.
7
+ //
8
+ // Partial-failure contract (per B-D7): on any write error mid-loop, collect
9
+ // the written-so-far list + the not-written list and re-throw a
10
+ // ScaffoldApplyError. Idempotency: callers can re-run --apply after fixing
11
+ // the disk/permission issue; written files are overwritten with identical
12
+ // content; merge files re-detect the BEGIN/END block.
13
+ import { writeEmit } from "./writers/emit.js";
14
+ import { writeMerge } from "./writers/merge.js";
15
+ import { writeSkip } from "./writers/skip.js";
16
+ import { renderDryRun } from "./writers/dry-run-renderer.js";
17
+ export class ScaffoldApplyError extends Error {
18
+ written;
19
+ notWritten;
20
+ cause;
21
+ constructor(message, written, notWritten, cause) {
22
+ super(message);
23
+ this.name = "ScaffoldApplyError";
24
+ this.written = written;
25
+ this.notWritten = notWritten;
26
+ this.cause = cause;
27
+ }
28
+ }
29
+ export async function applyPlan(rootDir, plan, opts) {
30
+ if (opts.mode !== "dry-run" && opts.mode !== "apply") {
31
+ throw new Error(`df onboard: applyPlan does not handle mode "${opts.mode}". ` +
32
+ "For pr mode, use runPrMode(plan, ...) from pr-writer.ts.");
33
+ }
34
+ if (opts.mode === "dry-run") {
35
+ const renderOpts = opts.color !== undefined ? { color: opts.color } : {};
36
+ const rendered = await renderDryRun(rootDir, plan, renderOpts);
37
+ const results = plan.files.map((f) => {
38
+ if (f.action === "skip")
39
+ return { path: f.path, action: "skip", rationale: f.rationale, wrote: false };
40
+ if (f.action === "emit")
41
+ return { path: f.path, action: "emit", wrote: false };
42
+ return { path: f.path, action: "merge", wrote: false, skipped: false };
43
+ });
44
+ return { mode: "dry-run", results, rendered };
45
+ }
46
+ // apply mode
47
+ const results = [];
48
+ const written = [];
49
+ for (let i = 0; i < plan.files.length; i++) {
50
+ const file = plan.files[i];
51
+ try {
52
+ if (file.action === "skip") {
53
+ results.push(await writeSkip(rootDir, file));
54
+ continue;
55
+ }
56
+ if (file.action === "emit") {
57
+ await writeEmit(rootDir, file, { force: opts.force ?? false });
58
+ results.push({ path: file.path, action: "emit", wrote: true });
59
+ written.push(file.path);
60
+ continue;
61
+ }
62
+ // merge
63
+ const mergeOpts = opts.stderr !== undefined ? { stderr: opts.stderr } : {};
64
+ const r = await writeMerge(rootDir, file, mergeOpts);
65
+ results.push(r);
66
+ if (r.wrote)
67
+ written.push(file.path);
68
+ }
69
+ catch (err) {
70
+ const notWritten = plan.files.slice(i).map((f) => f.path);
71
+ throw new ScaffoldApplyError(`df onboard: write failed at ${file.path} (${err instanceof Error ? err.message : String(err)}). ` +
72
+ `Files written before the failure: ${written.length ? written.join(", ") : "(none)"}. ` +
73
+ `Files NOT written: ${notWritten.join(", ")}. ` +
74
+ `Re-run \`df onboard --apply\` after addressing the disk/perm issue; ` +
75
+ `written files will be overwritten with the same content (idempotent).`, written, notWritten, err);
76
+ }
77
+ }
78
+ return { mode: "apply", results };
79
+ }
80
+ //# sourceMappingURL=apply-plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-plan.js","sourceRoot":"","sources":["../../src/onboard/apply-plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { RepoAnalysis } from "./schema.js";
2
+ export declare function autoProfile(analysis: RepoAnalysis): "local" | "cloud";
3
+ //# sourceMappingURL=auto-profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-profile.d.ts","sourceRoot":"","sources":["../../src/onboard/auto-profile.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,GAAG,OAAO,CAGrE"}
@@ -0,0 +1,20 @@
1
+ // packages/cli/src/onboard/auto-profile.ts
2
+ //
3
+ // Resolve the critic profile (local | cloud) from a RepoAnalysis when the
4
+ // operator did not pass --profile explicitly.
5
+ //
6
+ // This module is shipped as its own file (not inline in commands/onboard.ts)
7
+ // so Phase C's deterministic .agent-review/config.json seeder can import the
8
+ // same function via `import { autoProfile } from "../auto-profile.js"`. The
9
+ // single-source-of-truth keeps Phase B + Phase C aligned on the heuristic
10
+ // without either side re-implementing it.
11
+ //
12
+ // Heuristic (B-D8): a repo that already has a DF cli-pin AND a cloud PR
13
+ // workflow is one that already runs the cloud quartet — keep them on
14
+ // `cloud`. Everything else defaults to `local`. Explicit `--profile` always
15
+ // wins; this function is only the fallback.
16
+ export function autoProfile(analysis) {
17
+ const dfWired = analysis.dfPresence.cliPin !== null && analysis.dfPresence.prWorkflow;
18
+ return dfWired ? "cloud" : "local";
19
+ }
20
+ //# sourceMappingURL=auto-profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-profile.js","sourceRoot":"","sources":["../../src/onboard/auto-profile.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,EAAE;AACF,0EAA0E;AAC1E,8CAA8C;AAC9C,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,0EAA0E;AAC1E,0CAA0C;AAC1C,EAAE;AACF,wEAAwE;AACxE,qEAAqE;AACrE,4EAA4E;AAC5E,4CAA4C;AAI5C,MAAM,UAAU,WAAW,CAAC,QAAsB;IAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,MAAM,KAAK,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC;IACtF,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;AACrC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type ScaffoldPlan } from "./scaffold-schema.js";
2
+ import { type LlmInputs, type LlmResult } from "./llm-client.js";
3
+ import type { RepoAnalysis } from "./schema.js";
4
+ import type { Template } from "./template-loader.js";
5
+ export interface GeneratePlanOptions {
6
+ apiKey?: string;
7
+ modelId: string;
8
+ /** Resolved critic profile (B-D8). Flows into the scaffold prompt so the LLM
9
+ * emits a profile-correct `.agent-review/config.json`. The CLI resolves the
10
+ * profile (explicit `--profile` flag, else `autoProfile(analysis)`) before
11
+ * calling generatePlan. */
12
+ profile: "local" | "cloud";
13
+ callLlm?: (inputs: LlmInputs) => Promise<LlmResult>;
14
+ }
15
+ export interface GeneratePlanResult extends Omit<ScaffoldPlan, never> {
16
+ }
17
+ export declare function generatePlan(analysis: RepoAnalysis, template: Template, opts: GeneratePlanOptions): Promise<ScaffoldPlan>;
18
+ //# sourceMappingURL=generate-plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-plan.d.ts","sourceRoot":"","sources":["../../src/onboard/generate-plan.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB;;;gCAG4B;IAC5B,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;IAE3B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC;CAEpE;AAID,wBAAsB,YAAY,CAChC,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,YAAY,CAAC,CA0DvB"}
@@ -0,0 +1,72 @@
1
+ // packages/cli/src/onboard/generate-plan.ts
2
+ //
3
+ // Stage B entry point. Composes the prompt renderer, the LLM client, and
4
+ // the ScaffoldPlanSchema validator into one async function. One retry on
5
+ // validation failure (the model gets a chance to fix its own JSON when
6
+ // shown the Zod error); zero retries on transient LLM errors (the LLM
7
+ // client wrapper already handles those).
8
+ import { zodToJsonSchema } from "zod-to-json-schema";
9
+ import { ScaffoldPlanSchema, SCAFFOLD_PLAN_BYTE_BUDGET, } from "./scaffold-schema.js";
10
+ import { renderScaffoldPrompt } from "./prompts.js";
11
+ import { callScaffoldLlm, } from "./llm-client.js";
12
+ const TOOL_NAME = "emit_scaffold_plan";
13
+ export async function generatePlan(analysis, template, opts) {
14
+ const { systemPrompt, userMessage } = await renderScaffoldPrompt(analysis, template.files, { profile: opts.profile });
15
+ // Anthropic's tool-use `input_schema` requires `type: "object"` at the top
16
+ // level. zodToJsonSchema(s, NAME) emits `{$ref, definitions: {NAME: ...}}`
17
+ // — no top-level type, so the API rejects it with `tools.0.custom.input_schema.type:
18
+ // Field required`. Omit the name to get the schema inlined at top level.
19
+ const inputSchema = zodToJsonSchema(ScaffoldPlanSchema);
20
+ const callLlm = opts.callLlm ?? ((i) => callScaffoldLlm(i));
21
+ // exactOptionalPropertyTypes forbids assigning `undefined` to an optional
22
+ // property; conditionally spread `apiKey` only when defined so the llm-client
23
+ // falls through to its ANTHROPIC_API_KEY env-var lookup.
24
+ const baseInputs = {
25
+ systemPrompt,
26
+ toolName: TOOL_NAME,
27
+ toolInputSchema: inputSchema,
28
+ modelId: opts.modelId,
29
+ ...(opts.apiKey !== undefined ? { apiKey: opts.apiKey } : {}),
30
+ };
31
+ // Attempt 1
32
+ let result = await callLlm({ ...baseInputs, userMessage });
33
+ let parsed = ScaffoldPlanSchema.safeParse(stampTemplateRef(result.planJson, template));
34
+ if (!parsed.success) {
35
+ // Capture the first-attempt issues BEFORE the retry overwrites `parsed`,
36
+ // so the final error can cite both attempts (per W3 finding round-1).
37
+ const firstAttemptIssues = parsed.error.issues;
38
+ // Retry once with the validation error appended — the model often self-corrects.
39
+ const retryMessage = `${userMessage}\n\n## Previous attempt rejected\n` +
40
+ "Your last tool call did not validate. Issues:\n" +
41
+ firstAttemptIssues.map((i) => `- ${i.path.join(".")}: ${i.message}`).join("\n") +
42
+ "\n\nEmit `emit_scaffold_plan` again, fixing each issue above.";
43
+ result = await callLlm({ ...baseInputs, userMessage: retryMessage });
44
+ parsed = ScaffoldPlanSchema.safeParse(stampTemplateRef(result.planJson, template));
45
+ if (!parsed.success) {
46
+ throw new Error(`df onboard: scaffold plan validation failed after retry. ` +
47
+ `First-attempt issues: ${formatZodIssues(firstAttemptIssues)}; ` +
48
+ `second-attempt issues: ${formatZodIssues(parsed.error.issues)}`);
49
+ }
50
+ }
51
+ // Byte-budget backstop (per B-D2). Per-array caps and per-field maxLength
52
+ // catch most overflows; this is the final guardrail before the writer.
53
+ const serialized = JSON.stringify(parsed.data);
54
+ if (serialized.length > SCAFFOLD_PLAN_BYTE_BUDGET) {
55
+ throw new Error(`df onboard: ScaffoldPlan exceeds ${SCAFFOLD_PLAN_BYTE_BUDGET}-byte (64 KB) budget: ` +
56
+ `produced ${serialized.length} bytes. Tighten per-file tailored_content or files[] count.`);
57
+ }
58
+ return parsed.data;
59
+ }
60
+ // stampTemplateRef forces the plan's templateRef to be the loader's canonicalRef,
61
+ // not the LLM's value — the LLM could echo a stale or fabricated ref, and the
62
+ // plan's provenance must be ground-truth.
63
+ function stampTemplateRef(planJson, template) {
64
+ if (planJson && typeof planJson === "object" && !Array.isArray(planJson)) {
65
+ return { ...planJson, templateRef: template.canonicalRef };
66
+ }
67
+ return planJson;
68
+ }
69
+ function formatZodIssues(issues) {
70
+ return issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`).join("; ");
71
+ }
72
+ //# sourceMappingURL=generate-plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-plan.js","sourceRoot":"","sources":["../../src/onboard/generate-plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ export interface LlmInputs {
2
+ systemPrompt: string;
3
+ userMessage: string;
4
+ toolName: string;
5
+ toolInputSchema: object;
6
+ apiKey?: string;
7
+ modelId: string;
8
+ maxTokens?: number;
9
+ }
10
+ export interface LlmResult {
11
+ planJson: unknown;
12
+ modelId: string;
13
+ inputTokens: number;
14
+ outputTokens: number;
15
+ attempts: number;
16
+ }
17
+ export interface LlmClientLike {
18
+ messages: {
19
+ create(params: unknown): Promise<{
20
+ id: string;
21
+ model: string;
22
+ content: Array<{
23
+ type: "tool_use";
24
+ name: string;
25
+ input: unknown;
26
+ } | {
27
+ type: "text";
28
+ text: string;
29
+ } | {
30
+ type: string;
31
+ }>;
32
+ usage: {
33
+ input_tokens: number;
34
+ output_tokens: number;
35
+ };
36
+ stop_reason: string;
37
+ }>;
38
+ };
39
+ }
40
+ export type LlmClientFactory = (apiKey: string) => LlmClientLike;
41
+ export interface CallOptions {
42
+ createClient?: LlmClientFactory;
43
+ }
44
+ export declare function callScaffoldLlm(inputs: LlmInputs, opts?: CallOptions): Promise<LlmResult>;
45
+ //# sourceMappingURL=llm-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-client.d.ts","sourceRoot":"","sources":["../../src/onboard/llm-client.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE;QACR,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC;YAC/B,EAAE,EAAE,MAAM,CAAC;YACX,KAAK,EAAE,MAAM,CAAC;YACd,OAAO,EAAE,KAAK,CACV;gBAAE,IAAI,EAAE,UAAU,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAC;gBAAC,KAAK,EAAE,OAAO,CAAA;aAAE,GAClD;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAA;aAAE,GAC9B;gBAAE,IAAI,EAAE,MAAM,CAAA;aAAE,CACnB,CAAC;YACF,KAAK,EAAE;gBAAE,YAAY,EAAE,MAAM,CAAC;gBAAC,aAAa,EAAE,MAAM,CAAA;aAAE,CAAC;YACvD,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC,CAAC;KACJ,CAAC;CACH;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,aAAa,CAAC;AAkBjE,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,SAAS,EACjB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,SAAS,CAAC,CA8EpB"}
@@ -0,0 +1,101 @@
1
+ // packages/cli/src/onboard/llm-client.ts
2
+ //
3
+ // Anthropic Messages-API wrapper with tool-use structured output.
4
+ //
5
+ // Why a wrapper instead of calling the SDK directly: the call shape is
6
+ // shared by generatePlan() (Phase B) AND a future Phase B.1 prompt-test
7
+ // harness, and the retry classification (transient vs permanent) is
8
+ // security-critical (a 401 must never silently retry). Centralizing the
9
+ // shape here means a new caller can't accidentally drift from the policy.
10
+ //
11
+ // Tool-use binding: declare a single tool `emit_scaffold_plan` whose
12
+ // `input_schema` is the JSON-Schema rendering of ScaffoldPlanSchema; set
13
+ // tool_choice to force the model into a tool call; read the tool-use
14
+ // block's `input` as the candidate JSON. The SDK validates the JSON
15
+ // shape at the API boundary; downstream Zod validation re-checks the
16
+ // semantics (discriminated-union, byte budgets).
17
+ import Anthropic from "@anthropic-ai/sdk";
18
+ const DEFAULT_FACTORY = (apiKey) => new Anthropic({ apiKey });
19
+ const TRANSIENT_STATUSES = new Set([429, 500, 502, 503, 504]);
20
+ function isTransient(err) {
21
+ if (!(err instanceof Error))
22
+ return false;
23
+ const status = err.status;
24
+ if (typeof status === "number")
25
+ return TRANSIENT_STATUSES.has(status);
26
+ const code = err.code;
27
+ if (typeof code === "string" && /ECONNRESET|ETIMEDOUT|ENETUNREACH|EAI_AGAIN/.test(code)) {
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ export async function callScaffoldLlm(inputs, opts = {}) {
33
+ const apiKey = inputs.apiKey ?? process.env["ANTHROPIC_API_KEY"];
34
+ if (!apiKey || !apiKey.trim()) {
35
+ throw new Error("df onboard: ANTHROPIC_API_KEY is required for Phase B's scaffold-generation LLM call. " +
36
+ "Set the env var (or pass --api-key) and re-run.");
37
+ }
38
+ const factory = opts.createClient ?? DEFAULT_FACTORY;
39
+ const client = factory(apiKey);
40
+ const request = {
41
+ model: inputs.modelId,
42
+ // 8192 was the original default; sage-blueprint at 701 template files
43
+ // produced a ScaffoldPlan that truncated mid-tool-use (stop_reason:
44
+ // max_tokens), so the `files` array was incomplete and Zod validation
45
+ // failed on `files: Required`. Sonnet 4.6 supports up to 64k output
46
+ // tokens; 32k is the sweet spot for one full ScaffoldPlan with headroom
47
+ // without paying for capacity we never use.
48
+ max_tokens: inputs.maxTokens ?? 32768,
49
+ system: inputs.systemPrompt,
50
+ tools: [
51
+ {
52
+ name: inputs.toolName,
53
+ description: "Emit the tailored ScaffoldPlan JSON. The input schema is enforced strictly; " +
54
+ "fields not in the schema are rejected.",
55
+ input_schema: inputs.toolInputSchema,
56
+ },
57
+ ],
58
+ tool_choice: { type: "tool", name: inputs.toolName },
59
+ messages: [{ role: "user", content: inputs.userMessage }],
60
+ };
61
+ let attempts = 0;
62
+ let lastErr = null;
63
+ for (let attempt = 0; attempt < 2; attempt++) {
64
+ attempts++;
65
+ try {
66
+ const r = await client.messages.create(request);
67
+ // Opt-in diagnostic — stop_reason + token counts + content shape are the
68
+ // discriminating signals when downstream Zod validation fails on the
69
+ // returned plan (truncation vs. malformed output vs. wrong block type).
70
+ // Gated behind DF_ONBOARD_DEBUG so the benign-info line does not pollute
71
+ // stderr in normal use (per #57's severity-routing convention — benign
72
+ // info on stderr lights up GKE/Cloud Logging as severity:ERROR).
73
+ if (process.env["DF_ONBOARD_DEBUG"]) {
74
+ console.error(`[llm-diag] attempt=${attempts} stop_reason=${r.stop_reason} ` +
75
+ `input_tokens=${r.usage.input_tokens} output_tokens=${r.usage.output_tokens} ` +
76
+ `content_types=${JSON.stringify(r.content.map((b) => b.type))}`);
77
+ }
78
+ const toolBlock = r.content.find((b) => b.type === "tool_use" &&
79
+ b.name === inputs.toolName);
80
+ if (!toolBlock) {
81
+ throw new Error(`df onboard: no tool_use block found in LLM response (got stop_reason=${r.stop_reason}). ` +
82
+ "The model did not honor tool_choice; check the prompt and model selection.");
83
+ }
84
+ return {
85
+ planJson: toolBlock.input,
86
+ modelId: r.model,
87
+ inputTokens: r.usage.input_tokens,
88
+ outputTokens: r.usage.output_tokens,
89
+ attempts,
90
+ };
91
+ }
92
+ catch (err) {
93
+ lastErr = err;
94
+ if (attempt === 0 && isTransient(err))
95
+ continue;
96
+ throw err;
97
+ }
98
+ }
99
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
100
+ }
101
+ //# sourceMappingURL=llm-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-client.js","sourceRoot":"","sources":["../../src/onboard/llm-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ <!-- packages/cli/src/onboard/prompts/scaffold.md -->
2
+ # Dark Factory `df onboard` — Stage B scaffold prompt (cycle 15 Phase B)
3
+
4
+ You are the "Stage B" scaffold-tailoring agent inside `@momentiq/dark-factory-cli`'s
5
+ `df onboard` command. Your role is to consume a **structured `RepoAnalysis`** (the
6
+ deterministic Stage A output) and a **sage-blueprint template directory** (the raw
7
+ markdown/JSON/YAML files that define the agent-context-set scaffolding), then
8
+ emit a **`ScaffoldPlan`** describing how to tailor the template to the target repo.
9
+
10
+ ## Operating contract
11
+
12
+ You MUST emit your response via the `emit_scaffold_plan` tool call. Do NOT emit
13
+ prose text outside of the tool call. The tool's `input_schema` is strict; fields
14
+ outside the schema are rejected, and the action discriminator forces an unambiguous
15
+ choice per file.
16
+
17
+ For every file in the input template, choose exactly ONE action:
18
+
19
+ | Action | Use when | Required fields |
20
+ |---|---|---|
21
+ | `emit` | The target repo does NOT have this file yet, OR the existing file is empty/trivial and should be replaced. | `path`, `rationale`, `tailored_content` |
22
+ | `merge` | The target repo HAS a non-trivial version of this file and the LLM-tailored content should be APPENDED additively (NOT replace). Reserve for `CLAUDE.md` and `AGENTS.md`. | `path`, `rationale`, `tailored_content` |
23
+ | `skip` | The target repo already covers this file's intent, OR the file is irrelevant to the target's stack/services. | `path`, `rationale` (NO `tailored_content` — the schema rejects it) |
24
+
25
+ ### Tailoring rules
26
+
27
+ 1. **Cite the analysis, never invent.** Every claim in `tailored_content` must be
28
+ grounded in a field of `RepoAnalysis`. If you reference a service, it MUST
29
+ appear in `analysis.services[]`. If you cite a stack, it MUST appear in
30
+ `analysis.stacks[]`. If you describe a deploy story, cite `analysis.ci.deployStory`.
31
+ Hallucinating a non-existent service or stack is the worst possible failure
32
+ mode of this pipeline.
33
+
34
+ 2. **Substitute `{{ }}` placeholders.** The template files contain
35
+ `{{ project_name }}`, `{{ stack }}`, `{{ services }}` and similar Copier-style
36
+ placeholders. Replace each with the concrete value from `RepoAnalysis`:
37
+ - `{{ project_name }}` → `analysis.canonicalName.split("/")[1]`
38
+ - `{{ owner }}` → `analysis.canonicalName.split("/")[0]`
39
+ - `{{ stack }}` → `analysis.stacks[0]?.language` (or "polyglot" if multiple)
40
+ - `{{ services }}` → bulleted list from `analysis.services[]`
41
+ - `{{ default_branch }}` → `analysis.git.defaultBranch`
42
+ The bodies you emit must contain NO leftover `{{ }}` after substitution.
43
+
44
+ 3. **Use `merge` only for `CLAUDE.md` and `AGENTS.md`.** Other files don't have
45
+ the marker-comment infrastructure; use `emit` or `skip` for everything else.
46
+ When `analysis.docs.hasClaudeMd === true`, use `merge` for `CLAUDE.md`;
47
+ otherwise use `emit`. Same for `AGENTS.md`.
48
+
49
+ 4. **Skip when the target already covers it.** If `analysis.dfPresence.configJson === true`,
50
+ skip `.agent-review/config.json` — don't fight the existing gate. If
51
+ `analysis.dfPresence.prWorkflow === true`, skip `dark-factory-pr.yml`. Cite
52
+ the dfPresence field in the rationale.
53
+
54
+ 4a. **ALWAYS SKIP `.agent-review/config.json` (the Phase C seeder owns this path).** Emit a single `skip` entry for `.agent-review/config.json` with rationale "phase C seeder owns this path; phase B does not emit config.json"; do NOT include `tailored_content`. This applies regardless of `analysis.dfPresence.configJson`'s value and supersedes rule 4 for this specific path.
55
+
56
+ 5. **`merge` content rules.** When you emit `tailored_content` for a `merge`
57
+ action, that content is APPENDED after the existing file (with marker
58
+ comments managed by the CLI). Therefore:
59
+ - Do NOT include the user's existing headings.
60
+ - START with a top-level H2 section (e.g. `## Dark Factory onboarding`) so the
61
+ append reads naturally.
62
+ - Do NOT include `<!-- df onboard: inserted-by-cycle-15 -->` markers — the
63
+ CLI's merge writer adds them.
64
+
65
+ 6. **`rationale` is one sentence.** Plain English, ≤ 800 chars; reference the
66
+ specific analysis fields that drove the action choice.
67
+
68
+ 7. **`summary` is one paragraph.** ≤ 800 chars; what the LLM tailored, why, and
69
+ any caveats the operator should know.
70
+
71
+ ## Inputs
72
+
73
+ ### Resolved critic profile
74
+
75
+ `{{CRITIC_PROFILE}}`
76
+
77
+ (The phase-c seeder reads this resolved profile to emit the matching .agent-review/config.json; phase B's role is to thread it, not to emit the JSON.)
78
+
79
+ ### RepoAnalysis
80
+
81
+ ```json
82
+ {{ANALYSIS_JSON}}
83
+ ```
84
+
85
+ ### Template file list (path: sizeBytes)
86
+
87
+ {{TEMPLATE_FILE_LIST}}
88
+
89
+ ### Template file bodies
90
+
91
+ {{TEMPLATE_FILE_BODIES}}
92
+
93
+ ## Now emit the `emit_scaffold_plan` tool call.
@@ -0,0 +1,13 @@
1
+ import type { RepoAnalysis } from "./schema.js";
2
+ import type { TemplateFile } from "./template-loader.js";
3
+ export interface RenderedPrompt {
4
+ systemPrompt: string;
5
+ userMessage: string;
6
+ }
7
+ export interface RenderScaffoldPromptOptions {
8
+ /** Resolved critic profile (B-D8). Substituted into the system prompt's `{{CRITIC_PROFILE}}` placeholder so the LLM's `.agent-review/config.json` tailoring uses the right fleet. */
9
+ profile: "local" | "cloud";
10
+ }
11
+ export declare function renderScaffoldPromptSync(asset: string, analysis: RepoAnalysis, templateFiles: TemplateFile[], opts: RenderScaffoldPromptOptions): RenderedPrompt;
12
+ export declare function renderScaffoldPrompt(analysis: RepoAnalysis, templateFiles: TemplateFile[], opts: RenderScaffoldPromptOptions): Promise<RenderedPrompt>;
13
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/onboard/prompts.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAYzD,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,qLAAqL;IACrL,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;CAC5B;AAcD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,YAAY,EACtB,aAAa,EAAE,YAAY,EAAE,EAC7B,IAAI,EAAE,2BAA2B,GAChC,cAAc,CAsBhB;AAED,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,YAAY,EACtB,aAAa,EAAE,YAAY,EAAE,EAC7B,IAAI,EAAE,2BAA2B,GAChC,OAAO,CAAC,cAAc,CAAC,CAGzB"}
@@ -0,0 +1,54 @@
1
+ // packages/cli/src/onboard/prompts.ts
2
+ //
3
+ // Render the Stage B scaffold prompt by substituting the analysis + template
4
+ // into the static prompt asset. The asset path uses import.meta.url so the
5
+ // resolution works both from src/ (dev) and dist/ (npm install), matching
6
+ // the project's existing pattern in critic prompts.
7
+ import { readFile } from "node:fs/promises";
8
+ import { dirname, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ const HERE = dirname(fileURLToPath(import.meta.url));
11
+ const ASSET_PATH = join(HERE, "prompts", "scaffold.md");
12
+ let _cachedAsset = null;
13
+ async function loadAsset() {
14
+ if (_cachedAsset !== null)
15
+ return _cachedAsset;
16
+ _cachedAsset = await readFile(ASSET_PATH, "utf8");
17
+ return _cachedAsset;
18
+ }
19
+ function renderFileList(files) {
20
+ return files
21
+ .map((f) => `- ${f.path} (${Buffer.byteLength(f.content, "utf8")} bytes)`)
22
+ .join("\n");
23
+ }
24
+ function renderFileBodies(files) {
25
+ return files
26
+ .map((f) => `path: ${f.path}\n---\n${f.content}\n===\n`)
27
+ .join("\n");
28
+ }
29
+ export function renderScaffoldPromptSync(asset, analysis, templateFiles, opts) {
30
+ // Split the asset on a marker so the operating contract is the SYSTEM prompt
31
+ // (cached, prompt-cached on the SDK side) and the per-call payload is the
32
+ // USER message. The marker is the "## Inputs" heading.
33
+ const splitIdx = asset.indexOf("## Inputs");
34
+ if (splitIdx < 0) {
35
+ throw new Error("df onboard: scaffold.md asset missing '## Inputs' split marker — prompt-asset corruption.");
36
+ }
37
+ // {{CRITIC_PROFILE}} appears in both halves (rule 4a in the system prompt;
38
+ // the verbatim profile echo in the user payload), so replaceAll both sides.
39
+ const systemPrompt = asset.slice(0, splitIdx)
40
+ .replaceAll("{{CRITIC_PROFILE}}", opts.profile)
41
+ .trim();
42
+ const userTemplate = asset.slice(splitIdx);
43
+ const userMessage = userTemplate
44
+ .replaceAll("{{CRITIC_PROFILE}}", opts.profile)
45
+ .replace("{{ANALYSIS_JSON}}", JSON.stringify(analysis, null, 2))
46
+ .replace("{{TEMPLATE_FILE_LIST}}", renderFileList(templateFiles))
47
+ .replace("{{TEMPLATE_FILE_BODIES}}", renderFileBodies(templateFiles));
48
+ return { systemPrompt, userMessage };
49
+ }
50
+ export async function renderScaffoldPrompt(analysis, templateFiles, opts) {
51
+ const asset = await loadAsset();
52
+ return renderScaffoldPromptSync(asset, analysis, templateFiles, opts);
53
+ }
54
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/onboard/prompts.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,0EAA0E;AAC1E,oDAAoD;AAEpD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAKzC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACrD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;AAExD,IAAI,YAAY,GAAkB,IAAI,CAAC;AACvC,KAAK,UAAU,SAAS;IACtB,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO,YAAY,CAAC;IAC/C,YAAY,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAClD,OAAO,YAAY,CAAC;AACtB,CAAC;AAYD,SAAS,cAAc,CAAC,KAAqB;IAC3C,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC;SACzE,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAqB;IAC7C,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,OAAO,SAAS,CAAC;SACvD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,KAAa,EACb,QAAsB,EACtB,aAA6B,EAC7B,IAAiC;IAEjC,6EAA6E;IAC7E,0EAA0E;IAC1E,uDAAuD;IACvD,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC5C,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,2FAA2F,CAC5F,CAAC;IACJ,CAAC;IACD,2EAA2E;IAC3E,4EAA4E;IAC5E,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;SAC1C,UAAU,CAAC,oBAAoB,EAAE,IAAI,CAAC,OAAO,CAAC;SAC9C,IAAI,EAAE,CAAC;IACV,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC3C,MAAM,WAAW,GAAG,YAAY;SAC7B,UAAU,CAAC,oBAAoB,EAAE,IAAI,CAAC,OAAO,CAAC;SAC9C,OAAO,CAAC,mBAAmB,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;SAC/D,OAAO,CAAC,wBAAwB,EAAE,cAAc,CAAC,aAAa,CAAC,CAAC;SAChE,OAAO,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAC,CAAC;IACxE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,QAAsB,EACtB,aAA6B,EAC7B,IAAiC;IAEjC,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,OAAO,wBAAwB,CAAC,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;AACxE,CAAC"}