stageflow 0.2.0 → 0.8.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 (224) hide show
  1. package/README.md +179 -14
  2. package/dist/agent/activity.d.ts +12 -1
  3. package/dist/agent/activity.js +37 -2
  4. package/dist/agent/activityObserver.d.ts +5 -1
  5. package/dist/agent/activityObserver.js +174 -11
  6. package/dist/agent/cursorProvider.d.ts +1 -10
  7. package/dist/agent/cursorProvider.js +18 -10
  8. package/dist/agent/fakeAgent.js +15 -7
  9. package/dist/agent/piAdapter.d.ts +18 -1
  10. package/dist/agent/piAdapter.js +115 -45
  11. package/dist/agent/port.d.ts +7 -0
  12. package/dist/agent/port.js +3 -0
  13. package/dist/agent/providerAuth.js +12 -7
  14. package/dist/catalog/displayCatalogPath.d.ts +12 -0
  15. package/dist/catalog/displayCatalogPath.js +28 -0
  16. package/dist/cli/artifactCommand.d.ts +10 -0
  17. package/dist/cli/artifactCommand.js +120 -0
  18. package/dist/cli/envelopeCommand.d.ts +32 -0
  19. package/dist/cli/envelopeCommand.js +285 -0
  20. package/dist/cli/exportRunCommand.d.ts +10 -0
  21. package/dist/cli/exportRunCommand.js +150 -0
  22. package/dist/cli/handoffFormat.d.ts +22 -0
  23. package/dist/cli/handoffFormat.js +61 -0
  24. package/dist/cli/initCommand.d.ts +9 -0
  25. package/dist/cli/initCommand.js +71 -0
  26. package/dist/cli/initTemplates.d.ts +3 -0
  27. package/dist/cli/initTemplates.js +19 -0
  28. package/dist/cli/operatorCatalog.d.ts +10 -0
  29. package/dist/cli/operatorCatalog.js +16 -0
  30. package/dist/cli/runCommand.d.ts +7 -1
  31. package/dist/cli/runCommand.js +83 -11
  32. package/dist/cli/runOutput.d.ts +4 -1
  33. package/dist/cli/runOutput.js +36 -8
  34. package/dist/cli/skillsCommand.d.ts +16 -0
  35. package/dist/cli/skillsCommand.js +459 -0
  36. package/dist/cli/validateCommand.d.ts +1 -1
  37. package/dist/cli/validateCommand.js +20 -3
  38. package/dist/cli.d.ts +1 -0
  39. package/dist/cli.js +128 -22
  40. package/dist/config/browseCatalog.d.ts +45 -0
  41. package/dist/config/browseCatalog.js +170 -0
  42. package/dist/config/createPipeline.d.ts +12 -3
  43. package/dist/config/createPipeline.js +292 -100
  44. package/dist/config/createStage.d.ts +10 -4
  45. package/dist/config/createStage.js +44 -14
  46. package/dist/config/listConfig.d.ts +14 -19
  47. package/dist/config/listConfig.js +26 -191
  48. package/dist/config/listModelsFromManifest.d.ts +2 -0
  49. package/dist/config/listModelsFromManifest.js +4 -0
  50. package/dist/config/loadOutcome.d.ts +4 -0
  51. package/dist/config/loadPipeline.d.ts +1 -15
  52. package/dist/config/loadPipeline.js +88 -142
  53. package/dist/config/loadStage.d.ts +4 -0
  54. package/dist/config/loadStage.js +60 -32
  55. package/dist/config/loadStageflowManifest.d.ts +5 -0
  56. package/dist/config/loadStageflowManifest.js +157 -0
  57. package/dist/config/mergePipelineIncludes.d.ts +9 -0
  58. package/dist/config/mergePipelineIncludes.js +141 -0
  59. package/dist/config/normalizePipelineStageEntry.d.ts +18 -0
  60. package/dist/config/normalizePipelineStageEntry.js +210 -0
  61. package/dist/config/pipelineStageKeys.d.ts +4 -0
  62. package/dist/config/pipelineStageKeys.js +14 -0
  63. package/dist/config/resolveCatalogContext.d.ts +13 -0
  64. package/dist/config/resolveCatalogContext.js +13 -0
  65. package/dist/config/resolveForkEmitContext.d.ts +4 -0
  66. package/dist/config/resolveForkEmitContext.js +34 -0
  67. package/dist/config/resolvePipelineDag.d.ts +5 -1
  68. package/dist/config/resolvePipelineDag.js +86 -36
  69. package/dist/config/scanCatalogPaths.d.ts +4 -0
  70. package/dist/config/scanCatalogPaths.js +83 -0
  71. package/dist/config/validateCatalog.d.ts +22 -4
  72. package/dist/config/validateCatalog.js +169 -132
  73. package/dist/envelope/check.d.ts +2 -0
  74. package/dist/envelope/check.js +58 -0
  75. package/dist/envelope/cloneForks.d.ts +2 -0
  76. package/dist/envelope/cloneForks.js +62 -0
  77. package/dist/envelope/forkChoice.d.ts +3 -0
  78. package/dist/envelope/forkChoice.js +47 -0
  79. package/dist/index.d.ts +6 -0
  80. package/dist/index.js +6 -0
  81. package/dist/mcp/catalogTools.d.ts +3 -0
  82. package/dist/mcp/catalogTools.js +187 -0
  83. package/dist/mcp/controlTools.d.ts +3 -0
  84. package/dist/mcp/controlTools.js +147 -0
  85. package/dist/mcp/deps.d.ts +11 -0
  86. package/dist/mcp/deps.js +1 -0
  87. package/dist/mcp/projectRun.d.ts +1 -37
  88. package/dist/mcp/projectRun.js +1 -57
  89. package/dist/mcp/resources.d.ts +5 -0
  90. package/dist/mcp/resources.js +45 -0
  91. package/dist/mcp/server.d.ts +16 -3
  92. package/dist/mcp/server.js +159 -3
  93. package/dist/mcp/toolResults.d.ts +7 -0
  94. package/dist/mcp/toolResults.js +6 -0
  95. package/dist/mcp/tools.d.ts +2 -7
  96. package/dist/mcp/tools.js +52 -86
  97. package/dist/mcp/waitRun.d.ts +46 -0
  98. package/dist/mcp/waitRun.js +146 -0
  99. package/dist/mcp/waitingGates.d.ts +12 -0
  100. package/dist/mcp/waitingGates.js +83 -0
  101. package/dist/project/findProjectRoot.d.ts +2 -0
  102. package/dist/project/findProjectRoot.js +51 -0
  103. package/dist/project/globalHome.d.ts +2 -0
  104. package/dist/project/globalHome.js +19 -0
  105. package/dist/project/resolveProjectContext.d.ts +9 -0
  106. package/dist/project/resolveProjectContext.js +38 -0
  107. package/dist/project/resolveStageflowContext.d.ts +11 -0
  108. package/dist/project/resolveStageflowContext.js +66 -0
  109. package/dist/projection/projectRun.d.ts +44 -0
  110. package/dist/projection/projectRun.js +71 -0
  111. package/dist/prompt/priorEnvelope.d.ts +1 -1
  112. package/dist/prompt/priorEnvelope.js +4 -1
  113. package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
  114. package/dist/runstore/normalizeCatalogPath.js +4 -0
  115. package/dist/runstore/paths.js +2 -0
  116. package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
  117. package/dist/runstore/pipelineDagSnapshot.js +90 -1
  118. package/dist/runstore/port.d.ts +22 -2
  119. package/dist/runstore/port.js +4 -1
  120. package/dist/runstore/runProjection.d.ts +1 -1
  121. package/dist/runstore/runProjection.js +25 -4
  122. package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
  123. package/dist/runstore/sqlite/SqliteRunStore.js +65 -7
  124. package/dist/runstore/sqlite/schema.d.ts +1 -1
  125. package/dist/runstore/sqlite/schema.js +4 -1
  126. package/dist/runstore/stageInstanceId.d.ts +4 -0
  127. package/dist/runstore/stageInstanceId.js +23 -0
  128. package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
  129. package/dist/runstore/syntheticStageSnapshot.js +2 -1
  130. package/dist/runstore/trackProjection.js +28 -12
  131. package/dist/runstore/workspaceLayout.d.ts +1 -0
  132. package/dist/runstore/workspaceLayout.js +1 -1
  133. package/dist/runtime/cloneSchedule.d.ts +18 -0
  134. package/dist/runtime/cloneSchedule.js +233 -0
  135. package/dist/runtime/credentialBinding.d.ts +8 -6
  136. package/dist/runtime/credentialBinding.js +18 -24
  137. package/dist/runtime/envelopeRouting.d.ts +2 -1
  138. package/dist/runtime/envelopeRouting.js +80 -13
  139. package/dist/runtime/pipelineRunner.d.ts +3 -0
  140. package/dist/runtime/pipelineRunner.js +15 -1
  141. package/dist/runtime/pipelineScheduler.d.ts +8 -2
  142. package/dist/runtime/pipelineScheduler.js +215 -39
  143. package/dist/runtime/reloadRunCatalog.d.ts +5 -0
  144. package/dist/runtime/reloadRunCatalog.js +44 -0
  145. package/dist/runtime/resumeReconstruct.d.ts +1 -0
  146. package/dist/runtime/resumeReconstruct.js +31 -10
  147. package/dist/runtime/runChangeBus.d.ts +18 -0
  148. package/dist/runtime/runChangeBus.js +83 -0
  149. package/dist/runtime/runManager.d.ts +4 -0
  150. package/dist/runtime/runManager.js +36 -16
  151. package/dist/runtime/settingsFile.d.ts +12 -0
  152. package/dist/runtime/settingsFile.js +87 -6
  153. package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
  154. package/dist/runtime/stageAttemptBootstrap.js +18 -7
  155. package/dist/runtime/stageProcessLauncher.js +3 -0
  156. package/dist/runtime/stageRecovery.js +3 -1
  157. package/dist/runtime/stageRunner.d.ts +1 -0
  158. package/dist/runtime/stageRunner.js +17 -15
  159. package/dist/runtime/stageWorker.js +13 -10
  160. package/dist/server/bootstrap.d.ts +32 -0
  161. package/dist/server/bootstrap.js +56 -0
  162. package/dist/server/createHttpHost.d.ts +34 -0
  163. package/dist/server/createHttpHost.js +70 -0
  164. package/dist/server/http.d.ts +6 -13
  165. package/dist/server/http.js +297 -352
  166. package/dist/server/mcpHost.d.ts +22 -0
  167. package/dist/server/mcpHost.js +21 -0
  168. package/dist/server/operatorResults.d.ts +13 -0
  169. package/dist/server/operatorResults.js +22 -0
  170. package/dist/tools/emitStageEnvelope.d.ts +12 -1
  171. package/dist/tools/emitStageEnvelope.js +27 -1
  172. package/dist/types/envelope.d.ts +3 -0
  173. package/dist/types/forkChoice.d.ts +30 -0
  174. package/dist/types/forkChoice.js +1 -0
  175. package/dist/types/pipeline.d.ts +59 -2
  176. package/dist/types/stageflowManifest.d.ts +23 -0
  177. package/dist/types/stageflowManifest.js +1 -0
  178. package/dist/ui/assets/{index-DCsDopak.css → index-C3N7MXAC.css} +1 -1
  179. package/dist/ui/assets/index-CSoGDA3A.js +118 -0
  180. package/dist/ui/index.html +3 -2
  181. package/dist/ui/stageflow-icon.svg +12 -0
  182. package/package.json +8 -5
  183. package/skills/install-suite.sh +107 -0
  184. package/skills/stageflow/SKILL.md +26 -0
  185. package/skills/stageflow/references/control-surface.md +26 -0
  186. package/skills/stageflow/scripts/detect-host.mjs +54 -0
  187. package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
  188. package/skills/stageflow-author/SKILL.md +58 -0
  189. package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
  190. package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
  191. package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
  192. package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
  193. package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
  194. package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
  195. package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
  196. package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
  197. package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
  198. package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
  199. package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
  200. package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
  201. package/skills/stageflow-author/references/catalog-mapping.md +131 -0
  202. package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
  203. package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
  204. package/skills/stageflow-author/references/validate-and-report.md +38 -0
  205. package/skills/stageflow-delegate/SKILL.md +18 -0
  206. package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
  207. package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
  208. package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
  209. package/skills/stageflow-run/SKILL.md +151 -0
  210. package/skills/stageflow-run/references/mcp-call.md +17 -0
  211. package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
  212. package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
  213. package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
  214. package/skills/stageflow-session-capture/SKILL.md +94 -0
  215. package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
  216. package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
  217. package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
  218. package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
  219. package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
  220. package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
  221. package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
  222. package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
  223. package/skills/stageflow-setup/SKILL.md +110 -0
  224. package/dist/ui/assets/index-Cry0Tpfx.js +0 -118
