@interf/compiler 0.33.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +122 -226
  2. package/dist/cli/commands/agents.js +1 -32
  3. package/dist/cli/commands/benchmark.d.ts +2 -3
  4. package/dist/cli/commands/benchmark.js +1 -31
  5. package/dist/cli/commands/build-plan.js +26 -50
  6. package/dist/cli/commands/build.d.ts +2 -3
  7. package/dist/cli/commands/build.js +1 -31
  8. package/dist/cli/commands/graphs.js +177 -32
  9. package/dist/cli/commands/mcp.d.ts +1 -0
  10. package/dist/cli/commands/mcp.js +223 -126
  11. package/dist/cli/commands/project.js +10 -36
  12. package/dist/cli/commands/reset.d.ts +2 -3
  13. package/dist/cli/commands/reset.js +1 -22
  14. package/dist/cli/commands/runs.js +86 -33
  15. package/dist/cli/commands/status.js +3 -24
  16. package/dist/cli/commands/traces.js +1 -29
  17. package/dist/cli/commands/wizard.js +17 -29
  18. package/dist/cli/lib/http-client.d.ts +39 -0
  19. package/dist/cli/lib/http-client.js +73 -0
  20. package/dist/packages/build-plans/authoring/brief.d.ts +25 -4
  21. package/dist/packages/build-plans/authoring/build-plan-authoring.d.ts +42 -1
  22. package/dist/packages/build-plans/authoring/build-plan-authoring.js +470 -63
  23. package/dist/packages/build-plans/authoring/build-plan-edit-session.d.ts +9 -0
  24. package/dist/packages/build-plans/authoring/build-plan-edit-session.js +27 -10
  25. package/dist/packages/build-plans/authoring/build-plan-improvement.js +62 -8
  26. package/dist/packages/build-plans/authoring/lib/build-plan-edit-utils.d.ts +1 -0
  27. package/dist/packages/build-plans/package/build-plan-definitions.d.ts +0 -1
  28. package/dist/packages/build-plans/package/build-plan-definitions.js +5 -3
  29. package/dist/packages/build-plans/package/build-plan-stage-runner.d.ts +1 -0
  30. package/dist/packages/build-plans/package/build-plan-stage-runner.js +2 -1
  31. package/dist/packages/build-plans/package/builtin-build-plan.d.ts +2 -2
  32. package/dist/packages/build-plans/package/builtin-build-plan.js +3 -3
  33. package/dist/packages/build-plans/package/context-interface.d.ts +3 -0
  34. package/dist/packages/build-plans/package/context-interface.js +5 -5
  35. package/dist/packages/build-plans/package/interf-build-plan-package.js +22 -22
  36. package/dist/packages/build-plans/package/local-build-plans.d.ts +10 -5
  37. package/dist/packages/build-plans/package/local-build-plans.js +57 -32
  38. package/dist/packages/contracts/index.d.ts +4 -3
  39. package/dist/packages/contracts/index.js +2 -1
  40. package/dist/packages/contracts/lib/context-graph-layer.d.ts +161 -0
  41. package/dist/packages/contracts/lib/context-graph-layer.js +216 -0
  42. package/dist/packages/contracts/lib/project-paths.d.ts +7 -0
  43. package/dist/packages/contracts/lib/project-paths.js +9 -0
  44. package/dist/packages/contracts/lib/project-schema.d.ts +264 -1
  45. package/dist/packages/contracts/lib/project-schema.js +38 -13
  46. package/dist/packages/contracts/lib/schema.d.ts +556 -23
  47. package/dist/packages/contracts/lib/schema.js +279 -18
  48. package/dist/packages/contracts/utils/filesystem.d.ts +1 -0
  49. package/dist/packages/contracts/utils/filesystem.js +29 -1
  50. package/dist/packages/projects/lib/schema.d.ts +6 -8
  51. package/dist/packages/projects/lib/schema.js +3 -1
  52. package/dist/packages/projects/source-config.d.ts +0 -5
  53. package/dist/packages/projects/source-config.js +9 -22
  54. package/dist/packages/runtime/actions/fields.d.ts +4 -0
  55. package/dist/packages/runtime/actions/form-builders.js +79 -31
  56. package/dist/packages/runtime/actions/form-validators.js +9 -3
  57. package/dist/packages/runtime/actions/helpers.js +3 -3
  58. package/dist/packages/runtime/actions/registry.d.ts +1 -1
  59. package/dist/packages/runtime/actions/registry.js +1 -1
  60. package/dist/packages/runtime/actions/requests.d.ts +1 -1
  61. package/dist/packages/runtime/actions/requests.js +12 -6
  62. package/dist/packages/runtime/actions/schemas.d.ts +7 -0
  63. package/dist/packages/runtime/actions/schemas.js +1 -0
  64. package/dist/packages/runtime/agent-handoff.js +8 -7
  65. package/dist/packages/runtime/agents/lib/execution-profile.d.ts +14 -0
  66. package/dist/packages/runtime/agents/lib/execution-profile.js +23 -0
  67. package/dist/packages/runtime/agents/lib/execution.js +14 -8
  68. package/dist/packages/runtime/agents/lib/executors.d.ts +1 -0
  69. package/dist/packages/runtime/agents/lib/executors.js +11 -2
  70. package/dist/packages/runtime/agents/lib/logs.d.ts +10 -0
  71. package/dist/packages/runtime/agents/lib/logs.js +32 -8
  72. package/dist/packages/runtime/agents/lib/preflight.js +4 -1
  73. package/dist/packages/runtime/agents/lib/render.d.ts +18 -0
  74. package/dist/packages/runtime/agents/lib/render.js +44 -18
  75. package/dist/packages/runtime/agents/lib/shell-templates.js +105 -63
  76. package/dist/packages/runtime/agents/lib/shells.d.ts +29 -0
  77. package/dist/packages/runtime/agents/lib/shells.js +158 -32
  78. package/dist/packages/runtime/agents/lib/source-context-scan.d.ts +10 -0
  79. package/dist/packages/runtime/agents/lib/source-context-scan.js +388 -0
  80. package/dist/packages/runtime/agents/lib/status.js +1 -14
  81. package/dist/packages/runtime/agents/lib/string-utils.d.ts +16 -0
  82. package/dist/packages/runtime/agents/lib/string-utils.js +36 -0
  83. package/dist/packages/runtime/agents/lib/types.d.ts +1 -0
  84. package/dist/packages/runtime/agents/providers/codex.js +2 -0
  85. package/dist/packages/runtime/agents/role-executors.js +2 -1
  86. package/dist/packages/runtime/auth/session-store.js +11 -3
  87. package/dist/packages/runtime/benchmark-question-draft.d.ts +3 -0
  88. package/dist/packages/runtime/benchmark-question-draft.js +57 -28
  89. package/dist/packages/runtime/build/artifact-status.d.ts +1 -1
  90. package/dist/packages/runtime/build/artifact-status.js +1 -1
  91. package/dist/packages/runtime/build/build-evidence.d.ts +2 -1
  92. package/dist/packages/runtime/build/build-evidence.js +11 -5
  93. package/dist/packages/runtime/build/build-pipeline.js +89 -5
  94. package/dist/packages/runtime/build/build-stage-plan.js +3 -1
  95. package/dist/packages/runtime/build/build-stage-runner.js +169 -32
  96. package/dist/packages/runtime/build/build-target.d.ts +3 -0
  97. package/dist/packages/runtime/build/build-target.js +25 -1
  98. package/dist/packages/runtime/build/check-evaluator.d.ts +1 -1
  99. package/dist/packages/runtime/build/check-evaluator.js +655 -4
  100. package/dist/packages/runtime/build/context-graph-paths.d.ts +13 -0
  101. package/dist/packages/runtime/build/context-graph-paths.js +27 -0
  102. package/dist/packages/runtime/build/index.d.ts +2 -2
  103. package/dist/packages/runtime/build/index.js +2 -2
  104. package/dist/packages/runtime/build/inspect-map.d.ts +10 -0
  105. package/dist/packages/runtime/build/inspect-map.js +270 -0
  106. package/dist/packages/runtime/build/lib/schema.d.ts +246 -53
  107. package/dist/packages/runtime/build/lib/schema.js +173 -15
  108. package/dist/packages/runtime/build/native-entrypoint.d.ts +2 -0
  109. package/dist/packages/runtime/build/native-entrypoint.js +286 -0
  110. package/dist/packages/runtime/build/runtime-contracts.js +9 -3
  111. package/dist/packages/runtime/build/runtime-log-paths.d.ts +3 -0
  112. package/dist/packages/runtime/build/runtime-log-paths.js +16 -0
  113. package/dist/packages/runtime/build/runtime-prompt.js +6 -4
  114. package/dist/packages/runtime/build/runtime-runs.js +63 -10
  115. package/dist/packages/runtime/build/runtime-types.d.ts +4 -1
  116. package/dist/packages/runtime/build/runtime.d.ts +3 -1
  117. package/dist/packages/runtime/build/runtime.js +3 -1
  118. package/dist/packages/runtime/build/source-files.js +11 -2
  119. package/dist/packages/runtime/build/source-inventory.d.ts +1 -0
  120. package/dist/packages/runtime/build/source-inventory.js +246 -7
  121. package/dist/packages/runtime/build/source-manifest.d.ts +11 -0
  122. package/dist/packages/runtime/build/source-manifest.js +30 -2
  123. package/dist/packages/runtime/build/stage-evidence.js +80 -11
  124. package/dist/packages/runtime/build/stage-manifest.d.ts +45 -0
  125. package/dist/packages/runtime/build/stage-manifest.js +1125 -0
  126. package/dist/packages/runtime/build/stage-reuse.js +12 -0
  127. package/dist/packages/runtime/build/stage-session.d.ts +81 -0
  128. package/dist/packages/runtime/build/stage-session.js +308 -0
  129. package/dist/packages/runtime/build/state-io.js +10 -11
  130. package/dist/packages/runtime/build/state-view.js +1 -1
  131. package/dist/packages/runtime/build/state.d.ts +1 -1
  132. package/dist/packages/runtime/build/state.js +1 -1
  133. package/dist/packages/runtime/build/summary-coverage-index.d.ts +21 -0
  134. package/dist/packages/runtime/build/summary-coverage-index.js +189 -0
  135. package/dist/packages/runtime/build/traces.js +3 -3
  136. package/dist/packages/runtime/build/validate-context-graph.d.ts +1 -1
  137. package/dist/packages/runtime/build/validate-context-graph.js +5 -5
  138. package/dist/packages/runtime/build/validate.d.ts +1 -1
  139. package/dist/packages/runtime/build/validate.js +1 -1
  140. package/dist/packages/runtime/client.d.ts +3 -3
  141. package/dist/packages/runtime/client.js +8 -13
  142. package/dist/packages/runtime/context-checks.js +13 -0
  143. package/dist/packages/runtime/context-graph-scaffold.js +2 -1
  144. package/dist/packages/runtime/context-graph-semantic-graph.d.ts +9 -0
  145. package/dist/packages/runtime/context-graph-semantic-graph.js +416 -0
  146. package/dist/packages/runtime/execution/lib/schema.d.ts +34 -31
  147. package/dist/packages/runtime/index.d.ts +2 -2
  148. package/dist/packages/runtime/index.js +1 -1
  149. package/dist/packages/runtime/native-run-handlers.d.ts +38 -0
  150. package/dist/packages/runtime/native-run-handlers.js +52 -33
  151. package/dist/packages/runtime/plan-artifact-contract.js +1 -1
  152. package/dist/packages/runtime/project-source-state.d.ts +4 -4
  153. package/dist/packages/runtime/project-source-state.js +5 -2
  154. package/dist/packages/runtime/project-store.d.ts +5 -0
  155. package/dist/packages/runtime/project-store.js +30 -3
  156. package/dist/packages/runtime/requested-artifacts.js +1 -1
  157. package/dist/packages/runtime/run-observability.js +9 -4
  158. package/dist/packages/runtime/runtime-action-proposals.js +3 -3
  159. package/dist/packages/runtime/runtime-build-plans.js +47 -3
  160. package/dist/packages/runtime/runtime-build-runs.js +9 -16
  161. package/dist/packages/runtime/runtime-caches.d.ts +26 -0
  162. package/dist/packages/runtime/runtime-caches.js +47 -0
  163. package/dist/packages/runtime/runtime-jobs.js +6 -6
  164. package/dist/packages/runtime/runtime-project-mutations.js +1 -0
  165. package/dist/packages/runtime/runtime-project-reads.d.ts +4 -1
  166. package/dist/packages/runtime/runtime-project-reads.js +229 -36
  167. package/dist/packages/runtime/runtime-proposal-helpers.js +6 -6
  168. package/dist/packages/runtime/runtime-resource-builders.d.ts +4 -2
  169. package/dist/packages/runtime/runtime-resource-builders.js +16 -14
  170. package/dist/packages/runtime/runtime-status.d.ts +14 -0
  171. package/dist/packages/runtime/runtime-status.js +15 -0
  172. package/dist/packages/runtime/runtime-verify-runs.js +6 -5
  173. package/dist/packages/runtime/runtime.d.ts +439 -22
  174. package/dist/packages/runtime/runtime.js +16 -2
  175. package/dist/packages/runtime/schemas/actions.d.ts +24 -0
  176. package/dist/packages/runtime/schemas/agents.d.ts +28 -0
  177. package/dist/packages/runtime/schemas/agents.js +33 -0
  178. package/dist/packages/runtime/schemas/build-plans.d.ts +181 -8
  179. package/dist/packages/runtime/schemas/build-plans.js +36 -2
  180. package/dist/packages/runtime/schemas/context-graphs.d.ts +1522 -0
  181. package/dist/packages/runtime/schemas/context-graphs.js +110 -0
  182. package/dist/packages/runtime/schemas/files.d.ts +7 -347
  183. package/dist/packages/runtime/schemas/files.js +1 -24
  184. package/dist/packages/runtime/schemas/index.d.ts +1 -0
  185. package/dist/packages/runtime/schemas/index.js +1 -0
  186. package/dist/packages/runtime/schemas/jobs.js +4 -0
  187. package/dist/packages/runtime/schemas/projects.d.ts +48 -21
  188. package/dist/packages/runtime/schemas/projects.js +34 -10
  189. package/dist/packages/runtime/schemas/runs.d.ts +1009 -240
  190. package/dist/packages/runtime/schemas/runs.js +17 -0
  191. package/dist/packages/runtime/service/openapi.js +1 -0
  192. package/dist/packages/runtime/service/operations.d.ts +1666 -145
  193. package/dist/packages/runtime/service/operations.js +147 -17
  194. package/dist/packages/runtime/service/routes.d.ts +11 -3
  195. package/dist/packages/runtime/service/routes.js +11 -3
  196. package/dist/packages/runtime/service/server-app-boot.js +2 -2
  197. package/dist/packages/runtime/service/server-helpers.d.ts +11 -0
  198. package/dist/packages/runtime/service/server-helpers.js +19 -0
  199. package/dist/packages/runtime/service/server-routes-action-proposals.js +4 -2
  200. package/dist/packages/runtime/service/server-routes-agents.js +19 -85
  201. package/dist/packages/runtime/service/server-routes-build-plans.js +14 -11
  202. package/dist/packages/runtime/service/server-routes-project-context.js +102 -7
  203. package/dist/packages/runtime/service/server-routes-project-jobs.js +19 -12
  204. package/dist/packages/runtime/service/server-routes-project-runs.js +5 -2
  205. package/dist/packages/runtime/service/server-routes-projects.js +6 -2
  206. package/dist/packages/runtime/service/server-routes-runs.js +11 -4
  207. package/dist/packages/runtime/verify/lib/schema.js +12 -0
  208. package/dist/packages/runtime/verify/test-file-guard.d.ts +2 -0
  209. package/dist/packages/runtime/verify/test-file-guard.js +29 -0
  210. package/dist/packages/runtime/verify/verify-execution.d.ts +7 -0
  211. package/dist/packages/runtime/verify/verify-execution.js +109 -35
  212. package/dist/packages/runtime/verify/verify-paths.d.ts +1 -0
  213. package/dist/packages/runtime/verify/verify-paths.js +4 -0
  214. package/dist/packages/runtime/verify/verify-specs.js +49 -39
  215. package/dist/packages/runtime/wire-schemas.d.ts +1 -1
  216. package/dist/packages/runtime/wire-schemas.js +1 -1
  217. package/package.json +2 -8
  218. package/public-repo/CONTRIBUTING.md +10 -3
  219. package/public-repo/README.md +122 -226
  220. package/public-repo/build-plans/interf-default/README.md +15 -12
  221. package/public-repo/build-plans/interf-default/build/stages/entrypoint/SKILL.md +74 -0
  222. package/public-repo/build-plans/interf-default/build/stages/knowledge/SKILL.md +95 -0
  223. package/public-repo/build-plans/interf-default/build/stages/summarize/SKILL.md +38 -5
  224. package/public-repo/build-plans/interf-default/build-plan.json +27 -23
  225. package/public-repo/build-plans/interf-default/build-plan.schema.json +24 -20
  226. package/public-repo/build-plans/interf-default/use/query/SKILL.md +8 -7
  227. package/public-repo/openapi/local-service.openapi.json +11637 -4213
  228. package/public-repo/skills/interf/SKILL.md +174 -134
  229. package/dist/packages/runtime/build/runtime-paths.d.ts +0 -8
  230. package/dist/packages/runtime/build/runtime-paths.js +0 -26
  231. package/dist/packages/runtime/build/state-paths.d.ts +0 -7
  232. package/dist/packages/runtime/build/state-paths.js +0 -22
  233. package/public-repo/build-plans/interf-default/build/stages/shape/SKILL.md +0 -34
  234. package/public-repo/build-plans/interf-default/build/stages/structure/SKILL.md +0 -28
