@interf/compiler 0.33.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +122 -226
  2. package/dist/cli/commands/agents.js +1 -32
  3. package/dist/cli/commands/benchmark.d.ts +2 -3
  4. package/dist/cli/commands/benchmark.js +1 -31
  5. package/dist/cli/commands/build-plan.js +26 -50
  6. package/dist/cli/commands/build.d.ts +2 -3
  7. package/dist/cli/commands/build.js +1 -31
  8. package/dist/cli/commands/graphs.js +177 -32
  9. package/dist/cli/commands/mcp.d.ts +1 -0
  10. package/dist/cli/commands/mcp.js +223 -126
  11. package/dist/cli/commands/project.js +10 -36
  12. package/dist/cli/commands/reset.d.ts +2 -3
  13. package/dist/cli/commands/reset.js +1 -22
  14. package/dist/cli/commands/runs.js +86 -33
  15. package/dist/cli/commands/status.js +3 -24
  16. package/dist/cli/commands/traces.js +1 -29
  17. package/dist/cli/commands/wizard.js +17 -29
  18. package/dist/cli/lib/http-client.d.ts +39 -0
  19. package/dist/cli/lib/http-client.js +73 -0
  20. package/dist/packages/build-plans/authoring/brief.d.ts +25 -4
  21. package/dist/packages/build-plans/authoring/build-plan-authoring.d.ts +42 -1
  22. package/dist/packages/build-plans/authoring/build-plan-authoring.js +470 -63
  23. package/dist/packages/build-plans/authoring/build-plan-edit-session.d.ts +9 -0
  24. package/dist/packages/build-plans/authoring/build-plan-edit-session.js +27 -10
  25. package/dist/packages/build-plans/authoring/build-plan-improvement.js +62 -8
  26. package/dist/packages/build-plans/authoring/lib/build-plan-edit-utils.d.ts +1 -0
  27. package/dist/packages/build-plans/package/build-plan-definitions.d.ts +0 -1
  28. package/dist/packages/build-plans/package/build-plan-definitions.js +5 -3
  29. package/dist/packages/build-plans/package/build-plan-stage-runner.d.ts +1 -0
  30. package/dist/packages/build-plans/package/build-plan-stage-runner.js +2 -1
  31. package/dist/packages/build-plans/package/builtin-build-plan.d.ts +2 -2
  32. package/dist/packages/build-plans/package/builtin-build-plan.js +3 -3
  33. package/dist/packages/build-plans/package/context-interface.d.ts +3 -0
  34. package/dist/packages/build-plans/package/context-interface.js +5 -5
  35. package/dist/packages/build-plans/package/interf-build-plan-package.js +22 -22
  36. package/dist/packages/build-plans/package/local-build-plans.d.ts +10 -5
  37. package/dist/packages/build-plans/package/local-build-plans.js +57 -32
  38. package/dist/packages/contracts/index.d.ts +4 -3
  39. package/dist/packages/contracts/index.js +2 -1
  40. package/dist/packages/contracts/lib/context-graph-layer.d.ts +161 -0
  41. package/dist/packages/contracts/lib/context-graph-layer.js +216 -0
  42. package/dist/packages/contracts/lib/project-paths.d.ts +7 -0
  43. package/dist/packages/contracts/lib/project-paths.js +9 -0
  44. package/dist/packages/contracts/lib/project-schema.d.ts +264 -1
  45. package/dist/packages/contracts/lib/project-schema.js +38 -13
  46. package/dist/packages/contracts/lib/schema.d.ts +556 -23
  47. package/dist/packages/contracts/lib/schema.js +279 -18
  48. package/dist/packages/contracts/utils/filesystem.d.ts +1 -0
  49. package/dist/packages/contracts/utils/filesystem.js +29 -1
  50. package/dist/packages/projects/lib/schema.d.ts +6 -8
  51. package/dist/packages/projects/lib/schema.js +3 -1
  52. package/dist/packages/projects/source-config.d.ts +0 -5
  53. package/dist/packages/projects/source-config.js +9 -22
  54. package/dist/packages/runtime/actions/fields.d.ts +4 -0
  55. package/dist/packages/runtime/actions/form-builders.js +79 -31
  56. package/dist/packages/runtime/actions/form-validators.js +9 -3
  57. package/dist/packages/runtime/actions/helpers.js +3 -3
  58. package/dist/packages/runtime/actions/registry.d.ts +1 -1
  59. package/dist/packages/runtime/actions/registry.js +1 -1
  60. package/dist/packages/runtime/actions/requests.d.ts +1 -1
  61. package/dist/packages/runtime/actions/requests.js +12 -6
  62. package/dist/packages/runtime/actions/schemas.d.ts +7 -0
  63. package/dist/packages/runtime/actions/schemas.js +1 -0
  64. package/dist/packages/runtime/agent-handoff.js +8 -7
  65. package/dist/packages/runtime/agents/lib/execution-profile.d.ts +14 -0
  66. package/dist/packages/runtime/agents/lib/execution-profile.js +23 -0
  67. package/dist/packages/runtime/agents/lib/execution.js +14 -8
  68. package/dist/packages/runtime/agents/lib/executors.d.ts +1 -0
  69. package/dist/packages/runtime/agents/lib/executors.js +11 -2
  70. package/dist/packages/runtime/agents/lib/logs.d.ts +10 -0
  71. package/dist/packages/runtime/agents/lib/logs.js +32 -8
  72. package/dist/packages/runtime/agents/lib/preflight.js +4 -1
  73. package/dist/packages/runtime/agents/lib/render.d.ts +18 -0
  74. package/dist/packages/runtime/agents/lib/render.js +44 -18
  75. package/dist/packages/runtime/agents/lib/shell-templates.js +105 -63
  76. package/dist/packages/runtime/agents/lib/shells.d.ts +29 -0
  77. package/dist/packages/runtime/agents/lib/shells.js +158 -32
  78. package/dist/packages/runtime/agents/lib/source-context-scan.d.ts +10 -0
  79. package/dist/packages/runtime/agents/lib/source-context-scan.js +388 -0
  80. package/dist/packages/runtime/agents/lib/status.js +1 -14
  81. package/dist/packages/runtime/agents/lib/string-utils.d.ts +16 -0
  82. package/dist/packages/runtime/agents/lib/string-utils.js +36 -0
  83. package/dist/packages/runtime/agents/lib/types.d.ts +1 -0
  84. package/dist/packages/runtime/agents/providers/codex.js +2 -0
  85. package/dist/packages/runtime/agents/role-executors.js +2 -1
  86. package/dist/packages/runtime/auth/session-store.js +11 -3
  87. package/dist/packages/runtime/benchmark-question-draft.d.ts +3 -0
  88. package/dist/packages/runtime/benchmark-question-draft.js +57 -28
  89. package/dist/packages/runtime/build/artifact-status.d.ts +1 -1
  90. package/dist/packages/runtime/build/artifact-status.js +1 -1
  91. package/dist/packages/runtime/build/build-evidence.d.ts +2 -1
  92. package/dist/packages/runtime/build/build-evidence.js +11 -5
  93. package/dist/packages/runtime/build/build-pipeline.js +89 -5
  94. package/dist/packages/runtime/build/build-stage-plan.js +3 -1
  95. package/dist/packages/runtime/build/build-stage-runner.js +169 -32
  96. package/dist/packages/runtime/build/build-target.d.ts +3 -0
  97. package/dist/packages/runtime/build/build-target.js +25 -1
  98. package/dist/packages/runtime/build/check-evaluator.d.ts +1 -1
  99. package/dist/packages/runtime/build/check-evaluator.js +655 -4
  100. package/dist/packages/runtime/build/context-graph-paths.d.ts +13 -0
  101. package/dist/packages/runtime/build/context-graph-paths.js +27 -0
  102. package/dist/packages/runtime/build/index.d.ts +2 -2
  103. package/dist/packages/runtime/build/index.js +2 -2
  104. package/dist/packages/runtime/build/inspect-map.d.ts +10 -0
  105. package/dist/packages/runtime/build/inspect-map.js +270 -0
  106. package/dist/packages/runtime/build/lib/schema.d.ts +246 -53
  107. package/dist/packages/runtime/build/lib/schema.js +173 -15
  108. package/dist/packages/runtime/build/native-entrypoint.d.ts +2 -0
  109. package/dist/packages/runtime/build/native-entrypoint.js +286 -0
  110. package/dist/packages/runtime/build/runtime-contracts.js +9 -3
  111. package/dist/packages/runtime/build/runtime-log-paths.d.ts +3 -0
  112. package/dist/packages/runtime/build/runtime-log-paths.js +16 -0
  113. package/dist/packages/runtime/build/runtime-prompt.js +6 -4
  114. package/dist/packages/runtime/build/runtime-runs.js +63 -10
  115. package/dist/packages/runtime/build/runtime-types.d.ts +4 -1
  116. package/dist/packages/runtime/build/runtime.d.ts +3 -1
  117. package/dist/packages/runtime/build/runtime.js +3 -1
  118. package/dist/packages/runtime/build/source-files.js +11 -2
  119. package/dist/packages/runtime/build/source-inventory.d.ts +1 -0
  120. package/dist/packages/runtime/build/source-inventory.js +246 -7
  121. package/dist/packages/runtime/build/source-manifest.d.ts +11 -0
  122. package/dist/packages/runtime/build/source-manifest.js +30 -2
  123. package/dist/packages/runtime/build/stage-evidence.js +80 -11
  124. package/dist/packages/runtime/build/stage-manifest.d.ts +45 -0
  125. package/dist/packages/runtime/build/stage-manifest.js +1125 -0
  126. package/dist/packages/runtime/build/stage-reuse.js +12 -0
  127. package/dist/packages/runtime/build/stage-session.d.ts +81 -0
  128. package/dist/packages/runtime/build/stage-session.js +308 -0
  129. package/dist/packages/runtime/build/state-io.js +10 -11
  130. package/dist/packages/runtime/build/state-view.js +1 -1
  131. package/dist/packages/runtime/build/state.d.ts +1 -1
  132. package/dist/packages/runtime/build/state.js +1 -1
  133. package/dist/packages/runtime/build/summary-coverage-index.d.ts +21 -0
  134. package/dist/packages/runtime/build/summary-coverage-index.js +189 -0
  135. package/dist/packages/runtime/build/traces.js +3 -3
  136. package/dist/packages/runtime/build/validate-context-graph.d.ts +1 -1
  137. package/dist/packages/runtime/build/validate-context-graph.js +5 -5
  138. package/dist/packages/runtime/build/validate.d.ts +1 -1
  139. package/dist/packages/runtime/build/validate.js +1 -1
  140. package/dist/packages/runtime/client.d.ts +3 -3
  141. package/dist/packages/runtime/client.js +8 -13
  142. package/dist/packages/runtime/context-checks.js +13 -0
  143. package/dist/packages/runtime/context-graph-scaffold.js +2 -1
  144. package/dist/packages/runtime/context-graph-semantic-graph.d.ts +9 -0
  145. package/dist/packages/runtime/context-graph-semantic-graph.js +416 -0
  146. package/dist/packages/runtime/execution/lib/schema.d.ts +34 -31
  147. package/dist/packages/runtime/index.d.ts +2 -2
  148. package/dist/packages/runtime/index.js +1 -1
  149. package/dist/packages/runtime/native-run-handlers.d.ts +38 -0
  150. package/dist/packages/runtime/native-run-handlers.js +52 -33
  151. package/dist/packages/runtime/plan-artifact-contract.js +1 -1
  152. package/dist/packages/runtime/project-source-state.d.ts +4 -4
  153. package/dist/packages/runtime/project-source-state.js +5 -2
  154. package/dist/packages/runtime/project-store.d.ts +5 -0
  155. package/dist/packages/runtime/project-store.js +30 -3
  156. package/dist/packages/runtime/requested-artifacts.js +1 -1
  157. package/dist/packages/runtime/run-observability.js +9 -4
  158. package/dist/packages/runtime/runtime-action-proposals.js +3 -3
  159. package/dist/packages/runtime/runtime-build-plans.js +47 -3
  160. package/dist/packages/runtime/runtime-build-runs.js +9 -16
  161. package/dist/packages/runtime/runtime-caches.d.ts +26 -0
  162. package/dist/packages/runtime/runtime-caches.js +47 -0
  163. package/dist/packages/runtime/runtime-jobs.js +6 -6
  164. package/dist/packages/runtime/runtime-project-mutations.js +1 -0
  165. package/dist/packages/runtime/runtime-project-reads.d.ts +4 -1
  166. package/dist/packages/runtime/runtime-project-reads.js +229 -36
  167. package/dist/packages/runtime/runtime-proposal-helpers.js +6 -6
  168. package/dist/packages/runtime/runtime-resource-builders.d.ts +4 -2
  169. package/dist/packages/runtime/runtime-resource-builders.js +16 -14
  170. package/dist/packages/runtime/runtime-status.d.ts +14 -0
  171. package/dist/packages/runtime/runtime-status.js +15 -0
  172. package/dist/packages/runtime/runtime-verify-runs.js +6 -5
  173. package/dist/packages/runtime/runtime.d.ts +439 -22
  174. package/dist/packages/runtime/runtime.js +16 -2
  175. package/dist/packages/runtime/schemas/actions.d.ts +24 -0
  176. package/dist/packages/runtime/schemas/agents.d.ts +28 -0
  177. package/dist/packages/runtime/schemas/agents.js +33 -0
  178. package/dist/packages/runtime/schemas/build-plans.d.ts +181 -8
  179. package/dist/packages/runtime/schemas/build-plans.js +36 -2
  180. package/dist/packages/runtime/schemas/context-graphs.d.ts +1522 -0
  181. package/dist/packages/runtime/schemas/context-graphs.js +110 -0
  182. package/dist/packages/runtime/schemas/files.d.ts +7 -347
  183. package/dist/packages/runtime/schemas/files.js +1 -24
  184. package/dist/packages/runtime/schemas/index.d.ts +1 -0
  185. package/dist/packages/runtime/schemas/index.js +1 -0
  186. package/dist/packages/runtime/schemas/jobs.js +4 -0
  187. package/dist/packages/runtime/schemas/projects.d.ts +48 -21
  188. package/dist/packages/runtime/schemas/projects.js +34 -10
  189. package/dist/packages/runtime/schemas/runs.d.ts +1009 -240
  190. package/dist/packages/runtime/schemas/runs.js +17 -0
  191. package/dist/packages/runtime/service/openapi.js +1 -0
  192. package/dist/packages/runtime/service/operations.d.ts +1666 -145
  193. package/dist/packages/runtime/service/operations.js +147 -17
  194. package/dist/packages/runtime/service/routes.d.ts +11 -3
  195. package/dist/packages/runtime/service/routes.js +11 -3
  196. package/dist/packages/runtime/service/server-app-boot.js +2 -2
  197. package/dist/packages/runtime/service/server-helpers.d.ts +11 -0
  198. package/dist/packages/runtime/service/server-helpers.js +19 -0
  199. package/dist/packages/runtime/service/server-routes-action-proposals.js +4 -2
  200. package/dist/packages/runtime/service/server-routes-agents.js +19 -85
  201. package/dist/packages/runtime/service/server-routes-build-plans.js +14 -11
  202. package/dist/packages/runtime/service/server-routes-project-context.js +102 -7
  203. package/dist/packages/runtime/service/server-routes-project-jobs.js +19 -12
  204. package/dist/packages/runtime/service/server-routes-project-runs.js +5 -2
  205. package/dist/packages/runtime/service/server-routes-projects.js +6 -2
  206. package/dist/packages/runtime/service/server-routes-runs.js +11 -4
  207. package/dist/packages/runtime/verify/lib/schema.js +12 -0
  208. package/dist/packages/runtime/verify/test-file-guard.d.ts +2 -0
  209. package/dist/packages/runtime/verify/test-file-guard.js +29 -0
  210. package/dist/packages/runtime/verify/verify-execution.d.ts +7 -0
  211. package/dist/packages/runtime/verify/verify-execution.js +109 -35
  212. package/dist/packages/runtime/verify/verify-paths.d.ts +1 -0
  213. package/dist/packages/runtime/verify/verify-paths.js +4 -0
  214. package/dist/packages/runtime/verify/verify-specs.js +49 -39
  215. package/dist/packages/runtime/wire-schemas.d.ts +1 -1
  216. package/dist/packages/runtime/wire-schemas.js +1 -1
  217. package/package.json +2 -8
  218. package/public-repo/CONTRIBUTING.md +10 -3
  219. package/public-repo/README.md +122 -226
  220. package/public-repo/build-plans/interf-default/README.md +15 -12
  221. package/public-repo/build-plans/interf-default/build/stages/entrypoint/SKILL.md +74 -0
  222. package/public-repo/build-plans/interf-default/build/stages/knowledge/SKILL.md +95 -0
  223. package/public-repo/build-plans/interf-default/build/stages/summarize/SKILL.md +38 -5
  224. package/public-repo/build-plans/interf-default/build-plan.json +27 -23
  225. package/public-repo/build-plans/interf-default/build-plan.schema.json +24 -20
  226. package/public-repo/build-plans/interf-default/use/query/SKILL.md +8 -7
  227. package/public-repo/openapi/local-service.openapi.json +11637 -4213
  228. package/public-repo/skills/interf/SKILL.md +174 -134
  229. package/dist/packages/runtime/build/runtime-paths.d.ts +0 -8
  230. package/dist/packages/runtime/build/runtime-paths.js +0 -26
  231. package/dist/packages/runtime/build/state-paths.d.ts +0 -7
  232. package/dist/packages/runtime/build/state-paths.js +0 -22
  233. package/public-repo/build-plans/interf-default/build/stages/shape/SKILL.md +0 -34
  234. package/public-repo/build-plans/interf-default/build/stages/structure/SKILL.md +0 -28