@@ -0,0 +1,51 @@
1
+ import { existsSync, realpathSync } from "node:fs";
2
+ import { execFileSync } from "node:child_process";
3
+ import path from "node:path";
4
+ const cache = new Map();
5
+ function normalizeRoot(dir) {
6
+ try {
7
+ return realpathSync(path.resolve(dir));
8
+ }
9
+ catch {
10
+ return path.resolve(dir);
11
+ }
12
+ }
13
+ export function findProjectRoot(startDir) {
14
+ const normalized = path.resolve(startDir);
15
+ if (cache.has(normalized)) {
16
+ return cache.get(normalized) ?? null;
17
+ }
18
+ let result = null;
19
+ try {
20
+ const stdout = execFileSync("git", ["rev-parse", "--show-toplevel"], {
21
+ cwd: normalized,
22
+ encoding: "utf8",
23
+ stdio: ["ignore", "pipe", "ignore"],
24
+ });
25
+ const trimmed = stdout.trim();
26
+ if (trimmed.length > 0) {
27
+ result = normalizeRoot(trimmed);
28
+ }
29
+ }
30
+ catch {
31
+ result = walkForGitRoot(normalized);
32
+ }
33
+ cache.set(normalized, result);
34
+ return result;
35
+ }
36
+ function walkForGitRoot(startDir) {
37
+ let dir = path.resolve(startDir);
38
+ for (;;) {
39
+ if (existsSync(path.join(dir, ".git"))) {
40
+ return normalizeRoot(dir);
41
+ }
42
+ const parent = path.dirname(dir);
43
+ if (parent === dir) {
44
+ return null;
45
+ }
46
+ dir = parent;
47
+ }
48
+ }
49
+ export function clearFindProjectRootCacheForTests() {
50
+ cache.clear();
51
+ }
@@ -0,0 +1,2 @@
1
+ export declare function globalStageflowHome(): string;
2
+ export declare function ensureGlobalHome(): string;
@@ -0,0 +1,19 @@
1
+ import { chmodSync, mkdirSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ export function globalStageflowHome() {
5
+ return path.join(os.homedir(), ".stageflow");
6
+ }
7
+ export function ensureGlobalHome() {
8
+ const globalHome = globalStageflowHome();
9
+ mkdirSync(globalHome, { recursive: true });
10
+ const agentDir = path.join(globalHome, "agent");
11
+ mkdirSync(agentDir, { recursive: true });
12
+ try {
13
+ chmodSync(agentDir, 0o700);
14
+ }
15
+ catch {
16
+ // best-effort on non-POSIX
17
+ }
18
+ return globalHome;
19
+ }
@@ -0,0 +1,9 @@
1
+ export type ProjectContext = {
2
+ invocationCwd: string;
3
+ projectRoot: string;
4
+ globalHome: string;
5
+ isGitProject: boolean;
6
+ };
7
+ export declare function resolveProjectContext(invocationCwd: string): ProjectContext;
8
+ export declare function legacyProjectContext(cwd: string): ProjectContext;
9
+ export declare function asProjectContext(input: ProjectContext | string): ProjectContext;
@@ -0,0 +1,38 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { findProjectRoot } from "./findProjectRoot.js";
4
+ import { ensureGlobalHome, globalStageflowHome } from "./globalHome.js";
5
+ export function resolveProjectContext(invocationCwd) {
6
+ const resolvedInvocation = path.resolve(invocationCwd);
7
+ const gitRoot = findProjectRoot(resolvedInvocation);
8
+ const globalHome = ensureGlobalHome();
9
+ if (gitRoot !== null) {
10
+ return {
11
+ invocationCwd: resolvedInvocation,
12
+ projectRoot: gitRoot,
13
+ globalHome,
14
+ isGitProject: true,
15
+ };
16
+ }
17
+ return {
18
+ invocationCwd: resolvedInvocation,
19
+ projectRoot: os.homedir(),
20
+ globalHome,
21
+ isGitProject: false,
22
+ };
23
+ }
24
+ export function legacyProjectContext(cwd) {
25
+ const invocationCwd = path.resolve(cwd);
26
+ return {
27
+ invocationCwd,
28
+ projectRoot: invocationCwd,
29
+ globalHome: globalStageflowHome(),
30
+ isGitProject: false,
31
+ };
32
+ }
33
+ export function asProjectContext(input) {
34
+ if (typeof input !== "string") {
35
+ return input;
36
+ }
37
+ return resolveProjectContext(input);
38
+ }
@@ -0,0 +1,11 @@
1
+ import type { LoadedManifest } from "../types/stageflowManifest.js";
2
+ import type { LoadIssue } from "../config/loadOutcome.js";
3
+ import type { ProjectContext } from "./resolveProjectContext.js";
4
+ export type CatalogManifestStatus = "ok" | "missing" | "invalid" | "not_git";
5
+ export type StageflowContext = ProjectContext & {
6
+ manifest: LoadedManifest | null;
7
+ manifestStatus: CatalogManifestStatus;
8
+ manifestIssues: LoadIssue[];
9
+ };
10
+ export declare function projectContextFromStageflow(ctx: StageflowContext): ProjectContext;
11
+ export declare function resolveStageflowContext(invocationCwd: string): Promise<StageflowContext>;
@@ -0,0 +1,66 @@
1
+ import { access } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { loadStageflowManifestOutcome, manifestPathForProject, } from "../config/loadStageflowManifest.js";
5
+ import { findProjectRoot } from "./findProjectRoot.js";
6
+ import { ensureGlobalHome } from "./globalHome.js";
7
+ export function projectContextFromStageflow(ctx) {
8
+ return {
9
+ invocationCwd: ctx.invocationCwd,
10
+ projectRoot: ctx.projectRoot,
11
+ globalHome: ctx.globalHome,
12
+ isGitProject: ctx.isGitProject,
13
+ };
14
+ }
15
+ export async function resolveStageflowContext(invocationCwd) {
16
+ const resolvedInvocation = path.resolve(invocationCwd);
17
+ const gitRoot = findProjectRoot(resolvedInvocation);
18
+ const globalHome = ensureGlobalHome();
19
+ if (gitRoot === null) {
20
+ return {
21
+ invocationCwd: resolvedInvocation,
22
+ projectRoot: os.homedir(),
23
+ globalHome,
24
+ isGitProject: false,
25
+ manifest: null,
26
+ manifestStatus: "not_git",
27
+ manifestIssues: [],
28
+ };
29
+ }
30
+ const manifestPath = manifestPathForProject(gitRoot);
31
+ try {
32
+ await access(manifestPath);
33
+ }
34
+ catch {
35
+ return {
36
+ invocationCwd: resolvedInvocation,
37
+ projectRoot: gitRoot,
38
+ globalHome,
39
+ isGitProject: true,
40
+ manifest: null,
41
+ manifestStatus: "missing",
42
+ manifestIssues: [],
43
+ };
44
+ }
45
+ const outcome = await loadStageflowManifestOutcome(gitRoot);
46
+ if (!outcome.ok) {
47
+ return {
48
+ invocationCwd: resolvedInvocation,
49
+ projectRoot: gitRoot,
50
+ globalHome,
51
+ isGitProject: true,
52
+ manifest: null,
53
+ manifestStatus: "invalid",
54
+ manifestIssues: outcome.issues,
55
+ };
56
+ }
57
+ return {
58
+ invocationCwd: resolvedInvocation,
59
+ projectRoot: gitRoot,
60
+ globalHome,
61
+ isGitProject: true,
62
+ manifest: outcome.value,
63
+ manifestStatus: "ok",
64
+ manifestIssues: [],
65
+ };
66
+ }
@@ -0,0 +1,44 @@
1
+ import type { RunDetail, StageSnapshot } from "../runstore/port.js";
2
+ import type { PipelineTrackProjection, RunSummary } from "../runstore/port.js";
3
+ import type { AskOperatorPrompt } from "../tools/askOperator.js";
4
+ import type { StageEnvelope } from "../types/envelope.js";
5
+ import type { CloneForkItem } from "../types/forkChoice.js";
6
+ export type StageProjection = {
7
+ stage_id: string;
8
+ status: StageSnapshot["status"];
9
+ envelope: {
10
+ status: StageEnvelope["status"];
11
+ summary: string;
12
+ artifacts: string[];
13
+ payload?: Record<string, unknown>;
14
+ fork_choice?: string[];
15
+ clone_forks?: CloneForkItem[];
16
+ } | null;
17
+ artifacts: string[];
18
+ last_at?: string;
19
+ pending_prompt?: AskOperatorPrompt;
20
+ };
21
+ export type RunProjection = {
22
+ run_id: string;
23
+ pipeline_id: string;
24
+ task_id?: string;
25
+ pipeline_path?: string;
26
+ task_path?: string;
27
+ project_root?: string;
28
+ status: RunDetail["status"];
29
+ created_at: string;
30
+ updated_at?: string;
31
+ stages: StageProjection[];
32
+ pipeline_track: PipelineTrackProjection;
33
+ waiting_stage_id?: RunSummary["waiting_stage_id"];
34
+ waiting_stage_ids?: RunSummary["waiting_stage_ids"];
35
+ waiting_summary?: RunSummary["waiting_summary"];
36
+ waiting_kind?: RunSummary["waiting_kind"];
37
+ waiting_prompt_id?: RunSummary["waiting_prompt_id"];
38
+ waiting_artifacts?: RunSummary["waiting_artifacts"];
39
+ waiting_questions?: RunSummary["waiting_questions"];
40
+ failed_stage_id?: RunSummary["failed_stage_id"];
41
+ failed_reason?: RunSummary["failed_reason"];
42
+ };
43
+ export declare function projectRun(detail: RunDetail): RunProjection;
44
+ export { projectRun as projectRunForMcp };
@@ -0,0 +1,71 @@
1
+ export function projectRun(detail) {
2
+ return {
3
+ run_id: detail.run_id,
4
+ pipeline_id: detail.pipeline_id,
5
+ task_id: detail.task_id,
6
+ ...(detail.pipeline_path !== undefined
7
+ ? { pipeline_path: detail.pipeline_path }
8
+ : {}),
9
+ ...(detail.task_path !== undefined ? { task_path: detail.task_path } : {}),
10
+ ...(detail.project_root !== undefined
11
+ ? { project_root: detail.project_root }
12
+ : {}),
13
+ status: detail.status,
14
+ created_at: detail.created_at,
15
+ updated_at: detail.updated_at,
16
+ pipeline_track: detail.pipeline_track,
17
+ ...(detail.waiting_stage_id !== undefined
18
+ ? { waiting_stage_id: detail.waiting_stage_id }
19
+ : {}),
20
+ ...(detail.waiting_stage_ids !== undefined
21
+ ? { waiting_stage_ids: detail.waiting_stage_ids }
22
+ : {}),
23
+ ...(detail.waiting_summary !== undefined
24
+ ? { waiting_summary: detail.waiting_summary }
25
+ : {}),
26
+ ...(detail.waiting_kind !== undefined
27
+ ? { waiting_kind: detail.waiting_kind }
28
+ : {}),
29
+ ...(detail.waiting_prompt_id !== undefined
30
+ ? { waiting_prompt_id: detail.waiting_prompt_id }
31
+ : {}),
32
+ ...(detail.waiting_artifacts !== undefined
33
+ ? { waiting_artifacts: detail.waiting_artifacts }
34
+ : {}),
35
+ ...(detail.waiting_questions !== undefined
36
+ ? { waiting_questions: detail.waiting_questions }
37
+ : {}),
38
+ ...(detail.failed_stage_id !== undefined
39
+ ? { failed_stage_id: detail.failed_stage_id }
40
+ : {}),
41
+ ...(detail.failed_reason !== undefined
42
+ ? { failed_reason: detail.failed_reason }
43
+ : {}),
44
+ stages: detail.stages.map((stage) => ({
45
+ stage_id: stage.stage_id,
46
+ status: stage.status,
47
+ envelope: stage.envelope
48
+ ? {
49
+ status: stage.envelope.status,
50
+ summary: stage.envelope.summary,
51
+ artifacts: stage.envelope.artifacts,
52
+ ...(stage.envelope.payload !== undefined
53
+ ? { payload: stage.envelope.payload }
54
+ : {}),
55
+ ...(stage.envelope.fork_choice !== undefined
56
+ ? { fork_choice: stage.envelope.fork_choice }
57
+ : {}),
58
+ ...(stage.envelope.clone_forks !== undefined
59
+ ? { clone_forks: stage.envelope.clone_forks }
60
+ : {}),
61
+ }
62
+ : null,
63
+ artifacts: stage.artifacts,
64
+ last_at: stage.last_at,
65
+ ...(stage.pending_prompt
66
+ ? { pending_prompt: stage.pending_prompt }
67
+ : {}),
68
+ })),
69
+ };
70
+ }
71
+ export { projectRun as projectRunForMcp };
@@ -1,2 +1,2 @@
1
1
  import type { StageEnvelope } from "../types/envelope.js";
2
- export declare function formatPriorEnvelope(priorEnvelope: StageEnvelope | null): string;
2
+ export declare function formatPriorEnvelope(priorEnvelope: StageEnvelope | null, priorEnvelopes?: StageEnvelope[]): string;
@@ -1,4 +1,7 @@
1
- export function formatPriorEnvelope(priorEnvelope) {
1
+ export function formatPriorEnvelope(priorEnvelope, priorEnvelopes) {
2
+ if (priorEnvelopes !== undefined && priorEnvelopes.length > 0) {
3
+ return `Prior envelopes (${priorEnvelopes.length} clones, clone-list order):\n${JSON.stringify(priorEnvelopes, null, 2)}`;
4
+ }
2
5
  return priorEnvelope === null
3
6
  ? "No prior envelope (first stage)."
4
7
  : `Prior envelope JSON:\n${JSON.stringify(priorEnvelope, null, 2)}`;
@@ -0,0 +1 @@
1
+ export declare function normalizeCatalogPath(raw: string): string;
@@ -0,0 +1,4 @@
1
+ import path from "node:path";
2
+ export function normalizeCatalogPath(raw) {
3
+ return path.resolve(raw);
4
+ }
@@ -1,6 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { readdirSync, renameSync, rmdirSync, statSync } from "node:fs";
3
3
  import path from "node:path";
4
+ import { guardStageId } from "./workspaceLayout.js";
4
5
  export function newRunId() {
5
6
  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
6
7
  return `${stamp}-${randomBytes(3).toString("hex")}`;
@@ -56,5 +57,6 @@ export function runWorkspaceDir(storeRoot, runId) {
56
57
  return path.join(runsDir(storeRoot), runId);
57
58
  }
58
59
  export function stageDir(workspaceDir, stageId) {
60
+ guardStageId(stageId);
59
61
  return path.join(workspaceDir, "stages", stageId);
60
62
  }
@@ -3,3 +3,12 @@ import type { RunPipelineDagSnapshot } from "./port.js";
3
3
  export declare function buildPipelineDagSnapshotFromLoaded(loaded: LoadedPipeline): RunPipelineDagSnapshot;
4
4
  export declare function linearCompatDagSnapshot(stageIds: string[]): RunPipelineDagSnapshot;
5
5
  export declare function parsePipelineDagSnapshot(raw: unknown): RunPipelineDagSnapshot | null;
6
+ export declare function instancesOfDefinition(snapshot: RunPipelineDagSnapshot, catalogId: string): string[];
7
+ export declare function appendCloneInstances(snapshot: RunPipelineDagSnapshot, input: {
8
+ catalogId: string;
9
+ predecessorId: string;
10
+ count: number;
11
+ }): {
12
+ snapshot: RunPipelineDagSnapshot;
13
+ instanceIds: string[];
14
+ };
@@ -1,3 +1,4 @@
1
+ import { mintCloneInstanceIds } from "./stageInstanceId.js";
1
2
  export function buildPipelineDagSnapshotFromLoaded(loaded) {
2
3
  const gate_kinds = {};
3
4
  for (const stage of loaded.stages) {
@@ -7,7 +8,10 @@ export function buildPipelineDagSnapshotFromLoaded(loaded) {
7
8
  }
8
9
  return {
9
10
  stage_ids: [...loaded.pipeline.stages],
10
- nodes: loaded.dag.nodes,
11
+ nodes: loaded.dag.nodes.map((node) => ({
12
+ ...node,
13
+ definition_id: node.id,
14
+ })),
11
15
  roots: loaded.dag.roots,
12
16
  childrenOf: loaded.dag.childrenOf,
13
17
  ...(Object.keys(gate_kinds).length > 0 ? { gate_kinds } : {}),
@@ -19,6 +23,7 @@ export function linearCompatDagSnapshot(stageIds) {
19
23
  needs: index === 0 ? null : stageIds[index - 1],
20
24
  ancestors: index === 0 ? [] : stageIds.slice(0, index),
21
25
  stageIndex: index,
26
+ definition_id: id,
22
27
  }));
23
28
  const childrenOf = {};
24
29
  for (let i = 1; i < stageIds.length; i++) {
@@ -41,3 +46,87 @@ export function parsePipelineDagSnapshot(raw) {
41
46
  return null;
42
47
  return raw;
43
48
  }
49
+ export function instancesOfDefinition(snapshot, catalogId) {
50
+ const nodeById = new Map(snapshot.nodes.map((n) => [n.id, n]));
51
+ return snapshot.stage_ids.filter((id) => {
52
+ const node = nodeById.get(id);
53
+ return (node?.definition_id ?? node?.id) === catalogId;
54
+ });
55
+ }
56
+ export function appendCloneInstances(snapshot, input) {
57
+ const { catalogId, predecessorId, count } = input;
58
+ if (!snapshot.stage_ids.includes(catalogId)) {
59
+ throw new Error(`catalog stage "${catalogId}" is not in the run DAG snapshot`);
60
+ }
61
+ const predNode = snapshot.nodes.find((n) => n.id === predecessorId);
62
+ if (!predNode) {
63
+ throw new Error(`predecessor "${predecessorId}" is not in the run DAG snapshot`);
64
+ }
65
+ const instanceIds = mintCloneInstanceIds(catalogId, count);
66
+ for (const id of instanceIds) {
67
+ if (snapshot.stage_ids.includes(id) ||
68
+ snapshot.nodes.some((n) => n.id === id)) {
69
+ throw new Error(`instance id "${id}" already exists in the run DAG snapshot`);
70
+ }
71
+ }
72
+ const catalogNode = snapshot.nodes.find((n) => n.id === catalogId);
73
+ if (!catalogNode) {
74
+ throw new Error(`catalog stage "${catalogId}" is not in the run DAG snapshot`);
75
+ }
76
+ const catalogIdx = snapshot.stage_ids.indexOf(catalogId);
77
+ const stage_ids = [
78
+ ...snapshot.stage_ids.slice(0, catalogIdx),
79
+ ...instanceIds,
80
+ ...snapshot.stage_ids.slice(catalogIdx + 1),
81
+ ];
82
+ const instanceNodes = instanceIds.map((id) => ({
83
+ id,
84
+ definition_id: catalogId,
85
+ needs: predecessorId,
86
+ ancestors: [...predNode.ancestors, predecessorId],
87
+ stageIndex: 0,
88
+ ...(catalogNode.fork !== undefined ? { fork: catalogNode.fork } : {}),
89
+ }));
90
+ const remaining = new Map(snapshot.nodes
91
+ .filter((n) => n.id !== catalogId)
92
+ .map((n) => [n.id, n]));
93
+ const minted = new Map(instanceNodes.map((n) => [n.id, n]));
94
+ const nodes = stage_ids.map((id, index) => {
95
+ const node = minted.get(id) ?? remaining.get(id);
96
+ if (!node) {
97
+ throw new Error(`missing node for "${id}" after clone fan-out`);
98
+ }
99
+ return { ...node, stageIndex: index };
100
+ });
101
+ const childrenOf = {};
102
+ for (const [key, children] of Object.entries(snapshot.childrenOf)) {
103
+ childrenOf[key] = [...children];
104
+ }
105
+ const predChildren = childrenOf[predecessorId] ?? [];
106
+ const predIdx = predChildren.indexOf(catalogId);
107
+ childrenOf[predecessorId] =
108
+ predIdx === -1
109
+ ? [...predChildren, ...instanceIds]
110
+ : [
111
+ ...predChildren.slice(0, predIdx),
112
+ ...instanceIds,
113
+ ...predChildren.slice(predIdx + 1),
114
+ ];
115
+ const definitionParent = catalogNode.needs;
116
+ if (definitionParent && definitionParent !== predecessorId) {
117
+ const parentChildren = childrenOf[definitionParent] ?? [];
118
+ childrenOf[definitionParent] = parentChildren.filter((id) => id !== catalogId);
119
+ if (childrenOf[definitionParent].length === 0) {
120
+ delete childrenOf[definitionParent];
121
+ }
122
+ }
123
+ return {
124
+ snapshot: {
125
+ ...snapshot,
126
+ stage_ids,
127
+ nodes,
128
+ childrenOf,
129
+ },
130
+ instanceIds,
131
+ };
132
+ }
@@ -18,6 +18,7 @@ export type PipelineTrackNode = {
18
18
  blocked_by?: string[];
19
19
  gate_kinds?: StageGateKind[];
20
20
  attempt_count?: number;
21
+ definition_id?: string;
21
22
  };
22
23
  export type PipelineTrackEdge = {
23
24
  from: string;
@@ -40,6 +41,9 @@ export type RunMeta = {
40
41
  ci_pr_url?: string;
41
42
  ci_job_url?: string;
42
43
  pipeline_dag?: RunPipelineDagSnapshot;
44
+ pipeline_path?: string;
45
+ task_path?: string;
46
+ project_root?: string;
43
47
  };
44
48
  export type CreatedRun = {
45
49
  runId: string;
@@ -52,7 +56,8 @@ export type StageLogEvent = StageLogLine & {
52
56
  };
53
57
  export type StageSnapshot = {
54
58
  stage_id: string;
55
- status: "pending" | "running" | "waiting_for_input" | "succeeded" | "failed";
59
+ definition_id?: string;
60
+ status: "pending" | "running" | "waiting_for_input" | "succeeded" | "failed" | "skipped";
56
61
  events: StageLogEvent[];
57
62
  envelope: StageEnvelope | null;
58
63
  artifacts: string[];
@@ -79,11 +84,15 @@ export type CompactStage = {
79
84
  id: string;
80
85
  status: StageSnapshot["status"];
81
86
  attempt_count: number;
87
+ definition_id?: string;
82
88
  };
83
89
  export type RunSummary = {
84
90
  run_id: string;
85
91
  pipeline_id: string;
86
92
  task_id?: string;
93
+ pipeline_path?: string;
94
+ task_path?: string;
95
+ project_root?: string;
87
96
  status: RunStatus;
88
97
  created_at: string;
89
98
  updated_at?: string;
@@ -115,6 +124,16 @@ export type CreateRunInput = {
115
124
  ciPrUrl?: string;
116
125
  ciJobUrl?: string;
117
126
  pipelineDag?: RunPipelineDagSnapshot;
127
+ pipelinePath?: string;
128
+ taskPath?: string;
129
+ projectRoot?: string;
130
+ };
131
+ export type ListRunsFilter = {
132
+ status?: RunStatus;
133
+ /** ISO timestamp; keep runs with created_at >= since */
134
+ since?: string;
135
+ /** Match pipeline_id or pipeline_path */
136
+ pipeline?: string;
118
137
  };
119
138
  /**
120
139
  * Persistence port for pipeline run state.
@@ -145,8 +164,9 @@ export interface RunStore {
145
164
  attempt?: number;
146
165
  }): Promise<void>;
147
166
  listStageEvents(runId: string, stageId: string, attempt?: number): Promise<StageLogEvent[]>;
148
- listRuns(): Promise<RunSummary[]>;
167
+ listRuns(filter?: ListRunsFilter): Promise<RunSummary[]>;
149
168
  readRun(runId: string): Promise<RunDetail>;
169
+ updatePipelineDag(runId: string, dag: RunPipelineDagSnapshot): Promise<void>;
150
170
  }
151
171
  export declare function stageStatusFromEvents(events: StageLogEvent[]): StageSnapshot["status"];
152
172
  export declare function deriveStatusFromStages(stages: StageSnapshot[]): RunStatus;
@@ -9,6 +9,8 @@ export function stageStatusFromEvents(events) {
9
9
  status = "succeeded";
10
10
  if (ev.event === "failed")
11
11
  status = "failed";
12
+ if (ev.event === "skipped")
13
+ status = "skipped";
12
14
  }
13
15
  return status;
14
16
  }
@@ -17,8 +19,9 @@ export function deriveStatusFromStages(stages) {
17
19
  return "created";
18
20
  if (stages.some((s) => s.status === "failed"))
19
21
  return "failed";
20
- if (stages.every((s) => s.status === "succeeded"))
22
+ if (stages.every((s) => s.status === "succeeded" || s.status === "skipped")) {
21
23
  return "succeeded";
24
+ }
22
25
  if (stages.some((s) => s.status === "running" ||
23
26
  s.status === "waiting_for_input" ||
24
27
  s.status === "succeeded")) {
@@ -1,6 +1,6 @@
1
1
  import type { RunDetail, RunMeta, RunPipelineDagSnapshot, RunSummary, StageSnapshot } from "./port.js";
2
2
  export { syntheticPendingSnapshot } from "./syntheticStageSnapshot.js";
3
- export declare function overlayPlannedStages(stageIds: string[], snapshots: StageSnapshot[]): StageSnapshot[];
3
+ export declare function overlayPlannedStages(stageIds: string[], snapshots: StageSnapshot[], dag?: Pick<RunPipelineDagSnapshot, "nodes"> | null): StageSnapshot[];
4
4
  export declare function orderStageSnapshots(stageIds: string[], snapshots: StageSnapshot[]): StageSnapshot[];
5
5
  export declare function projectRunSummary(meta: RunMeta, stages: StageSnapshot[]): RunSummary;
6
6
  export declare function projectRunDetail(meta: RunMeta, stages: StageSnapshot[], task_yaml: string, dagSnapshot?: RunPipelineDagSnapshot | null): RunDetail;
@@ -3,9 +3,19 @@ import { linearCompatDagSnapshot } from "./pipelineDagSnapshot.js";
3
3
  import { syntheticPendingSnapshot } from "./syntheticStageSnapshot.js";
4
4
  import { buildPipelineTrack } from "./trackProjection.js";
5
5
  export { syntheticPendingSnapshot } from "./syntheticStageSnapshot.js";
6
- export function overlayPlannedStages(stageIds, snapshots) {
6
+ export function overlayPlannedStages(stageIds, snapshots, dag) {
7
7
  const byId = new Map(snapshots.map((s) => [s.stage_id, s]));
8
- return stageIds.map((id) => byId.get(id) ?? syntheticPendingSnapshot(id));
8
+ const nodeById = new Map((dag?.nodes ?? []).map((n) => [n.id, n]));
9
+ return stageIds.map((id) => {
10
+ const node = nodeById.get(id);
11
+ const definitionId = node?.definition_id ?? node?.id;
12
+ const existing = byId.get(id);
13
+ const base = existing ?? syntheticPendingSnapshot(id, definitionId);
14
+ if (definitionId === undefined || base.definition_id === definitionId) {
15
+ return base;
16
+ }
17
+ return { ...base, definition_id: definitionId };
18
+ });
9
19
  }
10
20
  export function orderStageSnapshots(stageIds, snapshots) {
11
21
  return overlayPlannedStages(stageIds, snapshots);
@@ -15,6 +25,7 @@ function compactStages(stages) {
15
25
  id: s.stage_id,
16
26
  status: s.status,
17
27
  attempt_count: s.attempt_count,
28
+ ...(s.definition_id !== undefined ? { definition_id: s.definition_id } : {}),
18
29
  }));
19
30
  }
20
31
  function waitingSummaryFromPrompt(prompt) {
@@ -65,14 +76,24 @@ function failedFieldsFromStages(stages) {
65
76
  };
66
77
  }
67
78
  function resolveListedStatus(stages, meta) {
79
+ if (stages.length === 0)
80
+ return meta.status ?? "created";
68
81
  const derived = deriveStatusFromStages(stages);
69
- return stages.length > 0 ? derived : (meta.status ?? derived);
82
+ if (meta.status === "succeeded" && derived === "running") {
83
+ const hasActiveStage = stages.some((s) => s.status === "running" || s.status === "waiting_for_input");
84
+ if (!hasActiveStage)
85
+ return "succeeded";
86
+ }
87
+ return derived;
70
88
  }
71
89
  export function projectRunSummary(meta, stages) {
72
90
  return {
73
91
  run_id: meta.run_id,
74
92
  pipeline_id: meta.pipeline_id,
75
93
  task_id: meta.task_id,
94
+ ...(meta.pipeline_path !== undefined ? { pipeline_path: meta.pipeline_path } : {}),
95
+ ...(meta.task_path !== undefined ? { task_path: meta.task_path } : {}),
96
+ ...(meta.project_root !== undefined ? { project_root: meta.project_root } : {}),
76
97
  status: resolveListedStatus(stages, meta),
77
98
  created_at: meta.created_at,
78
99
  updated_at: meta.updated_at,
@@ -83,7 +104,7 @@ export function projectRunSummary(meta, stages) {
83
104
  }
84
105
  export function projectRunDetail(meta, stages, task_yaml, dagSnapshot) {
85
106
  const ordered = dagSnapshot
86
- ? overlayPlannedStages(dagSnapshot.stage_ids, stages)
107
+ ? overlayPlannedStages(dagSnapshot.stage_ids, stages, dagSnapshot)
87
108
  : stages;
88
109
  const summary = projectRunSummary(meta, ordered);
89
110
  const snapshot = dagSnapshot ??