@interf/compiler 0.22.2 → 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 (616) hide show
  1. package/README.md +119 -282
  2. package/dist/bin-mcp.d.ts +2 -0
  3. package/dist/bin-mcp.js +63 -0
  4. package/dist/bin-runtime.d.ts +2 -0
  5. package/dist/bin-runtime.js +111 -0
  6. package/dist/cli/commands/agents.js +4 -35
  7. package/dist/cli/commands/auth.d.ts +20 -0
  8. package/dist/cli/commands/auth.js +161 -0
  9. package/dist/cli/commands/benchmark.d.ts +9 -0
  10. package/dist/cli/commands/benchmark.js +58 -0
  11. package/dist/cli/commands/build-plan.js +107 -139
  12. package/dist/cli/commands/build.d.ts +3 -4
  13. package/dist/cli/commands/build.js +16 -45
  14. package/dist/cli/commands/doctor.js +3 -3
  15. package/dist/cli/commands/graphs.d.ts +2 -0
  16. package/dist/cli/commands/graphs.js +344 -0
  17. package/dist/cli/commands/login.js +4 -6
  18. package/dist/cli/commands/logout.js +1 -1
  19. package/dist/cli/commands/mcp.d.ts +4 -2
  20. package/dist/cli/commands/mcp.js +846 -232
  21. package/dist/cli/commands/project.d.ts +2 -0
  22. package/dist/cli/commands/project.js +176 -0
  23. package/dist/cli/commands/reset.d.ts +3 -4
  24. package/dist/cli/commands/reset.js +10 -31
  25. package/dist/cli/commands/runs.js +136 -57
  26. package/dist/cli/commands/runtime.d.ts +24 -0
  27. package/dist/cli/commands/runtime.js +373 -0
  28. package/dist/cli/commands/status.d.ts +1 -0
  29. package/dist/cli/commands/status.js +35 -45
  30. package/dist/cli/commands/traces.d.ts +2 -0
  31. package/dist/cli/commands/traces.js +97 -0
  32. package/dist/cli/commands/wizard.js +171 -178
  33. package/dist/cli/index.d.ts +7 -4
  34. package/dist/cli/index.js +13 -7
  35. package/dist/cli/lib/http-client.d.ts +39 -0
  36. package/dist/cli/lib/http-client.js +73 -0
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.js +2 -2
  39. package/dist/packages/build-plans/authoring/brief.d.ts +538 -0
  40. package/dist/packages/build-plans/authoring/brief.js +89 -0
  41. package/dist/packages/build-plans/authoring/build-plan-authoring.d.ts +52 -11
  42. package/dist/packages/build-plans/authoring/build-plan-authoring.js +493 -46
  43. package/dist/packages/build-plans/authoring/build-plan-edit-session.d.ts +10 -1
  44. package/dist/packages/build-plans/authoring/build-plan-edit-session.js +27 -4
  45. package/dist/packages/build-plans/authoring/build-plan-improvement.d.ts +9 -6
  46. package/dist/packages/build-plans/authoring/build-plan-improvement.js +97 -46
  47. package/dist/packages/build-plans/authoring/lib/build-plan-edit-utils.d.ts +1 -0
  48. package/dist/packages/build-plans/authoring/lib/build-plan-edit-utils.js +7 -7
  49. package/dist/packages/build-plans/build-plan-resolution.d.ts +1 -1
  50. package/dist/packages/build-plans/build-plan-resolution.js +3 -3
  51. package/dist/packages/build-plans/index.d.ts +1 -1
  52. package/dist/packages/build-plans/index.js +1 -1
  53. package/dist/packages/build-plans/package/build-plan-definitions.d.ts +14 -13
  54. package/dist/packages/build-plans/package/build-plan-definitions.js +45 -42
  55. package/dist/packages/build-plans/package/build-plan-helpers.d.ts +3 -2
  56. package/dist/packages/build-plans/package/build-plan-helpers.js +27 -13
  57. package/dist/packages/build-plans/package/build-plan-review-paths.d.ts +5 -5
  58. package/dist/packages/build-plans/package/build-plan-review-paths.js +15 -15
  59. package/dist/packages/build-plans/package/build-plan-stage-runner.d.ts +5 -4
  60. package/dist/packages/build-plans/package/build-plan-stage-runner.js +23 -11
  61. package/dist/packages/build-plans/package/builtin-build-plan.d.ts +7 -8
  62. package/dist/packages/build-plans/package/builtin-build-plan.js +10 -11
  63. package/dist/packages/build-plans/package/context-interface.d.ts +14 -9
  64. package/dist/packages/build-plans/package/context-interface.js +14 -33
  65. package/dist/packages/build-plans/package/interf-build-plan-package.d.ts +6 -17
  66. package/dist/packages/build-plans/package/interf-build-plan-package.js +68 -64
  67. package/dist/packages/build-plans/package/local-build-plans.d.ts +21 -14
  68. package/dist/packages/build-plans/package/local-build-plans.js +105 -55
  69. package/dist/packages/build-plans/package/user-build-plans.js +1 -1
  70. package/dist/packages/contracts/index.d.ts +5 -2
  71. package/dist/packages/contracts/index.js +3 -1
  72. package/dist/packages/contracts/lib/context-graph-layer.d.ts +161 -0
  73. package/dist/packages/contracts/lib/context-graph-layer.js +216 -0
  74. package/dist/packages/contracts/lib/project-paths.d.ts +144 -0
  75. package/dist/packages/contracts/lib/project-paths.js +220 -0
  76. package/dist/packages/contracts/lib/project-schema.d.ts +423 -0
  77. package/dist/packages/contracts/lib/project-schema.js +138 -0
  78. package/dist/packages/contracts/lib/schema.d.ts +1273 -81
  79. package/dist/packages/contracts/lib/schema.js +675 -79
  80. package/dist/packages/contracts/utils/filesystem.d.ts +1 -0
  81. package/dist/packages/contracts/utils/filesystem.js +29 -1
  82. package/dist/packages/contracts/utils/parse.js +67 -0
  83. package/dist/packages/projects/index.d.ts +6 -0
  84. package/dist/packages/{project → projects}/index.js +0 -3
  85. package/dist/packages/{project → projects}/interf-detect.d.ts +12 -12
  86. package/dist/packages/{project → projects}/interf-detect.js +56 -50
  87. package/dist/packages/projects/interf.d.ts +2 -0
  88. package/dist/packages/projects/interf.js +1 -0
  89. package/dist/packages/projects/lib/schema.d.ts +77 -0
  90. package/dist/packages/projects/lib/schema.js +91 -0
  91. package/dist/packages/projects/source-config.d.ts +53 -0
  92. package/dist/packages/projects/source-config.js +339 -0
  93. package/dist/packages/projects/source-folders.d.ts +11 -0
  94. package/dist/packages/{project → projects}/source-folders.js +26 -26
  95. package/dist/packages/{engine → runtime}/action-planner.d.ts +1 -1
  96. package/dist/packages/{engine → runtime}/action-planner.js +20 -22
  97. package/dist/packages/runtime/action-values.d.ts +1 -0
  98. package/dist/packages/runtime/action-values.js +1 -0
  99. package/dist/packages/runtime/actions/errors.d.ts +2 -0
  100. package/dist/packages/runtime/actions/errors.js +12 -0
  101. package/dist/packages/runtime/actions/fields.d.ts +86 -0
  102. package/dist/packages/runtime/actions/form-builders.d.ts +14 -0
  103. package/dist/packages/runtime/actions/form-builders.js +667 -0
  104. package/dist/packages/runtime/actions/form-validators.d.ts +8 -0
  105. package/dist/packages/runtime/actions/form-validators.js +134 -0
  106. package/dist/packages/runtime/actions/helpers.d.ts +11 -0
  107. package/dist/packages/runtime/actions/helpers.js +80 -0
  108. package/dist/packages/runtime/actions/index.d.ts +8 -0
  109. package/dist/packages/runtime/actions/index.js +11 -0
  110. package/dist/packages/runtime/actions/registry.d.ts +64 -0
  111. package/dist/packages/runtime/actions/registry.js +62 -0
  112. package/dist/packages/runtime/actions/requests.d.ts +45 -0
  113. package/dist/packages/runtime/actions/requests.js +164 -0
  114. package/dist/packages/runtime/actions/schemas.d.ts +161 -0
  115. package/dist/packages/runtime/actions/schemas.js +37 -0
  116. package/dist/packages/runtime/agent-handoff.d.ts +11 -0
  117. package/dist/packages/runtime/agent-handoff.js +102 -0
  118. package/dist/packages/{engine → runtime}/agents/index.d.ts +1 -2
  119. package/dist/packages/{engine → runtime}/agents/index.js +1 -2
  120. package/dist/packages/runtime/agents/lib/args.d.ts +14 -0
  121. package/dist/packages/runtime/agents/lib/args.js +24 -0
  122. package/dist/packages/{engine → runtime}/agents/lib/constants.d.ts +4 -1
  123. package/dist/packages/runtime/agents/lib/constants.js +13 -0
  124. package/dist/packages/runtime/agents/lib/context-graph-bootstrap.d.ts +3 -0
  125. package/dist/packages/{engine/agents/lib/verifiable-context-bootstrap.js → runtime/agents/lib/context-graph-bootstrap.js} +5 -6
  126. package/dist/packages/{engine → runtime}/agents/lib/detection.d.ts +5 -0
  127. package/dist/packages/{engine → runtime}/agents/lib/detection.js +16 -7
  128. package/dist/packages/{engine → runtime}/agents/lib/execution-profile.d.ts +14 -0
  129. package/dist/packages/{engine → runtime}/agents/lib/execution-profile.js +31 -14
  130. package/dist/packages/{engine → runtime}/agents/lib/execution.js +22 -6
  131. package/dist/packages/{engine → runtime}/agents/lib/executors.d.ts +1 -0
  132. package/dist/packages/{engine → runtime}/agents/lib/executors.js +11 -2
  133. package/dist/packages/runtime/agents/lib/logs.d.ts +12 -0
  134. package/dist/packages/runtime/agents/lib/logs.js +41 -0
  135. package/dist/packages/{engine → runtime}/agents/lib/preflight.js +19 -14
  136. package/dist/packages/runtime/agents/lib/render.d.ts +26 -0
  137. package/dist/packages/{engine → runtime}/agents/lib/render.js +48 -22
  138. package/dist/packages/runtime/agents/lib/shell-fs.d.ts +18 -0
  139. package/dist/packages/runtime/agents/lib/shell-fs.js +190 -0
  140. package/dist/packages/runtime/agents/lib/shell-paths.d.ts +16 -0
  141. package/dist/packages/runtime/agents/lib/shell-paths.js +63 -0
  142. package/dist/packages/runtime/agents/lib/shell-projection.d.ts +25 -0
  143. package/dist/packages/runtime/agents/lib/shell-projection.js +314 -0
  144. package/dist/packages/runtime/agents/lib/shell-templates.d.ts +30 -0
  145. package/dist/packages/runtime/agents/lib/shell-templates.js +494 -0
  146. package/dist/packages/runtime/agents/lib/shell-workspace.d.ts +17 -0
  147. package/dist/packages/runtime/agents/lib/shell-workspace.js +70 -0
  148. package/dist/packages/runtime/agents/lib/shells.d.ts +92 -0
  149. package/dist/packages/runtime/agents/lib/shells.js +509 -0
  150. package/dist/packages/runtime/agents/lib/source-context-scan.d.ts +10 -0
  151. package/dist/packages/runtime/agents/lib/source-context-scan.js +388 -0
  152. package/dist/packages/{engine → runtime}/agents/lib/status.js +1 -14
  153. package/dist/packages/runtime/agents/lib/string-utils.d.ts +16 -0
  154. package/dist/packages/runtime/agents/lib/string-utils.js +36 -0
  155. package/dist/packages/{engine → runtime}/agents/lib/types.d.ts +1 -0
  156. package/dist/packages/{engine → runtime}/agents/lib/user-config.d.ts +8 -2
  157. package/dist/packages/{engine → runtime}/agents/lib/user-config.js +8 -2
  158. package/dist/packages/runtime/agents/providers/claude-code.d.ts +13 -0
  159. package/dist/packages/runtime/agents/providers/claude-code.js +45 -0
  160. package/dist/packages/runtime/agents/providers/codex.d.ts +17 -0
  161. package/dist/packages/runtime/agents/providers/codex.js +66 -0
  162. package/dist/packages/runtime/agents/providers/cursor.d.ts +9 -0
  163. package/dist/packages/runtime/agents/providers/cursor.js +24 -0
  164. package/dist/packages/runtime/agents/providers/index.d.ts +9 -0
  165. package/dist/packages/runtime/agents/providers/index.js +31 -0
  166. package/dist/packages/runtime/agents/providers/types.d.ts +50 -0
  167. package/dist/packages/{engine → runtime}/agents/registry.d.ts +13 -2
  168. package/dist/packages/{engine → runtime}/agents/registry.js +48 -10
  169. package/dist/packages/{engine → runtime}/agents/role-executors.d.ts +1 -1
  170. package/dist/packages/{engine → runtime}/agents/role-executors.js +9 -7
  171. package/dist/packages/{engine → runtime}/agents/role-router.js +7 -5
  172. package/dist/packages/runtime/auth/account-context.d.ts +52 -0
  173. package/dist/packages/runtime/auth/account-context.js +68 -0
  174. package/dist/packages/runtime/auth/auth-flow.d.ts +73 -0
  175. package/dist/packages/runtime/auth/auth-flow.js +189 -0
  176. package/dist/packages/runtime/auth/jwt-validator.d.ts +58 -0
  177. package/dist/packages/runtime/auth/jwt-validator.js +86 -0
  178. package/dist/packages/runtime/auth/keychain.d.ts +35 -0
  179. package/dist/packages/runtime/auth/keychain.js +85 -0
  180. package/dist/packages/runtime/auth/session-store.d.ts +38 -0
  181. package/dist/packages/runtime/auth/session-store.js +96 -0
  182. package/dist/packages/runtime/auth/workos-client.d.ts +58 -0
  183. package/dist/packages/runtime/auth/workos-client.js +87 -0
  184. package/dist/packages/runtime/benchmark-question-draft.d.ts +23 -0
  185. package/dist/packages/runtime/benchmark-question-draft.js +153 -0
  186. package/dist/packages/runtime/build/artifact-counts.d.ts +1 -0
  187. package/dist/packages/{engine → runtime}/build/artifact-counts.js +5 -9
  188. package/dist/packages/{engine → runtime}/build/artifact-status.d.ts +6 -6
  189. package/dist/packages/{engine → runtime}/build/artifact-status.js +26 -24
  190. package/dist/packages/runtime/build/atomic-fs.d.ts +3 -0
  191. package/dist/packages/runtime/build/atomic-fs.js +95 -0
  192. package/dist/packages/runtime/build/billing-events.d.ts +78 -0
  193. package/dist/packages/{engine → runtime}/build/billing-events.js +17 -19
  194. package/dist/packages/runtime/build/build-evidence.d.ts +16 -0
  195. package/dist/packages/runtime/build/build-evidence.js +179 -0
  196. package/dist/packages/{engine → runtime}/build/build-pipeline.d.ts +12 -8
  197. package/dist/packages/runtime/build/build-pipeline.js +388 -0
  198. package/dist/packages/{engine → runtime}/build/build-plan-primitives.d.ts +1 -1
  199. package/dist/packages/{engine → runtime}/build/build-plan-primitives.js +0 -1
  200. package/dist/packages/runtime/build/build-plan-runs.d.ts +14 -0
  201. package/dist/packages/runtime/build/build-plan-runs.js +31 -0
  202. package/dist/packages/runtime/build/build-stage-plan.d.ts +16 -0
  203. package/dist/packages/runtime/build/build-stage-plan.js +101 -0
  204. package/dist/packages/{engine → runtime}/build/build-stage-runner.d.ts +2 -1
  205. package/dist/packages/runtime/build/build-stage-runner.js +302 -0
  206. package/dist/packages/{engine → runtime}/build/build-target.d.ts +7 -4
  207. package/dist/packages/runtime/build/build-target.js +40 -0
  208. package/dist/packages/{engine → runtime}/build/check-evaluator.d.ts +14 -16
  209. package/dist/packages/runtime/build/check-evaluator.js +1226 -0
  210. package/dist/packages/runtime/build/context-graph-paths.d.ts +64 -0
  211. package/dist/packages/runtime/build/context-graph-paths.js +160 -0
  212. package/dist/packages/runtime/build/context-graph-schema.d.ts +19 -0
  213. package/dist/packages/runtime/build/context-graph-schema.js +39 -0
  214. package/dist/packages/{engine → runtime}/build/discovery.d.ts +2 -2
  215. package/dist/packages/{engine → runtime}/build/discovery.js +4 -4
  216. package/dist/packages/{engine → runtime}/build/index.d.ts +7 -5
  217. package/dist/packages/{engine → runtime}/build/index.js +7 -5
  218. package/dist/packages/runtime/build/inspect-map.d.ts +10 -0
  219. package/dist/packages/runtime/build/inspect-map.js +270 -0
  220. package/dist/packages/{engine → runtime}/build/lib/schema.d.ts +449 -123
  221. package/dist/packages/runtime/build/lib/schema.js +494 -0
  222. package/dist/packages/runtime/build/native-entrypoint.d.ts +2 -0
  223. package/dist/packages/runtime/build/native-entrypoint.js +286 -0
  224. package/dist/packages/runtime/build/reset.d.ts +2 -0
  225. package/dist/packages/runtime/build/reset.js +62 -0
  226. package/dist/packages/{engine → runtime}/build/runtime-contracts.js +13 -7
  227. package/dist/packages/runtime/build/runtime-inventory.d.ts +7 -0
  228. package/dist/packages/{engine → runtime}/build/runtime-inventory.js +3 -3
  229. package/dist/packages/runtime/build/runtime-log-paths.d.ts +3 -0
  230. package/dist/packages/runtime/build/runtime-log-paths.js +16 -0
  231. package/dist/packages/{engine → runtime}/build/runtime-prompt.js +12 -9
  232. package/dist/packages/{engine → runtime}/build/runtime-reconcile.d.ts +1 -1
  233. package/dist/packages/{engine → runtime}/build/runtime-reconcile.js +25 -21
  234. package/dist/packages/runtime/build/runtime-runs.d.ts +10 -0
  235. package/dist/packages/runtime/build/runtime-runs.js +318 -0
  236. package/dist/packages/{engine → runtime}/build/runtime-types.d.ts +9 -6
  237. package/dist/packages/runtime/build/runtime-types.js +1 -0
  238. package/dist/packages/runtime/build/runtime.d.ts +8 -0
  239. package/dist/packages/runtime/build/runtime.js +7 -0
  240. package/dist/packages/runtime/build/source-files.d.ts +58 -0
  241. package/dist/packages/runtime/build/source-files.js +193 -0
  242. package/dist/packages/runtime/build/source-inventory.d.ts +28 -0
  243. package/dist/packages/runtime/build/source-inventory.js +512 -0
  244. package/dist/packages/runtime/build/source-manifest.d.ts +63 -0
  245. package/dist/packages/runtime/build/source-manifest.js +220 -0
  246. package/dist/packages/runtime/build/stage-evidence.d.ts +22 -0
  247. package/dist/packages/runtime/build/stage-evidence.js +386 -0
  248. package/dist/packages/runtime/build/stage-manifest.d.ts +45 -0
  249. package/dist/packages/runtime/build/stage-manifest.js +1125 -0
  250. package/dist/packages/runtime/build/stage-reuse.d.ts +11 -0
  251. package/dist/packages/runtime/build/stage-reuse.js +154 -0
  252. package/dist/packages/runtime/build/stage-session.d.ts +81 -0
  253. package/dist/packages/runtime/build/stage-session.js +308 -0
  254. package/dist/packages/runtime/build/state-artifacts.d.ts +9 -0
  255. package/dist/packages/runtime/build/state-artifacts.js +14 -0
  256. package/dist/packages/runtime/build/state-health.d.ts +4 -0
  257. package/dist/packages/{engine → runtime}/build/state-health.js +21 -26
  258. package/dist/packages/runtime/build/state-io.d.ts +12 -0
  259. package/dist/packages/runtime/build/state-io.js +118 -0
  260. package/dist/packages/runtime/build/state-view.d.ts +5 -0
  261. package/dist/packages/runtime/build/state-view.js +121 -0
  262. package/dist/packages/runtime/build/state.d.ts +7 -0
  263. package/dist/packages/runtime/build/state.js +12 -0
  264. package/dist/packages/runtime/build/summary-coverage-index.d.ts +21 -0
  265. package/dist/packages/runtime/build/summary-coverage-index.js +189 -0
  266. package/dist/packages/runtime/build/traces.d.ts +30 -0
  267. package/dist/packages/runtime/build/traces.js +133 -0
  268. package/dist/packages/{engine/build/validate-verifiable-context.d.ts → runtime/build/validate-context-graph.d.ts} +6 -6
  269. package/dist/packages/{engine/build/validate-verifiable-context.js → runtime/build/validate-context-graph.js} +49 -36
  270. package/dist/packages/{engine → runtime}/build/validate.d.ts +5 -5
  271. package/dist/packages/{engine → runtime}/build/validate.js +26 -26
  272. package/dist/packages/{engine → runtime}/client.d.ts +18 -18
  273. package/dist/packages/{engine → runtime}/client.js +48 -36
  274. package/dist/packages/{engine → runtime}/connection-config.d.ts +3 -2
  275. package/dist/packages/{engine → runtime}/connection-config.js +9 -8
  276. package/dist/packages/runtime/context-checks.d.ts +10 -0
  277. package/dist/packages/runtime/context-checks.js +127 -0
  278. package/dist/packages/runtime/context-graph-scaffold.d.ts +9 -0
  279. package/dist/packages/runtime/context-graph-scaffold.js +135 -0
  280. package/dist/packages/runtime/context-graph-semantic-graph.d.ts +9 -0
  281. package/dist/packages/runtime/context-graph-semantic-graph.js +416 -0
  282. package/dist/packages/runtime/entitlement-guard.d.ts +43 -0
  283. package/dist/packages/runtime/entitlement-guard.js +70 -0
  284. package/dist/packages/{engine → runtime}/execution/index.d.ts +2 -2
  285. package/dist/packages/{engine → runtime}/execution/index.js +1 -1
  286. package/dist/packages/{engine → runtime}/execution/lib/schema.d.ts +272 -191
  287. package/dist/packages/{engine → runtime}/execution/lib/schema.js +35 -32
  288. package/dist/packages/runtime/index.d.ts +29 -0
  289. package/dist/packages/runtime/index.js +21 -0
  290. package/dist/packages/runtime/instance-paths.d.ts +30 -0
  291. package/dist/packages/runtime/instance-paths.js +29 -0
  292. package/dist/packages/runtime/native-run-handlers.d.ts +63 -0
  293. package/dist/packages/{engine → runtime}/native-run-handlers.js +217 -166
  294. package/dist/packages/runtime/plan-artifact-contract.d.ts +17 -0
  295. package/dist/packages/runtime/plan-artifact-contract.js +42 -0
  296. package/dist/packages/runtime/project-entries.d.ts +11 -0
  297. package/dist/packages/runtime/project-entries.js +49 -0
  298. package/dist/packages/runtime/project-source-state.d.ts +26 -0
  299. package/dist/packages/runtime/project-source-state.js +56 -0
  300. package/dist/packages/runtime/project-store.d.ts +90 -0
  301. package/dist/packages/runtime/project-store.js +195 -0
  302. package/dist/packages/runtime/requested-artifacts.d.ts +7 -0
  303. package/dist/packages/{engine → runtime}/requested-artifacts.js +23 -1
  304. package/dist/packages/{engine → runtime}/run-observability.d.ts +2 -1
  305. package/dist/packages/{engine → runtime}/run-observability.js +174 -87
  306. package/dist/packages/runtime/runtime-action-proposals.d.ts +7 -0
  307. package/dist/packages/runtime/runtime-action-proposals.js +542 -0
  308. package/dist/packages/runtime/runtime-build-plans.d.ts +5 -0
  309. package/dist/packages/runtime/runtime-build-plans.js +175 -0
  310. package/dist/packages/runtime/runtime-build-runs.d.ts +47 -0
  311. package/dist/packages/runtime/runtime-build-runs.js +555 -0
  312. package/dist/packages/runtime/runtime-caches.d.ts +117 -0
  313. package/dist/packages/runtime/runtime-caches.js +266 -0
  314. package/dist/packages/{engine → runtime}/runtime-event-applier.d.ts +3 -1
  315. package/dist/packages/{engine → runtime}/runtime-event-applier.js +53 -17
  316. package/dist/packages/runtime/runtime-executor.d.ts +22 -0
  317. package/dist/packages/runtime/runtime-executor.js +131 -0
  318. package/dist/packages/runtime/runtime-jobs.d.ts +13 -0
  319. package/dist/packages/runtime/runtime-jobs.js +463 -0
  320. package/dist/packages/runtime/runtime-observability.d.ts +11 -0
  321. package/dist/packages/runtime/runtime-observability.js +39 -0
  322. package/dist/packages/{engine → runtime}/runtime-persistence.d.ts +9 -18
  323. package/dist/packages/{engine → runtime}/runtime-persistence.js +25 -25
  324. package/dist/packages/runtime/runtime-project-mutations.d.ts +7 -0
  325. package/dist/packages/runtime/runtime-project-mutations.js +65 -0
  326. package/dist/packages/runtime/runtime-project-reads.d.ts +18 -0
  327. package/dist/packages/runtime/runtime-project-reads.js +574 -0
  328. package/dist/packages/runtime/runtime-proposal-helpers.d.ts +22 -0
  329. package/dist/packages/runtime/runtime-proposal-helpers.js +223 -0
  330. package/dist/packages/{engine → runtime}/runtime-resource-builders.d.ts +23 -16
  331. package/dist/packages/{engine → runtime}/runtime-resource-builders.js +58 -46
  332. package/dist/packages/runtime/runtime-status.d.ts +14 -0
  333. package/dist/packages/runtime/runtime-status.js +15 -0
  334. package/dist/packages/runtime/runtime-verify-runs.d.ts +84 -0
  335. package/dist/packages/runtime/runtime-verify-runs.js +296 -0
  336. package/dist/packages/runtime/runtime.d.ts +1582 -0
  337. package/dist/packages/runtime/runtime.js +431 -0
  338. package/dist/packages/runtime/schemas/actions.d.ts +1206 -0
  339. package/dist/packages/runtime/schemas/actions.js +117 -0
  340. package/dist/packages/runtime/schemas/agents.d.ts +104 -0
  341. package/dist/packages/runtime/schemas/agents.js +74 -0
  342. package/dist/packages/runtime/schemas/build-plans.d.ts +1132 -0
  343. package/dist/packages/runtime/schemas/build-plans.js +141 -0
  344. package/dist/packages/runtime/schemas/context-graphs.d.ts +1522 -0
  345. package/dist/packages/runtime/schemas/context-graphs.js +110 -0
  346. package/dist/packages/runtime/schemas/files.d.ts +227 -0
  347. package/dist/packages/runtime/schemas/files.js +28 -0
  348. package/dist/packages/runtime/schemas/index.d.ts +9 -0
  349. package/dist/packages/runtime/schemas/index.js +13 -0
  350. package/dist/packages/runtime/schemas/instance.d.ts +141 -0
  351. package/dist/packages/runtime/schemas/instance.js +143 -0
  352. package/dist/packages/runtime/schemas/jobs.d.ts +339 -0
  353. package/dist/packages/runtime/schemas/jobs.js +107 -0
  354. package/dist/packages/runtime/schemas/projects.d.ts +366 -0
  355. package/dist/packages/runtime/schemas/projects.js +160 -0
  356. package/dist/packages/runtime/schemas/runs.d.ts +3445 -0
  357. package/dist/packages/runtime/schemas/runs.js +115 -0
  358. package/dist/packages/runtime/service/index.d.ts +3 -0
  359. package/dist/packages/runtime/service/index.js +3 -0
  360. package/dist/packages/runtime/service/openapi.d.ts +7 -0
  361. package/dist/packages/runtime/service/openapi.js +118 -0
  362. package/dist/packages/runtime/service/operations.d.ts +3011 -0
  363. package/dist/packages/runtime/service/operations.js +375 -0
  364. package/dist/packages/runtime/service/routes.d.ts +114 -0
  365. package/dist/packages/runtime/service/routes.js +128 -0
  366. package/dist/packages/runtime/service/server-api-files.d.ts +10 -0
  367. package/dist/packages/runtime/service/server-api-files.js +85 -0
  368. package/dist/packages/runtime/service/server-app-boot.d.ts +4 -0
  369. package/dist/packages/runtime/service/server-app-boot.js +46 -0
  370. package/dist/packages/runtime/service/server-guards.d.ts +63 -0
  371. package/dist/packages/runtime/service/server-guards.js +181 -0
  372. package/dist/packages/runtime/service/server-helpers.d.ts +38 -0
  373. package/dist/packages/runtime/service/server-helpers.js +108 -0
  374. package/dist/packages/runtime/service/server-instance-helpers.d.ts +30 -0
  375. package/dist/packages/runtime/service/server-instance-helpers.js +114 -0
  376. package/dist/packages/runtime/service/server-routes-action-proposals.d.ts +3 -0
  377. package/dist/packages/runtime/service/server-routes-action-proposals.js +45 -0
  378. package/dist/packages/runtime/service/server-routes-agents.d.ts +4 -0
  379. package/dist/packages/runtime/service/server-routes-agents.js +132 -0
  380. package/dist/packages/runtime/service/server-routes-auth.d.ts +33 -0
  381. package/dist/packages/runtime/service/server-routes-auth.js +138 -0
  382. package/dist/packages/runtime/service/server-routes-build-plans.d.ts +3 -0
  383. package/dist/packages/runtime/service/server-routes-build-plans.js +86 -0
  384. package/dist/packages/runtime/service/server-routes-discovery.d.ts +4 -0
  385. package/dist/packages/runtime/service/server-routes-discovery.js +196 -0
  386. package/dist/packages/runtime/service/server-routes-events.d.ts +5 -0
  387. package/dist/packages/runtime/service/server-routes-events.js +99 -0
  388. package/dist/packages/runtime/service/server-routes-project-context.d.ts +9 -0
  389. package/dist/packages/runtime/service/server-routes-project-context.js +287 -0
  390. package/dist/packages/runtime/service/server-routes-project-jobs.d.ts +9 -0
  391. package/dist/packages/runtime/service/server-routes-project-jobs.js +137 -0
  392. package/dist/packages/runtime/service/server-routes-project-runs.d.ts +14 -0
  393. package/dist/packages/runtime/service/server-routes-project-runs.js +88 -0
  394. package/dist/packages/runtime/service/server-routes-projects.d.ts +4 -0
  395. package/dist/packages/runtime/service/server-routes-projects.js +96 -0
  396. package/dist/packages/runtime/service/server-routes-runs.d.ts +3 -0
  397. package/dist/packages/runtime/service/server-routes-runs.js +119 -0
  398. package/dist/packages/runtime/service/server.d.ts +37 -0
  399. package/dist/packages/runtime/service/server.js +300 -0
  400. package/dist/packages/{engine → runtime/service}/service-registry.d.ts +5 -5
  401. package/dist/packages/{engine → runtime/service}/service-registry.js +7 -7
  402. package/dist/packages/runtime/verify/benchmark-run.d.ts +81 -0
  403. package/dist/packages/runtime/verify/benchmark-run.js +303 -0
  404. package/dist/packages/{engine → runtime}/verify/index.d.ts +2 -2
  405. package/dist/packages/{engine → runtime}/verify/index.js +1 -1
  406. package/dist/packages/{engine → runtime}/verify/lib/schema.d.ts +83 -16
  407. package/dist/packages/{engine → runtime}/verify/lib/schema.js +38 -18
  408. package/dist/packages/runtime/verify/test-file-guard.d.ts +2 -0
  409. package/dist/packages/runtime/verify/test-file-guard.js +29 -0
  410. package/dist/packages/{engine → runtime}/verify/verify-execution.d.ts +7 -0
  411. package/dist/packages/{engine → runtime}/verify/verify-execution.js +119 -45
  412. package/dist/packages/{engine → runtime}/verify/verify-paths.d.ts +5 -4
  413. package/dist/packages/runtime/verify/verify-paths.js +65 -0
  414. package/dist/packages/{engine → runtime}/verify/verify-sandbox.d.ts +1 -1
  415. package/dist/packages/runtime/verify/verify-sandbox.js +88 -0
  416. package/dist/packages/{engine → runtime}/verify/verify-specs.d.ts +2 -0
  417. package/dist/packages/runtime/verify/verify-specs.js +126 -0
  418. package/dist/packages/runtime/verify/verify-targets.d.ts +5 -0
  419. package/dist/packages/{engine → runtime}/verify/verify-targets.js +12 -12
  420. package/dist/packages/runtime/verify/verify-types.js +1 -0
  421. package/dist/packages/{engine → runtime}/verify/verify.d.ts +1 -1
  422. package/dist/packages/{engine → runtime}/verify/verify.js +1 -1
  423. package/dist/packages/runtime/wire-schemas.d.ts +18 -0
  424. package/dist/packages/runtime/wire-schemas.js +27 -0
  425. package/package.json +32 -30
  426. package/public-repo/CONTRIBUTING.md +16 -18
  427. package/public-repo/README.md +119 -282
  428. package/public-repo/SECURITY.md +3 -4
  429. package/public-repo/build-plans/interf-default/README.md +24 -16
  430. package/public-repo/build-plans/interf-default/build/stages/entrypoint/SKILL.md +74 -0
  431. package/public-repo/build-plans/interf-default/build/stages/knowledge/SKILL.md +95 -0
  432. package/public-repo/build-plans/interf-default/build/stages/summarize/SKILL.md +49 -4
  433. package/public-repo/build-plans/interf-default/build-plan.json +49 -39
  434. package/public-repo/build-plans/interf-default/build-plan.schema.json +59 -33
  435. package/public-repo/build-plans/interf-default/improve/SKILL.md +3 -3
  436. package/public-repo/build-plans/interf-default/use/query/SKILL.md +18 -11
  437. package/public-repo/openapi/local-service.openapi.json +14227 -0
  438. package/public-repo/skills/interf/SKILL.md +508 -187
  439. package/dist/cli/commands/prep.d.ts +0 -2
  440. package/dist/cli/commands/prep.js +0 -240
  441. package/dist/cli/commands/test.d.ts +0 -10
  442. package/dist/cli/commands/test.js +0 -85
  443. package/dist/cli/commands/web.d.ts +0 -2
  444. package/dist/cli/commands/web.js +0 -286
  445. package/dist/interf-ui/404.html +0 -1
  446. package/dist/interf-ui/__next.__PAGE__.txt +0 -10
  447. package/dist/interf-ui/__next._full.txt +0 -20
  448. package/dist/interf-ui/__next._head.txt +0 -5
  449. package/dist/interf-ui/__next._index.txt +0 -5
  450. package/dist/interf-ui/__next._tree.txt +0 -5
  451. package/dist/interf-ui/_next/static/--reS3xBzM5zc6QxNjZd6/_buildManifest.js +0 -11
  452. package/dist/interf-ui/_next/static/--reS3xBzM5zc6QxNjZd6/_clientMiddlewareManifest.js +0 -1
  453. package/dist/interf-ui/_next/static/--reS3xBzM5zc6QxNjZd6/_ssgManifest.js +0 -1
  454. package/dist/interf-ui/_next/static/chunks/0.tjb6f4golw..css +0 -3
  455. package/dist/interf-ui/_next/static/chunks/03~yq9q893hmn.js +0 -1
  456. package/dist/interf-ui/_next/static/chunks/085-n_jv2ng_q.css +0 -1
  457. package/dist/interf-ui/_next/static/chunks/0dn41fa_zvgsl.js +0 -1
  458. package/dist/interf-ui/_next/static/chunks/0g-ea0zj5d-0k.js +0 -1
  459. package/dist/interf-ui/_next/static/chunks/0gwqglc4iz583.js +0 -1
  460. package/dist/interf-ui/_next/static/chunks/0haldgm65ve6l.js +0 -1
  461. package/dist/interf-ui/_next/static/chunks/0nv3am99vjzn4.js +0 -1
  462. package/dist/interf-ui/_next/static/chunks/0s77gt_o4jwtx.js +0 -1
  463. package/dist/interf-ui/_next/static/chunks/0y5z3t-z1c8ks.js.map +0 -5
  464. package/dist/interf-ui/_next/static/chunks/0~a36ujuzpaz..js +0 -116
  465. package/dist/interf-ui/_next/static/chunks/10jeodxe4nkgj.js +0 -31
  466. package/dist/interf-ui/_next/static/chunks/119h2rouych2t.js +0 -1
  467. package/dist/interf-ui/_next/static/chunks/13c8b~m8knjsf.js +0 -1
  468. package/dist/interf-ui/_next/static/chunks/14dznb2qpt-ho.js +0 -91
  469. package/dist/interf-ui/_next/static/chunks/15z_en80lrq-3.js +0 -5
  470. package/dist/interf-ui/_next/static/chunks/turbopack-0p.pvcjrtq-jh.js +0 -1
  471. package/dist/interf-ui/_next/static/chunks/turbopack-0usj_75.8frlw.js +0 -1
  472. package/dist/interf-ui/_next/static/chunks/turbopack-worker-0sjn--fhq~1cg.js +0 -1
  473. package/dist/interf-ui/_next/static/media/GeistMono_Variable.p.17jn9btb_52pq.woff2 +0 -0
  474. package/dist/interf-ui/_next/static/media/Geist_Variable-s.p.0-te~ja_gpvcf.woff2 +0 -0
  475. package/dist/interf-ui/_next/static/media/worker.102zas1s52_pf.js +0 -109
  476. package/dist/interf-ui/_not-found/__next._full.txt +0 -15
  477. package/dist/interf-ui/_not-found/__next._head.txt +0 -5
  478. package/dist/interf-ui/_not-found/__next._index.txt +0 -5
  479. package/dist/interf-ui/_not-found/__next._not-found.__PAGE__.txt +0 -5
  480. package/dist/interf-ui/_not-found/__next._not-found.txt +0 -5
  481. package/dist/interf-ui/_not-found/__next._tree.txt +0 -2
  482. package/dist/interf-ui/_not-found.html +0 -1
  483. package/dist/interf-ui/_not-found.txt +0 -15
  484. package/dist/interf-ui/index.html +0 -1
  485. package/dist/interf-ui/index.txt +0 -20
  486. package/dist/packages/contracts/lib/preparation-paths.d.ts +0 -117
  487. package/dist/packages/contracts/lib/preparation-paths.js +0 -177
  488. package/dist/packages/engine/action-definitions.d.ts +0 -407
  489. package/dist/packages/engine/action-definitions.js +0 -1158
  490. package/dist/packages/engine/action-values.d.ts +0 -1
  491. package/dist/packages/engine/action-values.js +0 -1
  492. package/dist/packages/engine/agents/lib/args.d.ts +0 -4
  493. package/dist/packages/engine/agents/lib/args.js +0 -52
  494. package/dist/packages/engine/agents/lib/chart-guidance.d.ts +0 -1
  495. package/dist/packages/engine/agents/lib/chart-guidance.js +0 -8
  496. package/dist/packages/engine/agents/lib/constants.js +0 -28
  497. package/dist/packages/engine/agents/lib/logs.d.ts +0 -2
  498. package/dist/packages/engine/agents/lib/logs.js +0 -17
  499. package/dist/packages/engine/agents/lib/render.d.ts +0 -8
  500. package/dist/packages/engine/agents/lib/schema.d.ts +0 -8
  501. package/dist/packages/engine/agents/lib/schema.js +0 -7
  502. package/dist/packages/engine/agents/lib/shells.d.ts +0 -74
  503. package/dist/packages/engine/agents/lib/shells.js +0 -1052
  504. package/dist/packages/engine/agents/lib/verifiable-context-bootstrap.d.ts +0 -3
  505. package/dist/packages/engine/build/artifact-counts.d.ts +0 -1
  506. package/dist/packages/engine/build/billing-events.d.ts +0 -89
  507. package/dist/packages/engine/build/build-pipeline.js +0 -175
  508. package/dist/packages/engine/build/build-plan-runs.d.ts +0 -14
  509. package/dist/packages/engine/build/build-plan-runs.js +0 -31
  510. package/dist/packages/engine/build/build-stage-plan.d.ts +0 -16
  511. package/dist/packages/engine/build/build-stage-plan.js +0 -100
  512. package/dist/packages/engine/build/build-stage-runner.js +0 -94
  513. package/dist/packages/engine/build/build-target.js +0 -16
  514. package/dist/packages/engine/build/check-evaluator.js +0 -298
  515. package/dist/packages/engine/build/lib/schema.js +0 -316
  516. package/dist/packages/engine/build/reset.d.ts +0 -2
  517. package/dist/packages/engine/build/reset.js +0 -74
  518. package/dist/packages/engine/build/runtime-inventory.d.ts +0 -7
  519. package/dist/packages/engine/build/runtime-paths.d.ts +0 -8
  520. package/dist/packages/engine/build/runtime-paths.js +0 -26
  521. package/dist/packages/engine/build/runtime-runs.d.ts +0 -10
  522. package/dist/packages/engine/build/runtime-runs.js +0 -224
  523. package/dist/packages/engine/build/runtime.d.ts +0 -5
  524. package/dist/packages/engine/build/runtime.js +0 -4
  525. package/dist/packages/engine/build/source-files.d.ts +0 -46
  526. package/dist/packages/engine/build/source-files.js +0 -149
  527. package/dist/packages/engine/build/state-artifacts.d.ts +0 -9
  528. package/dist/packages/engine/build/state-artifacts.js +0 -14
  529. package/dist/packages/engine/build/state-health.d.ts +0 -4
  530. package/dist/packages/engine/build/state-io.d.ts +0 -11
  531. package/dist/packages/engine/build/state-io.js +0 -82
  532. package/dist/packages/engine/build/state-paths.d.ts +0 -5
  533. package/dist/packages/engine/build/state-paths.js +0 -16
  534. package/dist/packages/engine/build/state-view.d.ts +0 -5
  535. package/dist/packages/engine/build/state-view.js +0 -94
  536. package/dist/packages/engine/build/state.d.ts +0 -7
  537. package/dist/packages/engine/build/state.js +0 -12
  538. package/dist/packages/engine/build/validate-helpers.d.ts +0 -12
  539. package/dist/packages/engine/build/validate-helpers.js +0 -41
  540. package/dist/packages/engine/build/verifiable-context-paths.d.ts +0 -47
  541. package/dist/packages/engine/build/verifiable-context-paths.js +0 -121
  542. package/dist/packages/engine/build/verifiable-context-schema.d.ts +0 -21
  543. package/dist/packages/engine/build/verifiable-context-schema.js +0 -126
  544. package/dist/packages/engine/cloud-seams.d.ts +0 -115
  545. package/dist/packages/engine/cloud-seams.js +0 -84
  546. package/dist/packages/engine/index.d.ts +0 -22
  547. package/dist/packages/engine/index.js +0 -15
  548. package/dist/packages/engine/instance-paths.d.ts +0 -106
  549. package/dist/packages/engine/instance-paths.js +0 -171
  550. package/dist/packages/engine/lib/schema.d.ts +0 -6304
  551. package/dist/packages/engine/lib/schema.js +0 -730
  552. package/dist/packages/engine/native-run-handlers.d.ts +0 -25
  553. package/dist/packages/engine/preparation-store.d.ts +0 -105
  554. package/dist/packages/engine/preparation-store.js +0 -213
  555. package/dist/packages/engine/readiness-check-draft.d.ts +0 -20
  556. package/dist/packages/engine/readiness-check-draft.js +0 -111
  557. package/dist/packages/engine/requested-artifacts.d.ts +0 -5
  558. package/dist/packages/engine/routes.d.ts +0 -85
  559. package/dist/packages/engine/routes.js +0 -99
  560. package/dist/packages/engine/runtime-caches.d.ts +0 -76
  561. package/dist/packages/engine/runtime-caches.js +0 -191
  562. package/dist/packages/engine/runtime-proposal-helpers.d.ts +0 -35
  563. package/dist/packages/engine/runtime-proposal-helpers.js +0 -247
  564. package/dist/packages/engine/runtime.d.ts +0 -371
  565. package/dist/packages/engine/runtime.js +0 -2463
  566. package/dist/packages/engine/server.d.ts +0 -58
  567. package/dist/packages/engine/server.js +0 -1399
  568. package/dist/packages/engine/verify/readiness-check-run.d.ts +0 -82
  569. package/dist/packages/engine/verify/readiness-check-run.js +0 -265
  570. package/dist/packages/engine/verify/verify-paths.js +0 -61
  571. package/dist/packages/engine/verify/verify-sandbox.js +0 -88
  572. package/dist/packages/engine/verify/verify-specs.js +0 -114
  573. package/dist/packages/engine/verify/verify-targets.d.ts +0 -5
  574. package/dist/packages/engine/wire-schemas.d.ts +0 -547
  575. package/dist/packages/engine/wire-schemas.js +0 -59
  576. package/dist/packages/project/index.d.ts +0 -9
  577. package/dist/packages/project/interf-bootstrap.d.ts +0 -1
  578. package/dist/packages/project/interf-bootstrap.js +0 -1
  579. package/dist/packages/project/interf-scaffold.d.ts +0 -3
  580. package/dist/packages/project/interf-scaffold.js +0 -136
  581. package/dist/packages/project/interf.d.ts +0 -4
  582. package/dist/packages/project/interf.js +0 -3
  583. package/dist/packages/project/lib/schema.d.ts +0 -328
  584. package/dist/packages/project/lib/schema.js +0 -136
  585. package/dist/packages/project/preparation-entries.d.ts +0 -11
  586. package/dist/packages/project/preparation-entries.js +0 -49
  587. package/dist/packages/project/source-config.d.ts +0 -46
  588. package/dist/packages/project/source-config.js +0 -394
  589. package/dist/packages/project/source-folders.d.ts +0 -11
  590. package/public-repo/build-plans/interf-default/build/stages/shape/SKILL.md +0 -27
  591. package/public-repo/build-plans/interf-default/build/stages/structure/SKILL.md +0 -21
  592. package/public-repo/plugins/README.md +0 -9
  593. package/public-repo/plugins/interf/.claude-plugin/plugin.json +0 -21
  594. package/public-repo/plugins/interf/.mcp.json +0 -12
  595. package/public-repo/plugins/interf/README.md +0 -32
  596. package/public-repo/plugins/interf/skills/interf/SKILL.md +0 -376
  597. /package/dist/packages/{engine/agents/lib/types.js → runtime/actions/fields.js} +0 -0
  598. /package/dist/packages/{engine → runtime}/agents/lib/agents.d.ts +0 -0
  599. /package/dist/packages/{engine → runtime}/agents/lib/agents.js +0 -0
  600. /package/dist/packages/{engine → runtime}/agents/lib/execution.d.ts +0 -0
  601. /package/dist/packages/{engine → runtime}/agents/lib/preflight.d.ts +0 -0
  602. /package/dist/packages/{engine → runtime}/agents/lib/status.d.ts +0 -0
  603. /package/dist/packages/{engine/build/runtime-types.js → runtime/agents/lib/types.js} +0 -0
  604. /package/dist/packages/{engine/verify/verify-types.js → runtime/agents/providers/types.js} +0 -0
  605. /package/dist/packages/{engine → runtime}/agents/role-router.d.ts +0 -0
  606. /package/dist/packages/{engine → runtime}/build/build-execution.d.ts +0 -0
  607. /package/dist/packages/{engine → runtime}/build/build-execution.js +0 -0
  608. /package/dist/packages/{engine → runtime}/build/runtime-contracts.d.ts +0 -0
  609. /package/dist/packages/{engine → runtime}/build/runtime-prompt.d.ts +0 -0
  610. /package/dist/packages/{engine → runtime}/execution/adapters.d.ts +0 -0
  611. /package/dist/packages/{engine → runtime}/execution/adapters.js +0 -0
  612. /package/dist/packages/{engine → runtime}/execution/events.d.ts +0 -0
  613. /package/dist/packages/{engine → runtime}/execution/events.js +0 -0
  614. /package/dist/packages/{engine → runtime}/verify/verify-profile-presets.d.ts +0 -0
  615. /package/dist/packages/{engine → runtime}/verify/verify-profile-presets.js +0 -0
  616. /package/dist/packages/{engine → runtime}/verify/verify-types.d.ts +0 -0