@@ -1,10 +1,13 @@
1
1
  import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { createBuildPlanAuthoringShell, } from "../../runtime/agents/lib/shells.js";
3
+ import { CANONICAL_LAYER_DIRS, isCanonicalDeclarationPath, layerForPath, normalizeLayerPath, } from "../../contracts/lib/context-graph-layer.js";
4
+ import { createBuildPlanAuthoringShell, freezeBuildPlanAuthoringShell, } from "../../runtime/agents/lib/shells.js";
5
+ import { asProjectDataDir, projectServiceJobShellsRoot, } from "../../contracts/lib/project-paths.js";
6
+ import { scanSourceContext } from "../../runtime/agents/lib/source-context-scan.js";
4
7
  import { createScratchLocalBuildPlanPackage } from "../package/interf-build-plan-package.js";
5
8
  import { CONTEXT_INTERFACE_FILE, listContextInterfaceArtifacts } from "../package/context-interface.js";
6
9
  import { loadBuildPlanDefinitionFromDir, validateBuildPlanPackage, } from "../package/local-build-plans.js";
7
- import { runBuildPlanEditSession } from "./build-plan-edit-session.js";
10
+ import { runBuildPlanEditSession, SHARED_BUILD_PLAN_EDIT_RULES, } from "./build-plan-edit-session.js";
8
11
  /**
9
12
  * Walk build-plan.json after a successful authoring run and fill in
10
13
  * `role: "general"` for any stage missing a role field. Custom role
@@ -61,50 +64,60 @@ function buildBuildPlanAuthoringPrompt() {
61
64
  return [
62
65
  "This is an automated Interf Build Plan authoring run, not an open-ended chat session.",
63
66
  "Execute it now.",
64
- "Create one standalone Build Plan package for the Source data, requested Artifacts, Context Checks, and evidence requirements behind the Source locator.",
67
+ "Create one standalone Build Plan package for the Source data, Project intent, requested outputs, coverage metrics, and evidence requirements behind the Source locator.",
65
68
  "Read `runtime/authoring-context.json` first.",
66
- "Treat `intent`, `requested_artifacts`, `checks`, `user_prep_instructions`, and `source_context` as the user's agent task and evidence brief.",
69
+ "Treat `intent`, `requested_artifacts` (legacy field for requested outputs and graph entrypoints), `checks` (diagnostic context only), `user_prep_instructions`, and `source_context` as the user's agent task and coverage brief.",
67
70
  `Then read \`build-plan/README.md\`, \`build-plan/build-plan.json\`, and \`build-plan/${CONTEXT_INTERFACE_FILE}\`.`,
68
71
  "The `build-plan/` directory starts as a neutral Build Plan scaffold, not as a suggested final plan.",
69
- "Replace the scaffold purpose, inputs, Artifacts, stages, stage docs, checks, and query guidance with the Build Plan this agent task actually needs.",
70
- "Read `runtime/source-locator.json` and inspect the Source through your own available tools before editing the package.",
72
+ "Replace the scaffold purpose, inputs, outputs, stages, stage docs, coverage expectations, and query guidance with the Build Plan this agent task actually needs.",
73
+ "Read `runtime/source-locator.json`, then use the `source_context` inventory in `runtime/authoring-context.json` as your primary picture of what is actually in this Source: its `summary`, the `items[]` (each with `name`, `path`, `kind`, optional `page_count`, and `note`), and the listed `observations` and `limitations`. Open specific Source files through your own available tools only to confirm or fill gaps the inventory leaves open before editing the package.",
74
+ "Derive the stage shape from that inventory. Choose stage ids and stage labels that NAME what is genuinely present in this Source — its folder structure, the dominant file kinds, the natural groupings, and any date or period ranges the inventory reveals. Do NOT emit a generic cover/build/verify/assemble (or extract/summarize/structure/verify) skeleton unless the inventory shows the Source is genuinely unstructured and offers no better organizing principle. There is no fixed stage taxonomy: reason from this Source's inventory, not from a template.",
71
75
  "Prefer direct file-reading and search tools over shell commands for routine file inspection.",
72
76
  "Do not use shell helpers like `cat`, `sed`, `ls`, or `find` when a native read/search tool can inspect the same files.",
73
77
  "Edit only files under `build-plan/`.",
74
78
  "Keep the Build Plan valid for the current local-engine API and `build-plan.schema.json`.",
75
- "Choose stage ids, stage order, Artifact reads, Artifact writes, Artifact diagnostics, and stage docs from the source data, agent task, and evidence brief.",
76
- "Stage `reads` may only contain requested Artifact ids that are declared in `build-plan.schema.json` and produced by an earlier stage. Source files are implicit input through runtime stage assignment; never put `source`, source file paths, or source-folder ids in stage `reads`.",
77
- "Use kebab-case ids everywhere: stage ids, skill_dir values, Artifact ids, reads/writes values, and package ids. Never use underscores.",
79
+ "Do not edit the root `brief`; the service restores it from Project intent, requested outputs, diagnostic checks, and Source context after every edit attempt.",
80
+ "Do not add ad-hoc top-level keys to `build-plan/build-plan.json`; only use keys already present in the scaffold or documented by the local Build Plan schema.",
81
+ "If you want to express user-facing promises, use the Build Plan's existing root `brief` field, `purpose.label`, `purpose.task_hint`, and requested output contract. Never add `purpose.brief` or any other ad-hoc nested promise field.",
82
+ "Choose stage ids, stage order, requested output reads/writes, StageManifest coverage expectations, and stage docs from the `source_context` inventory, the agent task, and the coverage brief. The stage shape must reflect this Source's real structure and content, not a reused skeleton.",
83
+ "Stage `reads` may only contain requested output ids that are declared in `build-plan.schema.json` and produced by an earlier stage. Source files are implicit input through runtime stage assignment; never put `source`, source file paths, or source-folder ids in stage `reads`.",
84
+ // Stable kebab-case-id rule (shared verbatim with the repair prompt).
85
+ SHARED_BUILD_PLAN_EDIT_RULES[0],
78
86
  "Each stage MAY declare a `role`: one of `extractor`, `summarizer`, `structurer`, `verifier`, or `general`. Pick the role that best matches what the stage's prompt asks the agent to do. Omit `role` (or use `general`) when the stage doesn't fit any specialized role. Custom role names are allowed; the engine treats unknown roles as `general`.",
79
87
  "Do not preserve the placeholder `prepare` stage unless the final Build Plan genuinely needs a stage with that exact role.",
80
- "Treat `checks` as user-facing Context Checks: plain-English promises under `What Interf will prove`.",
81
- "Create Context Checks first, then requested Artifacts that back those checks, then deterministic Artifact diagnostics (`checks[]`) that validate the produced files.",
82
- "For file-based Projects, every final Build Plan must preserve the three-layer Context Graph shape: `summaries/` for source coverage, `knowledge/` for task-aware claims/entities/topics/indexes, and `artifacts/` for the task-specific agent handoff.",
88
+ "Treat `checks` as optional context, not the primary product surface.",
89
+ "Design coverage first: expected inputs, reviewed inputs, produced outputs, entrypoints, and missing/blocked/not-relevant coverage decisions that can roll into a GraphManifest.",
90
+ "For file-based Projects, every final Build Plan must preserve the Context Graph shape: `summaries/` for source coverage, `knowledge/` for task-aware claims/entities/topics/indexes, `home.md` as primary entrypoint, and `artifacts/` for supplemental task handoffs.",
83
91
  "`summaries/` is the coverage layer. It should prove what source files were inspected. For PDFs, decks, or paginated reports, prefer page-level notes or explicit page coverage records when practical; each summary must link back to the original Source path, page, section, figure, table, or other source reference instead of copying the Source file.",
84
- "`knowledge/` is the graph layer. The current default contract is flat `knowledge/*.md` notes. It should be built from summaries and source references, and may contain claims, entities, topics, and indexes. Treat knowledge notes as navigation and assurance, not as the authority for exact wording, table values, chart reads, or provenance-sensitive claims.",
85
- "`artifacts/` is the agent handoff layer. Requested Artifacts should live under `artifacts/` by default and should tell the downstream agent which original Source files, pages, figures, tables, sections, units, periods, series, and caveats matter for the task.",
86
- "Requested Artifacts should normally route the downstream agent to the right original Source evidence. They should not present generated summaries or knowledge notes as the dataset or source of truth.",
87
- "For tasks that depend on charts, tables, images, or other visual evidence, requested Artifacts must capture the source semantics needed to answer: target period, unit, series/legend, axis or table interpretation, exact printed values when available, bounded ranges when not, evidence tier, and source page.",
88
- "For unlabeled charts, screenshots, diagrams, or visual marks, do not write a final numeric answer unless the Artifact also captures a source-grounded measurement method, visual reference, axis/table calibration, and uncertainty. Otherwise the Artifact must route the downstream agent to inspect the original Source before answering.",
89
- "Do not let structural diagnostics such as file_exists, frontmatter_required_keys, or must_contain stand in for evidence fidelity. Use stage docs and Artifact descriptions to require the semantic fields the agent task needs.",
90
- "If a visual source has no printed data labels, the Artifact should preserve a bounded read at source granularity and should mark unresolved semantics instead of inventing pseudo-exact precision.",
91
- "Verification stages must not pass by comparing generated notes only to other generated notes. If a verifier did not inspect the relevant original Source reference, page-level summary with source refs, table extraction, visual trace, or measurement Artifact required by the claim, it must report the claim as not verified.",
92
- "Preserve `backed_by_artifact_ids` in the Build Plan brief and use the matching requested Artifact ids in `build-plan.schema.json`.",
92
+ "`knowledge/` is the graph layer. The current default contract is flat `knowledge/*.md` notes, and may contain claims, entities, topics, and indexes. Treat knowledge notes as navigation and assurance, not as the authority for exact wording, table values, chart reads, or provenance-sensitive claims.",
93
+ "Knowledge notes MUST connect to the coverage layer, not orphan it. This is a hard requirement: for every summary whose evidence a knowledge/entity note draws on, that note MUST include at least one `[[summaries/<source-name>/summary]]` wikilink to that summary, IN ADDITION to its source_refs. The wikilink to the summary and the source_ref to the original are both required; one does not substitute for the other. A knowledge note that cites a source through source_refs without also linking that source's summary is INCOMPLETE and must not be treated as done. Encode this mandate in the knowledge stage's SKILL.md and in the StageManifest coverage so it is enforced, not optional — never describe it as preferred, optional, or a choice between summary links and plain source refs.",
94
+ "Make this mandate machine-enforced, not just prose: on the requested output for the knowledge/graph layer (the directory output that holds the `knowledge/*.md` notes), declare a required `summary_backlinks_present` deterministic check in `build-plan.schema.json`. Use the default params (summaries directory, `summary.md`, `manifest.md`) unless this Build Plan renames those, in which case pass `summaries_dir`, `summary_file`, and `manifest_file` to match. This is the only deterministic check that proves no cited summary is orphaned; every file-based Build Plan that produces a `knowledge/` layer must include it.",
95
+ "Enforce the WHOLE-GRAPH connectivity floor, not only the knowledge layer: `summary_backlinks_present` and `knowledge_web_connectivity` only police summaries a knowledge note CITES and the knowledge web itself — a summary no note cites or links would still ship as a free-floating island and pass readiness. To close that, declare a required `graph_notes_connected` deterministic check on the graph spine output (the `home.md` requested output, or another always-present output) in `build-plan.schema.json`. It scans the WHOLE Context Graph root by default and fails readiness if any note — including any uncited summary — is link-disconnected from every other note. Every file-based Build Plan that produces a Context Graph must include it. Its job is reachability, not a quota: an inbound OR outbound link satisfies a note. Make sure the entrypoint/coverage routing actually links every summary (directly or through a coverage index) so this floor can pass honestly.",
96
+ "`home.md` is the primary agent entrypoint. Supplemental graph entrypoints may live under `artifacts/` when they help the downstream agent and should tell it which original Source files, pages, figures, tables, sections, units, periods, series, and caveats matter for the task.",
97
+ "Requested outputs should normally route the downstream agent to the right original Source evidence. They should not present generated summaries or knowledge notes as the dataset or source of truth.",
98
+ "For tasks that depend on charts, tables, images, or other visual evidence, requested outputs must capture the source semantics needed to answer: target period, unit, series/legend, axis or table interpretation, exact printed values when available, bounded ranges when not, evidence tier, and source page.",
99
+ "For unlabeled charts, screenshots, diagrams, or visual marks, do not write a final numeric answer unless the requested output also captures a source-grounded measurement method, visual reference, axis/table calibration, and uncertainty. Otherwise the output must route the downstream agent to inspect the original Source before answering.",
100
+ "Do not let structural diagnostics such as file_exists, frontmatter_required_keys, or must_contain stand in for coverage or evidence fidelity. Use stage docs, StageManifest coverage, and requested output descriptions to require the semantic fields the agent task needs.",
101
+ "If a visual source has no printed data labels, the requested output should preserve a bounded read at source granularity and should mark unresolved semantics instead of inventing pseudo-exact precision.",
102
+ "Verification stages must not pass by comparing generated notes only to other generated notes. If a verifier did not inspect the relevant original Source reference, page-level summary with source refs, table extraction, visual trace, or measurement output required by the claim, it must report the claim as not verified.",
103
+ "Preserve `backed_by_artifact_ids` in the root Build Plan `brief` when present and use matching requested output ids in `build-plan.schema.json`.",
93
104
  "When `runtime/authoring-context.json.artifact_requirements[]` is non-empty, it is binding: copy each requirement's `id`, `shape`, and `checks[]` exactly into `build-plan.schema.json`.",
94
- "Do not rename, move, merge, split, or reinterpret required Artifacts. `stage_hint` can guide which stage writes the Artifact, but it never changes the required `shape.path`, `shape.artifact_kind`, or diagnostic contract.",
95
- "Put deterministic validation on Artifact `checks[]`; do not add stage `acceptance` blocks.",
105
+ "Do not rename, move, merge, split, or reinterpret required requested outputs. `stage_hint` can guide which stage writes the output, but it never changes the required `shape.path`, `shape.artifact_kind`, or diagnostic contract.",
106
+ "Keep deterministic validation on requested output `checks[]` minimal; do not use diagnostics as the primary proof surface.",
96
107
  "Use only the CheckKind values listed in `runtime/authoring-context.json`; do not invent aliases.",
97
- "Use `runtime/authoring-context.json.check_param_contracts` as the exact parameter contract for each check kind.",
98
- "Every Artifact diagnostic must include a human-readable `description` that names the user's assertion; keep `kind` and `params` as the machine evaluator and never as user copy.",
99
- "Keep Artifact diagnostics aligned with produced requested Artifacts. Use stage docs to describe the Artifact contract, but do not turn source-specific user wording into brittle stage-doc wording requirements.",
108
+ // Stable check-param-contract + diagnostic-description rules (shared verbatim
109
+ // with the repair prompt).
110
+ SHARED_BUILD_PLAN_EDIT_RULES[1],
111
+ SHARED_BUILD_PLAN_EDIT_RULES[2],
112
+ "Keep diagnostics aligned with produced requested outputs. Use stage docs and StageManifest coverage to describe the output contract, but do not turn source-specific user wording into brittle stage-doc wording requirements.",
100
113
  "For every stage in `build-plan.json`, create `build-plan/build/stages/<skill_dir>/SKILL.md` and make the folder name exactly match that stage's kebab-case `skill_dir`.",
101
- "Every Artifact path in `build-plan.schema.json` must be unique. Child Artifacts may live under the required layer directories `summaries/`, `knowledge/`, and `artifacts/`; unrelated Artifact paths must not overlap.",
102
- "Make the package materially specific to this Project's agent task, output shape, checks, and evidence requirement. A no-op scaffold is not acceptable.",
103
- "Treat the Build Plan as four aligned layers: purpose, inputs, requested Artifact contract, and stages.",
104
- "If the Build Plan writes a graph index, declare it as an Artifact and make the final shaping stage own it.",
105
- "Prefer explicit stage-doc, stage-policy, purpose, input-contract, and requested-Artifact-contract edits over vague rewrites.",
114
+ "Every requested output path in `build-plan.schema.json` must be unique. Child outputs may live under the required layer directories `summaries/`, `knowledge/`, and `artifacts/`; unrelated output paths must not overlap.",
115
+ "Make the package materially specific to this Project's agent task, output shape, coverage, and evidence requirement. A no-op scaffold is not acceptable.",
116
+ "Treat the Build Plan as four aligned layers: purpose, inputs, requested output contract, and stages.",
117
+ "If the Build Plan writes `home.md`, declare it as a requested output and make the entrypoint stage own it.",
118
+ "Prefer explicit stage-doc, stage-policy, purpose, input-contract, and requested-output-contract edits over vague rewrites.",
106
119
  "Do not introduce wikilinks unless the Build Plan also creates the target note by exact basename or explicit relative path.",
107
- "Respect stage boundaries: a stage may only introduce links that resolve within that stage's declared writes or already-existing read Artifacts.",
120
+ "Respect stage boundaries: a stage may only introduce links that resolve within that stage's declared writes or already-existing read outputs.",
108
121
  "Do not make one stage point at files or notes that are only created later by another stage.",
109
122
  "Prefer conservative retrieval routing over speculative note sprawl.",
110
123
  "Do not narrate plans or ask follow-up questions.",
@@ -125,20 +138,35 @@ function stableJson(value) {
125
138
  .map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`)
126
139
  .join(",")}}`;
127
140
  }
141
+ /**
142
+ * Persist the authoring brief (intent, requested outputs, `source_context`,
143
+ * `artifact_requirements`) into the package's `build-plan.json` so the saved
144
+ * plan and every validation pass carry the SAME brief the shell was built
145
+ * from.
146
+ *
147
+ * Returns `true` when the brief was written, `false` when it could not be
148
+ * (missing or unparseable `build-plan.json`). The caller MUST observe the
149
+ * `false` case: previously this no-op was silent, so a brief carrying the
150
+ * Source inventory and the binding artifact requirements could be dropped
151
+ * with no signal — validation would then run against a plan that has no
152
+ * brief, and an untailored draft could persist. The drop is now reportable,
153
+ * never silent.
154
+ */
128
155
  function writeBuildPlanAuthoringBrief(buildPlanPath, brief) {
129
156
  const buildPlanJsonPath = join(buildPlanPath, "build-plan.json");
130
157
  if (!existsSync(buildPlanJsonPath))
131
- return;
158
+ return false;
132
159
  let parsed;
133
160
  try {
134
161
  parsed = JSON.parse(readFileSync(buildPlanJsonPath, "utf8"));
135
162
  }
136
163
  catch {
137
- return;
164
+ return false;
138
165
  }
139
166
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
140
- return;
167
+ return false;
141
168
  writeFileSync(buildPlanJsonPath, `${JSON.stringify({ ...parsed, brief }, null, 2)}\n`);
169
+ return true;
142
170
  }
