stageflow 0.3.0 → 0.9.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.
- package/README.md +44 -14
- package/dist/agent/activity.d.ts +2 -0
- package/dist/agent/cursorProvider.d.ts +1 -10
- package/dist/agent/cursorProvider.js +18 -10
- package/dist/agent/fakeAgent.js +11 -7
- package/dist/agent/piAdapter.d.ts +2 -1
- package/dist/agent/piAdapter.js +28 -25
- package/dist/agent/port.d.ts +6 -1
- package/dist/agent/port.js +3 -0
- package/dist/cli/artifactCommand.d.ts +10 -0
- package/dist/cli/artifactCommand.js +120 -0
- package/dist/cli/envelopeCommand.d.ts +32 -0
- package/dist/cli/envelopeCommand.js +285 -0
- package/dist/cli/exportRunCommand.d.ts +10 -0
- package/dist/cli/exportRunCommand.js +150 -0
- package/dist/cli/handoffFormat.d.ts +22 -0
- package/dist/cli/handoffFormat.js +61 -0
- package/dist/cli/runCommand.d.ts +5 -1
- package/dist/cli/runCommand.js +36 -5
- package/dist/cli/runOutput.d.ts +4 -1
- package/dist/cli/runOutput.js +36 -8
- package/dist/cli/runsCommand.d.ts +22 -0
- package/dist/cli/runsCommand.js +591 -0
- package/dist/cli/skillsCommand.d.ts +16 -0
- package/dist/cli/skillsCommand.js +459 -0
- package/dist/cli/validateOutput.d.ts +2 -2
- package/dist/cli/validateOutput.js +2 -2
- package/dist/cli.js +110 -5
- package/dist/config/loadPipeline.js +5 -1
- package/dist/config/normalizePipelineStageEntry.d.ts +2 -0
- package/dist/config/normalizePipelineStageEntry.js +25 -1
- package/dist/config/pipelineStageKeys.js +1 -1
- package/dist/config/resolveForkEmitContext.d.ts +2 -1
- package/dist/config/resolveForkEmitContext.js +23 -1
- package/dist/config/resolvePipelineDag.js +36 -4
- package/dist/envelope/check.d.ts +2 -0
- package/dist/envelope/check.js +51 -0
- package/dist/envelope/cloneForks.d.ts +2 -0
- package/dist/envelope/cloneForks.js +62 -0
- package/dist/envelope/forkChoice.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/mcp/catalogTools.d.ts +3 -0
- package/dist/mcp/catalogTools.js +187 -0
- package/dist/mcp/controlTools.d.ts +3 -0
- package/dist/mcp/controlTools.js +147 -0
- package/dist/mcp/deps.d.ts +11 -0
- package/dist/mcp/deps.js +1 -0
- package/dist/mcp/projectRun.d.ts +1 -40
- package/dist/mcp/projectRun.js +1 -64
- package/dist/mcp/resources.d.ts +5 -0
- package/dist/mcp/resources.js +45 -0
- package/dist/mcp/server.d.ts +16 -3
- package/dist/mcp/server.js +159 -3
- package/dist/mcp/toolResults.d.ts +7 -0
- package/dist/mcp/toolResults.js +6 -0
- package/dist/mcp/tools.d.ts +2 -7
- package/dist/mcp/tools.js +52 -103
- package/dist/mcp/waitRun.d.ts +46 -0
- package/dist/mcp/waitRun.js +146 -0
- package/dist/mcp/waitingGates.d.ts +12 -0
- package/dist/mcp/waitingGates.js +83 -0
- package/dist/package-meta.d.ts +1 -0
- package/dist/package-meta.js +1 -0
- package/dist/projection/projectRun.d.ts +44 -0
- package/dist/projection/projectRun.js +71 -0
- package/dist/prompt/priorEnvelope.d.ts +1 -1
- package/dist/prompt/priorEnvelope.js +4 -1
- package/dist/runstore/paths.js +2 -0
- package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
- package/dist/runstore/pipelineDagSnapshot.js +90 -1
- package/dist/runstore/port.d.ts +13 -2
- package/dist/runstore/port.js +4 -1
- package/dist/runstore/runProjection.d.ts +1 -1
- package/dist/runstore/runProjection.js +14 -3
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
- package/dist/runstore/sqlite/SqliteRunStore.js +35 -3
- package/dist/runstore/stageInstanceId.d.ts +4 -0
- package/dist/runstore/stageInstanceId.js +23 -0
- package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
- package/dist/runstore/syntheticStageSnapshot.js +2 -1
- package/dist/runstore/trackProjection.js +28 -12
- package/dist/runstore/workspaceLayout.d.ts +1 -0
- package/dist/runstore/workspaceLayout.js +1 -1
- package/dist/runtime/cloneSchedule.d.ts +18 -0
- package/dist/runtime/cloneSchedule.js +233 -0
- package/dist/runtime/envelopeRouting.d.ts +2 -1
- package/dist/runtime/envelopeRouting.js +80 -13
- package/dist/runtime/pipelineScheduler.d.ts +6 -2
- package/dist/runtime/pipelineScheduler.js +166 -30
- package/dist/runtime/resumeReconstruct.js +8 -3
- package/dist/runtime/runChangeBus.d.ts +18 -0
- package/dist/runtime/runChangeBus.js +83 -0
- package/dist/runtime/runManager.d.ts +7 -0
- package/dist/runtime/runManager.js +31 -2
- package/dist/runtime/runRetryCoordinator.d.ts +2 -0
- package/dist/runtime/runRetryCoordinator.js +4 -1
- package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
- package/dist/runtime/stageAttemptBootstrap.js +17 -9
- package/dist/runtime/stageRunner.d.ts +1 -0
- package/dist/runtime/stageRunner.js +17 -15
- package/dist/runtime/stageWorker.js +11 -4
- package/dist/server/bootstrap.d.ts +32 -0
- package/dist/server/bootstrap.js +56 -0
- package/dist/server/createHttpHost.d.ts +34 -0
- package/dist/server/createHttpHost.js +70 -0
- package/dist/server/http.d.ts +6 -13
- package/dist/server/http.js +295 -374
- package/dist/server/mcpHost.d.ts +22 -0
- package/dist/server/mcpHost.js +21 -0
- package/dist/server/operatorResults.d.ts +13 -0
- package/dist/server/operatorResults.js +22 -0
- package/dist/tools/emitStageEnvelope.d.ts +11 -2
- package/dist/tools/emitStageEnvelope.js +22 -6
- package/dist/types/envelope.d.ts +2 -0
- package/dist/types/forkChoice.d.ts +22 -0
- package/dist/types/pipeline.d.ts +8 -0
- package/dist/ui/assets/{index-DefBlEvN.css → index-C3N7MXAC.css} +1 -1
- package/dist/ui/assets/index-CSoGDA3A.js +118 -0
- package/dist/ui/index.html +2 -2
- package/package.json +2 -1
- package/skills/install-suite.sh +107 -0
- package/skills/stageflow/SKILL.md +26 -0
- package/skills/stageflow/references/control-surface.md +28 -0
- package/skills/stageflow/scripts/detect-host.mjs +54 -0
- package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
- package/skills/stageflow-author/SKILL.md +58 -0
- package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
- package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
- package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
- package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
- package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
- package/skills/stageflow-author/references/catalog-mapping.md +131 -0
- package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
- package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
- package/skills/stageflow-author/references/validate-and-report.md +38 -0
- package/skills/stageflow-delegate/SKILL.md +18 -0
- package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
- package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
- package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
- package/skills/stageflow-run/SKILL.md +182 -0
- package/skills/stageflow-run/references/mcp-call.md +17 -0
- package/skills/stageflow-run/references/native-question-ui.md +78 -0
- package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
- package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
- package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
- package/skills/stageflow-session-capture/SKILL.md +94 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
- package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
- package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
- package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
- package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
- package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
- package/skills/stageflow-setup/SKILL.md +110 -0
- package/dist/ui/assets/index-CFSzDZje.js +0 -118
|
@@ -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
|
+
}
|
package/dist/runstore/port.d.ts
CHANGED
|
@@ -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;
|
|
@@ -55,7 +56,8 @@ export type StageLogEvent = StageLogLine & {
|
|
|
55
56
|
};
|
|
56
57
|
export type StageSnapshot = {
|
|
57
58
|
stage_id: string;
|
|
58
|
-
|
|
59
|
+
definition_id?: string;
|
|
60
|
+
status: "pending" | "running" | "waiting_for_input" | "succeeded" | "failed" | "skipped";
|
|
59
61
|
events: StageLogEvent[];
|
|
60
62
|
envelope: StageEnvelope | null;
|
|
61
63
|
artifacts: string[];
|
|
@@ -82,6 +84,7 @@ export type CompactStage = {
|
|
|
82
84
|
id: string;
|
|
83
85
|
status: StageSnapshot["status"];
|
|
84
86
|
attempt_count: number;
|
|
87
|
+
definition_id?: string;
|
|
85
88
|
};
|
|
86
89
|
export type RunSummary = {
|
|
87
90
|
run_id: string;
|
|
@@ -125,6 +128,13 @@ export type CreateRunInput = {
|
|
|
125
128
|
taskPath?: string;
|
|
126
129
|
projectRoot?: string;
|
|
127
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;
|
|
137
|
+
};
|
|
128
138
|
/**
|
|
129
139
|
* Persistence port for pipeline run state.
|
|
130
140
|
* Call sites should prefer CreatedRun.workspaceDir from createRun over
|
|
@@ -154,8 +164,9 @@ export interface RunStore {
|
|
|
154
164
|
attempt?: number;
|
|
155
165
|
}): Promise<void>;
|
|
156
166
|
listStageEvents(runId: string, stageId: string, attempt?: number): Promise<StageLogEvent[]>;
|
|
157
|
-
listRuns(): Promise<RunSummary[]>;
|
|
167
|
+
listRuns(filter?: ListRunsFilter): Promise<RunSummary[]>;
|
|
158
168
|
readRun(runId: string): Promise<RunDetail>;
|
|
169
|
+
updatePipelineDag(runId: string, dag: RunPipelineDagSnapshot): Promise<void>;
|
|
159
170
|
}
|
|
160
171
|
export declare function stageStatusFromEvents(events: StageLogEvent[]): StageSnapshot["status"];
|
|
161
172
|
export declare function deriveStatusFromStages(stages: StageSnapshot[]): RunStatus;
|
package/dist/runstore/port.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|
|
@@ -93,7 +104,7 @@ export function projectRunSummary(meta, stages) {
|
|
|
93
104
|
}
|
|
94
105
|
export function projectRunDetail(meta, stages, task_yaml, dagSnapshot) {
|
|
95
106
|
const ordered = dagSnapshot
|
|
96
|
-
? overlayPlannedStages(dagSnapshot.stage_ids, stages)
|
|
107
|
+
? overlayPlannedStages(dagSnapshot.stage_ids, stages, dagSnapshot)
|
|
97
108
|
: stages;
|
|
98
109
|
const summary = projectRunSummary(meta, ordered);
|
|
99
110
|
const snapshot = dagSnapshot ??
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StageEnvelope } from "../../types/envelope.js";
|
|
2
2
|
import type { StageLogLine } from "../../agent/activity.js";
|
|
3
|
-
import { type CreateRunInput, type CreatedRun, type RunDetail, type RunMeta, type RunStatus, type RunStore, type RunSummary, type StageExecution, type StageExecutionPatch, type StageLogEvent } from "../port.js";
|
|
3
|
+
import { type CreateRunInput, type CreatedRun, type ListRunsFilter, type RunDetail, type RunMeta, type RunPipelineDagSnapshot, type RunStatus, type RunStore, type RunSummary, type StageExecution, type StageExecutionPatch, type StageLogEvent } from "../port.js";
|
|
4
4
|
export declare class SqliteRunStore implements RunStore {
|
|
5
5
|
private readonly storeRoot;
|
|
6
6
|
private readonly db;
|
|
@@ -11,6 +11,7 @@ export declare class SqliteRunStore implements RunStore {
|
|
|
11
11
|
getWorkspaceDir(runId: string): string;
|
|
12
12
|
createRun(input: CreateRunInput): Promise<CreatedRun>;
|
|
13
13
|
updateRunStatus(runId: string, status: RunStatus): Promise<void>;
|
|
14
|
+
updatePipelineDag(runId: string, dag: RunPipelineDagSnapshot): Promise<void>;
|
|
14
15
|
readRunMeta(runId: string): Promise<RunMeta>;
|
|
15
16
|
readTaskYaml(runId: string): Promise<string>;
|
|
16
17
|
ensureStageWorkspace(runId: string, stageId: string): Promise<void>;
|
|
@@ -37,7 +38,7 @@ export declare class SqliteRunStore implements RunStore {
|
|
|
37
38
|
*/
|
|
38
39
|
private updateStageStatusDenorm;
|
|
39
40
|
listStageEvents(runId: string, stageId: string, attempt?: number): Promise<StageLogEvent[]>;
|
|
40
|
-
listRuns(): Promise<RunSummary[]>;
|
|
41
|
+
listRuns(filter?: ListRunsFilter): Promise<RunSummary[]>;
|
|
41
42
|
readRun(runId: string): Promise<RunDetail>;
|
|
42
43
|
private readPipelineDagSnapshotFromRow;
|
|
43
44
|
private runMetaFromRow;
|
|
@@ -174,6 +174,19 @@ export class SqliteRunStore {
|
|
|
174
174
|
throw new Error(`Run not found: ${runId}`);
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
+
async updatePipelineDag(runId, dag) {
|
|
178
|
+
await this.ready();
|
|
179
|
+
const result = this.db
|
|
180
|
+
.prepare(`UPDATE runs SET pipeline_dag_json = @pipeline_dag_json, updated_at = @updated_at WHERE run_id = @run_id`)
|
|
181
|
+
.run({
|
|
182
|
+
run_id: runId,
|
|
183
|
+
pipeline_dag_json: JSON.stringify(dag),
|
|
184
|
+
updated_at: new Date().toISOString(),
|
|
185
|
+
});
|
|
186
|
+
if (result.changes === 0) {
|
|
187
|
+
throw new Error(`Run not found: ${runId}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
177
190
|
async readRunMeta(runId) {
|
|
178
191
|
await this.ready();
|
|
179
192
|
return this.runMetaFromRow(this.getRunRow(runId));
|
|
@@ -411,12 +424,29 @@ export class SqliteRunStore {
|
|
|
411
424
|
await this.ready();
|
|
412
425
|
return this.loadEvents(runId, stageId, attempt);
|
|
413
426
|
}
|
|
414
|
-
async listRuns() {
|
|
427
|
+
async listRuns(filter) {
|
|
415
428
|
await this.ready();
|
|
429
|
+
const clauses = [];
|
|
430
|
+
const params = [];
|
|
431
|
+
if (filter?.status !== undefined) {
|
|
432
|
+
clauses.push("status = ?");
|
|
433
|
+
params.push(filter.status);
|
|
434
|
+
}
|
|
435
|
+
if (filter?.since !== undefined) {
|
|
436
|
+
clauses.push("created_at >= ?");
|
|
437
|
+
params.push(filter.since);
|
|
438
|
+
}
|
|
439
|
+
if (filter?.pipeline !== undefined && filter.pipeline.trim().length > 0) {
|
|
440
|
+
const pipeline = filter.pipeline.trim();
|
|
441
|
+
const normalized = normalizeCatalogPath(pipeline);
|
|
442
|
+
clauses.push("(pipeline_id = ? OR pipeline_path = ? OR pipeline_path = ?)");
|
|
443
|
+
params.push(pipeline, pipeline, normalized);
|
|
444
|
+
}
|
|
445
|
+
const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
416
446
|
const rows = this.db
|
|
417
447
|
.prepare(`SELECT run_id, pipeline_id, task_id, task_yaml, status, created_at, updated_at, checkout_root, pipeline_dag_json, git_sha, ci_pr_url, ci_job_url, pipeline_path, task_path, project_root
|
|
418
|
-
FROM runs ORDER BY created_at DESC`)
|
|
419
|
-
.all();
|
|
448
|
+
FROM runs ${where} ORDER BY created_at DESC`)
|
|
449
|
+
.all(...params);
|
|
420
450
|
const summaries = [];
|
|
421
451
|
for (const row of rows) {
|
|
422
452
|
const dagSnapshot = this.readPipelineDagSnapshotFromRow(row);
|
|
@@ -438,6 +468,7 @@ export class SqliteRunStore {
|
|
|
438
468
|
return parsePipelineDagSnapshot(JSON.parse(row.pipeline_dag_json));
|
|
439
469
|
}
|
|
440
470
|
runMetaFromRow(row) {
|
|
471
|
+
const pipeline_dag = this.readPipelineDagSnapshotFromRow(row);
|
|
441
472
|
return {
|
|
442
473
|
run_id: row.run_id,
|
|
443
474
|
pipeline_id: row.pipeline_id,
|
|
@@ -452,6 +483,7 @@ export class SqliteRunStore {
|
|
|
452
483
|
...(row.pipeline_path != null ? { pipeline_path: row.pipeline_path } : {}),
|
|
453
484
|
...(row.task_path != null ? { task_path: row.task_path } : {}),
|
|
454
485
|
...(row.project_root != null ? { project_root: row.project_root } : {}),
|
|
486
|
+
...(pipeline_dag ? { pipeline_dag } : {}),
|
|
455
487
|
};
|
|
456
488
|
}
|
|
457
489
|
getRunRow(runId) {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RunPipelineDagSnapshot } from "./port.js";
|
|
2
|
+
export declare function mintCloneInstanceId(catalogId: string, n: number): string;
|
|
3
|
+
export declare function mintCloneInstanceIds(catalogId: string, count: number): string[];
|
|
4
|
+
export declare function definitionIdForInstance(snapshot: RunPipelineDagSnapshot | null | undefined, instanceId: string): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { guardStageId } from "./workspaceLayout.js";
|
|
2
|
+
function guardPositiveInteger(n, label) {
|
|
3
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
4
|
+
throw new Error(`${label} must be a positive integer`);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function mintCloneInstanceId(catalogId, n) {
|
|
8
|
+
guardPositiveInteger(n, "n");
|
|
9
|
+
guardStageId(catalogId);
|
|
10
|
+
const instanceId = `${catalogId}~${n}`;
|
|
11
|
+
guardStageId(instanceId);
|
|
12
|
+
return instanceId;
|
|
13
|
+
}
|
|
14
|
+
export function mintCloneInstanceIds(catalogId, count) {
|
|
15
|
+
guardPositiveInteger(count, "count");
|
|
16
|
+
return Array.from({ length: count }, (_, i) => mintCloneInstanceId(catalogId, i + 1));
|
|
17
|
+
}
|
|
18
|
+
export function definitionIdForInstance(snapshot, instanceId) {
|
|
19
|
+
const node = snapshot?.nodes.find((n) => n.id === instanceId);
|
|
20
|
+
if (!node)
|
|
21
|
+
return instanceId;
|
|
22
|
+
return node.definition_id ?? node.id;
|
|
23
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { StageSnapshot } from "./port.js";
|
|
2
|
-
export declare function syntheticPendingSnapshot(stageId: string): StageSnapshot;
|
|
2
|
+
export declare function syntheticPendingSnapshot(stageId: string, definitionId?: string): StageSnapshot;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function syntheticPendingSnapshot(stageId) {
|
|
1
|
+
export function syntheticPendingSnapshot(stageId, definitionId) {
|
|
2
2
|
return {
|
|
3
3
|
stage_id: stageId,
|
|
4
4
|
status: "pending",
|
|
@@ -6,5 +6,6 @@ export function syntheticPendingSnapshot(stageId) {
|
|
|
6
6
|
envelope: null,
|
|
7
7
|
artifacts: [],
|
|
8
8
|
attempt_count: 1,
|
|
9
|
+
...(definitionId !== undefined ? { definition_id: definitionId } : {}),
|
|
9
10
|
};
|
|
10
11
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { linearCompatDagSnapshot } from "./pipelineDagSnapshot.js";
|
|
1
|
+
import { instancesOfDefinition, linearCompatDagSnapshot, } from "./pipelineDagSnapshot.js";
|
|
2
2
|
function stageStarted(stage) {
|
|
3
3
|
return stage.events.some((e) => e.event === "started" || e.event === "resumed");
|
|
4
4
|
}
|
|
5
5
|
function runHalted(stages) {
|
|
6
6
|
return stages.some((s) => s.status === "failed");
|
|
7
7
|
}
|
|
8
|
+
function predecessorIds(dag, needs) {
|
|
9
|
+
const preds = instancesOfDefinition(dag, needs);
|
|
10
|
+
return preds.length > 0 ? preds : [needs];
|
|
11
|
+
}
|
|
8
12
|
function computeLayers(dag) {
|
|
9
13
|
const layers = new Map();
|
|
10
14
|
const nodeById = new Map(dag.nodes.map((n) => [n.id, n]));
|
|
@@ -17,7 +21,8 @@ function computeLayers(dag) {
|
|
|
17
21
|
layers.set(id, 0);
|
|
18
22
|
return 0;
|
|
19
23
|
}
|
|
20
|
-
const
|
|
24
|
+
const fromIds = predecessorIds(dag, node.needs);
|
|
25
|
+
const l = Math.max(...fromIds.map(layerFor)) + 1;
|
|
21
26
|
layers.set(id, l);
|
|
22
27
|
return l;
|
|
23
28
|
}
|
|
@@ -32,6 +37,8 @@ function deriveReadiness(stage, dag, snapshotsById, halted) {
|
|
|
32
37
|
return { readiness: "succeeded" };
|
|
33
38
|
if (status === "failed")
|
|
34
39
|
return { readiness: "failed" };
|
|
40
|
+
if (status === "skipped")
|
|
41
|
+
return { readiness: "skipped" };
|
|
35
42
|
if (status === "running")
|
|
36
43
|
return { readiness: "running" };
|
|
37
44
|
if (status === "waiting_for_input")
|
|
@@ -42,9 +49,13 @@ function deriveReadiness(stage, dag, snapshotsById, halted) {
|
|
|
42
49
|
const node = dag.nodes.find((n) => n.id === stage.stage_id);
|
|
43
50
|
const need = node?.needs;
|
|
44
51
|
if (need) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
const fromIds = predecessorIds(dag, need);
|
|
53
|
+
const unresolved = fromIds.filter((id) => {
|
|
54
|
+
const predecessor = snapshotsById.get(id);
|
|
55
|
+
return !predecessor || predecessor.status !== "succeeded";
|
|
56
|
+
});
|
|
57
|
+
if (unresolved.length > 0) {
|
|
58
|
+
return { readiness: "blocked", blocked_by: unresolved };
|
|
48
59
|
}
|
|
49
60
|
}
|
|
50
61
|
return { readiness: "ready" };
|
|
@@ -63,6 +74,7 @@ export function buildPipelineTrack(input) {
|
|
|
63
74
|
}
|
|
64
75
|
const node = dag.nodes.find((n) => n.id === stageId);
|
|
65
76
|
const { readiness, blocked_by } = deriveReadiness(stage, dag, snapshotsById, halted);
|
|
77
|
+
const definitionId = node?.definition_id ?? stage.definition_id ?? stageId;
|
|
66
78
|
const trackNode = {
|
|
67
79
|
stage_id: stageId,
|
|
68
80
|
status: stage.status,
|
|
@@ -70,10 +82,11 @@ export function buildPipelineTrack(input) {
|
|
|
70
82
|
layer: layers.get(stageId) ?? 0,
|
|
71
83
|
layer_order: node?.stageIndex ?? dag.stage_ids.indexOf(stageId),
|
|
72
84
|
attempt_count: stage.attempt_count,
|
|
85
|
+
definition_id: definitionId,
|
|
73
86
|
};
|
|
74
87
|
if (blocked_by?.length)
|
|
75
88
|
trackNode.blocked_by = blocked_by;
|
|
76
|
-
const gateKinds = dag.gate_kinds?.[stageId];
|
|
89
|
+
const gateKinds = dag.gate_kinds?.[definitionId] ?? dag.gate_kinds?.[stageId];
|
|
77
90
|
if (gateKinds?.length)
|
|
78
91
|
trackNode.gate_kinds = gateKinds;
|
|
79
92
|
return trackNode;
|
|
@@ -82,13 +95,16 @@ export function buildPipelineTrack(input) {
|
|
|
82
95
|
for (const node of dag.nodes) {
|
|
83
96
|
if (!node.needs)
|
|
84
97
|
continue;
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
const fromIds = predecessorIds(dag, node.needs);
|
|
99
|
+
for (const fromId of fromIds) {
|
|
100
|
+
const fromStage = snapshotsById.get(fromId);
|
|
101
|
+
const edge = { from: fromId, to: node.id };
|
|
102
|
+
if (fromStage?.status === "succeeded" &&
|
|
103
|
+
fromStage.envelope?.summary) {
|
|
104
|
+
edge.envelope_summary = fromStage.envelope.summary;
|
|
105
|
+
}
|
|
106
|
+
edges.push(edge);
|
|
90
107
|
}
|
|
91
|
-
edges.push(edge);
|
|
92
108
|
}
|
|
93
109
|
return { nodes, edges };
|
|
94
110
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { stageDir } from "./paths.js";
|
|
2
|
+
export declare function guardStageId(stageId: string): void;
|
|
2
3
|
export declare function attemptWorkspaceDir(workspaceDir: string, stageId: string, attempt: number): string;
|
|
3
4
|
export declare function attemptLogPath(workspaceDir: string, stageId: string, attempt: number): string;
|
|
4
5
|
export declare function attemptEnvelopePath(workspaceDir: string, stageId: string, attempt: number): string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { access, lstat, mkdir, readdir, realpath } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
export { stageDir } from "./paths.js";
|
|
4
|
-
function guardStageId(stageId) {
|
|
4
|
+
export function guardStageId(stageId) {
|
|
5
5
|
if (stageId.includes("..") ||
|
|
6
6
|
stageId.includes("/") ||
|
|
7
7
|
stageId.includes("\\")) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RunPipelineDagSnapshot } from "../runstore/port.js";
|
|
2
|
+
import type { StageEnvelope } from "../types/envelope.js";
|
|
3
|
+
import type { ResolvedPipelineDag } from "../types/pipeline.js";
|
|
4
|
+
import type { StageScheduleState } from "./pipelineScheduler.js";
|
|
5
|
+
export declare function isCloneInstance(dag: ResolvedPipelineDag, stageId: string): boolean;
|
|
6
|
+
export declare function sequentialLaterCloneIds(dag: ResolvedPipelineDag, stageId: string, completedEnvelopes: Map<string, StageEnvelope>): string[];
|
|
7
|
+
export declare function cloneFanoutConflict(dag: ResolvedPipelineDag, envelope: StageEnvelope): string | undefined;
|
|
8
|
+
export declare function protectedClonableChildIds(dag: ResolvedPipelineDag, predecessorId: string, envelope: StageEnvelope): Set<string>;
|
|
9
|
+
export declare function cloneRetryDownstream(dag: ResolvedPipelineDag, stageIds: string[]): Set<string>;
|
|
10
|
+
export declare function cloneFailureContinuesSchedule(dag: ResolvedPipelineDag, stageId: string, completedEnvelopes: Map<string, StageEnvelope>): boolean;
|
|
11
|
+
export declare function cloneFailFastSkipIds(dag: ResolvedPipelineDag, stageId: string, completedEnvelopes: Map<string, StageEnvelope>): string[];
|
|
12
|
+
export declare function cloneScheduleAllowsRun(dag: ResolvedPipelineDag, stageId: string, states: Map<string, StageScheduleState>, completedEnvelopes: Map<string, StageEnvelope>): boolean;
|
|
13
|
+
export type ApplyCloneForksResult = {
|
|
14
|
+
dag: RunPipelineDagSnapshot;
|
|
15
|
+
skippedIds: string[];
|
|
16
|
+
};
|
|
17
|
+
export declare function applyCloneForksToSchedule(dag: ResolvedPipelineDag, predecessorId: string, envelope: StageEnvelope, states: Map<string, StageScheduleState>): ApplyCloneForksResult;
|
|
18
|
+
export declare function applyCloneForksFromEnvelopes(dag: ResolvedPipelineDag, states: Map<string, StageScheduleState>, completedEnvelopes: Map<string, StageEnvelope>): ApplyCloneForksResult;
|