@@ -0,0 +1,1226 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { basename, dirname, join, relative, resolve } from "node:path";
3
+ import { listFilesRecursive } from "../../contracts/utils/filesystem.js";
4
+ import { parseJsonFrontmatter } from "../../contracts/utils/parse.js";
5
+ import { CANONICAL_LAYER_DIRS, HOME_SPINE_FILE, graphRelativePathPattern, } from "../../contracts/lib/context-graph-layer.js";
6
+ import { CheckKindSchema, SourceManifestSchema, } from "../../contracts/lib/schema.js";
7
+ import { countBrokenWikilinks, isOutputMarkdownFile, validateSynthFiles, } from "./validate.js";
8
+ /**
9
+ * Build a CheckResult envelope from an evaluator outcome. Centralizes the
10
+ * timestamp + required-flag plumbing so individual evaluators can
11
+ * focus on the pass/fail decision and a one-line summary.
12
+ */
13
+ function makeCheckResult(check, passed, summary, details) {
14
+ return {
15
+ check_id: check.id,
16
+ kind: check.kind,
17
+ passed,
18
+ required: check.required,
19
+ summary,
20
+ ...(details !== undefined ? { details } : {}),
21
+ evaluated_at: new Date().toISOString(),
22
+ };
23
+ }
24
+ function resolveTargetPath(check, context) {
25
+ const target = context.targetPath;
26
+ if (!target)
27
+ return null;
28
+ const root = resolve(context.rootPath);
29
+ const absolute = resolve(root, target);
30
+ // Defense in depth — schema-level validation already rejects path
31
+ // traversal, but a hand-edited Build Plan or stale on-disk fixture could
32
+ // slip through. Reject anything that escapes the root.
33
+ if (absolute !== root && !absolute.startsWith(`${root}/`)) {
34
+ return null;
35
+ }
36
+ return absolute;
37
+ }
38
+ function listMarkdownFiles(absolutePath) {
39
+ if (!existsSync(absolutePath))
40
+ return [];
41
+ try {
42
+ const stats = statSync(absolutePath);
43
+ if (stats.isFile()) {
44
+ return isOutputMarkdownFile(absolutePath) ? [absolutePath] : [];
45
+ }
46
+ if (stats.isDirectory()) {
47
+ return listFilesRecursive(absolutePath, isOutputMarkdownFile);
48
+ }
49
+ }
50
+ catch {
51
+ return [];
52
+ }
53
+ return [];
54
+ }
55
+ function countFiles(absolutePath) {
56
+ if (!existsSync(absolutePath))
57
+ return 0;
58
+ try {
59
+ const stats = statSync(absolutePath);
60
+ if (stats.isFile())
61
+ return 1;
62
+ if (stats.isDirectory()) {
63
+ return listFilesRecursive(absolutePath, () => true).length;
64
+ }
65
+ }
66
+ catch {
67
+ return 0;
68
+ }
69
+ return 0;
70
+ }
71
+ function countSourceSummaryFolders(absolutePath, options) {
72
+ if (!existsSync(absolutePath))
73
+ return { actual: 0, missingSummary: [] };
74
+ try {
75
+ const stats = statSync(absolutePath);
76
+ if (!stats.isDirectory())
77
+ return { actual: 0, missingSummary: [] };
78
+ const missingSummary = [];
79
+ let actual = 0;
80
+ for (const entry of readdirSync(absolutePath, { withFileTypes: true })) {
81
+ if (!entry.isDirectory())
82
+ continue;
83
+ const dirPath = join(absolutePath, entry.name);
84
+ const hasSummary = existsSync(join(dirPath, options.summaryFile));
85
+ const hasManifest = existsSync(join(dirPath, options.manifestFile));
86
+ if (hasSummary || hasManifest) {
87
+ actual += 1;
88
+ }
89
+ else {
90
+ missingSummary.push(entry.name);
91
+ }
92
+ }
93
+ return { actual, missingSummary };
94
+ }
95
+ catch {
96
+ return { actual: 0, missingSummary: [] };
97
+ }
98
+ }
99
+ function summaryDirectoriesWithEvidence(absolutePath, options) {
100
+ const dirs = new Set();
101
+ if (!existsSync(absolutePath))
102
+ return dirs;
103
+ for (const file of listFilesRecursive(absolutePath, () => true)) {
104
+ const name = basename(file);
105
+ if (name !== options.summaryFile && name !== options.manifestFile)
106
+ continue;
107
+ dirs.add(relative(absolutePath, dirname(file)).replaceAll("\\", "/"));
108
+ }
109
+ return dirs;
110
+ }
111
+ function sourceSummaryFolderCandidates(sourcePath, sourceFileId, basenameCounts) {
112
+ const normalized = sourcePath.replaceAll("\\", "/");
113
+ const base = basename(normalized);
114
+ const candidates = [
115
+ normalized,
116
+ sourceFileId,
117
+ basenameCounts.get(base) === 1 ? base : "",
118
+ ].filter((candidate) => candidate.length > 0);
119
+ return [...new Set(candidates)];
120
+ }
121
+ function sourceSummaryCoverage(absolutePath, manifest, options) {
122
+ const evidenceDirs = summaryDirectoriesWithEvidence(absolutePath, options);
123
+ const matchedDirs = new Set();
124
+ const missingSourcePaths = [];
125
+ const basenameCounts = new Map();
126
+ for (const sourceFile of manifest.files) {
127
+ const base = basename(sourceFile.path.replaceAll("\\", "/"));
128
+ basenameCounts.set(base, (basenameCounts.get(base) ?? 0) + 1);
129
+ }
130
+ for (const sourceFile of manifest.files) {
131
+ const candidates = sourceSummaryFolderCandidates(sourceFile.path, sourceFile.id, basenameCounts);
132
+ const matched = candidates.find((candidate) => evidenceDirs.has(candidate));
133
+ if (matched) {
134
+ matchedDirs.add(matched);
135
+ }
136
+ else {
137
+ missingSourcePaths.push(sourceFile.path);
138
+ }
139
+ }
140
+ return {
141
+ actual: manifest.files.length - missingSourcePaths.length,
142
+ expected: manifest.source_total,
143
+ missingSourcePaths,
144
+ extraSummaryFolders: [...evidenceDirs].filter((dir) => !matchedDirs.has(dir)).sort((left, right) => left.localeCompare(right)),
145
+ };
146
+ }
147
+ const WIKILINK_TARGET_PATTERN = /\[\[([^[\]\n]+)\]\]/g;
148
+ const MARKDOWN_LINK_TARGET_PATTERN = /!?\[[^\]\n]*\]\(([^)\n]+)\)/g;
149
+ function normalizeGraphPath(value) {
150
+ return value.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\.md$/i, "").replace(/\/+$/g, "");
151
+ }
152
+ /**
153
+ * Like normalizeGraphPath but preserves a trailing `.md` — summary folders are
154
+ * named after the source file and legitimately end in `.md` (e.g.
155
+ * `summaries/meeting-notes.md/`). Stripping it would split the folder identity.
156
+ */
157
+ function normalizeFolderPath(value) {
158
+ return value.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+$/g, "");
159
+ }
160
+ /**
161
+ * Wikilink targets referenced by a single note, normalized to graph-relative
162
+ * basenames (no `.md`, no `#anchor`, no `|alias`). Source-agnostic: reads only
163
+ * the `[[...]]` syntax, never a task taxonomy.
164
+ */
165
+ function noteWikilinkTargets(content) {
166
+ const targets = [];
167
+ for (const match of content.matchAll(WIKILINK_TARGET_PATTERN)) {
168
+ const raw = match[1]?.split("|")[0]?.split("#")[0]?.trim();
169
+ if (raw)
170
+ targets.push(normalizeGraphPath(raw));
171
+ }
172
+ return targets;
173
+ }
174
+ /**
175
+ * Every link token a note references, across the three link forms the
176
+ * StageManifest's `parseLinks` reads: `[[wikilinks]]`, `[markdown](relative.md)`
177
+ * links (http(s) excluded), and bare graph-relative path mentions in body text
178
+ * (`knowledge/foo`, via the shared `graphRelativePathPattern` so the scanner and
179
+ * the layer model can never list different folders). Each token is normalized to
180
+ * a graph-relative path with no `.md`, anchor, or alias. Reusing the same link
181
+ * surface the manifest uses is what keeps the Check and the manifest rollup in
182
+ * lockstep on which notes are web-connected.
183
+ */
184
+ function noteLinkTargets(content) {
185
+ const targets = new Set();
186
+ for (const target of noteWikilinkTargets(content))
187
+ targets.add(target);
188
+ for (const match of content.matchAll(MARKDOWN_LINK_TARGET_PATTERN)) {
189
+ const raw = match[1]?.split("#")[0]?.trim();
190
+ if (!raw || /^https?:\/\//i.test(raw))
191
+ continue;
192
+ targets.add(normalizeGraphPath(raw));
193
+ }
194
+ for (const match of content.matchAll(graphRelativePathPattern())) {
195
+ const raw = match[1]?.split("#")[0]?.trim().replace(/[),.;:]+$/g, "");
196
+ if (raw)
197
+ targets.add(normalizeGraphPath(raw));
198
+ }
199
+ return [...targets].filter((target) => target.length > 0);
200
+ }
201
+ /**
202
+ * Resolve a single link token to the knowledge note it names, or report that it
203
+ * is ambiguous. Mirrors the basename-aliasing discipline `existingSummaryFolderSet`
204
+ * already uses in this file: a full graph-path always resolves (paths are unique
205
+ * within the subtree); a token ending `/<basename>` resolves to the unique note
206
+ * with that path suffix; a BARE basename resolves ONLY when exactly one note
207
+ * carries it. A bare basename two or more notes share (e.g. `[[claim]]` for both
208
+ * `topics/claim` and `entities/claim`) is `ambiguous` — it must NOT credit a web
209
+ * edge to one arbitrarily, which would silently connect the linker and hide the
210
+ * other namesake's island. Returns the matched note, `ambiguous`, or `null`.
211
+ */
212
+ function resolveLinkToNote(token, byGraphPath, byBasename, ambiguousBasenames) {
213
+ const clean = normalizeGraphPath(token);
214
+ if (clean.length === 0)
215
+ return null;
216
+ const exact = byGraphPath.get(clean);
217
+ if (exact)
218
+ return exact;
219
+ if (clean.includes("/")) {
220
+ // A path-shaped token: credit a note whose full path is the token's trailing
221
+ // segments. Unique by construction (graph paths are unique), so no ambiguity.
222
+ const suffix = basename(clean);
223
+ for (const note of byGraphPath.values()) {
224
+ if (clean.endsWith(`/${note.base}`) && note.base === suffix && clean.endsWith(note.graphPath)) {
225
+ return note;
226
+ }
227
+ }
228
+ return null;
229
+ }
230
+ // A bare basename: resolves only when unambiguous; a shared basename is a gap.
231
+ if (ambiguousBasenames.has(clean))
232
+ return "ambiguous";
233
+ return byBasename.get(clean) ?? null;
234
+ }
235
+ /**
236
+ * Resolve a note-relative link token (`./x`, `../x`) against the linking note's
237
+ * directory into an absolute graph path, so a relative wikilink credits a web
238
+ * edge the same way the wikilink validator resolves it. Without this, a note
239
+ * that links `[[../launch]]` is falsely scored a disconnected island. Non-relative
240
+ * tokens (full graph paths, bare basenames) are returned untouched for the global
241
+ * lookup.
242
+ */
243
+ function resolveRelativeGraphPath(fromGraphPath, token) {
244
+ const lastSlash = fromGraphPath.lastIndexOf("/");
245
+ const segments = lastSlash >= 0 ? fromGraphPath.slice(0, lastSlash).split("/") : [];
246
+ for (const part of token.split("/")) {
247
+ if (part === "" || part === ".")
248
+ continue;
249
+ if (part === "..") {
250
+ segments.pop();
251
+ continue;
252
+ }
253
+ segments.push(part);
254
+ }
255
+ return normalizeGraphPath(segments.join("/"));
256
+ }
257
+ /**
258
+ * Markdown notes that form the CONTENT of a Context Graph — the canonical content
259
+ * layers (`summaries/`, `knowledge/`, `artifacts/`) plus the `home.md` spine —
260
+ * resolved to absolute file paths. Deliberately EXCLUDES the runtime scaffolding
261
+ * that also lives under the graph root (`.interf/`, `.claude/`, `.agents/`,
262
+ * skill `SKILL.md` docs, `CLAUDE.md`, `AGENTS.md`, view specs): those are not
263
+ * graph notes and must never be scored for web connectivity — they are always
264
+ * "islands" and several share the basename `SKILL`, which would inject false
265
+ * ambiguity. This mirrors the dot-entry skipping the semantic-graph builder
266
+ * already does, and keys off the central `CANONICAL_LAYER_DIRS` / `HOME_SPINE_FILE`
267
+ * so the connectivity floor and the layer model never list different folders.
268
+ */
269
+ function collectGraphContentNotes(graphRoot) {
270
+ const files = [];
271
+ for (const layer of CANONICAL_LAYER_DIRS) {
272
+ files.push(...listMarkdownFiles(join(graphRoot, layer)));
273
+ }
274
+ const home = join(graphRoot, HOME_SPINE_FILE);
275
+ if (existsSync(home) && isOutputMarkdownFile(home))
276
+ files.push(home);
277
+ return files;
278
+ }
279
+ /**
280
+ * Note-web connectivity over a pre-collected note set — the filesystem-side
281
+ * mirror of the StageManifest's `knowledgeWebConnectivity`. Each file in `noteFiles`
282
+ * IS a note in the web (the caller chooses the scope and the file set: the
283
+ * knowledge layer for `knowledge_web_connectivity`, the canonical CONTENT layers
284
+ * for `graph_notes_connected`), so no layer re-derivation is needed here. A note
285
+ * is web-connected when it links a DIFFERENT note in the same set OR is linked
286
+ * by one (UNDIRECTED, degree ≥ 1); degree 0 is a disconnected island.
287
+ * Connectedness, not a count. Vacuous pass: ≤ 1 note cannot form a web, so it is
288
+ * reported connected with no islands. Edges and matching reuse the same link
289
+ * surface and basename-aliasing rule the manifest and the backlink check use, so
290
+ * the gates agree on islands.
291
+ */
292
+ function analyzeNoteWeb(noteFiles, context) {
293
+ const root = resolve(context.rootPath);
294
+ const notes = noteFiles.map((file) => {
295
+ const graphPath = normalizeGraphPath(relative(root, file).replaceAll("\\", "/"));
296
+ return { file, graphPath, base: basename(graphPath) };
297
+ });
298
+ if (notes.length <= 1) {
299
+ return { notes: notes.length, connected: notes.length, islands: [], ambiguousLinkNotes: [] };
300
+ }
301
+ // Full path always keys a note; a basename keys a note only when unique. A
302
+ // basename two or more notes share is ambiguous and never resolves a bare link.
303
+ const byGraphPath = new Map();
304
+ const basenameCounts = new Map();
305
+ for (const note of notes) {
306
+ byGraphPath.set(note.graphPath, note);
307
+ basenameCounts.set(note.base, (basenameCounts.get(note.base) ?? 0) + 1);
308
+ }
309
+ const byBasename = new Map();
310
+ const ambiguousBasenames = new Set();
311
+ for (const note of notes) {
312
+ if ((basenameCounts.get(note.base) ?? 0) > 1) {
313
+ ambiguousBasenames.add(note.base);
314
+ continue;
315
+ }
316
+ byBasename.set(note.base, note);
317
+ }
318
+ const hasOutbound = new Set();
319
+ const hasInbound = new Set();
320
+ const ambiguousLinkNotes = new Set();
321
+ for (const from of notes) {
322
+ for (const token of noteLinkTargets(readFileSync(from.file, "utf8"))) {
323
+ const candidate = token.startsWith("../") || token.startsWith("./")
324
+ ? resolveRelativeGraphPath(from.graphPath, token)
325
+ : token;
326
+ const resolved = resolveLinkToNote(candidate, byGraphPath, byBasename, ambiguousBasenames);
327
+ if (resolved === "ambiguous") {
328
+ ambiguousLinkNotes.add(from.graphPath);
329
+ continue;
330
+ }
331
+ if (!resolved || resolved.graphPath === from.graphPath)
332
+ continue;
333
+ hasOutbound.add(from.graphPath);
334
+ hasInbound.add(resolved.graphPath);
335
+ }
336
+ }
337
+ const islands = notes
338
+ .filter((note) => !hasOutbound.has(note.graphPath) && !hasInbound.has(note.graphPath))
339
+ .map((note) => note.graphPath)
340
+ .sort((left, right) => left.localeCompare(right));
341
+ return {
342
+ notes: notes.length,
343
+ connected: notes.length - islands.length,
344
+ islands,
345
+ ambiguousLinkNotes: [...ambiguousLinkNotes].sort((left, right) => left.localeCompare(right)),
346
+ };
347
+ }
348
+ /**
349
+ * Source refs a note declares, drawn from frontmatter source keys and from any
350
+ * literal source path mentioned in the body. Mirrors the StageManifest reader so
351
+ * the check and the manifest agree on what a note "cites".
352
+ */
353
+ function noteSourceRefs(content, frontmatter, knownSourcePaths) {
354
+ const refs = new Set();
355
+ for (const key of ["source_refs", "source_ref", "source_path", "source"]) {
356
+ const value = frontmatter[key];
357
+ if (typeof value === "string" && value.trim().length > 0)
358
+ refs.add(value.trim());
359
+ if (Array.isArray(value)) {
360
+ for (const entry of value) {
361
+ if (typeof entry === "string" && entry.trim().length > 0)
362
+ refs.add(entry.trim());
363
+ }
364
+ }
365
+ }
366
+ for (const sourcePath of knownSourcePaths) {
367
+ if (content.includes(sourcePath))
368
+ refs.add(sourcePath);
369
+ }
370
+ return [...refs];
371
+ }
372
+ /**
373
+ * Resolve which summary folders actually exist on disk (folders under the
374
+ * summaries directory that hold a summary or manifest file). Returns a lookup
375
+ * from any reasonable reference form — the folder's graph-relative path and its
376
+ * basename — to the canonical folder path, plus the set of basenames that more
377
+ * than one folder carries. Reuses the same summary/manifest evidence convention
378
+ * the summary-coverage check uses.
379
+ *
380
+ * A basename alias is registered ONLY when that basename is unambiguous across
381
+ * summary folders. When two folders share a basename (e.g. `dept/report.pdf`
382
+ * and `legal/report.pdf` both basename `report.pdf`), aliasing one of them
383
+ * would silently resolve a basename-only ref to the wrong folder and HIDE a
384
+ * real orphan. Ambiguous basenames are left out of the `lookup` so a
385
+ * basename-only ref to them never resolves to one arbitrary folder; the
386
+ * `ambiguousBasenames` set lets the caller treat such a ref as an unresolved
387
+ * gap to surface rather than a silent pass. Full-path refs always resolve
388
+ * regardless of basename collisions.
389
+ */
390
+ function existingSummaryFolderSet(summariesAbsolutePath, options) {
391
+ const lookup = new Map();
392
+ const evidenceDirs = summaryDirectoriesWithEvidence(summariesAbsolutePath, options);
393
+ const folders = [];
394
+ const basenameCounts = new Map();
395
+ for (const dir of evidenceDirs) {
396
+ const folder = normalizeFolderPath(dir);
397
+ if (folder.length === 0)
398
+ continue;
399
+ folders.push(folder);
400
+ // Full-path key always wins; it is unique per folder.
401
+ lookup.set(folder, folder);
402
+ const base = basename(folder);
403
+ if (base)
404
+ basenameCounts.set(base, (basenameCounts.get(base) ?? 0) + 1);
405
+ }
406
+ const ambiguousBasenames = new Set();
407
+ // Second pass: alias a basename to its folder only when exactly one folder
408
+ // carries that basename. Skip any basename that already collides with a
409
+ // full-path key (a folder literally named like another folder's basename) —
410
+ // the path key must not be shadowed.
411
+ for (const folder of folders) {
412
+ const base = basename(folder);
413
+ if (!base || base === folder)
414
+ continue;
415
+ if ((basenameCounts.get(base) ?? 0) > 1) {
416
+ ambiguousBasenames.add(base);
417
+ continue;
418
+ }
419
+ if (lookup.has(base))
420
+ continue;
421
+ lookup.set(base, folder);
422
+ }
423
+ return { lookup, ambiguousBasenames };
424
+ }
425
+ /**
426
+ * Whether one normalized graph path contains another at a path-segment (or
427
+ * trailing-delimiter) boundary, not mid-segment. `decks/q3.pptx` contains
428
+ * `decks/q3.pptx/pages/3` and `decks/q3.pptx#page=25`, but `decks/q3.pptx` does
429
+ * NOT contain `decks/q3.pptx-archive` — the suffix must begin at a `/` or `#`
430
+ * boundary. Source-agnostic: pure string-shape, no task taxonomy.
431
+ */
432
+ function containsAtBoundary(container, inner) {
433
+ if (inner.length === 0 || container.length < inner.length)
434
+ return false;
435
+ const index = container.indexOf(inner);
436
+ if (index < 0)
437
+ return false;
438
+ // Inner must start at a segment boundary (path start or right after a "/").
439
+ if (index > 0 && container[index - 1] !== "/")
440
+ return false;
441
+ // Inner must end at a segment boundary (path end, or before a "/" / "#"
442
+ // anchor). Anything else (e.g. "q3.pptx" inside "q3.pptx-archive") is a
443
+ // mid-segment false match.
444
+ const after = container[index + inner.length];
445
+ return after === undefined || after === "/" || after === "#";
446
+ }
447
+ /**
448
+ * Map an arbitrary source ref to the summary-folder names that could hold its
449
+ * summary. Generic across any Source: a ref like `decks/q3.pptx` matches the
450
+ * `decks/q3.pptx` folder, the basename `q3.pptx` (when unambiguous), or the
451
+ * manifest file id. Never hardcodes a task or filename.
452
+ */
453
+ function summaryFolderCandidatesForRef(ref, manifest, basenameCounts) {
454
+ const normalizedRef = ref.replaceAll("\\", "/");
455
+ const normalized = normalizeFolderPath(ref).replace(/^summaries\//, "");
456
+ const candidates = new Set();
457
+ candidates.add(normalized);
458
+ const base = basename(normalized);
459
+ if (base)
460
+ candidates.add(base);
461
+ // Resolve through the Source Manifest so a page-level or partial ref still
462
+ // points at the file-level summary folder. Match only at path-segment
463
+ // boundaries: a bare substring test over-matches (`q3` would hit
464
+ // `q3-archive`), crediting the wrong summary and hiding a real orphan.
465
+ for (const file of manifest?.files ?? []) {
466
+ const filePath = file.path.replaceAll("\\", "/");
467
+ if (containsAtBoundary(normalizedRef, filePath) ||
468
+ containsAtBoundary(filePath, normalized) ||
469
+ containsAtBoundary(normalized, filePath)) {
470
+ candidates.add(filePath);
471
+ if (basenameCounts.get(basename(filePath)) === 1)
472
+ candidates.add(basename(filePath));
473
+ candidates.add(file.id);
474
+ }
475
+ }
476
+ return [...candidates].filter((candidate) => candidate.length > 0);
477
+ }
478
+ /**
479
+ * Orphaned-summary analysis for a knowledge-style layer: a summary folder that is
480
+ * cited by some note's source_refs but is wikilinked by no note in the layer.
481
+ * Fully generic — derives expected backlinks from the notes' own refs and the
482
+ * Source Manifest, with no project-specific or task-specific input.
483
+ */
484
+ function analyzeSummaryBacklinks(layerDir, context, options) {
485
+ const root = resolve(context.rootPath);
486
+ const manifest = loadSourceManifestForCheck(context).manifest;
487
+ const knownSourcePaths = manifest?.files.map((file) => file.path.replaceAll("\\", "/")) ?? [];
488
+ const basenameCounts = new Map();
489
+ for (const path of knownSourcePaths) {
490
+ const base = basename(path);
491
+ basenameCounts.set(base, (basenameCounts.get(base) ?? 0) + 1);
492
+ }
493
+ // Summary folders that actually exist on disk. You can only orphan a summary
494
+ // that exists — a note citing a source with no summary folder is not a
495
+ // backlink violation (there is nothing to link to). The lookup keys a folder
496
+ // by its full path and, only when unambiguous, its basename; ambiguousBasenames
497
+ // names the basenames carried by more than one folder.
498
+ const { lookup: existingSummaryFolders, ambiguousBasenames } = existingSummaryFolderSet(join(root, options.summariesDir), { summaryFile: options.summaryFile, manifestFile: options.manifestFile });
499
+ // Resolve a ref to a canonical summary folder. Three outcomes:
500
+ // "resolved" — a candidate matched an existing folder.
501
+ // "ambiguous" — nothing matched, but a basename candidate collides with two
502
+ // or more existing folders, so we refuse to pick one. This is a
503
+ // gap to surface, not a clean miss.
504
+ // "absent" — nothing matched and no summary folder exists for the ref.
505
+ const resolveCitedFolder = (ref) => {
506
+ const candidates = summaryFolderCandidatesForRef(ref, manifest, basenameCounts);
507
+ for (const candidate of candidates) {
508
+ const folder = existingSummaryFolders.get(candidate);
509
+ if (folder)
510
+ return { kind: "resolved", folder };
511
+ }
512
+ if (candidates.some((candidate) => ambiguousBasenames.has(candidate))) {
513
+ return { kind: "ambiguous" };
514
+ }
515
+ return { kind: "absent" };
516
+ };
517
+ // Set of every summary-folder backlink wikilink target present anywhere in the
518
+ // layer, normalized to the folder name (drop the trailing summary/manifest leaf).
519
+ const linkedSummaries = new Set();
520
+ const summaryLeaf = normalizeGraphPath(options.summaryFile);
521
+ const manifestLeaf = normalizeGraphPath(options.manifestFile);
522
+ const citedSummaryToNotes = new Map();
523
+ const notesWithUnlinkedCitations = new Set();
524
+ const notesWithAmbiguousCitations = new Set();
525
+ let notesScanned = 0;
526
+ const notes = listMarkdownFiles(layerDir);
527
+ // First pass: every existing summary folder linked anywhere in the layer,
528
+ // resolved to its canonical folder name so basename and path links agree. A
529
+ // wikilink is a concrete graph path, so it credits a backlink ONLY when the
530
+ // target resolves to a real summary folder. `existingSummaryFolders` already
531
+ // keys both each folder's full path and its basename (the latter only when
532
+ // unambiguous), so a legitimate bare-basename link still resolves. We do NOT
533
+ // fall back to a separate basename(folderRef) lookup: that would let a broken
534
+ // wikilink — one whose folder path does not exist (e.g.
535
+ // `[[summaries/typo/q3.pptx/summary]]`) — launder through its basename and
536
+ // wrongly credit an unrelated namesake folder, hiding a real orphan.
537
+ for (const note of notes) {
538
+ const content = readFileSync(note, "utf8");
539
+ for (const target of noteWikilinkTargets(content)) {
540
+ const withinSummaries = target.startsWith(`${options.summariesDir}/`)
541
+ ? target.slice(options.summariesDir.length + 1)
542
+ : null;
543
+ if (withinSummaries === null)
544
+ continue;
545
+ const segments = withinSummaries.split("/");
546
+ const leaf = segments[segments.length - 1] ?? "";
547
+ const folderRef = leaf === summaryLeaf || leaf === manifestLeaf
548
+ ? segments.slice(0, -1).join("/")
549
+ : withinSummaries;
550
+ const folder = existingSummaryFolders.get(folderRef);
551
+ if (folder)
552
+ linkedSummaries.add(folder);
553
+ }
554
+ }
555
+ // Second pass: every existing summary a note cites, and whether it is linked.
556
+ for (const note of notes) {
557
+ notesScanned += 1;
558
+ const content = readFileSync(note, "utf8");
559
+ const parsed = parseJsonFrontmatter(content);
560
+ const frontmatter = parsed?.frontmatter ?? {};
561
+ const refs = noteSourceRefs(content, frontmatter, knownSourcePaths);
562
+ if (refs.length === 0)
563
+ continue;
564
+ const noteRel = relative(root, note).replaceAll("\\", "/");
565
+ let noteHasUnlinked = false;
566
+ let noteHasAmbiguous = false;
567
+ for (const ref of refs) {
568
+ const resolution = resolveCitedFolder(ref);
569
+ // An ambiguous basename-only citation cannot be proven backlinked; treat it
570
+ // as a surfaced gap rather than skipping it (which would hide the orphan).
571
+ if (resolution.kind === "ambiguous") {
572
+ noteHasAmbiguous = true;
573
+ continue;
574
+ }
575
+ // Only an existing summary folder can be orphaned; skip refs that point at
576
+ // a source with no summary in this graph.
577
+ if (resolution.kind === "absent")
578
+ continue;
579
+ const folder = resolution.folder;
580
+ const linked = linkedSummaries.has(folder);
581
+ const list = citedSummaryToNotes.get(folder) ?? [];
582
+ list.push(noteRel);
583
+ citedSummaryToNotes.set(folder, list);
584
+ if (!linked)
585
+ noteHasUnlinked = true;
586
+ }
587
+ if (noteHasUnlinked)
588
+ notesWithUnlinkedCitations.add(noteRel);
589
+ if (noteHasAmbiguous)
590
+ notesWithAmbiguousCitations.add(noteRel);
591
+ }
592
+ const citedSummaries = [...citedSummaryToNotes.keys()];
593
+ const orphanedSummaries = citedSummaries
594
+ .filter((summary) => !linkedSummaries.has(summary))
595
+ .sort((left, right) => left.localeCompare(right));
596
+ return {
597
+ citedSummaries,
598
+ linkedSummaries,
599
+ orphanedSummaries,
600
+ ambiguousCitations: [...notesWithAmbiguousCitations].sort((left, right) => left.localeCompare(right)),
601
+ notesWithUnlinkedCitations: [...notesWithUnlinkedCitations].sort((left, right) => left.localeCompare(right)),
602
+ notesScanned,
603
+ };
604
+ }
605
+ function checkPhrases(check) {
606
+ if (Array.isArray(check.params?.phrases)) {
607
+ return check.params.phrases.filter((phrase) => typeof phrase === "string");
608
+ }
609
+ if (typeof check.params?.text === "string") {
610
+ return [check.params.text];
611
+ }
612
+ return [];
613
+ }
614
+ function frontmatterKeys(check) {
615
+ return Array.isArray(check.params?.keys)
616
+ ? check.params.keys.filter((key) => typeof key === "string" && key.trim().length > 0)
617
+ : [];
618
+ }
619
+ function hasNonEmptyFrontmatterValue(value) {
620
+ if (typeof value === "string")
621
+ return value.trim().length > 0;
622
+ if (typeof value === "number" || typeof value === "boolean")
623
+ return true;
624
+ if (Array.isArray(value))
625
+ return value.some(hasNonEmptyFrontmatterValue);
626
+ if (value && typeof value === "object")
627
+ return Object.keys(value).length > 0;
628
+ return false;
629
+ }
630
+ function collectFrontmatterFailures(files, predicate) {
631
+ const invalid = [];
632
+ const missing = [];
633
+ for (const file of files) {
634
+ const parsed = parseJsonFrontmatter(readFileSync(file, "utf8"));
635
+ if (!parsed) {
636
+ invalid.push(file);
637
+ continue;
638
+ }
639
+ if (!predicate(parsed.frontmatter)) {
640
+ missing.push(file);
641
+ }
642
+ }
643
+ return { invalid, missing };
644
+ }
645
+ /**
646
+ * Declarative exemption clause for `source_refs_required`: `params.exempt_when`
647
+ * maps a frontmatter key to the values that exempt a note from the requirement
648
+ * (an empty value list means "any non-empty value exempts"). A note is exempt
649
+ * when it declares any matching signal — e.g. a pure index/navigation note that
650
+ * asserts nothing can opt out with `note_role: index` rather than being pushed
651
+ * to fabricate `source_refs`. This is config, not engine taxonomy: the keys and
652
+ * values live in the Build Plan, so no concept is hardcoded in the runtime.
653
+ */
654
+ function exemptWhenClause(check) {
655
+ const raw = check.params?.exempt_when;
656
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
657
+ return {};
658
+ const out = {};
659
+ for (const [key, value] of Object.entries(raw)) {
660
+ if (typeof key !== "string" || key.trim().length === 0)
661
+ continue;
662
+ out[key] = Array.isArray(value)
663
+ ? value.filter((entry) => typeof entry === "string").map((entry) => entry.trim().toLowerCase())
664
+ : [];
665
+ }
666
+ return out;
667
+ }
668
+ function isExemptFromSourceRefs(frontmatter, exemptWhen) {
669
+ for (const [key, allowed] of Object.entries(exemptWhen)) {
670
+ const value = frontmatter[key];
671
+ if (!hasNonEmptyFrontmatterValue(value))
672
+ continue;
673
+ if (allowed.length === 0)
674
+ return true;
675
+ if (typeof value === "string" && allowed.includes(value.trim().toLowerCase()))
676
+ return true;
677
+ if (Array.isArray(value) && value.some((entry) => typeof entry === "string" && allowed.includes(entry.trim().toLowerCase()))) {
678
+ return true;
679
+ }
680
+ }
681
+ return false;
682
+ }
683
+ function loadSourceManifestForCheck(context, path = ".interf/runtime/source-manifest.json") {
684
+ const manifestPath = resolve(context.rootPath, path);
685
+ if (!existsSync(manifestPath))
686
+ return { manifest: null, error: `Source Manifest does not exist at ${path}.` };
687
+ try {
688
+ const manifest = SourceManifestSchema.parse(JSON.parse(readFileSync(manifestPath, "utf8")));
689
+ return { manifest, error: null };
690
+ }
691
+ catch (error) {
692
+ return {
693
+ manifest: null,
694
+ error: `Source Manifest is invalid: ${error instanceof Error ? error.message : String(error)}`,
695
+ };
696
+ }
697
+ }
698
+ function loadRequiredSourceManifestForCheck(check, context) {
699
+ const manifestPath = typeof check.params?.manifest_path === "string"
700
+ ? check.params.manifest_path
701
+ : ".interf/runtime/source-manifest.json";
702
+ const loaded = loadSourceManifestForCheck(context, manifestPath);
703
+ if (loaded.manifest)
704
+ return { manifest: loaded.manifest, failure: null };
705
+ return {
706
+ manifest: null,
707
+ failure: makeCheckResult(check, false, loaded.error ?? "Source Manifest is required for this source-bound check.", { manifest_path: manifestPath }),
708
+ };
709
+ }
710
+ function sourceManifestPageTotal(context) {
711
+ const loaded = loadSourceManifestForCheck(context);
712
+ if (!loaded.manifest)
713
+ return { total: 0, error: loaded.error };
714
+ let total = 0;
715
+ for (const file of loaded.manifest.files) {
716
+ const pageUnits = file.inspectable_units.filter((unit) => unit.kind === "page").length;
717
+ total += pageUnits > 0 ? pageUnits : file.page_count ?? 0;
718
+ }
719
+ return { total, error: null };
720
+ }
721
+ const EVALUATORS = {
722
+ file_exists(check, context) {
723
+ const target = resolveTargetPath(check, context);
724
+ if (!target) {
725
+ return makeCheckResult(check, false, "No target path provided for file_exists check.");
726
+ }
727
+ if (!existsSync(target)) {
728
+ return makeCheckResult(check, false, "File or directory does not exist.", { path: context.targetPath });
729
+ }
730
+ try {
731
+ const stats = statSync(target);
732
+ if (stats.isFile() && stats.size === 0) {
733
+ return makeCheckResult(check, false, "File exists but is empty.", { path: context.targetPath });
734
+ }
735
+ return makeCheckResult(check, true, `Path exists (${stats.isDirectory() ? "directory" : "file"}).`);
736
+ }
737
+ catch {
738
+ return makeCheckResult(check, false, "Could not stat target path.", { path: context.targetPath });
739
+ }
740
+ },
741
+ source_manifest_valid(check, context) {
742
+ const manifestPath = typeof check.params?.path === "string"
743
+ ? check.params.path
744
+ : ".interf/runtime/source-manifest.json";
745
+ const loaded = loadSourceManifestForCheck(context, manifestPath);
746
+ if (!loaded.manifest) {
747
+ return makeCheckResult(check, false, loaded.error ?? "Source Manifest is invalid.");
748
+ }
749
+ return makeCheckResult(check, true, `Source Manifest lists ${loaded.manifest.source_total} source file(s).`, { source_manifest_id: loaded.manifest.manifest_id, source_total: loaded.manifest.source_total });
750
+ },
751
+ min_file_count(check, context) {
752
+ const target = resolveTargetPath(check, context);
753
+ if (!target) {
754
+ return makeCheckResult(check, false, "No target path provided for min_file_count check.");
755
+ }
756
+ const min = typeof check.params?.min === "number" ? check.params.min : 1;
757
+ const actual = countFiles(target);
758
+ if (actual >= min) {
759
+ return makeCheckResult(check, true, `${actual} file(s) (≥ ${min}).`, { actual, min });
760
+ }
761
+ return makeCheckResult(check, false, `Found ${actual} file(s); expected at least ${min}.`, { actual, min });
762
+ },
763
+ min_file_count_matches_source(check, context) {
764
+ const target = resolveTargetPath(check, context);
765
+ if (!target) {
766
+ return makeCheckResult(check, false, "No target path provided for min_file_count_matches_source check.");
767
+ }
768
+ const sourceManifest = loadRequiredSourceManifestForCheck(check, context);
769
+ if (!sourceManifest.manifest)
770
+ return sourceManifest.failure ?? makeCheckResult(check, false, "Source Manifest is required.");
771
+ const matchKey = typeof check.params?.match === "string" ? check.params.match : "source_total";
772
+ const expected = matchKey === "source_total"
773
+ ? sourceManifest.manifest.source_total
774
+ : context.counts?.[matchKey];
775
+ if (typeof expected !== "number") {
776
+ return makeCheckResult(check, false, `Cannot evaluate: count "${matchKey}" not available in context.`, { matchKey });
777
+ }
778
+ if (expected <= 0) {
779
+ return makeCheckResult(check, false, `Cannot evaluate: count "${matchKey}" must be greater than zero.`, { matchKey, expected });
780
+ }
781
+ const actual = countFiles(target);
782
+ if (actual >= expected) {
783
+ return makeCheckResult(check, true, `${actual} of ${expected} source files covered.`, { actual, expected, source_manifest_id: sourceManifest.manifest.manifest_id });
784
+ }
785
+ return makeCheckResult(check, false, `Only ${actual} of ${expected} source files covered.`, { actual, expected, matchKey, source_manifest_id: sourceManifest.manifest.manifest_id });
786
+ },
787
+ source_summary_folders(check, context) {
788
+ const target = resolveTargetPath(check, context);
789
+ if (!target) {
790
+ return makeCheckResult(check, false, "No target path provided for source_summary_folders check.");
791
+ }
792
+ const sourceManifest = loadRequiredSourceManifestForCheck(check, context);
793
+ if (!sourceManifest.manifest)
794
+ return sourceManifest.failure ?? makeCheckResult(check, false, "Source Manifest is required.");
795
+ const matchKey = typeof check.params?.match === "string" ? check.params.match : "source_total";
796
+ const expected = matchKey === "source_total"
797
+ ? sourceManifest.manifest.source_total
798
+ : context.counts?.[matchKey];
799
+ if (typeof expected !== "number") {
800
+ return makeCheckResult(check, false, `Cannot evaluate: count "${matchKey}" not available in context.`, { matchKey });
801
+ }
802
+ if (expected <= 0) {
803
+ return makeCheckResult(check, false, `Cannot evaluate: count "${matchKey}" must be greater than zero.`, { matchKey, expected });
804
+ }
805
+ const summaryFile = typeof check.params?.summary_file === "string" ? check.params.summary_file : "summary.md";
806
+ const manifestFile = typeof check.params?.manifest_file === "string" ? check.params.manifest_file : "manifest.md";
807
+ const coverage = matchKey === "source_total"
808
+ ? sourceSummaryCoverage(target, sourceManifest.manifest, { summaryFile, manifestFile })
809
+ : {
810
+ ...countSourceSummaryFolders(target, { summaryFile, manifestFile }),
811
+ expected,
812
+ missingSourcePaths: [],
813
+ extraSummaryFolders: [],
814
+ };
815
+ if (coverage.actual >= expected) {
816
+ return makeCheckResult(check, true, `${coverage.actual} source summary folder(s) covered for ${expected} source file(s).`, {
817
+ actual: coverage.actual,
818
+ expected,
819
+ summaryFile,
820
+ manifestFile,
821
+ source_manifest_id: sourceManifest.manifest.manifest_id,
822
+ extraSummaryFolders: coverage.extraSummaryFolders,
823
+ });
824
+ }
825
+ return makeCheckResult(check, false, `Only ${coverage.actual} source summary folder(s) covered for ${expected} source file(s).`, {
826
+ actual: coverage.actual,
827
+ expected,
828
+ matchKey,
829
+ summaryFile,
830
+ manifestFile,
831
+ missingSourcePaths: coverage.missingSourcePaths,
832
+ extraSummaryFolders: coverage.extraSummaryFolders,
833
+ });
834
+ },
835
+ source_page_coverage(check, context) {
836
+ const target = resolveTargetPath(check, context);
837
+ if (!target) {
838
+ return makeCheckResult(check, false, "No target path provided for source_page_coverage check.");
839
+ }
840
+ const expected = sourceManifestPageTotal(context);
841
+ if (expected.error)
842
+ return makeCheckResult(check, false, expected.error);
843
+ if (expected.total <= 0) {
844
+ if (check.params?.allow_no_pages === true) {
845
+ return makeCheckResult(check, true, "Source Manifest does not declare page-level units.");
846
+ }
847
+ return makeCheckResult(check, false, "Source Manifest does not declare page-level units. Add page_count or page inspectable_units, or set params.allow_no_pages explicitly.");
848
+ }
849
+ const pagesDir = typeof check.params?.pages_dir === "string" ? check.params.pages_dir : "pages";
850
+ const summaryFile = typeof check.params?.summary_file === "string" ? check.params.summary_file : "summary.md";
851
+ const actual = listFilesRecursive(target)
852
+ .filter((file) => file.endsWith(`/${summaryFile}`) && file.includes(`/${pagesDir}/`))
853
+ .length;
854
+ if (actual >= expected.total) {
855
+ return makeCheckResult(check, true, `${actual} page summary file(s) cover ${expected.total} page unit(s).`, { actual, expected: expected.total });
856
+ }
857
+ return makeCheckResult(check, false, `Only ${actual} page summary file(s) cover ${expected.total} page unit(s).`, { actual, expected: expected.total, pagesDir, summaryFile });
858
+ },
859
+ frontmatter_valid(check, context) {
860
+ const target = resolveTargetPath(check, context);
861
+ if (!target) {
862
+ return makeCheckResult(check, false, "No target path provided for frontmatter_valid check.");
863
+ }
864
+ const files = listMarkdownFiles(target);
865
+ if (files.length === 0) {
866
+ return makeCheckResult(check, true, "No markdown files to validate.");
867
+ }
868
+ const validation = validateSynthFiles(files);
869
+ if (validation.invalid_frontmatter === 0) {
870
+ return makeCheckResult(check, true, `${files.length} markdown file(s) have valid frontmatter.`);
871
+ }
872
+ return makeCheckResult(check, false, `${validation.invalid_frontmatter} of ${files.length} markdown file(s) have invalid frontmatter.`, { invalid: validation.invalid_frontmatter, total: files.length });
873
+ },
874
+ frontmatter_required_keys(check, context) {
875
+ const target = resolveTargetPath(check, context);
876
+ if (!target) {
877
+ return makeCheckResult(check, false, "No target path provided for frontmatter_required_keys check.");
878
+ }
879
+ const keys = frontmatterKeys(check);
880
+ if (keys.length === 0) {
881
+ return makeCheckResult(check, false, "Build Plan check is missing required frontmatter keys. Use `params.keys: string[]`.");
882
+ }
883
+ const files = listMarkdownFiles(target);
884
+ if (files.length === 0) {
885
+ return makeCheckResult(check, true, "No markdown files to validate.");
886
+ }
887
+ const validation = validateSynthFiles(files, { requiredFrontmatterKeys: keys });
888
+ if (validation.invalid_frontmatter === 0) {
889
+ return makeCheckResult(check, true, `All ${files.length} markdown file(s) have required keys: ${keys.join(", ")}.`);
890
+ }
891
+ return makeCheckResult(check, false, `${validation.invalid_frontmatter} of ${files.length} file(s) missing required keys (${keys.join(", ")}).`, { invalid: validation.invalid_frontmatter, total: files.length, requiredKeys: keys });
892
+ },
893
+ frontmatter_nonempty_keys(check, context) {
894
+ const target = resolveTargetPath(check, context);
895
+ if (!target) {
896
+ return makeCheckResult(check, false, "No target path provided for frontmatter_nonempty_keys check.");
897
+ }
898
+ const keys = frontmatterKeys(check);
899
+ if (keys.length === 0) {
900
+ return makeCheckResult(check, false, "Build Plan check is missing non-empty frontmatter keys. Use `params.keys: string[]`.");
901
+ }
902
+ const files = listMarkdownFiles(target);
903
+ if (files.length === 0) {
904
+ return makeCheckResult(check, false, "No markdown files to validate.");
905
+ }
906
+ const { invalid, missing } = collectFrontmatterFailures(files, (frontmatter) => keys.every((key) => hasNonEmptyFrontmatterValue(frontmatter[key])));
907
+ if (invalid.length === 0 && missing.length === 0) {
908
+ return makeCheckResult(check, true, `All ${files.length} markdown file(s) have non-empty keys: ${keys.join(", ")}.`);
909
+ }
910
+ return makeCheckResult(check, false, `${invalid.length + missing.length} of ${files.length} markdown file(s) are missing non-empty keys (${keys.join(", ")}).`, { invalid, missing, requiredKeys: keys });
911
+ },
912
+ source_refs_required(check, context) {
913
+ const target = resolveTargetPath(check, context);
914
+ if (!target) {
915
+ return makeCheckResult(check, false, "No target path provided for source_refs_required check.");
916
+ }
917
+ const keys = frontmatterKeys(check);
918
+ const sourceRefKeys = keys.length > 0 ? keys : ["source_refs", "source_ref", "source_path"];
919
+ const exemptWhen = exemptWhenClause(check);
920
+ const files = listMarkdownFiles(target);
921
+ if (files.length === 0) {
922
+ return makeCheckResult(check, false, "No markdown files to validate.");
923
+ }
924
+ const { invalid, missing } = collectFrontmatterFailures(files, (frontmatter) => isExemptFromSourceRefs(frontmatter, exemptWhen)
925
+ || sourceRefKeys.some((key) => hasNonEmptyFrontmatterValue(frontmatter[key])));
926
+ if (invalid.length === 0 && missing.length === 0) {
927
+ return makeCheckResult(check, true, `All ${files.length} markdown file(s) have source refs.`);
928
+ }
929
+ return makeCheckResult(check, false, `${invalid.length + missing.length} of ${files.length} markdown file(s) are missing source refs.`, { invalid, missing, sourceRefKeys });
930
+ },
931
+ summary_backlinks_present(check, context) {
932
+ const target = resolveTargetPath(check, context);
933
+ if (!target) {
934
+ return makeCheckResult(check, false, "No target path provided for summary_backlinks_present check.");
935
+ }
936
+ const summariesDir = typeof check.params?.summaries_dir === "string" ? check.params.summaries_dir : "summaries";
937
+ const summaryFile = typeof check.params?.summary_file === "string" ? check.params.summary_file : "summary.md";
938
+ const manifestFile = typeof check.params?.manifest_file === "string" ? check.params.manifest_file : "manifest.md";
939
+ const analysis = analyzeSummaryBacklinks(target, context, { summariesDir, summaryFile, manifestFile });
940
+ // An ambiguous basename-only citation cannot be proven backlinked: it names a
941
+ // basename two or more summary folders share, so we refuse to credit one
942
+ // arbitrarily. Surface it as a gap rather than silently passing — that is the
943
+ // exact orphan the old first-basename-wins alias hid. Fails even when nothing
944
+ // resolved cleanly, because the citation itself is unverifiable.
945
+ if (analysis.ambiguousCitations.length > 0) {
946
+ return makeCheckResult(check, false, `${analysis.ambiguousCitations.length} note(s) cite a summary by an ambiguous basename that two or more summary folders share; cite the full summary-folder path so the backlink can be verified.`, {
947
+ cited_summaries: analysis.citedSummaries.length,
948
+ linked_summaries: analysis.linkedSummaries.size,
949
+ ambiguous_citations: analysis.ambiguousCitations.length,
950
+ notes_with_ambiguous_citations: analysis.ambiguousCitations,
951
+ orphaned_summaries: analysis.orphanedSummaries.length,
952
+ orphaned_summary_folders: analysis.orphanedSummaries,
953
+ });
954
+ }
955
+ if (analysis.citedSummaries.length === 0) {
956
+ return makeCheckResult(check, true, "No notes cite a summary source ref yet; nothing to backlink.", { notesScanned: analysis.notesScanned });
957
+ }
958
+ if (analysis.orphanedSummaries.length === 0) {
959
+ return makeCheckResult(check, true, `All ${analysis.citedSummaries.length} cited summary folder(s) are wikilinked from this layer.`, {
960
+ cited_summaries: analysis.citedSummaries.length,
961
+ linked_summaries: analysis.linkedSummaries.size,
962
+ orphaned_summaries: 0,
963
+ });
964
+ }
965
+ return makeCheckResult(check, false, `${analysis.orphanedSummaries.length} of ${analysis.citedSummaries.length} cited summary folder(s) are orphaned (cited via source_refs but never wikilinked).`, {
966
+ cited_summaries: analysis.citedSummaries.length,
967
+ linked_summaries: analysis.linkedSummaries.size,
968
+ orphaned_summaries: analysis.orphanedSummaries.length,
969
+ orphaned_summary_folders: analysis.orphanedSummaries,
970
+ notes_with_unlinked_citations: analysis.notesWithUnlinkedCitations,
971
+ });
972
+ },
973
+ knowledge_web_connectivity(check, context) {
974
+ const target = resolveTargetPath(check, context);
975
+ if (!target) {
976
+ return makeCheckResult(check, false, "No target path provided for knowledge_web_connectivity check.");
977
+ }
978
+ // The artifact's target path IS the knowledge layer to scan — derived from the
979
+ // artifact this check is attached to, never hardcoded to `knowledge/` from the
980
+ // root. `params.knowledge_dir` is a graph-root-relative override that may only
981
+ // NARROW the scan to a sub-directory inside the target; an override that escapes
982
+ // the target layer (e.g. a sibling layer) is rejected so the check cannot be
983
+ // pointed at a different, possibly-passing subtree.
984
+ let scanRoot = target;
985
+ if (typeof check.params?.knowledge_dir === "string" && check.params.knowledge_dir.trim().length > 0) {
986
+ const narrowed = resolve(context.rootPath, check.params.knowledge_dir);
987
+ if (narrowed !== target && !narrowed.startsWith(`${target}/`)) {
988
+ return makeCheckResult(check, false, "params.knowledge_dir resolves outside this check's target layer; it may only narrow the scan inside the target.", { knowledge_dir: check.params.knowledge_dir, target_dir: context.targetPath });
989
+ }
990
+ scanRoot = narrowed;
991
+ }
992
+ const web = analyzeNoteWeb(listMarkdownFiles(scanRoot), context);
993
+ // A bare basename two or more notes share cannot be proven to connect either
994
+ // namesake, so it credits no edge — surface it as a "cite the full path" gap
995
+ // rather than silently connecting the linker and hiding an island. Fails even
996
+ // when no island remains, because the link itself is unverifiable.
997
+ if (web.ambiguousLinkNotes.length > 0) {
998
+ return makeCheckResult(check, false, `${web.ambiguousLinkNotes.length} knowledge note(s) link another knowledge note by an ambiguous basename two or more notes share; link the full graph path so the web edge can be verified.`, {
999
+ knowledge_notes: web.notes,
1000
+ connected: web.connected,
1001
+ islands: web.islands.length,
1002
+ island_notes: web.islands,
1003
+ ambiguous_links: web.ambiguousLinkNotes.length,
1004
+ notes_with_ambiguous_links: web.ambiguousLinkNotes,
1005
+ });
1006
+ }
1007
+ if (web.islands.length === 0) {
1008
+ return makeCheckResult(check, true, web.notes <= 1
1009
+ ? `${web.notes} knowledge note(s); too few to form a web.`
1010
+ : `${web.connected} / ${web.notes} knowledge notes link another knowledge note.`, { knowledge_notes: web.notes, connected: web.connected, islands: 0 });
1011
+ }
1012
+ return makeCheckResult(check, false, `${web.islands.length} of ${web.notes} knowledge note(s) link no other knowledge note (disconnected island${web.islands.length === 1 ? "" : "s"}).`, {
1013
+ knowledge_notes: web.notes,
1014
+ connected: web.connected,
1015
+ islands: web.islands.length,
1016
+ island_notes: web.islands,
1017
+ });
1018
+ },
1019
+ /**
1020
+ * Whole-graph connectivity floor. The `knowledge_web_connectivity` check only
1021
+ * scans the `knowledge/` layer, and `summary_backlinks_present` only flags a
1022
+ * summary that a knowledge note CITES via source_refs but does not wikilink.
1023
+ * Neither sees a summary that no note cites at all — so a Context Graph can
1024
+ * pass readiness while the bulk of its `summaries/` notes are free-floating
1025
+ * islands no entrypoint or note reaches. This is the "no disconnected island
1026
+ * passing silently" rule applied to the WHOLE graph, not just the 7 knowledge
1027
+ * notes: every markdown note across `summaries/`, `knowledge/`, `artifacts/`,
1028
+ * and `home.md` must have undirected degree ≥ 1 in the note-link web. A note no
1029
+ * other note links AND that links no other note is a disconnected island and
1030
+ * fails readiness. Connectedness, not a count — one genuine inbound or outbound
1031
+ * edge is enough. By default the scan root is the Context Graph root; a Build
1032
+ * Plan may pass `params.graph_root` to narrow the floor to one layer (it may
1033
+ * only narrow inside the graph root, never escape it).
1034
+ */
1035
+ graph_notes_connected(check, context) {
1036
+ // Default scope: the canonical CONTENT layers of the whole Context Graph
1037
+ // (summaries/, knowledge/, artifacts/, home.md) as one web — NOT the raw graph
1038
+ // root, which also holds runtime scaffolding (.interf/, .claude/, SKILL.md
1039
+ // docs, CLAUDE.md) that is not graph content and would inject false islands.
1040
+ // Intentionally spans all CONTENT layers, not `targetPath`: a knowledge note
1041
+ // or an entrypoint route may be the only thing connecting a summary, so scoping
1042
+ // to one layer in isolation would re-introduce the uncited-summary-island bug.
1043
+ const graphRoot = resolve(context.rootPath);
1044
+ let noteFiles;
1045
+ if (typeof check.params?.graph_root === "string" && check.params.graph_root.trim().length > 0) {
1046
+ const narrowed = resolve(graphRoot, check.params.graph_root);
1047
+ // May only NARROW inside the graph root; an override that escapes the root
1048
+ // is rejected so the floor cannot be pointed at a different, passing tree.
1049
+ if (narrowed !== graphRoot && !narrowed.startsWith(`${graphRoot}/`)) {
1050
+ return makeCheckResult(check, false, "params.graph_root resolves outside the Context Graph root; it may only narrow the connectivity floor to a path inside the graph.", { graph_root: check.params.graph_root });
1051
+ }
1052
+ // A narrowed scope scans that subtree's markdown notes directly (the caller
1053
+ // is naming a content path, so no scaffolding filter is applied beyond the
1054
+ // shared output-markdown filter).
1055
+ noteFiles = listMarkdownFiles(narrowed);
1056
+ }
1057
+ else {
1058
+ noteFiles = collectGraphContentNotes(graphRoot);
1059
+ }
1060
+ const web = analyzeNoteWeb(noteFiles, context);
1061
+ if (web.ambiguousLinkNotes.length > 0) {
1062
+ return makeCheckResult(check, false, `${web.ambiguousLinkNotes.length} note(s) link another note by an ambiguous basename two or more notes share; link the full graph path so the web edge can be verified.`, {
1063
+ graph_notes: web.notes,
1064
+ connected: web.connected,
1065
+ islands: web.islands.length,
1066
+ island_notes: web.islands,
1067
+ ambiguous_links: web.ambiguousLinkNotes.length,
1068
+ notes_with_ambiguous_links: web.ambiguousLinkNotes,
1069
+ });
1070
+ }
1071
+ if (web.islands.length === 0) {
1072
+ return makeCheckResult(check, true, web.notes <= 1
1073
+ ? `${web.notes} note(s); too few to form a web.`
1074
+ : `${web.connected} / ${web.notes} notes are link-connected to another note (no islands).`, { graph_notes: web.notes, connected: web.connected, islands: 0 });
1075
+ }
1076
+ return makeCheckResult(check, false, `${web.islands.length} of ${web.notes} note(s) link no other note and are linked by none (disconnected island${web.islands.length === 1 ? "" : "s"}). Every Context Graph note — including every summary — must be reachable through the link web.`, {
1077
+ graph_notes: web.notes,
1078
+ connected: web.connected,
1079
+ islands: web.islands.length,
1080
+ island_notes: web.islands,
1081
+ });
1082
+ },
1083
+ wikilinks_valid(check, context) {
1084
+ const target = resolveTargetPath(check, context);
1085
+ if (!target) {
1086
+ return makeCheckResult(check, false, "No target path provided for wikilinks_valid check.");
1087
+ }
1088
+ const broken = countBrokenWikilinks(context.rootPath, [context.rootPath], [target]);
1089
+ if (broken === 0) {
1090
+ return makeCheckResult(check, true, "All wikilinks resolve.");
1091
+ }
1092
+ return makeCheckResult(check, false, `${broken} broken wikilink(s).`, { broken });
1093
+ },
1094
+ must_not_contain(check, context) {
1095
+ const target = resolveTargetPath(check, context);
1096
+ if (!target || !existsSync(target)) {
1097
+ return makeCheckResult(check, false, "Target path does not exist.", { path: context.targetPath });
1098
+ }
1099
+ const phrases = checkPhrases(check);
1100
+ if (phrases.length === 0) {
1101
+ return makeCheckResult(check, false, "Requested output diagnostic is missing forbidden text. Use `params.phrases: string[]` or `params.text: string`.");
1102
+ }
1103
+ try {
1104
+ const stats = statSync(target);
1105
+ const files = stats.isFile() ? [target] : listFilesRecursive(target, () => true);
1106
+ const offenders = [];
1107
+ for (const file of files) {
1108
+ const content = readFileSync(file, "utf8");
1109
+ for (const phrase of phrases) {
1110
+ if (content.includes(phrase)) {
1111
+ offenders.push(`${file}: contains "${phrase}"`);
1112
+ break;
1113
+ }
1114
+ }
1115
+ }
1116
+ if (offenders.length === 0) {
1117
+ return makeCheckResult(check, true, `No forbidden phrases found across ${files.length} file(s).`);
1118
+ }
1119
+ return makeCheckResult(check, false, `${offenders.length} file(s) contain forbidden phrases.`, { offenders, phrases });
1120
+ }
1121
+ catch (error) {
1122
+ return makeCheckResult(check, false, `Error reading target: ${error instanceof Error ? error.message : String(error)}`);
1123
+ }
1124
+ },
1125
+ must_contain(check, context) {
1126
+ const target = resolveTargetPath(check, context);
1127
+ if (!target || !existsSync(target)) {
1128
+ return makeCheckResult(check, false, "Target path does not exist.", { path: context.targetPath });
1129
+ }
1130
+ const phrases = checkPhrases(check);
1131
+ if (phrases.length === 0) {
1132
+ return makeCheckResult(check, false, "Requested output diagnostic is missing required text. Use `params.phrases: string[]` or `params.text: string`.");
1133
+ }
1134
+ try {
1135
+ const stats = statSync(target);
1136
+ // For directories, must_contain checks across ALL files (every phrase
1137
+ // must appear in at least one file). For files, all phrases must
1138
+ // appear in that single file.
1139
+ if (stats.isFile()) {
1140
+ const content = readFileSync(target, "utf8");
1141
+ const missing = phrases.filter((phrase) => !content.includes(phrase));
1142
+ if (missing.length === 0) {
1143
+ return makeCheckResult(check, true, `All ${phrases.length} required phrase(s) present.`);
1144
+ }
1145
+ return makeCheckResult(check, false, `${missing.length} required phrase(s) missing.`, { missing });
1146
+ }
1147
+ const files = listFilesRecursive(target, () => true);
1148
+ const remaining = new Set(phrases);
1149
+ for (const file of files) {
1150
+ const content = readFileSync(file, "utf8");
1151
+ for (const phrase of [...remaining]) {
1152
+ if (content.includes(phrase))
1153
+ remaining.delete(phrase);
1154
+ }
1155
+ if (remaining.size === 0)
1156
+ break;
1157
+ }
1158
+ if (remaining.size === 0) {
1159
+ return makeCheckResult(check, true, `All ${phrases.length} required phrase(s) found across files.`);
1160
+ }
1161
+ return makeCheckResult(check, false, `${remaining.size} required phrase(s) missing across all files.`, { missing: [...remaining] });
1162
+ }
1163
+ catch (error) {
1164
+ return makeCheckResult(check, false, `Error reading target: ${error instanceof Error ? error.message : String(error)}`);
1165
+ }
1166
+ },
1167
+ qa_match(check, context) {
1168
+ const expected = typeof check.params?.expected === "string" ? check.params.expected : "";
1169
+ const strictness = typeof check.params?.strictness === "string" ? check.params.strictness : "loose";
1170
+ const answer = context.agentAnswer ?? "";
1171
+ if (!expected) {
1172
+ return makeCheckResult(check, false, "qa_match check requires `params.expected: string`.");
1173
+ }
1174
+ if (!answer) {
1175
+ return makeCheckResult(check, false, "No agent answer available for qa_match evaluation.");
1176
+ }
1177
+ if (strictness === "strict") {
1178
+ const passed = answer.trim() === expected.trim();
1179
+ return makeCheckResult(check, passed, passed ? "Answer matches expected exactly." : "Answer does not match expected.");
1180
+ }
1181
+ // Loose: case-insensitive substring match either direction.
1182
+ const a = answer.trim().toLowerCase();
1183
+ const e = expected.trim().toLowerCase();
1184
+ const passed = a.includes(e) || e.includes(a);
1185
+ return makeCheckResult(check, passed, passed ? "Answer matches expected (loose)." : "Answer does not match expected.");
1186
+ },
1187
+ };
1188
+ /**
1189
+ * Evaluate a single Check against a context, returning a CheckResult.
1190
+ */
1191
+ export function evaluateCheck(check, context) {
1192
+ const evaluator = EVALUATORS[check.kind];
1193
+ if (!evaluator) {
1194
+ return {
1195
+ check_id: check.id,
1196
+ kind: check.kind,
1197
+ passed: false,
1198
+ required: check.required,
1199
+ summary: `Unknown check kind: ${check.kind}`,
1200
+ evaluated_at: new Date().toISOString(),
1201
+ };
1202
+ }
1203
+ return evaluator(check, context);
1204
+ }
1205
+ /**
1206
+ * Evaluate a list of checks. Aggregate ready/not_ready verdict over
1207
+ * the check_results: `ready` if every required check passed, otherwise
1208
+ * `not_ready`. Soft checks (required: false) that fail produce a
1209
+ * check result but don't change the verdict.
1210
+ */
1211
+ export function evaluateChecks(checks, context) {
1212
+ const check_results = checks.map((check) => evaluateCheck(check, context));
1213
+ const failures = check_results.filter((result) => !result.passed && result.required);
1214
+ return {
1215
+ check_results,
1216
+ ready: failures.length === 0,
1217
+ failures,
1218
+ };
1219
+ }
1220
+ /**
1221
+ * Type guard for the canonical CheckKinds. Useful at parse boundaries
1222
+ * where a string came from on-disk JSON.
1223
+ */
1224
+ export function isCheckKind(value) {
1225
+ return CheckKindSchema.safeParse(value).success;
1226
+ }