human-to-code 0.1.27 → 0.1.28

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 (68) hide show
  1. package/Readme.md +56 -36
  2. package/dist/agents/direct/file-memory.d.ts.map +1 -1
  3. package/dist/agents/direct/file-memory.js +70 -1
  4. package/dist/agents/direct/file-memory.js.map +1 -1
  5. package/dist/agents/direct/generation-client.d.ts +10 -0
  6. package/dist/agents/direct/generation-client.d.ts.map +1 -1
  7. package/dist/agents/direct/generation-client.js +19 -0
  8. package/dist/agents/direct/generation-client.js.map +1 -1
  9. package/dist/agents/direct/index.d.ts +3 -0
  10. package/dist/agents/direct/index.d.ts.map +1 -1
  11. package/dist/agents/direct/index.js +3 -0
  12. package/dist/agents/direct/index.js.map +1 -1
  13. package/dist/agents/direct/presentation.d.ts +20 -0
  14. package/dist/agents/direct/presentation.d.ts.map +1 -1
  15. package/dist/agents/direct/presentation.js +33 -3
  16. package/dist/agents/direct/presentation.js.map +1 -1
  17. package/dist/agents/direct/project-blueprint.d.ts +29 -0
  18. package/dist/agents/direct/project-blueprint.d.ts.map +1 -0
  19. package/dist/agents/direct/project-blueprint.js +170 -0
  20. package/dist/agents/direct/project-blueprint.js.map +1 -0
  21. package/dist/agents/direct/project-contracts.d.ts +34 -0
  22. package/dist/agents/direct/project-contracts.d.ts.map +1 -1
  23. package/dist/agents/direct/project-contracts.js +59 -42
  24. package/dist/agents/direct/project-contracts.js.map +1 -1
  25. package/dist/agents/direct/project-memory.d.ts +6 -0
  26. package/dist/agents/direct/project-memory.d.ts.map +1 -1
  27. package/dist/agents/direct/project-memory.js +20 -0
  28. package/dist/agents/direct/project-memory.js.map +1 -1
  29. package/dist/agents/direct/reference-validation.d.ts +55 -0
  30. package/dist/agents/direct/reference-validation.d.ts.map +1 -0
  31. package/dist/agents/direct/reference-validation.js +415 -0
  32. package/dist/agents/direct/reference-validation.js.map +1 -0
  33. package/dist/agents/direct/types.d.ts +45 -0
  34. package/dist/agents/direct/types.d.ts.map +1 -1
  35. package/dist/agents/direct/unit-todos.d.ts +51 -0
  36. package/dist/agents/direct/unit-todos.d.ts.map +1 -0
  37. package/dist/agents/direct/unit-todos.js +223 -0
  38. package/dist/agents/direct/unit-todos.js.map +1 -0
  39. package/dist/cli.d.ts.map +1 -1
  40. package/dist/cli.js +147 -10
  41. package/dist/cli.js.map +1 -1
  42. package/dist/config/config.d.ts +22 -1
  43. package/dist/config/config.d.ts.map +1 -1
  44. package/dist/config/config.js +40 -6
  45. package/dist/config/config.js.map +1 -1
  46. package/dist/prompts/direct-blueprint.d.ts +19 -0
  47. package/dist/prompts/direct-blueprint.d.ts.map +1 -0
  48. package/dist/prompts/direct-blueprint.js +49 -0
  49. package/dist/prompts/direct-blueprint.js.map +1 -0
  50. package/dist/prompts/direct-conversion.d.ts +8 -0
  51. package/dist/prompts/direct-conversion.d.ts.map +1 -1
  52. package/dist/prompts/direct-conversion.js +26 -0
  53. package/dist/prompts/direct-conversion.js.map +1 -1
  54. package/dist/prompts/direct-todos.d.ts +17 -0
  55. package/dist/prompts/direct-todos.d.ts.map +1 -0
  56. package/dist/prompts/direct-todos.js +43 -0
  57. package/dist/prompts/direct-todos.js.map +1 -0
  58. package/dist/prompts/index.d.ts +2 -0
  59. package/dist/prompts/index.d.ts.map +1 -1
  60. package/dist/prompts/index.js +2 -0
  61. package/dist/prompts/index.js.map +1 -1
  62. package/dist/providers/provider.d.ts.map +1 -1
  63. package/dist/providers/provider.js +3 -1
  64. package/dist/providers/provider.js.map +1 -1
  65. package/docs/ARCHITECTURE.md +23 -6
  66. package/docs/CONFIGURATION.md +251 -0
  67. package/docs/MODULES.md +5 -2
  68. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unit-todos.d.ts","sourceRoot":"","sources":["../../../src/agents/direct/unit-todos.ts"],"names":[],"mappings":"AAeA,OAAO,EAA8B,KAAK,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAGlG,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,mBAAmB,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,uEAAuE;IACvE,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,2EAA2E;IAC3E,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAOD,mFAAmF;AACnF,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAuB/C;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,YAAY,CA4ClG;AAyCD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,YAAY,CAad;AAED,yDAAyD;AACzD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,QAAQ,EAAE,YAAY,GACrB,MAAM,EAAE,CAQV;AAED,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,MAAM,CAOjE;AA0BD,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,2HAA2H;AAC3H,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,kBAAkB,CAOpB;AAED,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAEzE"}
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Per-unit planning and the guard that makes a second coding pass safe.
3
+ *
4
+ * A todo list is agreed for one target before it is written, then a
5
+ * deterministic coverage check looks for the artifacts that list promised. Only
6
+ * a real coverage gap triggers a refinement request, and the refinement is
7
+ * accepted only if it keeps everything the previous pass produced — the
8
+ * regression ratchet. That inverts the usual risk of re-emitting a whole file:
9
+ * a model that drops most of a stylesheet loses its own output, not the run's.
10
+ *
11
+ * Coverage means "the named artifact appears in the output", never "the
12
+ * requirement is implemented" and never verification.
13
+ */
14
+ import { extname } from "node:path";
15
+ import { extractStaticFileMemory } from "../../pipeline/file-memory.js";
16
+ import { BLUEPRINT_VOCABULARY_KINDS } from "./project-blueprint.js";
17
+ import { cssFacts, htmlFacts, javaScriptFacts } from "./project-contracts.js";
18
+ const MAX_TODOS = 24;
19
+ const MAX_EXPECTED_NAMES = 8;
20
+ const MAX_REQUIREMENT_CHARS = 240;
21
+ const ID_PATTERN = /^T[0-9]{1,2}$/u;
22
+ const NAME_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$:.\-/]{0,119}$/u;
23
+ /** A refinement may not shrink a file below this fraction of the previous pass. */
24
+ export const MIN_REFINEMENT_LENGTH_RATIO = 0.6;
25
+ const HTML_EXTENSIONS = new Set([".html", ".htm"]);
26
+ const SCRIPT_EXTENSIONS = new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"]);
27
+ function oneLine(value, limit) {
28
+ const clean = value
29
+ .replace(/[\u0000-\u0008\u000b-\u001f\u007f]/gu, " ")
30
+ .replace(/\s+/gu, " ")
31
+ .trim();
32
+ return clean.length <= limit ? clean : `${clean.slice(0, Math.max(0, limit - 1))}…`;
33
+ }
34
+ function ownKeys(value, expected) {
35
+ const actual = Object.keys(value).sort();
36
+ const wanted = [...expected].sort();
37
+ return actual.length === wanted.length && actual.every((key, index) => key === wanted[index]);
38
+ }
39
+ function object(value) {
40
+ return typeof value === "object" && value !== null && !Array.isArray(value);
41
+ }
42
+ /**
43
+ * Strictly validate a todo list. When `allowedNames` is supplied, an expectation
44
+ * naming something outside the agreed vocabulary is dropped rather than
45
+ * rejected: the requirement text is still useful, and a stray name must never
46
+ * become a new instruction.
47
+ */
48
+ export function parseUnitTodoList(output, allowedNames) {
49
+ let parsed;
50
+ try {
51
+ parsed = JSON.parse(output);
52
+ }
53
+ catch {
54
+ throw new Error("Todo list is not valid JSON.");
55
+ }
56
+ if (!object(parsed) || !ownKeys(parsed, ["todos"])) {
57
+ throw new Error("Todo list must contain exactly todos.");
58
+ }
59
+ if (!Array.isArray(parsed.todos) || parsed.todos.length === 0 || parsed.todos.length > MAX_TODOS) {
60
+ throw new Error(`Todo list must be an array with 1-${MAX_TODOS} entries.`);
61
+ }
62
+ const seen = new Set();
63
+ const todos = parsed.todos.map((value, index) => {
64
+ if (!object(value) || (!ownKeys(value, ["id", "requirement"]) && !ownKeys(value, ["expects", "id", "requirement"]))) {
65
+ throw new Error(`Todo ${index} has unknown or missing fields.`);
66
+ }
67
+ if (typeof value.id !== "string" || !ID_PATTERN.test(value.id)) {
68
+ throw new Error(`Todo ${index} has an invalid id.`);
69
+ }
70
+ if (seen.has(value.id))
71
+ throw new Error(`Todo list repeats the id ${value.id}.`);
72
+ seen.add(value.id);
73
+ if (typeof value.requirement !== "string")
74
+ throw new Error(`Todo ${index} has an invalid requirement.`);
75
+ const requirement = oneLine(value.requirement, MAX_REQUIREMENT_CHARS);
76
+ if (requirement.length === 0)
77
+ throw new Error(`Todo ${index} has an empty requirement.`);
78
+ if (value.expects === undefined)
79
+ return { id: value.id, requirement };
80
+ if (!object(value.expects) || !ownKeys(value.expects, ["kind", "names"])) {
81
+ throw new Error(`Todo ${index} has an invalid expects object.`);
82
+ }
83
+ const { kind, names } = value.expects;
84
+ if (typeof kind !== "string" || !BLUEPRINT_VOCABULARY_KINDS.includes(kind)) {
85
+ throw new Error(`Todo ${index} expects an unknown kind.`);
86
+ }
87
+ if (!Array.isArray(names) || names.length === 0 || names.length > MAX_EXPECTED_NAMES) {
88
+ throw new Error(`Todo ${index} must expect 1-${MAX_EXPECTED_NAMES} names.`);
89
+ }
90
+ const cleaned = names.filter((name) => typeof name === "string" && NAME_PATTERN.test(name) && (allowedNames === undefined || allowedNames.has(name)));
91
+ if (cleaned.length === 0)
92
+ return { id: value.id, requirement };
93
+ return { id: value.id, requirement, expects: { kind, names: [...new Set(cleaned)] } };
94
+ });
95
+ return { todos };
96
+ }
97
+ /** Names a candidate demonstrably contains, by the file's own syntax. */
98
+ function presentNames(targetPath, code) {
99
+ const extension = extname(targetPath).toLowerCase();
100
+ const present = new Set();
101
+ const add = (values) => {
102
+ for (const value of values)
103
+ if (value.length > 0)
104
+ present.add(value);
105
+ };
106
+ if (HTML_EXTENSIONS.has(extension)) {
107
+ const facts = htmlFacts(code);
108
+ add(facts.classes);
109
+ add(facts.ids);
110
+ add(facts.handlerCalls);
111
+ add(facts.elements);
112
+ }
113
+ else if (extension === ".css") {
114
+ const facts = cssFacts(code);
115
+ add(facts.customProperties);
116
+ add(facts.selectors.map((selector) => selector.trim()));
117
+ for (const selector of facts.selectors) {
118
+ add((selector.match(/[.#][A-Za-z0-9_-]+/gu) ?? []).map((token) => token.slice(1)));
119
+ }
120
+ }
121
+ else if (SCRIPT_EXTENSIONS.has(extension)) {
122
+ const facts = javaScriptFacts(code);
123
+ add(facts.toggledClasses);
124
+ add(facts.selectors);
125
+ for (const selector of facts.selectors) {
126
+ add((selector.match(/[.#][A-Za-z0-9_-]+/gu) ?? []).map((token) => token.slice(1)));
127
+ }
128
+ }
129
+ for (const entry of extractStaticFileMemory(targetPath, code)) {
130
+ add((entry.code.match(/[A-Za-z_$][A-Za-z0-9_$]*/gu) ?? []).slice(0, 8));
131
+ }
132
+ return present;
133
+ }
134
+ function mentionsName(code, name) {
135
+ const escaped = name.replace(/[.*+?^${}()|[\]\\/-]/gu, "\\$&");
136
+ return new RegExp(`(?:^|[^A-Za-z0-9_$-])${escaped}(?:[^A-Za-z0-9_$-]|$)`, "u").test(code);
137
+ }
138
+ /**
139
+ * Deterministically check which todos left a trace in the candidate. No model
140
+ * request, no execution. Generous on purpose: a false "addressed" only skips a
141
+ * refinement, while a false "unaddressed" would spend a request for nothing.
142
+ */
143
+ export function todoCoverage(todos, targetPath, code) {
144
+ const present = presentNames(targetPath, code);
145
+ const coverage = { addressed: [], unaddressed: [], unverifiable: [] };
146
+ for (const todo of todos) {
147
+ if (todo.expects === undefined) {
148
+ coverage.unverifiable.push(todo.id);
149
+ continue;
150
+ }
151
+ const missing = todo.expects.names.filter((name) => !present.has(name) && !mentionsName(code, name));
152
+ if (missing.length === 0)
153
+ coverage.addressed.push(todo.id);
154
+ else
155
+ coverage.unaddressed.push(todo.id);
156
+ }
157
+ return coverage;
158
+ }
159
+ /** Render unaddressed todos for a refinement request. */
160
+ export function unaddressedRequirements(todos, coverage) {
161
+ const byId = new Map(todos.map((todo) => [todo.id, todo]));
162
+ return coverage.unaddressed.flatMap((id) => {
163
+ const todo = byId.get(id);
164
+ if (todo === undefined)
165
+ return [];
166
+ const names = todo.expects === undefined ? "" : ` (expected ${todo.expects.kind}: ${todo.expects.names.join(", ")})`;
167
+ return [`${todo.id}: ${todo.requirement}${names}`];
168
+ });
169
+ }
170
+ /** Render a todo list into the block the coding prompt receives. */
171
+ export function renderTodoList(todos) {
172
+ return todos.map((todo) => {
173
+ const names = todo.expects === undefined
174
+ ? ""
175
+ : ` [must produce ${todo.expects.kind}: ${todo.expects.names.join(", ")}]`;
176
+ return `${todo.id}. ${todo.requirement}${names}`;
177
+ }).join("\n");
178
+ }
179
+ /** Countable artifacts a refinement is not allowed to lose. */
180
+ function ratchetSet(targetPath, code) {
181
+ const extension = extname(targetPath).toLowerCase();
182
+ const values = new Set();
183
+ if (HTML_EXTENSIONS.has(extension)) {
184
+ const facts = htmlFacts(code);
185
+ for (const value of facts.ids)
186
+ values.add(`id:${value}`);
187
+ for (const value of facts.classes)
188
+ values.add(`class:${value}`);
189
+ }
190
+ else if (extension === ".css") {
191
+ const facts = cssFacts(code);
192
+ for (const value of facts.selectors)
193
+ values.add(`selector:${value.trim()}`);
194
+ for (const value of facts.customProperties)
195
+ values.add(`var:${value}`);
196
+ }
197
+ else if (SCRIPT_EXTENSIONS.has(extension)) {
198
+ const facts = javaScriptFacts(code);
199
+ for (const value of facts.selectors)
200
+ values.add(`selector:${value}`);
201
+ for (const value of facts.modules)
202
+ values.add(`module:${value}`);
203
+ }
204
+ for (const entry of extractStaticFileMemory(targetPath, code)) {
205
+ values.add(`declaration:${entry.code.trim()}`);
206
+ }
207
+ values.delete("");
208
+ return values;
209
+ }
210
+ /** Compare two passes over the same target. Empty `lost` plus an acceptable ratio means the refinement kept everything. */
211
+ export function contractRegression(targetPath, previous, next) {
212
+ const before = ratchetSet(targetPath, previous);
213
+ const after = ratchetSet(targetPath, next);
214
+ const lost = [...before].filter((value) => !after.has(value));
215
+ const previousLength = previous.trim().length;
216
+ const shrinkRatio = previousLength === 0 ? 1 : next.trim().length / previousLength;
217
+ return { lost, shrinkRatio };
218
+ }
219
+ /** The ratchet: a refinement is only accepted when it is a strict improvement in coverage terms. */
220
+ export function acceptsRefinement(regression) {
221
+ return regression.lost.length === 0 && regression.shrinkRatio >= MIN_REFINEMENT_LENGTH_RATIO;
222
+ }
223
+ //# sourceMappingURL=unit-todos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unit-todos.js","sourceRoot":"","sources":["../../../src/agents/direct/unit-todos.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,sGAAsG;AA86BtG,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiFzD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,sGAAsG;AA2jCtG,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiFzD"}
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  /** Production CLI: analyze -> reviewed contract -> grounded patch -> validation -> explicit apply. */
3
3
  import { constants as fsConstants, realpathSync } from "node:fs";
4
4
  import { copyFile, lstat, readFile, rename, rm, writeFile } from "node:fs/promises";
5
- import { dirname, relative, resolve, sep } from "node:path";
5
+ import { dirname, extname, relative, resolve, sep } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { parseArgs } from "node:util";
8
8
  import { analyzeProject } from "./analysis/analyzer.js";
@@ -14,7 +14,7 @@ import { PlanningError, contractPathForSource, createDraftContract, loadReviewed
14
14
  import { ProviderError } from "./providers/provider.js";
15
15
  import { createOllamaProvider, createOpenAIProvider } from "./providers/providers.js";
16
16
  import { RunStore } from "./pipeline/run-store.js";
17
- import { applyWholeFileBatch, applyUnit, buildProjectMemory, conditionalRequestAllowance, discoverDirectUnits, generateConversionUnits, generateCode, generateIntegrationAudit, generateIntegrationRepairCode, generateRepairCode, reconcileGeneratedIntegrations, renderReceipt, validateCandidateProject, validateGeneratedUnit, } from "./agents/direct/index.js";
17
+ import { applyWholeFileBatch, applyUnit, buildProjectMemory, collectReferenceFindings, conditionalRequestAllowance, plannedRequestCounts, discoverDirectUnits, generateBlueprint, generateConversionUnits, generateCode, generateIntegrationAudit, generateIntegrationRepairCode, generateRepairCode, generateUnitTodos, parseProjectBlueprint, parseUnitTodoList, reconcileGeneratedIntegrations, REFERENCE_EXTENSIONS, renderBlueprintFor, renderReceipt, validateCandidateProject, validateGeneratedUnit, } from "./agents/direct/index.js";
18
18
  import { applyVerifiedRun, buildContextPreview, generateRun, resolveWorkspaceConfig, rollbackAppliedRun, validateStoredRun, } from "./agents/guided/index.js";
19
19
  const HELP = `human-to-code — reviewed, grounded, isolated human-to-code compiler agent
20
20
 
@@ -480,7 +480,10 @@ async function buildCommand(cli, rootInput) {
480
480
  provider: providerName,
481
481
  model,
482
482
  context: "project-memory-v1",
483
+ // `requests` keeps its established meaning — the planned minimum — so an
484
+ // existing consumer is not silently redefined. The breakdown is additive.
483
485
  requests: units.length,
486
+ plannedRequests: plannedRequestCounts(units, effective.direct.planning),
484
487
  ...(conditionalRequests !== undefined ? {
485
488
  additionalRequests: {
486
489
  conditional: true,
@@ -505,6 +508,7 @@ async function buildCommand(cli, rootInput) {
505
508
  else {
506
509
  output(renderReceipt(units, providerName, model, languages, {
507
510
  reconcileIntegrations: effective.direct.reconcileIntegrations,
511
+ planning: effective.direct.planning,
508
512
  }), false);
509
513
  for (const notice of discovery.notices)
510
514
  output(` ! ${notice.message}`, false);
@@ -537,9 +541,8 @@ async function buildCommand(cli, rootInput) {
537
541
  excludedPaths: effective.privacy.excludedPaths,
538
542
  maxFileBytes: effective.privacy.maxFileBytes,
539
543
  });
540
- // Default engine: deterministic per-marker generation. Each marker is one
541
- // plain model completion (no tool calls), applied by exact range. One marker
542
- // failing never aborts the others.
544
+ // Default engine: deterministic per-target generation, optionally preceded by
545
+ // a shared planning pass. One target failing never aborts the others.
543
546
  const describeUnit = (unit) => unit.kind === "file"
544
547
  ? `${unit.sourcePath} → ${unit.outputPath}`
545
548
  : `${unit.sourcePath} (inline @human, line ${unit.line ?? "?"})`;
@@ -552,6 +555,12 @@ async function buildCommand(cli, rootInput) {
552
555
  const retry = event.attempt > 1 ? ` (retry ${event.attempt - 1})` : "";
553
556
  spinner.label(`generating ${describeUnit(event.unit)}${retry}`);
554
557
  }
558
+ else if (event.kind === "plan") {
559
+ spinner.label(`planning ${describeUnit(event.unit)}`);
560
+ }
561
+ else if (event.kind === "refine") {
562
+ spinner.label(`completing ${describeUnit(event.unit)} (${event.unaddressed} unaddressed item(s))`);
563
+ }
555
564
  else if (event.kind === "skip") {
556
565
  spinner.note(` ⊘ skipped ${describeUnit(event.unit)}: ${event.reason}`);
557
566
  }
@@ -559,6 +568,68 @@ async function buildCommand(cli, rootInput) {
559
568
  : undefined;
560
569
  if (interactive)
561
570
  output(`\nConverting ${units.length} item(s) with ${model}…`, false);
571
+ const planning = effective.direct.planning;
572
+ const requestOptions = {
573
+ provider: providerName,
574
+ model,
575
+ ...(baseUrl ? { baseUrl } : {}),
576
+ ...(apiKey ? { apiKey } : {}),
577
+ };
578
+ // One shared planning request, before any file is generated. Each target is
579
+ // generated in isolation afterwards, so this is the only chance for them to
580
+ // agree on names. Best-effort: a failure loses the shared contract, never the
581
+ // run.
582
+ const plannedTargets = projectMemory.plannedTargets;
583
+ let blueprint;
584
+ let blueprintRequests = 0;
585
+ let blueprintNotice;
586
+ if (planning.enabled && planning.projectBlueprint && plannedTargets.length >= 2) {
587
+ if (interactive)
588
+ spinner.label(`agreeing a shared contract across ${plannedTargets.length} file(s)`);
589
+ blueprintRequests = 1;
590
+ try {
591
+ const raw = await generateBlueprint({
592
+ targets: plannedTargets.map((target) => ({
593
+ path: target.path,
594
+ language: target.language,
595
+ instruction: target.purposes.join(" | "),
596
+ })),
597
+ currentTree: discovery.scannedPaths.slice(0, 72),
598
+ }, { ...requestOptions, language });
599
+ blueprint = parseProjectBlueprint(raw, new Set(plannedTargets.map((target) => target.path)));
600
+ projectMemory.adoptBlueprint(blueprint);
601
+ }
602
+ catch (error) {
603
+ blueprintNotice = `shared contract unavailable (${error instanceof Error ? error.message : String(error)});`
604
+ + " files were generated without it";
605
+ if (interactive)
606
+ spinner.note(` ! ${blueprintNotice}`);
607
+ }
608
+ }
609
+ // One todo request per unit, when its kind is enabled. Returning undefined
610
+ // leaves that unit on the single-pass path.
611
+ const planningOutcomes = [];
612
+ const todoEnabled = (unit) => planning.enabled && (unit.kind === "file" ? planning.fileTodo : planning.markerTodo);
613
+ const planningEnabledFor = planning.enabled && (planning.fileTodo || planning.markerTodo)
614
+ ? async (unit, context) => {
615
+ if (!todoEnabled(unit))
616
+ return undefined;
617
+ const target = unit.kind === "file" ? unit.outputPath : unit.sourcePath;
618
+ const raw = await generateUnitTodos({
619
+ targetPath: target,
620
+ instruction: unit.prompt,
621
+ inline: unit.kind === "inline",
622
+ ...(blueprint ? { blueprint: renderBlueprintFor(blueprint, target) } : {}),
623
+ ...(context.projectMemory ? { projectMemory: context.projectMemory } : {}),
624
+ }, { ...requestOptions, language: unit.language ?? language, targetPath: target });
625
+ // Deliberately unconstrained by the shared vocabulary: a todo may also
626
+ // expect target-local artifacts the blueprint has no reason to name
627
+ // (a @media block, a local helper). Injection safety comes from the
628
+ // name charset in the parser, and cross-file drift is caught
629
+ // deterministically by reference checking, not by dropping coverage.
630
+ return parseUnitTodoList(raw);
631
+ }
632
+ : undefined;
562
633
  let generated;
563
634
  try {
564
635
  generated = await generateConversionUnits(units, (unit, context) => {
@@ -567,11 +638,8 @@ async function buildCommand(cli, rootInput) {
567
638
  }
568
639
  return generateCode(unit.prompt, {
569
640
  language: unit.language ?? language,
570
- provider: providerName,
571
- model,
641
+ ...requestOptions,
572
642
  targetPath: unit.kind === "file" ? unit.outputPath : unit.sourcePath,
573
- ...(baseUrl ? { baseUrl } : {}),
574
- ...(apiKey ? { apiKey } : {}),
575
643
  ...context,
576
644
  });
577
645
  }, {
@@ -579,6 +647,9 @@ async function buildCommand(cli, rootInput) {
579
647
  validate: validateGeneratedUnit,
580
648
  projectMemory,
581
649
  contextCharBudget,
650
+ maxCodingPasses: planning.enabled ? planning.maxCodingPassesPerUnit : 1,
651
+ ...(planningEnabledFor !== undefined ? { plan: planningEnabledFor } : {}),
652
+ onPlanningOutcome: (outcome) => planningOutcomes.push(outcome),
582
653
  ...(onProgress ? { onProgress } : {}),
583
654
  });
584
655
  }
@@ -592,6 +663,44 @@ async function buildCommand(cli, rootInput) {
592
663
  }
593
664
  // Optional and off by default. ProjectMemory supplies language-aware
594
665
  // relationships; this orchestrator remains cross-language and performs a
666
+ // Deterministic cross-file reference checking. No model request: the same
667
+ // static extractors ProjectMemory uses are cross-referenced across the
668
+ // candidate web files. This is reference checking, not verification.
669
+ let referenceFindings = [];
670
+ if (effective.direct.crossFileChecks) {
671
+ if (interactive)
672
+ spinner.label("cross-checking generated references");
673
+ const referenceFiles = [];
674
+ const seenReferencePaths = new Set();
675
+ for (const item of generated) {
676
+ if (item.error !== undefined || item.code.trim().length === 0)
677
+ continue;
678
+ const target = item.unit.kind === "file" ? item.unit.outputPath : item.unit.sourcePath;
679
+ if (!REFERENCE_EXTENSIONS.has(extname(target).toLowerCase()) || seenReferencePaths.has(target))
680
+ continue;
681
+ seenReferencePaths.add(target);
682
+ referenceFiles.push({ path: target, content: item.code, generated: true });
683
+ }
684
+ // Untouched project files provide the other half of every cross-reference.
685
+ for (const path of discovery.scannedPaths) {
686
+ if (seenReferencePaths.has(path) || !REFERENCE_EXTENSIONS.has(extname(path).toLowerCase()))
687
+ continue;
688
+ try {
689
+ const content = await readFile(resolve(root, path), "utf8");
690
+ seenReferencePaths.add(path);
691
+ referenceFiles.push({ path, content, generated: false });
692
+ }
693
+ catch {
694
+ continue;
695
+ }
696
+ }
697
+ referenceFindings = collectReferenceFindings(referenceFiles);
698
+ if (interactive) {
699
+ for (const finding of referenceFindings) {
700
+ spinner.note(` ${finding.severity === "blocking" ? "✖" : "!"} ${finding.code}: ${finding.detail}`);
701
+ }
702
+ }
703
+ }
595
704
  // bounded audit -> target repair -> verification cycle over generated groups.
596
705
  let integrationAuditRequests = 0;
597
706
  let integrationRepairRequests = 0;
@@ -788,12 +897,33 @@ async function buildCommand(cli, rootInput) {
788
897
  }
789
898
  }
790
899
  const seconds = Math.round((Date.now() - started) / 1000);
900
+ const todoRequests = planningOutcomes.reduce((total, outcome) => total + outcome.todoRequests, 0);
901
+ const codingRequests = planningOutcomes.reduce((total, outcome) => total + outcome.codingRequests, 0);
902
+ const refinementsRejected = planningOutcomes.filter((outcome) => outcome.refinementRejected !== undefined);
791
903
  if (cli.json) {
792
904
  output({
793
905
  status: written.length === 0 && skipped.length > 0 ? "FAILED" : "DONE",
794
906
  engine: "simple",
795
907
  written,
796
908
  skipped,
909
+ blueprintRequests,
910
+ ...(blueprintNotice !== undefined ? { blueprintNotice } : {}),
911
+ todoRequests,
912
+ codingRequests,
913
+ ...(refinementsRejected.length > 0 ? {
914
+ refinementsRejected: refinementsRejected.map((outcome) => ({
915
+ target: outcome.unit.kind === "file" ? outcome.unit.outputPath : outcome.unit.sourcePath,
916
+ reason: outcome.refinementRejected,
917
+ })),
918
+ } : {}),
919
+ ...(effective.direct.crossFileChecks ? {
920
+ referenceFindings: referenceFindings.map((finding) => ({
921
+ code: finding.code,
922
+ severity: finding.severity,
923
+ path: finding.path,
924
+ detail: finding.detail,
925
+ })),
926
+ } : {}),
797
927
  ...(effective.direct.reconcileIntegrations ? {
798
928
  integrationAuditRequests,
799
929
  integrationRepairRequests,
@@ -808,7 +938,14 @@ async function buildCommand(cli, rootInput) {
808
938
  ? `, ${integrationAuditRequests} integration audit request(s), ${integrationRepairRequests} integration repair request(s)`
809
939
  : "";
810
940
  const repairs = repairRequests > 0 ? `, ${repairRequests} bounded repair request(s)` : "";
811
- output(`\nDone in ${seconds}s. ${written.length} written${skipped.length > 0 ? `, ${skipped.length} skipped` : ""}${integrations}${repairs}.`, false);
941
+ const planned = blueprintRequests + todoRequests > 0
942
+ ? `, ${blueprintRequests} blueprint and ${todoRequests} todo request(s)`
943
+ : "";
944
+ output(`\nDone in ${seconds}s. ${written.length} written${skipped.length > 0 ? `, ${skipped.length} skipped` : ""}${planned}${integrations}${repairs}.`, false);
945
+ for (const outcome of refinementsRejected) {
946
+ const target = outcome.unit.kind === "file" ? outcome.unit.outputPath : outcome.unit.sourcePath;
947
+ output(` ! ${target}: ${outcome.refinementRejected}`, false);
948
+ }
812
949
  }
813
950
  return written.length === 0 && skipped.length > 0 ? 5 : 0;
814
951
  }