@@ -16,13 +16,11 @@ import { BuildRunCreateRequestSchema, BuildRunResourceSchema, LocalRunHandlerRes
16
16
  import { uniqueArtifacts } from "./run-observability.js";
17
17
  import { requireSelectedBuildPlan } from "./runtime-proposal-helpers.js";
18
18
  import { readinessUpdatedEvent } from "./runtime-verify-runs.js";
19
+ import { isTerminalStatus } from "./runtime-status.js";
19
20
  /** TTL for `POST .../build-runs` idempotency-key dedupe entries. */
20
21
  const IDEMPOTENCY_TTL_MS = 60 * 60 * 1000;
21
22
  /** Idempotency cache size at which to schedule an opportunistic prune. */
22
23
  const IDEMPOTENCY_PRUNE_THRESHOLD = 64;
23
- function isTerminalBuildRunStatus(status) {
24
- return status === "succeeded" || status === "failed" || status === "cancelled";
25
- }
26
24
  function countsFromContextGraphState(state, contextGraphPath) {
27
25
  const counts = {};
28
26
  const sourceManifest = loadContextGraphSourceManifest(contextGraphPath);
@@ -60,9 +58,7 @@ export function listBuildRuns(runtime, projectDataDir) {
60
58
  .flatMap((project) => listBuildRunsForProject(runtime, projectDataDir, project.name))).map((run) => BuildRunResourceSchema.parse({ run }));
61
59
  }
62
60
  export function listBuildRunsForProject(runtime, projectDataDir, projectName) {
63
- return runtime.buildRunCache.get(projectDataDir, projectName, () => {
64
- return byCreatedAtDesc(readBuildRunRecordsForProject(projectDataDir, projectName)).map((run) => hydrateBuildRunFromRuntime(runtime, projectDataDir, run));
65
- }, (run) => run.run_id);
61
+ return runtime.buildRunCache.get(projectDataDir, projectName, () => byCreatedAtDesc(readBuildRunRecordsForProject(projectDataDir, projectName)), (run) => run.run_id);
66
62
  }
67
63
  export function getBuildRun(runtime, projectDataDir, runId) {
68
64
  // Fast path: if the runId was seen during a recent listing, look up
@@ -143,7 +139,7 @@ export async function createBuildRun(runtime, projectDataDir, requestValue) {
143
139
  events: [],
144
140
  context_checks: buildPlanContextChecks({
145
141
  brief: buildPlan.brief ?? null,
146
- artifacts: buildPlan.contextInterface?.artifacts ?? buildPlan.schema?.artifacts ?? [],
142
+ artifacts: buildPlan.contextInterface?.artifacts ?? [],
147
143
  }),
148
144
  });
149
145
  writeBuildRun(runtime, projectDataDir, run);
@@ -198,7 +194,7 @@ export function cancelBuildRun(runtime, runId) {
198
194
  handle.cancelled = true;
199
195
  handle.cancelledAt = cancelledAt;
200
196
  const current = readBuildRun(handle.projectDataDir, runId);
201
- if (current && !isTerminalBuildRunStatus(current.status)) {
197
+ if (current && !isTerminalStatus(current.status)) {
202
198
  writeBuildRun(runtime, handle.projectDataDir, applyEventToBuildRun(current, {
203
199
  type: "run.cancelled",
204
200
  event_id: createRunEventId("event"),
@@ -352,12 +348,6 @@ async function runBuildInBackground(runtime, projectDataDir, request, context) {
352
348
  export function readBuildRun(projectDataDir, runId) {
353
349
  return readBuildRunAt(projectBuildRunPath(projectDataDir, runId));
354
350
  }
355
- function hydrateBuildRunFromRuntime(runtime, projectDataDir, run) {
356
- if (!isTerminalBuildRunStatus(run.status))
357
- return run;
358
- refreshBuildRunFromRuntime(runtime, projectDataDir, run.context_graph_path, run.run_id);
359
- return readBuildRun(projectDataDir, run.run_id) ?? run;
360
- }
361
351
  export function finalizeInterruptedBuildRuns(runtime, projectDataDir) {
362
352
  let projects;
363
353
  try {
@@ -368,7 +358,7 @@ export function finalizeInterruptedBuildRuns(runtime, projectDataDir) {
368
358
  }
369
359
  for (const project of projects) {
370
360
  for (const run of readBuildRunRecordsForProject(projectDataDir, project.name)) {
371
- if (isTerminalBuildRunStatus(run.status) || runtime.activeBuildRuns.has(run.run_id))
361
+ if (isTerminalStatus(run.status) || runtime.activeBuildRuns.has(run.run_id))
372
362
  continue;
373
363
  const timestamp = createRunEventTimestamp();
374
364
  const interruptedRun = {
@@ -438,6 +428,9 @@ export function writeBuildRun(runtime, projectDataDir, run) {
438
428
  // and the simpler model avoids fan-out bugs.
439
429
  runtime.buildRunCache.invalidateProject(projectDataDir, run.project);
440
430
  runtime.readinessCache.invalidateProject(projectDataDir, run.project);
431
+ // The Context Graph resource embeds the joined readiness verdict, so bust it
432
+ // whenever a Build run is written (build completion, refresh, cancel).
433
+ runtime.contextGraphResourceCache.invalidateProject(projectDataDir);
441
434
  }
442
435
  async function recordBuildRunEvent(runtime, projectDataDir, contextGraphPath, runId, event) {
443
436
  const current = readBuildRun(projectDataDir, runId);
@@ -510,7 +503,7 @@ function refreshBuildRunFromRuntime(runtime, projectDataDir, contextGraphPath, r
510
503
  next.context_checks = projectContextChecksForRun({
511
504
  contextChecks: buildPlanContextChecks({
512
505
  brief: buildPlan.brief ?? null,
513
- artifacts: buildPlan.contextInterface?.artifacts ?? buildPlan.schema?.artifacts ?? [],
506
+ artifacts: buildPlan.contextInterface?.artifacts ?? [],
514
507
  }),
515
508
  artifactStatuses: next.artifacts,
516
509
  });
@@ -89,3 +89,29 @@ export declare class ProjectSourceStateCache<TState> {
89
89
  invalidateProject(projectId: string): void;
90
90
  invalidateAll(): void;
91
91
  }
92
+ /**
93
+ * Per-Build-run Context Graph resource cache.
94
+ *
95
+ * A succeeded Build's Context Graph is immutable: its on-disk folder
96
+ * (`graph-manifest.json`, source manifest, stage sessions, semantic graph) is
97
+ * frozen once the run reaches `succeeded`. Rebuilding the full
98
+ * `ContextGraphResource` — readiness join, semantic graph, build evidence,
99
+ * validation — on every read is the dominant cost of opening a built Project.
100
+ *
101
+ * The cache keys by `(projectDataDir, runId, manifestMtimeMs)`:
102
+ * - `runId` scopes a single Build's output.
103
+ * - `manifestMtimeMs` is the mtime of the Build's
104
+ * `.interf/runtime/graph-manifest.json`. It busts the entry if the graph is
105
+ * rebuilt or edited in place; a fresh Build produces a fresh `runId`, so
106
+ * those never collide either.
107
+ *
108
+ * Entries without a readable manifest mtime (mtime 0) are not cached — that
109
+ * only happens for an in-flight or malformed Build, where the resource is
110
+ * still changing and must be recomputed.
111
+ */
112
+ export declare class ContextGraphResourceCache<TResource> {
113
+ private byKey;
114
+ get(projectDataDir: string, runId: string, manifestPath: string, compute: () => TResource): TResource;
115
+ invalidateProject(projectDataDir: string): void;
116
+ invalidateAll(): void;
117
+ }
@@ -217,3 +217,50 @@ export class ProjectSourceStateCache {
217
217
  this.byProjectId.clear();
218
218
  }
219
219
  }
220
+ /**
221
+ * Per-Build-run Context Graph resource cache.
222
+ *
223
+ * A succeeded Build's Context Graph is immutable: its on-disk folder
224
+ * (`graph-manifest.json`, source manifest, stage sessions, semantic graph) is
225
+ * frozen once the run reaches `succeeded`. Rebuilding the full
226
+ * `ContextGraphResource` — readiness join, semantic graph, build evidence,
227
+ * validation — on every read is the dominant cost of opening a built Project.
228
+ *
229
+ * The cache keys by `(projectDataDir, runId, manifestMtimeMs)`:
230
+ * - `runId` scopes a single Build's output.
231
+ * - `manifestMtimeMs` is the mtime of the Build's
232
+ * `.interf/runtime/graph-manifest.json`. It busts the entry if the graph is
233
+ * rebuilt or edited in place; a fresh Build produces a fresh `runId`, so
234
+ * those never collide either.
235
+ *
236
+ * Entries without a readable manifest mtime (mtime 0) are not cached — that
237
+ * only happens for an in-flight or malformed Build, where the resource is
238
+ * still changing and must be recomputed.
239
+ */
240
+ export class ContextGraphResourceCache {
241
+ byKey = new Map();
242
+ get(projectDataDir, runId, manifestPath, compute) {
243
+ const mtimeMs = safeStat(manifestPath)?.mtimeMs ?? 0;
244
+ // mtime 0 means the manifest is missing/unreadable — the graph is not yet
245
+ // a frozen, succeeded output, so recompute every time without caching.
246
+ if (mtimeMs === 0)
247
+ return compute();
248
+ const cacheKey = `${resolve(projectDataDir)}${runId}`;
249
+ const entry = this.byKey.get(cacheKey);
250
+ if (entry && entry.mtimeMs === mtimeMs)
251
+ return entry.value;
252
+ const value = compute();
253
+ this.byKey.set(cacheKey, { mtimeMs, value });
254
+ return value;
255
+ }
256
+ invalidateProject(projectDataDir) {
257
+ const prefix = `${resolve(projectDataDir)}`;
258
+ for (const key of this.byKey.keys()) {
259
+ if (key.startsWith(prefix))
260
+ this.byKey.delete(key);
261
+ }
262
+ }
263
+ invalidateAll() {
264
+ this.byKey.clear();
265
+ }
266
+ }
@@ -7,10 +7,8 @@ import { createRunId } from "./runtime-resource-builders.js";
7
7
  import { upsertProjectConfig, findProjectConfig, loadSourceFolderConfig, writeBenchmarkSpecsForProject, } from "../projects/source-config.js";
8
8
  import { BuildPlanAuthoringRuntimeRequestSchema, BuildPlanAuthoringResultSchema, LocalJobEventAppendRequestSchema, LocalJobRunCreateRequestSchema, LocalJobRunResourceSchema, BenchmarkQuestionDraftRuntimeRequestSchema, BenchmarkQuestionDraftResultSchema, } from "./schemas/index.js";
9
9
  import { artifactRequirementsForPlanAuthoring, requestedArtifactsForPlanAuthoring, } from "./plan-artifact-contract.js";
10
+ import { isTerminalStatus } from "./runtime-status.js";
10
11
  const INTERRUPTED_JOB_RUN_MESSAGE = "Job interrupted because the local service stopped before the job reached a terminal state.";
11
- function isTerminalJobStatus(status) {
12
- return status === "succeeded" || status === "failed" || status === "cancelled";
13
- }
14
12
  export function listJobs(projectDataDir) {
15
13
  return byCreatedAtDesc(listJsonFiles(localJobsRoot(projectDataDir))
16
14
  .map(readLocalJobRunAt)
@@ -62,7 +60,7 @@ export function appendJobRunEvent(runtime, projectDataDir, runId, requestValue)
62
60
  const current = getJob(projectDataDir, runId);
63
61
  if (!current)
64
62
  return null;
65
- if (isTerminalJobStatus(current.status))
63
+ if (isTerminalStatus(current.status))
66
64
  return current;
67
65
  const event = {
68
66
  type: request.type,
@@ -349,6 +347,7 @@ async function runBuildPlanAuthoringInBackgroundInner(runtime, projectDataDir, r
349
347
  runtime.readinessCache.invalidateProject(projectDataDir, selected.name);
350
348
  runtime.buildRunCache.invalidateProject(projectDataDir, selected.name);
351
349
  runtime.verifyRunCache.invalidateProject(projectDataDir, selected.name);
350
+ runtime.contextGraphResourceCache.invalidateProject(projectDataDir);
352
351
  runtime.buildPlanListingCache.invalidate(projectDataDir);
353
352
  selectedProject = selected.name;
354
353
  }
@@ -418,13 +417,14 @@ export function writeJobRun(runtime, projectDataDir, run) {
418
417
  if (run.project) {
419
418
  // Some job runs (benchmark-question drafts) flip readiness state.
420
419
  runtime.readinessCache.invalidateProject(projectDataDir, run.project);
420
+ runtime.contextGraphResourceCache.invalidateProject(projectDataDir);
421
421
  }
422
422
  }
423
423
  export function setJobRunResult(runtime, projectDataDir, runId, result) {
424
424
  const current = getJob(projectDataDir, runId);
425
425
  if (!current)
426
426
  return;
427
- if (isTerminalJobStatus(current.status))
427
+ if (isTerminalStatus(current.status))
428
428
  return;
429
429
  const normalizedResult = result && typeof result === "object" && !Array.isArray(result)
430
430
  ? result
@@ -436,7 +436,7 @@ export function setJobRunResult(runtime, projectDataDir, runId, result) {
436
436
  }
437
437
  export function finalizeInterruptedJobRuns(runtime, projectDataDir) {
438
438
  for (const run of listJobs(projectDataDir)) {
439
- if (isTerminalJobStatus(run.status) || runtime.activeJobRuns.has(run.run_id))
439
+ if (isTerminalStatus(run.status) || runtime.activeJobRuns.has(run.run_id))
440
440
  continue;
441
441
  const timestamp = createRunEventTimestamp();
442
442
  const interruptedRun = LocalJobRunResourceSchema.parse({
@@ -23,6 +23,7 @@ export function applyReset(runtime, projectDataDir, requestValue) {
23
23
  runtime.buildRunCache.invalidateProject(projectDataDir, request.project);
24
24
  runtime.verifyRunCache.invalidateProject(projectDataDir, request.project);
25
25
  runtime.readinessCache.invalidateProject(projectDataDir, request.project);
26
+ runtime.contextGraphResourceCache.invalidateProject(projectDataDir);
26
27
  return ResetResultSchema.parse({
27
28
  kind: "interf-reset-result",
28
29
  version: 1,
@@ -1,5 +1,6 @@
1
1
  import { type Readiness } from "../contracts/lib/schema.js";
2
- import { type ProjectResource, type SourceFileResource, type ContextGraphResource } from "./schemas/index.js";
2
+ import { type ContextGraphHandoff } from "../contracts/lib/project-schema.js";
3
+ import { type ProjectResource, type SourceFileResource, type ContextGraphResource, type StageExecutionSession } from "./schemas/index.js";
3
4
  import type { ProjectConfig } from "../projects/lib/schema.js";
4
5
  import type { LocalServiceRuntime } from "./runtime.js";
5
6
  export declare function listProjects(runtime: LocalServiceRuntime, projectDataDir: string): ProjectResource[];
@@ -13,3 +14,5 @@ export declare function listSourceFiles(runtime: LocalServiceRuntime, projectDat
13
14
  export declare function listContextGraphs(runtime: LocalServiceRuntime, projectDataDir: string, projectName?: string | null): ContextGraphResource[];
14
15
  export declare function getContextGraph(runtime: LocalServiceRuntime, projectDataDir: string, graphId: string): ContextGraphResource | null;
15
16
  export declare function getLatestContextGraph(runtime: LocalServiceRuntime, projectDataDir: string, projectName: string): ContextGraphResource | null;
17
+ export declare function listLatestContextGraphStageSessions(runtime: LocalServiceRuntime, projectDataDir: string, projectName: string): StageExecutionSession[];
18
+ export declare function getContextGraphHandoff(runtime: LocalServiceRuntime, projectDataDir: string, projectName: string): ContextGraphHandoff | null;
@@ -1,14 +1,21 @@
1
- import { existsSync } from "node:fs";
2
- import { asProjectDataDir, projectConfigPath, } from "../contracts/lib/project-paths.js";
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { asProjectDataDir, projectConfigPath, projectRunContextGraphPath, } from "../contracts/lib/project-paths.js";
4
+ import { layerForPath } from "../contracts/lib/context-graph-layer.js";
3
5
  import { buildPlanIdForProjectConfig, buildTestSpecFromSourceFolderConfig, fingerprintSavedBenchmarkSpec, findProjectConfig, listProjectConfigs, loadSourceFolderConfig, resolveProjectSourceBindingPath, } from "../projects/source-config.js";
4
- import { ReadinessSchema, } from "../contracts/lib/schema.js";
6
+ import { isInterfRelativePath, ReadinessSchema, } from "../contracts/lib/schema.js";
7
+ import { ContextGraphHandoffSchema, } from "../contracts/lib/project-schema.js";
5
8
  import { readInterfConfig, } from "../projects/interf.js";
6
9
  import { loadContextGraphSourceManifest, } from "./build/source-manifest.js";
7
- import { buildProjectResource, readinessStateToProjectReadiness, readinessSummaryForStatus, readinessTargetResult, } from "./runtime-resource-builders.js";
10
+ import { loadGraphManifest, } from "./build/stage-manifest.js";
11
+ import { contextGraphRuntimeGraphManifestPath, } from "./build/context-graph-paths.js";
12
+ import { listStageExecutionSessions, } from "./build/stage-session.js";
13
+ import { buildProjectResource, readinessSummaryForStatus, readinessTargetResult, } from "./runtime-resource-builders.js";
8
14
  import { aggregateArtifactVerdict, } from "./build/artifact-status.js";
9
15
  import { buildBuildEvidence, } from "./build/build-evidence.js";
16
+ import { buildContextGraphSemanticGraph, } from "./context-graph-semantic-graph.js";
10
17
  import { validateContextGraph } from "./build/validate.js";
11
- import { SourceFileResourceSchema, ContextGraphResourceSchema, } from "./schemas/index.js";
18
+ import { SourceFileResourceSchema, ContextGraphResourceSchema, StageExecutionSessionSchema, } from "./schemas/index.js";
12
19
  import { uniqueArtifacts } from "./run-observability.js";
13
20
  import { listBuildRunsForProject, } from "./runtime-build-runs.js";
14
21
  import { readExecutionStageLedgerHistory } from "./runtime-persistence.js";
@@ -17,6 +24,18 @@ import { listContextInterfaceArtifacts } from "../build-plans/package/context-in
17
24
  import { latestTopLevelVerifyRun, listVerifyRunsForProject, readLatestBenchmarkRun, } from "./runtime-verify-runs.js";
18
25
  export function listProjects(runtime, projectDataDir) {
19
26
  const config = loadSourceFolderConfig(projectDataDir);
27
+ // Per-project config file mtime is a migration-free "last touched" signal —
28
+ // each Project owns its `~/.interf/projects/<id>/interf.json`. Clients sort
29
+ // the Projects list newest-first by this so a just-created Project lands on
30
+ // top instead of buried alphabetically.
31
+ const configPath = projectConfigPath(asProjectDataDir(projectDataDir));
32
+ let updatedAt = null;
33
+ try {
34
+ updatedAt = statSync(configPath).mtime.toISOString();
35
+ }
36
+ catch {
37
+ updatedAt = null;
38
+ }
20
39
  return listProjectConfigs(config).map((project) => {
21
40
  const buildRuns = listBuildRunsForProject(runtime, projectDataDir, project.name);
22
41
  const verifyRuns = listVerifyRunsForProject(runtime, projectDataDir, project.name);
@@ -27,21 +46,20 @@ export function listProjects(runtime, projectDataDir) {
27
46
  latestBuildRunId: buildRuns[0]?.run_id ?? null,
28
47
  latestContextGraphBuildRunId: contextGraphRun?.run_id ?? null,
29
48
  latestTestRunId: latestVerifyRun?.run_id ?? null,
30
- }, resolveProjectSourceBindingPath(projectDataDir, project, config), projectConfigPath(asProjectDataDir(projectDataDir)),
49
+ }, resolveProjectSourceBindingPath(projectDataDir, project, config), configPath,
31
50
  // Surface per-Artifact status from the latest Build run so the UI can
32
51
  // render Artifact rows on the Project page without a separate fetch.
33
- contextGraphRun?.artifacts ?? []);
52
+ contextGraphRun?.artifacts ?? [], updatedAt);
34
53
  });
35
54
  }
36
55
  export function getProject(runtime, projectDataDir, projectName) {
37
56
  return listProjects(runtime, projectDataDir).find((project) => project.name === projectName) ?? null;
38
57
  }
39
58
  export function listProjectReadiness(runtime, projectDataDir) {
40
- return listReadiness(runtime, projectDataDir).map(readinessStateToProjectReadiness);
59
+ return listReadiness(runtime, projectDataDir);
41
60
  }
42
61
  export function getProjectReadiness(runtime, projectDataDir, projectName) {
43
- const readiness = getReadiness(runtime, projectDataDir, projectName);
44
- return readiness ? readinessStateToProjectReadiness(readiness) : null;
62
+ return getReadiness(runtime, projectDataDir, projectName);
45
63
  }
46
64
  export function listReadiness(runtime, projectDataDir) {
47
65
  const config = loadSourceFolderConfig(projectDataDir);
@@ -60,16 +78,27 @@ function latestContextGraphBuildRun(project, buildRuns) {
60
78
  : null;
61
79
  return selected ?? buildRuns.find((run) => run.status === "succeeded") ?? null;
62
80
  }
81
+ function contextGraphPathForBuildRun(projectDataDir, buildRun) {
82
+ if (!buildRun)
83
+ return null;
84
+ if (existsSync(buildRun.context_graph_path))
85
+ return buildRun.context_graph_path;
86
+ const projectOwnedPath = projectRunContextGraphPath(asProjectDataDir(projectDataDir), buildRun.run_id);
87
+ if (existsSync(projectOwnedPath))
88
+ return projectOwnedPath;
89
+ return buildRun.context_graph_path;
90
+ }
63
91
  function computeProjectReadinessUncached(runtime, projectDataDir, project) {
64
92
  const generatedAt = new Date().toISOString();
65
93
  const buildRuns = listBuildRunsForProject(runtime, projectDataDir, project.name);
66
94
  const buildRun = buildRuns[0] ?? null;
67
95
  const contextGraphRun = latestContextGraphBuildRun(project, buildRuns);
68
- const contextGraphPath = contextGraphRun?.context_graph_path ?? null;
96
+ const contextGraphPath = contextGraphPathForBuildRun(projectDataDir, contextGraphRun);
69
97
  const contextExists = Boolean(contextGraphPath && existsSync(contextGraphPath));
70
98
  const contextGraphValidation = contextGraphPath && contextExists
71
99
  ? validateContextGraph(contextGraphPath)
72
100
  : null;
101
+ const graphManifest = contextGraphPath && contextExists ? loadGraphManifest(contextGraphPath) : null;
73
102
  const contextReady = Boolean(contextGraphRun && contextGraphValidation?.ok);
74
103
  const verifyRun = latestTopLevelVerifyRun(listVerifyRunsForProject(runtime, projectDataDir, project.name));
75
104
  const benchmarkRun = readLatestBenchmarkRun(projectDataDir, project.name);
@@ -143,9 +172,45 @@ function computeProjectReadinessUncached(runtime, projectDataDir, project) {
143
172
  artifact_path: contextReady ? contextGraphPath : null,
144
173
  };
145
174
  })();
146
- // Drop the tautological "Project is saved" gate if this readiness object
147
- // exists, the Project exists. Keep the Context Graph / Build / Artifact /
148
- // benchmark gates in product vocabulary.
175
+ if (graphManifest && buildRun?.status === "succeeded" && contextReady) {
176
+ const status = graphManifest.readiness.ready ? "ready" : "not-ready";
177
+ return ReadinessSchema.parse({
178
+ kind: "interf-readiness-state",
179
+ version: 1,
180
+ generated_at: generatedAt,
181
+ project: project.name,
182
+ status,
183
+ ready: graphManifest.readiness.ready,
184
+ summary: graphManifest.readiness.summary,
185
+ context_graph_path: contextGraphPath,
186
+ latest_build_run_id: buildRun.run_id,
187
+ latest_benchmark_run_id: verifyRun?.run_id ?? null,
188
+ build: buildCheck,
189
+ check_results: {
190
+ configured: configuredChecks,
191
+ fingerprint: currentFingerprint,
192
+ source_files: sourceResult,
193
+ context_graph: contextResult,
194
+ },
195
+ checks: [
196
+ {
197
+ gate: "context-graph",
198
+ ok: graphManifest.readiness.ready,
199
+ status,
200
+ summary: graphManifest.readiness.summary,
201
+ artifact_path: contextGraphPath,
202
+ },
203
+ buildCheck,
204
+ ],
205
+ });
206
+ }
207
+ // COMPAT fallback — reached only when no GraphManifest exists yet
208
+ // (pre-refactor build or build still running). The primary readiness path
209
+ // is the early return above that reads graphManifest.readiness directly.
210
+ // Keep these legacy gates so old UIs that consume readiness.checks don't
211
+ // crash; do not add new gates here. Removal path: once every active Build
212
+ // produces a GraphManifest, the early return covers all cases and this
213
+ // fallback can be replaced with a single "not yet built" check.
149
214
  const checks = [
150
215
  {
151
216
  gate: "context-graph",
@@ -164,7 +229,7 @@ function computeProjectReadinessUncached(runtime, projectDataDir, project) {
164
229
  ? "not-configured"
165
230
  : artifactVerdict === "ready" ? "ready" : "not-ready",
166
231
  summary: !hasArtifactContract
167
- ? "No Artifacts declared by the selected Build Plan."
232
+ ? "No outputs declared by the selected Build Plan."
168
233
  : artifactVerdict === "ready"
169
234
  ? `${artifactStatuses.length} Artifact${artifactStatuses.length === 1 ? "" : "s"} ready; ${passedArtifactCheckResults.length}/${requiredArtifactCheckResults.length} required diagnostic${requiredArtifactCheckResults.length === 1 ? "" : "s"} passed.`
170
235
  : `${artifactFailures.length} Artifact${artifactFailures.length === 1 ? "" : "s"} not ready.`,
@@ -176,10 +241,10 @@ function computeProjectReadinessUncached(runtime, projectDataDir, project) {
176
241
  ? "not-configured"
177
242
  : contextChecksPassed ? "ready" : "not-ready",
178
243
  summary: requiredContextChecks.length === 0
179
- ? "No Context Checks declared by the selected Build Plan."
244
+ ? "No GraphManifest coverage rollup exists for this Build."
180
245
  : contextChecksPassed
181
- ? `${requiredContextChecks.length} required Context Check${requiredContextChecks.length === 1 ? "" : "s"} passed.`
182
- : `${failedContextChecks.length} required Context Check${failedContextChecks.length === 1 ? "" : "s"} not passed.`,
246
+ ? `${requiredContextChecks.length} legacy readiness check${requiredContextChecks.length === 1 ? "" : "s"} passed.`
247
+ : `${failedContextChecks.length} legacy readiness check${failedContextChecks.length === 1 ? "" : "s"} not passed.`,
183
248
  },
184
249
  {
185
250
  gate: "benchmarks",
@@ -241,6 +306,25 @@ function computeProjectReadinessUncached(runtime, projectDataDir, project) {
241
306
  checks,
242
307
  });
243
308
  }
309
+ /**
310
+ * Real on-disk size + mtime for a source file, read live from the source
311
+ * folder. The build manifest only sometimes carries `size_bytes` (older
312
+ * builds omit it) and never carries an mtime, so reads that trusted the
313
+ * manifest showed "0 B / -" for files that are really tens of KB to MBs.
314
+ * Returns null when the file moved or the locator isn't a local path.
315
+ */
316
+ function statSourceFile(sourceLocator, relativePath) {
317
+ try {
318
+ const absolute = join(sourceLocator, relativePath);
319
+ if (!existsSync(absolute))
320
+ return null;
321
+ const info = statSync(absolute);
322
+ return info.isFile() ? { size: info.size, mtime: info.mtime } : null;
323
+ }
324
+ catch {
325
+ return null;
326
+ }
327
+ }
244
328
  export function listSourceFiles(runtime, projectDataDir, projectName) {
245
329
  const config = loadSourceFolderConfig(projectDataDir);
246
330
  const projects = listProjectConfigs(config)
@@ -248,22 +332,25 @@ export function listSourceFiles(runtime, projectDataDir, projectName) {
248
332
  return projects.flatMap((project) => {
249
333
  const buildRuns = listBuildRunsForProject(runtime, projectDataDir, project.name);
250
334
  const latestRun = latestContextGraphBuildRun(project, buildRuns);
251
- const contextGraphPath = latestRun?.context_graph_path ?? null;
335
+ const contextGraphPath = contextGraphPathForBuildRun(projectDataDir, latestRun);
252
336
  const manifest = contextGraphPath ? loadContextGraphSourceManifest(contextGraphPath) : null;
253
337
  if (!manifest)
254
338
  return [];
255
- return manifest.files.map((file) => SourceFileResourceSchema.parse({
256
- project: project.name,
257
- source_manifest_id: manifest.manifest_id,
258
- source_file_id: file.id,
259
- path: file.path,
260
- locator: file.locator,
261
- kind: file.kind,
262
- absolute_path: file.locator,
263
- size_bytes: file.size_bytes ?? 0,
264
- modified_at: null,
265
- source_folder_path: manifest.source.locator,
266
- }));
339
+ return manifest.files.map((file) => {
340
+ const stat = statSourceFile(manifest.source.locator, file.path);
341
+ return SourceFileResourceSchema.parse({
342
+ project: project.name,
343
+ source_manifest_id: manifest.manifest_id,
344
+ source_file_id: file.id,
345
+ path: file.path,
346
+ locator: file.locator,
347
+ kind: file.kind,
348
+ absolute_path: file.locator,
349
+ size_bytes: stat?.size ?? file.size_bytes ?? 0,
350
+ modified_at: stat ? stat.mtime.toISOString() : null,
351
+ source_folder_path: manifest.source.locator,
352
+ });
353
+ });
267
354
  });
268
355
  }
269
356
  export function listContextGraphs(runtime, projectDataDir, projectName) {
@@ -288,6 +375,7 @@ export function getLatestContextGraph(runtime, projectDataDir, projectName) {
288
375
  const buildRunId = contextGraphRun?.run_id ?? null;
289
376
  const rows = context.build_evidence?.rows ?? [];
290
377
  const passing = rows.filter((row) => !row.issue_state || row.issue_state === "pass").length;
378
+ const primaryMetricCount = context.primary_metrics.length;
291
379
  return ContextGraphResourceSchema.parse({
292
380
  ...context,
293
381
  kind: "interf-context-graph",
@@ -300,11 +388,36 @@ export function getLatestContextGraph(runtime, projectDataDir, projectName) {
300
388
  build_evidence: context.build_evidence
301
389
  ? {
302
390
  ...context.build_evidence,
303
- summary: `${context.build_evidence.summary} · ${passing}/${rows.length} checks`,
391
+ summary: primaryMetricCount > 0
392
+ ? `${context.build_evidence.summary} · ${primaryMetricCount} primary metric${primaryMetricCount === 1 ? "" : "s"}`
393
+ : `${context.build_evidence.summary} · ${passing}/${rows.length} diagnostic rows`,
304
394
  }
305
395
  : context.build_evidence,
306
396
  });
307
397
  }
398
+ export function listLatestContextGraphStageSessions(runtime, projectDataDir, projectName) {
399
+ const project = findProjectConfig(loadSourceFolderConfig(projectDataDir), projectName);
400
+ if (!project)
401
+ return [];
402
+ const latestRun = listBuildRunsForProject(runtime, projectDataDir, project.name)[0] ?? null;
403
+ const contextGraphPath = contextGraphPathForBuildRun(projectDataDir, latestRun);
404
+ if (!contextGraphPath || !existsSync(contextGraphPath))
405
+ return [];
406
+ return listStageExecutionSessions(contextGraphPath)
407
+ .map((session) => StageExecutionSessionSchema.parse(session));
408
+ }
409
+ export function getContextGraphHandoff(runtime, projectDataDir, projectName) {
410
+ const context = getLatestContextGraph(runtime, projectDataDir, projectName);
411
+ if (!context)
412
+ return null;
413
+ return ContextGraphHandoffSchema.parse({
414
+ kind: "interf-context-graph-handoff",
415
+ version: 1,
416
+ entrypoint: context.entrypoint_artifact ?? contextGraphEntrypointHandoff(context.path),
417
+ artifacts: context.artifact_handoffs,
418
+ ...(context.portable_handoff ? { portable: context.portable_handoff } : {}),
419
+ });
420
+ }
308
421
  function listProjectContextGraphs(runtime, projectDataDir, project) {
309
422
  const buildRuns = listBuildRunsForProject(runtime, projectDataDir, project.name);
310
423
  const latest = latestContextGraphBuildRun(project, buildRuns);
@@ -317,8 +430,23 @@ function listProjectContextGraphs(runtime, projectDataDir, project) {
317
430
  function getProjectContextGraph(runtime, projectDataDir, project, buildRun, isLatest = true) {
318
431
  if (!buildRun)
319
432
  return null;
320
- const path = buildRun.context_graph_path;
433
+ const path = contextGraphPathForBuildRun(projectDataDir, buildRun);
434
+ if (!path)
435
+ return null;
436
+ // A succeeded Build's Context Graph is immutable: cache the fully-built
437
+ // resource keyed by run id + graph-manifest mtime. Non-succeeded runs (and
438
+ // graphs without a readable manifest) fall through to a fresh compute every
439
+ // time. `isLatest` flips the `is_latest` flag but is otherwise stable per
440
+ // run within a request, so fold it into the cache key suffix.
441
+ if (buildRun.status === "succeeded") {
442
+ return runtime.contextGraphResourceCache.get(projectDataDir, `${buildRun.run_id}:${isLatest ? "latest" : "historical"}`, contextGraphRuntimeGraphManifestPath(path), () => buildProjectContextGraphResource(runtime, projectDataDir, project, buildRun, path, isLatest));
443
+ }
444
+ return buildProjectContextGraphResource(runtime, projectDataDir, project, buildRun, path, isLatest);
445
+ }
446
+ function buildProjectContextGraphResource(runtime, projectDataDir, project, buildRun, path, isLatest) {
321
447
  const config = readInterfConfig(path);
448
+ const sourceManifest = loadContextGraphSourceManifest(path);
449
+ const graphManifest = loadGraphManifest(path);
322
450
  const verifyRuns = listVerifyRunsForProject(runtime, projectDataDir, project.name);
323
451
  const latestVerifyRun = latestTopLevelVerifyRun(verifyRuns);
324
452
  const readiness = computeProjectReadiness(runtime, projectDataDir, project);
@@ -330,7 +458,7 @@ function getProjectContextGraph(runtime, projectDataDir, project, buildRun, isLa
330
458
  try {
331
459
  const activeBuildPlan = getBuildPlan(buildPlan, { projectDataDir });
332
460
  expectedStageCount = activeBuildPlan.stages.length;
333
- const contextInterface = activeBuildPlan.contextInterface ?? activeBuildPlan.schema;
461
+ const contextInterface = activeBuildPlan.contextInterface;
334
462
  expectedOutputCount = contextInterface ? listContextInterfaceArtifacts(contextInterface).length : undefined;
335
463
  }
336
464
  catch {
@@ -338,13 +466,21 @@ function getProjectContextGraph(runtime, projectDataDir, project, buildRun, isLa
338
466
  expectedOutputCount = undefined;
339
467
  }
340
468
  }
469
+ const artifacts = uniqueArtifacts(buildRun.stages.flatMap((stage) => stage.artifacts));
470
+ const artifactHandoffs = contextGraphArtifactHandoffs(path, artifacts);
471
+ const createdAt = buildRun.finished_at ?? buildRun.started_at ?? buildRun.created_at ?? readiness.generated_at;
472
+ const semanticEntrypointPath = artifactHandoffs.find((handoff) => handoff.path?.endsWith(".md"))?.path
473
+ ?? artifactHandoffs[0]?.path
474
+ ?? null;
341
475
  return ContextGraphResourceSchema.parse({
342
476
  kind: "interf-context-graph",
343
477
  version: 1,
344
478
  graph_id: buildRun.run_id,
345
479
  project_id: project.name,
480
+ intent: project.intent ?? "",
346
481
  build_run_id: buildRun.run_id,
347
- created_at: buildRun.finished_at ?? buildRun.started_at ?? buildRun.created_at ?? readiness.generated_at,
482
+ source_manifest_id: sourceManifest?.manifest_id ?? null,
483
+ created_at: createdAt,
348
484
  is_latest: isLatest,
349
485
  project: project.name,
350
486
  path,
@@ -353,7 +489,27 @@ function getProjectContextGraph(runtime, projectDataDir, project, buildRun, isLa
353
489
  build_plan: buildPlan,
354
490
  latest_build_run_id: buildRun.run_id,
355
491
  latest_benchmark_run_id: latestVerifyRun?.run_id ?? null,
356
- artifacts: uniqueArtifacts(buildRun.stages.flatMap((stage) => stage.artifacts)),
492
+ entrypoint_artifact: artifactHandoffs[0] ?? null,
493
+ artifact_handoffs: artifactHandoffs,
494
+ portable_handoff: {
495
+ locator: { kind: "local-path", value: path },
496
+ instructions: "Start from home.md, then follow links into knowledge/, summaries/, and source refs.",
497
+ },
498
+ artifacts,
499
+ semantic_graph: buildContextGraphSemanticGraph({
500
+ contextGraphPath: path,
501
+ projectId: project.name,
502
+ generatedAt: createdAt,
503
+ sourceManifest,
504
+ entrypointPath: semanticEntrypointPath,
505
+ }),
506
+ graph_manifest: graphManifest,
507
+ primary_metrics: graphManifest?.primary_metrics ?? [],
508
+ stage_summaries: graphManifest?.stages ?? [],
509
+ entrypoints: graphManifest?.entrypoints ?? [],
510
+ resources: graphManifest?.resources ?? [],
511
+ readiness_rollup: graphManifest?.readiness ?? null,
512
+ stage_sessions: listStageExecutionSessions(path),
357
513
  build_evidence: buildBuildEvidence({
358
514
  projectId: project.name,
359
515
  buildRun,
@@ -362,9 +518,46 @@ function getProjectContextGraph(runtime, projectDataDir, project, buildRun, isLa
362
518
  expectedStageCount,
363
519
  expectedOutputCount,
364
520
  stageEvidence: latestStageEvidenceForContext(path),
521
+ primaryMetrics: graphManifest?.primary_metrics ?? [],
365
522
  }),
366
523
  });
367
524
  }
525
+ function contextGraphArtifactHandoffs(contextGraphPath, artifacts) {
526
+ const entrypoint = contextGraphEntrypointHandoff(contextGraphPath);
527
+ const outputs = artifacts.filter((artifact) => artifact.role === "output" && isInterfRelativePath(artifact.path));
528
+ const artifactEntrypoints = outputs.filter((artifact) => layerForPath(artifact.path) === "artifacts");
529
+ const ordered = [
530
+ ...artifactEntrypoints,
531
+ ...outputs.filter((artifact) => !artifactEntrypoints.includes(artifact)),
532
+ ];
533
+ const seen = new Set([entrypoint.path]);
534
+ const handoffs = ordered
535
+ .filter((artifact) => {
536
+ if (seen.has(artifact.path))
537
+ return false;
538
+ seen.add(artifact.path);
539
+ return true;
540
+ })
541
+ .map((artifact) => ({
542
+ label: artifact.label ?? artifact.path,
543
+ locator: { kind: "local-path", value: join(contextGraphPath, artifact.path) },
544
+ path: artifact.path,
545
+ role: "artifact",
546
+ ...(artifact.stage_id ? { summary: `Produced by stage ${artifact.stage_id}.` } : {}),
547
+ }));
548
+ return [entrypoint, ...handoffs];
549
+ }
550
+ function contextGraphEntrypointHandoff(contextGraphPath) {
551
+ return {
552
+ artifact_id: "home",
553
+ label: "Context Graph home",
554
+ locator: { kind: "local-path", value: join(contextGraphPath, "home.md") },
555
+ path: "home.md",
556
+ artifact_kind: "file",
557
+ role: "entrypoint",
558
+ summary: "Primary downstream agent entrypoint. Start here before following links into knowledge/ and summaries/.",
559
+ };
560
+ }
368
561
  function latestStageEvidenceForContext(contextGraphPath) {
369
562
  const latestByStage = new Map();
370
563
  for (const entry of readExecutionStageLedgerHistory(contextGraphPath)) {