143
171
  function checkMatchesRequirement(actual, required) {
144
172
  return actual.id === required.id &&
@@ -158,32 +186,372 @@ function validateArtifactRequirements(definition, requirements) {
158
186
  for (const requirement of requirements) {
159
187
  const artifact = artifacts.find((candidate) => candidate.id === requirement.id);
160
188
  if (!artifact) {
161
- errors.push(`Missing required Artifact \`${requirement.id}\`.`);
189
+ errors.push(`Missing required requested output \`${requirement.id}\`.`);
162
190
  continue;
163
191
  }
164
192
  if (artifact.shape.kind !== requirement.shape.kind ||
165
193
  artifact.shape.path !== requirement.shape.path ||
166
194
  artifact.shape.artifact_kind !== requirement.shape.artifact_kind) {
167
- errors.push(`Artifact \`${requirement.id}\` must declare required shape ${compactJson(requirement.shape)}; found ${compactJson(artifact.shape)}.`);
195
+ errors.push(`Requested output \`${requirement.id}\` must declare required shape ${compactJson(requirement.shape)}; found ${compactJson(artifact.shape)}.`);
168
196
  }
169
197
  for (const check of requirement.checks) {
170
198
  if (!artifact.checks.some((candidate) => checkMatchesRequirement(candidate, check))) {
171
- errors.push(`Artifact \`${requirement.id}\` must include required diagnostic ${compactJson(check)}.`);
199
+ errors.push(`Requested output \`${requirement.id}\` must include required diagnostic ${compactJson(check)}.`);
172
200
  }
173
201
  }
174
202
  const producingStages = producingStagesForArtifact(definition, requirement.id);
175
203
  if (producingStages.length === 0) {
176
- errors.push(`Artifact \`${requirement.id}\` is not written by any stage.`);
204
+ errors.push(`Requested output \`${requirement.id}\` is not written by any stage.`);
177
205
  }
178
206
  }
179
207
  return errors;
180
208
  }
181
- function validateAuthoredBuildPlanPackage(buildPlanPath, artifactRequirements = []) {
209
+ /**
210
+ * A requested output materializes the `knowledge/` graph layer when it is a
211
+ * directory output whose path is `knowledge` or lives under `knowledge/`.
212
+ * Path-only and layer-agnostic — no Build-Plan-specific naming.
213
+ */
214
+ function isKnowledgeLayerArtifact(artifact) {
215
+ if (artifact.shape.kind !== "path" || artifact.shape.artifact_kind !== "directory") {
216
+ return false;
217
+ }
218
+ // Central tolerant classifier: a directory output at `knowledge` or anything
219
+ // under `knowledge/` (any casing) is the knowledge graph layer.
220
+ return layerForPath(artifact.shape.path) === "knowledge";
221
+ }
222
+ /**
223
+ * Backlink guard. The product mandate is that no cited summary is left
224
+ * orphaned: every file-based Build Plan that produces a `knowledge/` graph
225
+ * layer must declare a required `summary_backlinks_present` deterministic
226
+ * check on the directory output holding the `knowledge/*.md` notes. That
227
+ * check is the only deterministic proof the graph layer connects back to the
228
+ * coverage layer.
229
+ *
230
+ * The authoring prompt already asks for this in prose, but prose is advisory.
231
+ * This guard makes it machine-enforced: if any `knowledge/` directory output
232
+ * exists, at least one such knowledge-layer output must carry a REQUIRED
233
+ * `summary_backlinks_present` check, or the package fails validation. Generic
234
+ * — it keys only off the layer path and the central CheckKind, never off
235
+ * intent or per-Build-Plan naming.
236
+ */
237
+ function validateSummaryBacklinkGuard(definition) {
238
+ const artifacts = definition.build_plan_schema.artifacts ?? [];
239
+ const knowledgeArtifacts = artifacts.filter(isKnowledgeLayerArtifact);
240
+ if (knowledgeArtifacts.length === 0)
241
+ return [];
242
+ const hasRequiredBacklinkCheck = knowledgeArtifacts.some((artifact) => artifact.checks.some((check) => check.kind === "summary_backlinks_present" && (check.required ?? true)));
243
+ if (hasRequiredBacklinkCheck)
244
+ return [];
245
+ const layerPaths = knowledgeArtifacts
246
+ .map((artifact) => artifact.shape.kind === "path" ? artifact.shape.path : artifact.id)
247
+ .join(", ");
248
+ return [
249
+ `Build Plan produces a \`knowledge/\` graph layer (${layerPaths}) but no knowledge-layer ` +
250
+ "requested output declares a required `summary_backlinks_present` check. Add that check so no " +
251
+ "summary cited by a knowledge note is left orphaned (the graph layer must connect to the coverage layer).",
252
+ ];
253
+ }
254
+ /**
255
+ * Whole-graph connectivity floor guard. The product promise is a "deeply
256
+ * connected web": EVERY note across the Context Graph — including every summary,
257
+ * not only the ones a knowledge note happened to cite — must be reachable
258
+ * through the link web. The `summary_backlinks_present` guard above only covers
259
+ * CITED summaries, and `knowledge_web_connectivity` only the `knowledge/` layer;
260
+ * an uncited summary would still ship as a free-floating island and pass
261
+ * readiness silently. The `graph_notes_connected` check closes that floor by
262
+ * scanning the whole graph root.
263
+ *
264
+ * This makes the floor machine-enforced rather than advisory: a Build Plan that
265
+ * produces a `knowledge/` graph layer (the same trigger condition the backlink
266
+ * guard uses — a file-based Context Graph) must declare at least one REQUIRED
267
+ * `graph_notes_connected` check somewhere in its requested outputs, or the
268
+ * package fails validation. Generic — keys only off the layer path and the
269
+ * central CheckKind, never off intent or per-Build-Plan naming.
270
+ */
271
+ function validateGraphConnectivityFloorGuard(definition) {
272
+ const artifacts = definition.build_plan_schema.artifacts ?? [];
273
+ // Same trigger as the backlink guard: a file-based Context Graph that
274
+ // materializes a `knowledge/` layer. A plan with no knowledge graph layer is
275
+ // not the connected-web product and is exempt.
276
+ const knowledgeArtifacts = artifacts.filter(isKnowledgeLayerArtifact);
277
+ if (knowledgeArtifacts.length === 0)
278
+ return [];
279
+ const hasRequiredFloorCheck = artifacts.some((artifact) => artifact.checks.some((check) => check.kind === "graph_notes_connected" && (check.required ?? true)));
280
+ if (hasRequiredFloorCheck)
281
+ return [];
282
+ return [
283
+ "Build Plan produces a Context Graph but no requested output declares a required " +
284
+ "`graph_notes_connected` check. Add that check (typically on the `home.md` spine output) so the " +
285
+ "WHOLE graph — every summary included, not only cited ones — is link-connected and no note ships " +
286
+ "as a free-floating island that passes readiness silently.",
287
+ ];
288
+ }
289
+ /**
290
+ * Canonical top-level-layer guard (FIXED SKELETON / FLEXIBLE INTERIOR). A Build
291
+ * Plan's core derived content must live under the three canonical top-level layers
292
+ * — `summaries/`, `knowledge/`, `artifacts/` — or the `home.md` spine. A plan may
293
+ * NOT invent a new top-level folder (e.g. `graph/`, `insights/`) for core content;
294
+ * graph-shaped content goes under `knowledge/<your-interior>/`, whose interior is
295
+ * free (`entities/`, `claims/`, `timelines/`, `tables/`, `atlas/`, anything).
296
+ *
297
+ * Authoring is STRICT on case: a layer must be DECLARED in exact lowercase. A
298
+ * mixed-case declaration like `Knowledge/` is REJECTED here, because package
299
+ * validation and the runtime scanners are lowercase-path based — a mixed-case
300
+ * declaration would pass a tolerant authoring check and then silently break
301
+ * downstream (`collectMarkdownFiles(graph, "knowledge")` finds nothing under
302
+ * `Knowledge/`). The tolerant, case-insensitive classifier (`layerForPath`)
303
+ * exists only to POLICE already-built outputs, never to bless a bad
304
+ * declaration. Both faces come from the one central layer model:
305
+ * `isCanonicalDeclarationPath` is the strict authoring face;
306
+ * `layerForPath`/`roleForPath` is the tolerant policing face.
307
+ *
308
+ * Keys ONLY off each path artifact's FIRST path segment and the central
309
+ * canonical set — no task taxonomy, no per-plan naming, no hardcoded interior.
310
+ * Only the first segment is checked, so the entire interior of `knowledge/` is
311
+ * allowed. Non-path artifacts declare no folder and are skipped.
312
+ */
313
+ function validateCanonicalTopLevelLayers(definition) {
314
+ const errors = [];
315
+ for (const artifact of definition.build_plan_schema.artifacts ?? []) {
316
+ if (artifact.shape.kind !== "path")
317
+ continue;
318
+ const normalized = normalizeLayerPath(artifact.shape.path);
319
+ if (normalized.length === 0)
320
+ continue;
321
+ // Strict: exact-lowercase canonical declaration (`knowledge/…` or the exact
322
+ // `home.md` spine). `Knowledge/` deliberately does NOT pass.
323
+ if (isCanonicalDeclarationPath(normalized))
324
+ continue;
325
+ const segment = normalized.split("/")[0] ?? "";
326
+ // A path that the tolerant classifier WOULD route into a canonical layer but
327
+ // that is not an exact lowercase declaration is a case-drift declaration
328
+ // (e.g. `Knowledge/`). Name the canonical lowercase form it must use.
329
+ const classified = layerForPath(normalized);
330
+ const detail = classified !== "other"
331
+ ? `Declare it in exact lowercase as \`${classified === "home" ? "home.md" : `${classified}/`}\`; ` +
332
+ "canonical layers must be declared lowercase so package validation and the runtime scanners find it."
333
+ : "Core Context Graph content must live under the canonical layers `summaries/`, `knowledge/`, " +
334
+ `\`artifacts/\`, or \`home.md\` (interior free, e.g. \`knowledge/<your-interior>/\`). Canonical layers: ${CANONICAL_LAYER_DIRS.join(", ")}.`;
335
+ errors.push(`Build Plan declares a core requested output at top-level folder \`${segment}/\` (${artifact.id}). ${detail}`);
336
+ }
337
+ return errors;
338
+ }
339
+ /**
340
+ * Lowercase alphanumeric tokens of length >= 3 drawn from free text. Used to
341
+ * compare the plan's own wording against the actual Source inventory.
342
+ */
343
+ function tailoringTokens(text) {
344
+ const tokens = new Set();
345
+ for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {
346
+ if (raw.length >= 3)
347
+ tokens.add(raw);
348
+ }
349
+ return tokens;
350
+ }
351
+ /**
352
+ * Generic structural words the contents-free scan emits for EVERY Source
353
+ * (boilerplate about files/folders/groups and the scan's own caveats). They
354
+ * carry no Source-specific signal, so they are excluded from the tailoring
355
+ * vocabulary — otherwise a generic skeleton could "pass" by echoing scan
356
+ * boilerplate. These are not intent or task words; they are the scan's own
357
+ * structural vocabulary.
358
+ */
359
+ const TAILORING_STOP_TOKENS = new Set([
360
+ "file", "files", "folder", "folders", "root", "group", "groups", "extension",
361
+ "kind", "kinds", "the", "and", "across", "top", "level", "free", "source",
362
+ "scan", "inventory", "interf", "did", "not", "open", "read", "any", "before",
363
+ "locking", "stages", "treat", "counts", "lower", "bound", "some", "deep",
364
+ "were", "enumerated", "capped", "cost", "names", "contents", "parsed",
365
+ "period", "span", "reference", "filenames", "structure",
366
+ ]);
367
+ /**
368
+ * Distinctive vocabulary the contents-free Source scan surfaced about THIS
369
+ * Source: folder/group names, the file-kind labels actually present, and any
370
+ * date/period tokens parsed from filenames. Derived entirely from the
371
+ * inventory — no intent keywords, no task taxonomy. Generic structural words
372
+ * are stripped so the vocabulary is Source-specific, not scan-specific.
373
+ */
374
+ function sourceInventoryVocabulary(sourceContext) {
375
+ const vocabulary = new Set();
376
+ const add = (text) => {
377
+ if (!text)
378
+ return;
379
+ for (const token of tailoringTokens(text)) {
380
+ if (!TAILORING_STOP_TOKENS.has(token))
381
+ vocabulary.add(token);
382
+ }
383
+ };
384
+ // The items and observations carry the Source-specific signal (folder
385
+ // names, present file kinds, period tokens). The summary and limitations
386
+ // are mostly scan boilerplate, so they are intentionally not mined here.
387
+ // Callers may hand in a partial SourceContext (the type defaults `items`
388
+ // and `observations` to [], but raw caller objects are not re-parsed), so
389
+ // coalesce missing arrays defensively.
390
+ for (const item of sourceContext.items ?? []) {
391
+ add(item.name);
392
+ add(item.path);
393
+ add(item.kind);
394
+ add(item.note);
395
+ }
396
+ for (const observation of sourceContext.observations ?? [])
397
+ add(observation);
398
+ return vocabulary;
399
+ }
400
+ /**
401
+ * Tailoring guard. The original failure was a drafted Build Plan that was
402
+ * generic boilerplate — a cover/build/verify-style skeleton that ignored what
403
+ * was actually in the Source. The authoring prompt asks the agent to NAME
404
+ * what is present in the Source, but prose is advisory.
405
+ *
406
+ * This makes "provably tailored" machine-checked WITHOUT a task taxonomy: it
407
+ * derives a vocabulary from the contents-free Source inventory (folder/group
408
+ * names, present file kinds, period tokens) and requires the authored plan's
409
+ * own wording — stage ids/labels/descriptions and the purpose — to reference
410
+ * at least one of those Source-specific tokens. A plan that names nothing from
411
+ * the actual Source is, by construction, not tailored to it.
412
+ *
413
+ * It only runs when a real inventory is available; with no inventory there is
414
+ * nothing to tailor against, so the guard is a no-op (it never blocks on
415
+ * fabricated signal when the caller supplied no Source context).
416
+ */
417
+ function validateTailoringGuard(definition, sourceContext) {
418
+ if (!sourceContext)
419
+ return [];
420
+ const vocabulary = sourceInventoryVocabulary(sourceContext);
421
+ // No distinctive Source vocabulary to tailor against (e.g. an inventory of
422
+ // only generic structural words). Tailoring cannot be proven either way, so
423
+ // do not block.
424
+ if (vocabulary.size === 0)
425
+ return [];
426
+ const planText = [
427
+ definition.purpose?.label ?? "",
428
+ definition.purpose?.task_hint ?? "",
429
+ ...(definition.stages ?? []).flatMap((stage) => [
430
+ stage.id,
431
+ stage.label,
432
+ stage.description ?? "",
433
+ ]),
434
+ ].join(" ");
435
+ for (const token of tailoringTokens(planText)) {
436
+ if (vocabulary.has(token))
437
+ return [];
438
+ }
439
+ return [
440
+ "Authored Build Plan is not tailored to this Source: its stage ids, stage labels, descriptions, " +
441
+ "and purpose reference nothing from the Source inventory (folder names, present file kinds, or " +
442
+ "period tokens). Reshape the stages to name what is actually in this Source instead of a generic skeleton.",
443
+ ];
444
+ }
445
+ /**
446
+ * Does the Build Plan package at `buildPlanPath` name at least one
447
+ * Source-specific token from `sourceContext`? True when it is tailored to the
448
+ * Source (or when there is nothing to tailor against — see below), false only
449
+ * when a real Source vocabulary exists and the plan references none of it.
450
+ *
451
+ * Re-uses the exact draft-time tailoring guard so the two faces of "tailored"
452
+ * cannot drift: a plan is tailored iff `validateTailoringGuard` raises no
453
+ * error. A null inventory, an empty Source vocabulary, or an unloadable
454
+ * package all return `true` (nothing to tailor against / nothing to compare),
455
+ * which keeps the non-regression guard below a no-op in exactly those cases.
456
+ */
457
+ function buildPlanIsTailoredToSource(buildPlanPath, sourceContext) {
458
+ const definition = loadBuildPlanDefinitionFromDir(buildPlanPath);
459
+ if (!definition)
460
+ return true;
461
+ return validateTailoringGuard(definition, sourceContext).length === 0;
462
+ }
463
+ /**
464
+ * IMPROVE-loop tailoring NON-REGRESSION guard (the improve-path analogue of the
465
+ * draft-time tailoring guard).
466
+ *
467
+ * The draft guard (`validateTailoringGuard`) is an absolute "this plan must
468
+ * name something from the Source" check, correct for a from-scratch draft. It
469
+ * is the WRONG shape for the improvement loop: a user may select and improve a
470
+ * plan that is generic by design (the shipped `interf-default` carries no
471
+ * draft-time tailoring at all), and the absolute guard would reject every
472
+ * legitimate improvement to it the moment the Source vocabulary happened not to
473
+ * collide with the plan's generic wording. Running the absolute guard on
474
+ * improve would therefore false-reject faithful improvements, not just
475
+ * de-tailoring ones.
476
+ *
477
+ * The real C1 threat is narrower and is what this guards: an improvement edit
478
+ * must not take a plan that WAS tailored to the Source and strip that tailoring
479
+ * back to a generic skeleton. So this is a before/after DELTA check against the
480
+ * SAME scanned Source inventory: if the pre-edit plan was tailored and the
481
+ * post-edit plan is not, the edit de-tailored the plan and is rejected. A plan
482
+ * that was already generic before the edit (e.g. `interf-default`) has no
483
+ * tailoring to lose, so the guard is a no-op for it — improve is free to refine
484
+ * it without being forced to invent Source-specific vocabulary. With no
485
+ * Source inventory available (no Source binding, unreadable Source), there is
486
+ * nothing to compare and the guard is likewise a no-op.
487
+ */
488
+ export function validateImproveTailoringNonRegression(beforeBuildPlanPath, afterBuildPlanPath, sourceContext) {
489
+ if (!sourceContext)
490
+ return [];
491
+ // Only a plan that was tailored BEFORE the edit can be de-tailored by it.
492
+ if (!buildPlanIsTailoredToSource(beforeBuildPlanPath, sourceContext))
493
+ return [];
494
+ if (buildPlanIsTailoredToSource(afterBuildPlanPath, sourceContext))
495
+ return [];
496
+ return [
497
+ "Improvement edit de-tailored the Build Plan: the pre-edit plan referenced this Source " +
498
+ "(folder names, present file kinds, or period tokens) in its stage ids, labels, descriptions, " +
499
+ "or purpose, but the edited plan references none of it. An improvement may refine the plan but " +
500
+ "must not strip its Source-specific tailoring back to a generic skeleton.",
501
+ ];
502
+ }
503
+ /**
504
+ * Re-shape a base-validation result plus extra guard errors into one result.
505
+ * Shared so authoring and the improvement loop fold their guard errors in the
506
+ * exact same way (no per-call-site re-implementation of the ok/summary logic).
507
+ */
508
+ function withExtraGuardErrors(validation, extraErrors) {
509
+ if (extraErrors.length === 0)
510
+ return validation;
511
+ const errors = [...validation.errors, ...extraErrors];
512
+ return {
513
+ ok: validation.ok && errors.length === 0,
514
+ summary: errors.length === 0
515
+ ? validation.summary
516
+ : `Build Plan package has ${errors.length} issue(s).`,
517
+ errors,
518
+ counts: validation.counts,
519
+ };
520
+ }
521
+ /**
522
+ * Lifecycle guards that apply to EVERY lifecycle stage that mutates a Build
523
+ * Plan package (authoring draft AND self-improvement loop): the base package
524
+ * validation plus the product-critical structural invariants a self-improvement
525
+ * run must not be allowed to silently strip —
526
+ * `summary_backlinks_present` (no orphaned CITED summary),
527
+ * `graph_notes_connected` (the WHOLE-graph connectivity floor: no uncited-summary
528
+ * or any-note island), and the canonical-top-level-layer rule (core content stays
529
+ * under `summaries/`, `knowledge/`, `artifacts/`, or `home.md`).
530
+ *
531
+ * The authoring-only guards (scaffold-removal, artifact-requirements, tailoring)
532
+ * live in `validateAuthoredBuildPlanPackage` because they are draft-time
533
+ * concerns, not improvement concerns.
534
+ */
535
+ export function validateLifecycleBuildPlanPackage(buildPlanPath) {
182
536
  const validation = validateBuildPlanPackage(buildPlanPath);
183
537
  const definition = loadBuildPlanDefinitionFromDir(buildPlanPath);
184
538
  if (!definition)
185
539
  return validation;
186
- const errors = [...validation.errors];
540
+ return withExtraGuardErrors(validation, [
541
+ ...validateSummaryBacklinkGuard(definition),
542
+ ...validateGraphConnectivityFloorGuard(definition),
543
+ ...validateCanonicalTopLevelLayers(definition),
544
+ ]);
545
+ }
546
+ function validateAuthoredBuildPlanPackage(buildPlanPath, artifactRequirements = [], sourceContext = null) {
547
+ // Start from the shared lifecycle guards (base + backlink + canonical layers),
548
+ // then layer the authoring-only guards on top. The lifecycle guards are
549
+ // defined once and shared with the improvement loop.
550
+ const validation = validateLifecycleBuildPlanPackage(buildPlanPath);
551
+ const definition = loadBuildPlanDefinitionFromDir(buildPlanPath);
552
+ if (!definition)
553
+ return validation;
554
+ const authoringErrors = [];
187
555
  const placeholderStage = definition.stages?.find((stage) => stage.id === "prepare" &&
188
556
  stage.contract_type === "build-plan-draft-stage" &&
189
557
  stage.writes.includes("draft-context"));
@@ -191,23 +559,17 @@ function validateAuthoredBuildPlanPackage(buildPlanPath, artifactRequirements =
191
559
  const placeholderPurpose = definition.purpose?.label === "From-scratch Build Plan draft" ||
192
560
  definition.purpose?.task_hint?.includes("Replace this neutral scaffold");
193
561
  if (placeholderStage) {
194
- errors.push("Authored Build Plan still contains the neutral scaffold stage `prepare`.");
562
+ authoringErrors.push("Authored Build Plan still contains the neutral scaffold stage `prepare`.");
195
563
  }
196
564
  if (placeholderArtifact) {
197
- errors.push("Authored Build Plan still contains the neutral scaffold artifact `draft-context`.");
565
+ authoringErrors.push("Authored Build Plan still contains the neutral scaffold artifact `draft-context`.");
198
566
  }
199
567
  if (placeholderPurpose) {
200
- errors.push("Authored Build Plan still contains the neutral scaffold purpose.");
568
+ authoringErrors.push("Authored Build Plan still contains the neutral scaffold purpose.");
201
569
  }
202
- errors.push(...validateArtifactRequirements(definition, artifactRequirements));
203
- return {
204
- ok: validation.ok && errors.length === 0,
205
- summary: errors.length === 0
206
- ? validation.summary
207
- : `Build Plan package has ${errors.length} issue(s).`,
208
- errors,
209
- counts: validation.counts,
210
- };
570
+ authoringErrors.push(...validateArtifactRequirements(definition, artifactRequirements));
571
+ authoringErrors.push(...validateTailoringGuard(definition, sourceContext));
572
+ return withExtraGuardErrors(validation, authoringErrors);
211
573
  }
212
574
  export async function runBuildPlanAuthoringDraft(options) {
213
575
  const buildPlanPath = createScratchLocalBuildPlanPackage({
@@ -216,6 +578,18 @@ export async function runBuildPlanAuthoringDraft(options) {
216
578
  label: options.label,
217
579
  hint: options.hint ?? options.intent,
218
580
  });
581
+ // Resolve the Source context ONCE here so the shell's authoring-context.json
582
+ // and the persisted authoring brief carry the SAME value. Caller-supplied
583
+ // context wins (e.g. an MCP brief); otherwise run the cheap, contents-free
584
+ // Source scan so the auto path never feeds the draft a null inventory while
585
+ // the shell quietly held the real scan. This is the single source of truth
586
+ // for source_context across both surfaces.
587
+ const sourceContext = options.sourceContext ?? scanSourceContext(options.sourceFolderPath) ?? null;
588
+ // Create the authoring shell under the Project's durable service job storage
589
+ // (not /tmp) so it is preserved and inspectable like a stage shell: the
590
+ // rendered SKILL the agent received, its reasoning transcript, the build-plan
591
+ // before/after, and the validation verdict all survive for debugging.
592
+ const shellRoot = join(projectServiceJobShellsRoot(asProjectDataDir(options.projectDataDir)), `build-plan-author-${options.buildPlanId}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`);
219
593
  const shell = createBuildPlanAuthoringShell({
220
594
  buildPlanPath,
221
595
  buildPlanId: options.buildPlanId,
@@ -226,15 +600,45 @@ export async function runBuildPlanAuthoringDraft(options) {
226
600
  checks: options.checks ?? [],
227
601
  userPrepInstructions: options.userPrepInstructions,
228
602
  requestedArtifacts: options.requestedArtifacts ?? [],
229
- sourceContext: options.sourceContext ?? null,
603
+ sourceContext,
230
604
  artifactRequirements: options.artifactRequirements ?? [],
605
+ shellRoot,
231
606
  });
607
+ const authoringBrief = {
608
+ intent: options.intent,
609
+ requested_artifacts: options.requestedArtifacts ?? [],
610
+ checks: options.checks ?? [],
611
+ ...(options.userPrepInstructions ? { user_prep_instructions: options.userPrepInstructions } : {}),
612
+ source_context: sourceContext,
613
+ artifact_requirements: options.artifactRequirements ?? [],
614
+ };
232
615
  const session = await runBuildPlanEditSession({
233
616
  executor: options.executor,
234
617
  buildPlanPath,
235
618
  shell,
236
619
  prompt: buildBuildPlanAuthoringPrompt(),
237
- validate: (buildPlanPath) => validateAuthoredBuildPlanPackage(buildPlanPath, options.artifactRequirements ?? []),
620
+ validate: (buildPlanPath) => {
621
+ // Restore the brief BEFORE validating so coverage runs against the same
622
+ // source_context + artifact_requirements the shell was built from. If
623
+ // the restore can't land (missing/unparseable build-plan.json), do NOT
624
+ // silently validate a brief-less plan — fail loudly so the drop is
625
+ // visible instead of producing a plan that lost its tailoring inputs.
626
+ const briefWritten = writeBuildPlanAuthoringBrief(buildPlanPath, authoringBrief);
627
+ const result = validateAuthoredBuildPlanPackage(buildPlanPath, options.artifactRequirements ?? [], sourceContext);
628
+ if (briefWritten)
629
+ return result;
630
+ const errors = [
631
+ "Could not restore the authoring brief into build-plan.json (file missing or unparseable); " +
632
+ "refusing to accept a draft that dropped its source_context and artifact_requirements.",
633
+ ...result.errors,
634
+ ];
635
+ return {
636
+ ok: false,
637
+ summary: `Build Plan package has ${errors.length} issue(s).`,
638
+ errors,
639
+ counts: result.counts,
640
+ };
641
+ },
238
642
  maxValidationRepairAttempts: 2,
239
643
  onStatus: options.onStatus,
240
644
  });
@@ -249,15 +653,18 @@ export async function runBuildPlanAuthoringDraft(options) {
249
653
  if (touched.length > 0 && options.onStatus) {
250
654
  options.onStatus(`STATUS: filled role=general on ${touched.length} stage(s) the author didn't tag (${touched.join(", ")}).`);
251
655
  }
252
- writeBuildPlanAuthoringBrief(buildPlanPath, {
253
- intent: options.intent,
254
- requested_artifacts: options.requestedArtifacts ?? [],
255
- checks: options.checks ?? [],
256
- ...(options.userPrepInstructions ? { user_prep_instructions: options.userPrepInstructions } : {}),
257
- source_context: options.sourceContext ?? null,
258
- artifact_requirements: options.artifactRequirements ?? [],
259
- });
656
+ // Final brief restore on the persisted package. `ensureStageRoles` may
657
+ // have rewritten build-plan.json, so re-stamp the brief and surface the
658
+ // rare case where it can't land rather than shipping a brief-less plan.
659
+ if (!writeBuildPlanAuthoringBrief(buildPlanPath, authoringBrief) && options.onStatus) {
660
+ options.onStatus("ERROR: could not restore the authoring brief into the saved Build Plan (source_context/artifact_requirements dropped).");
661
+ }
260
662
  }
663
+ // Freeze the shell so the draft execution is preserved and inspectable: the
664
+ // path is unchanged (so result.shellPath stays valid), symlinks are
665
+ // materialized into real files, and a preserved-shell manifest is written.
666
+ // Best-effort — a preservation hiccup must never fail the draft itself.
667
+ freezeBuildPlanAuthoringShell(shell.rootPath);
261
668
  return {
262
669
  status: session.status,
263
670
  changed: session.changed,