stageflow 0.1.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/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/agent/activity.d.ts +76 -0
- package/dist/agent/activity.js +89 -0
- package/dist/agent/activityObserver.d.ts +17 -0
- package/dist/agent/activityObserver.js +66 -0
- package/dist/agent/cursorExtension.d.ts +12 -0
- package/dist/agent/cursorExtension.js +88 -0
- package/dist/agent/cursorProvider.d.ts +14 -0
- package/dist/agent/cursorProvider.js +139 -0
- package/dist/agent/fakeAgent.d.ts +29 -0
- package/dist/agent/fakeAgent.js +295 -0
- package/dist/agent/oauthSessionManager.d.ts +33 -0
- package/dist/agent/oauthSessionManager.js +214 -0
- package/dist/agent/piAdapter.d.ts +128 -0
- package/dist/agent/piAdapter.js +967 -0
- package/dist/agent/port.d.ts +63 -0
- package/dist/agent/port.js +46 -0
- package/dist/agent/providerAuth.d.ts +101 -0
- package/dist/agent/providerAuth.js +282 -0
- package/dist/agent/providerSupport.d.ts +28 -0
- package/dist/agent/providerSupport.js +19 -0
- package/dist/cli/providersCommand.d.ts +11 -0
- package/dist/cli/providersCommand.js +275 -0
- package/dist/cli/terminalAuthInteraction.d.ts +8 -0
- package/dist/cli/terminalAuthInteraction.js +65 -0
- package/dist/cli/terminalSecret.d.ts +2 -0
- package/dist/cli/terminalSecret.js +67 -0
- package/dist/cli/validateCommand.d.ts +11 -0
- package/dist/cli/validateCommand.js +81 -0
- package/dist/cli/validateOutput.d.ts +8 -0
- package/dist/cli/validateOutput.js +75 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +273 -0
- package/dist/config/createPipeline.d.ts +31 -0
- package/dist/config/createPipeline.js +255 -0
- package/dist/config/createStage.d.ts +25 -0
- package/dist/config/createStage.js +181 -0
- package/dist/config/listConfig.d.ts +33 -0
- package/dist/config/listConfig.js +195 -0
- package/dist/config/listExtensions.d.ts +26 -0
- package/dist/config/listExtensions.js +50 -0
- package/dist/config/listSkills.d.ts +29 -0
- package/dist/config/listSkills.js +55 -0
- package/dist/config/loadOutcome.d.ts +27 -0
- package/dist/config/loadOutcome.js +6 -0
- package/dist/config/loadPipeline.d.ts +25 -0
- package/dist/config/loadPipeline.js +196 -0
- package/dist/config/loadStage.d.ts +4 -0
- package/dist/config/loadStage.js +127 -0
- package/dist/config/loadTask.d.ts +7 -0
- package/dist/config/loadTask.js +69 -0
- package/dist/config/readYamlObject.d.ts +1 -0
- package/dist/config/readYamlObject.js +5 -0
- package/dist/config/resolvePipelineDag.d.ts +12 -0
- package/dist/config/resolvePipelineDag.js +282 -0
- package/dist/config/validateCatalog.d.ts +36 -0
- package/dist/config/validateCatalog.js +322 -0
- package/dist/envelope/check.d.ts +3 -0
- package/dist/envelope/check.js +52 -0
- package/dist/envelope/payloadSchema.d.ts +10 -0
- package/dist/envelope/payloadSchema.js +91 -0
- package/dist/hitl/qaTrail.d.ts +31 -0
- package/dist/hitl/qaTrail.js +65 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +7 -0
- package/dist/mcp/projectRun.d.ts +37 -0
- package/dist/mcp/projectRun.js +57 -0
- package/dist/mcp/readArtifact.d.ts +2 -0
- package/dist/mcp/readArtifact.js +46 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +26 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.js +96 -0
- package/dist/package-meta.d.ts +1 -0
- package/dist/package-meta.js +1 -0
- package/dist/prompt/priorEnvelope.d.ts +2 -0
- package/dist/prompt/priorEnvelope.js +5 -0
- package/dist/runstore/catalog.d.ts +8 -0
- package/dist/runstore/catalog.js +16 -0
- package/dist/runstore/createStore.d.ts +15 -0
- package/dist/runstore/createStore.js +23 -0
- package/dist/runstore/disk/DiskRunStore.d.ts +30 -0
- package/dist/runstore/disk/DiskRunStore.js +238 -0
- package/dist/runstore/disk/catalogHelpers.d.ts +11 -0
- package/dist/runstore/disk/catalogHelpers.js +102 -0
- package/dist/runstore/layout.d.ts +22 -0
- package/dist/runstore/layout.js +58 -0
- package/dist/runstore/paths.d.ts +7 -0
- package/dist/runstore/paths.js +60 -0
- package/dist/runstore/pipelineDagSnapshot.d.ts +5 -0
- package/dist/runstore/pipelineDagSnapshot.js +43 -0
- package/dist/runstore/port.d.ts +146 -0
- package/dist/runstore/port.js +28 -0
- package/dist/runstore/runProjection.d.ts +6 -0
- package/dist/runstore/runProjection.js +102 -0
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +48 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +460 -0
- package/dist/runstore/sqlite/migrateFromDisk.d.ts +5 -0
- package/dist/runstore/sqlite/migrateFromDisk.js +108 -0
- package/dist/runstore/sqlite/schema.d.ts +1 -0
- package/dist/runstore/sqlite/schema.js +59 -0
- package/dist/runstore/stageExecution.d.ts +13 -0
- package/dist/runstore/stageExecution.js +22 -0
- package/dist/runstore/stageSnapshot.d.ts +2 -0
- package/dist/runstore/stageSnapshot.js +46 -0
- package/dist/runstore/syntheticStageSnapshot.d.ts +2 -0
- package/dist/runstore/syntheticStageSnapshot.js +10 -0
- package/dist/runstore/trackProjection.d.ts +7 -0
- package/dist/runstore/trackProjection.js +94 -0
- package/dist/runstore/workspaceLayout.d.ts +31 -0
- package/dist/runstore/workspaceLayout.js +172 -0
- package/dist/runtime/answerResume.d.ts +16 -0
- package/dist/runtime/answerResume.js +22 -0
- package/dist/runtime/credentialBinding.d.ts +16 -0
- package/dist/runtime/credentialBinding.js +80 -0
- package/dist/runtime/dagTraversal.d.ts +3 -0
- package/dist/runtime/dagTraversal.js +26 -0
- package/dist/runtime/envelopeRouting.d.ts +20 -0
- package/dist/runtime/envelopeRouting.js +59 -0
- package/dist/runtime/hitlSeams.d.ts +38 -0
- package/dist/runtime/hitlSeams.js +2 -0
- package/dist/runtime/pipelineRunner.d.ts +77 -0
- package/dist/runtime/pipelineRunner.js +141 -0
- package/dist/runtime/pipelineScheduler.d.ts +90 -0
- package/dist/runtime/pipelineScheduler.js +494 -0
- package/dist/runtime/pipelineValidationError.d.ts +6 -0
- package/dist/runtime/pipelineValidationError.js +8 -0
- package/dist/runtime/resumeReconstruct.d.ts +33 -0
- package/dist/runtime/resumeReconstruct.js +144 -0
- package/dist/runtime/runManager.d.ts +129 -0
- package/dist/runtime/runManager.js +818 -0
- package/dist/runtime/runRetryCoordinator.d.ts +85 -0
- package/dist/runtime/runRetryCoordinator.js +372 -0
- package/dist/runtime/settingsFile.d.ts +17 -0
- package/dist/runtime/settingsFile.js +88 -0
- package/dist/runtime/stageAttemptBootstrap.d.ts +40 -0
- package/dist/runtime/stageAttemptBootstrap.js +82 -0
- package/dist/runtime/stageAttemptContext.d.ts +11 -0
- package/dist/runtime/stageAttemptContext.js +26 -0
- package/dist/runtime/stageConcurrency.d.ts +10 -0
- package/dist/runtime/stageConcurrency.js +51 -0
- package/dist/runtime/stageHitl.d.ts +95 -0
- package/dist/runtime/stageHitl.js +198 -0
- package/dist/runtime/stageProcessLauncher.d.ts +45 -0
- package/dist/runtime/stageProcessLauncher.js +199 -0
- package/dist/runtime/stageRecovery.d.ts +10 -0
- package/dist/runtime/stageRecovery.js +43 -0
- package/dist/runtime/stageRoots.d.ts +22 -0
- package/dist/runtime/stageRoots.js +93 -0
- package/dist/runtime/stageRunner.d.ts +59 -0
- package/dist/runtime/stageRunner.js +221 -0
- package/dist/runtime/stageWorker.d.ts +6 -0
- package/dist/runtime/stageWorker.js +109 -0
- package/dist/runtime/stageWorkerProtocol.d.ts +27 -0
- package/dist/runtime/stageWorkerProtocol.js +16 -0
- package/dist/runtime/taskInput.d.ts +15 -0
- package/dist/runtime/taskInput.js +38 -0
- package/dist/server/http.d.ts +31 -0
- package/dist/server/http.js +561 -0
- package/dist/server/providerRoutes.d.ts +15 -0
- package/dist/server/providerRoutes.js +186 -0
- package/dist/tools/askOperator.d.ts +185 -0
- package/dist/tools/askOperator.js +409 -0
- package/dist/tools/emitStageEnvelope.d.ts +42 -0
- package/dist/tools/emitStageEnvelope.js +61 -0
- package/dist/tools/writeStageArtifact.d.ts +26 -0
- package/dist/tools/writeStageArtifact.js +103 -0
- package/dist/types/envelope.d.ts +12 -0
- package/dist/types/envelope.js +6 -0
- package/dist/types/pipeline.d.ts +25 -0
- package/dist/types/pipeline.js +1 -0
- package/dist/types/stage.d.ts +12 -0
- package/dist/types/stage.js +6 -0
- package/dist/types/task.d.ts +7 -0
- package/dist/types/task.js +1 -0
- package/dist/ui/assets/index-Cry0Tpfx.js +118 -0
- package/dist/ui/assets/index-DCsDopak.css +1 -0
- package/dist/ui/index.html +21 -0
- package/package.json +72 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { access, mkdir, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { STAGE_GATE_KINDS, } from "../types/stage.js";
|
|
4
|
+
import { loadStage } from "./loadStage.js";
|
|
5
|
+
import { readYamlObject } from "./readYamlObject.js";
|
|
6
|
+
export const STAGE_ID_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
7
|
+
function isGateKind(value) {
|
|
8
|
+
return STAGE_GATE_KINDS.includes(value);
|
|
9
|
+
}
|
|
10
|
+
function isPlainObject(value) {
|
|
11
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function validateStageId(id) {
|
|
14
|
+
if (id.length === 0 || id.length > 64) {
|
|
15
|
+
return "id must be 1-64 characters";
|
|
16
|
+
}
|
|
17
|
+
if (!STAGE_ID_PATTERN.test(id)) {
|
|
18
|
+
return "id must be lowercase kebab-case";
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function parseGateKinds(raw) {
|
|
23
|
+
if (raw === undefined)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
26
|
+
return "invalid";
|
|
27
|
+
const kinds = [];
|
|
28
|
+
for (const item of raw) {
|
|
29
|
+
if (typeof item !== "string" || !isGateKind(item)) {
|
|
30
|
+
return "invalid";
|
|
31
|
+
}
|
|
32
|
+
kinds.push(item);
|
|
33
|
+
}
|
|
34
|
+
return kinds;
|
|
35
|
+
}
|
|
36
|
+
export function parseCreateStageBody(body) {
|
|
37
|
+
if (!isPlainObject(body)) {
|
|
38
|
+
return { ok: false, status: 400, error: "Request body must be an object" };
|
|
39
|
+
}
|
|
40
|
+
if ("payload_schema" in body) {
|
|
41
|
+
return { ok: false, status: 400, error: "payload_schema is not supported" };
|
|
42
|
+
}
|
|
43
|
+
if ("skill" in body) {
|
|
44
|
+
return { ok: false, status: 400, error: "skill is not supported" };
|
|
45
|
+
}
|
|
46
|
+
if (typeof body.id !== "string") {
|
|
47
|
+
return { ok: false, status: 400, error: "id is required" };
|
|
48
|
+
}
|
|
49
|
+
const idError = validateStageId(body.id);
|
|
50
|
+
if (idError) {
|
|
51
|
+
return { ok: false, status: 400, error: idError };
|
|
52
|
+
}
|
|
53
|
+
if (typeof body.system_prompt !== "string" || body.system_prompt.length === 0) {
|
|
54
|
+
return { ok: false, status: 400, error: "system_prompt is required" };
|
|
55
|
+
}
|
|
56
|
+
if (typeof body.model !== "string" || body.model.length === 0) {
|
|
57
|
+
return { ok: false, status: 400, error: "model is required" };
|
|
58
|
+
}
|
|
59
|
+
const gateKinds = parseGateKinds(body.gate_kinds);
|
|
60
|
+
if (gateKinds === "invalid") {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
status: 400,
|
|
64
|
+
error: `gate_kinds must be a non-empty array of: ${STAGE_GATE_KINDS.join(", ")}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
id: body.id,
|
|
69
|
+
system_prompt: body.system_prompt,
|
|
70
|
+
model: body.model,
|
|
71
|
+
...(gateKinds ? { gate_kinds: gateKinds } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function formatInlineYamlScalar(value) {
|
|
75
|
+
if (value.includes("\n") ||
|
|
76
|
+
/[:#"'&*!|>@`[\]{}]/.test(value) ||
|
|
77
|
+
value.startsWith(" ") ||
|
|
78
|
+
value.endsWith(" ") ||
|
|
79
|
+
value.startsWith("-") ||
|
|
80
|
+
value.startsWith("?")) {
|
|
81
|
+
return JSON.stringify(value);
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
export function stageConfigToYaml(stage) {
|
|
86
|
+
const lines = [`id: ${stage.id}`];
|
|
87
|
+
if (stage.gate_kinds && stage.gate_kinds.length > 0) {
|
|
88
|
+
lines.push("gate_kinds:");
|
|
89
|
+
for (const kind of stage.gate_kinds) {
|
|
90
|
+
lines.push(` - ${kind}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (stage.system_prompt.includes("\n")) {
|
|
94
|
+
lines.push("system_prompt: |");
|
|
95
|
+
for (const line of stage.system_prompt.split("\n")) {
|
|
96
|
+
lines.push(` ${line}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
lines.push(`system_prompt: ${formatInlineYamlScalar(stage.system_prompt)}`);
|
|
101
|
+
}
|
|
102
|
+
lines.push(`model: ${formatInlineYamlScalar(stage.model)}`);
|
|
103
|
+
lines.push("");
|
|
104
|
+
return lines.join("\n");
|
|
105
|
+
}
|
|
106
|
+
async function fileExists(filePath) {
|
|
107
|
+
try {
|
|
108
|
+
await access(filePath);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function findStageIdCollision(cwd, id, targetPath) {
|
|
116
|
+
if (await fileExists(targetPath)) {
|
|
117
|
+
return path.relative(cwd, targetPath);
|
|
118
|
+
}
|
|
119
|
+
const dir = path.join(cwd, "stages");
|
|
120
|
+
let entries;
|
|
121
|
+
try {
|
|
122
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
for (const entry of entries) {
|
|
128
|
+
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
129
|
+
continue;
|
|
130
|
+
const filePath = path.join(dir, entry.name);
|
|
131
|
+
if (filePath === targetPath)
|
|
132
|
+
continue;
|
|
133
|
+
try {
|
|
134
|
+
const raw = await readYamlObject(filePath);
|
|
135
|
+
if (raw?.id === id) {
|
|
136
|
+
return path.relative(cwd, filePath);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// ignore unreadable files for collision scan
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
export async function createStage(cwd, input) {
|
|
146
|
+
const stagesDir = path.join(cwd, "stages");
|
|
147
|
+
const filePath = path.join(stagesDir, `${input.id}.yaml`);
|
|
148
|
+
const relPath = path.relative(cwd, filePath);
|
|
149
|
+
const collision = await findStageIdCollision(cwd, input.id, filePath);
|
|
150
|
+
if (collision) {
|
|
151
|
+
return {
|
|
152
|
+
ok: false,
|
|
153
|
+
status: 409,
|
|
154
|
+
error: `Stage id already exists (${collision})`,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
await mkdir(stagesDir, { recursive: true });
|
|
159
|
+
await writeFile(filePath, stageConfigToYaml(input), "utf8");
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
163
|
+
return { ok: false, status: 500, error: message };
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const stage = await loadStage(filePath);
|
|
167
|
+
return {
|
|
168
|
+
ok: true,
|
|
169
|
+
stage: {
|
|
170
|
+
path: relPath,
|
|
171
|
+
id: stage.id,
|
|
172
|
+
used_by_pipeline_ids: [],
|
|
173
|
+
...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
179
|
+
return { ok: false, status: 500, error: message };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type StageGateKind } from "../types/stage.js";
|
|
2
|
+
export type TaskListing = {
|
|
3
|
+
path: string;
|
|
4
|
+
id: string;
|
|
5
|
+
goal: string;
|
|
6
|
+
};
|
|
7
|
+
export type PipelineStageListing = {
|
|
8
|
+
id: string;
|
|
9
|
+
gate_kinds?: StageGateKind[];
|
|
10
|
+
};
|
|
11
|
+
export type PipelineListing = {
|
|
12
|
+
path: string;
|
|
13
|
+
id: string;
|
|
14
|
+
stages: PipelineStageListing[];
|
|
15
|
+
};
|
|
16
|
+
export type ValidStageListing = {
|
|
17
|
+
path: string;
|
|
18
|
+
id: string;
|
|
19
|
+
used_by_pipeline_ids: string[];
|
|
20
|
+
gate_kinds?: StageGateKind[];
|
|
21
|
+
};
|
|
22
|
+
export type BrokenStageListing = {
|
|
23
|
+
path: string;
|
|
24
|
+
error: string;
|
|
25
|
+
id?: string;
|
|
26
|
+
model?: string;
|
|
27
|
+
gate_kinds?: StageGateKind[];
|
|
28
|
+
};
|
|
29
|
+
export type StageListing = ValidStageListing | BrokenStageListing;
|
|
30
|
+
export declare function listTasks(cwd: string): Promise<TaskListing[]>;
|
|
31
|
+
export declare function listPipelines(cwd: string): Promise<PipelineListing[]>;
|
|
32
|
+
export declare function listStages(cwd: string): Promise<StageListing[]>;
|
|
33
|
+
export declare function listModels(cwd: string): Promise<string[]>;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { STAGE_GATE_KINDS, } from "../types/stage.js";
|
|
4
|
+
import { loadPipeline } from "./loadPipeline.js";
|
|
5
|
+
import { loadStage } from "./loadStage.js";
|
|
6
|
+
import { loadTask } from "./loadTask.js";
|
|
7
|
+
import { readYamlObject } from "./readYamlObject.js";
|
|
8
|
+
import { extractPipelineStageIds } from "./resolvePipelineDag.js";
|
|
9
|
+
const BAKED_MODEL_IDS = [
|
|
10
|
+
"anthropic/claude-sonnet-4-5",
|
|
11
|
+
"cursor/auto",
|
|
12
|
+
"cursor/composer-2-5",
|
|
13
|
+
];
|
|
14
|
+
function parseSafeGateKinds(raw) {
|
|
15
|
+
if (!Array.isArray(raw) || !raw.every((item) => typeof item === "string")) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const allowed = new Set(STAGE_GATE_KINDS);
|
|
19
|
+
const gateKinds = raw.filter((item) => allowed.has(item));
|
|
20
|
+
return gateKinds.length > 0 ? gateKinds : undefined;
|
|
21
|
+
}
|
|
22
|
+
async function listPipelineUsageByStage(cwd) {
|
|
23
|
+
const dir = path.join(cwd, "pipelines");
|
|
24
|
+
let entries;
|
|
25
|
+
try {
|
|
26
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return new Map();
|
|
30
|
+
}
|
|
31
|
+
const usage = new Map();
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
34
|
+
continue;
|
|
35
|
+
const filePath = path.join(dir, entry.name);
|
|
36
|
+
try {
|
|
37
|
+
const raw = await readYamlObject(filePath);
|
|
38
|
+
if (typeof raw?.id !== "string" || !Array.isArray(raw.stages))
|
|
39
|
+
continue;
|
|
40
|
+
const stageIds = extractPipelineStageIds(raw.stages);
|
|
41
|
+
if (!stageIds)
|
|
42
|
+
continue;
|
|
43
|
+
for (const stageId of stageIds) {
|
|
44
|
+
let pipelineIds = usage.get(stageId);
|
|
45
|
+
if (!pipelineIds) {
|
|
46
|
+
pipelineIds = new Set();
|
|
47
|
+
usage.set(stageId, pipelineIds);
|
|
48
|
+
}
|
|
49
|
+
pipelineIds.add(raw.id);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// skip invalid pipeline files
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return usage;
|
|
57
|
+
}
|
|
58
|
+
export async function listTasks(cwd) {
|
|
59
|
+
const dir = path.join(cwd, "tasks");
|
|
60
|
+
let entries;
|
|
61
|
+
try {
|
|
62
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const listings = [];
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
70
|
+
continue;
|
|
71
|
+
const filePath = path.join(dir, entry.name);
|
|
72
|
+
try {
|
|
73
|
+
const task = await loadTask(filePath);
|
|
74
|
+
listings.push({
|
|
75
|
+
path: path.relative(cwd, filePath),
|
|
76
|
+
id: task.id,
|
|
77
|
+
goal: task.goal,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// skip invalid
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
listings.sort((a, b) => a.id.localeCompare(b.id));
|
|
85
|
+
return listings;
|
|
86
|
+
}
|
|
87
|
+
export async function listPipelines(cwd) {
|
|
88
|
+
const dir = path.join(cwd, "pipelines");
|
|
89
|
+
let entries;
|
|
90
|
+
try {
|
|
91
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
const listings = [];
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
99
|
+
continue;
|
|
100
|
+
const filePath = path.join(dir, entry.name);
|
|
101
|
+
try {
|
|
102
|
+
const loaded = await loadPipeline(filePath, { cwd });
|
|
103
|
+
listings.push({
|
|
104
|
+
path: path.relative(cwd, filePath),
|
|
105
|
+
id: loaded.pipeline.id,
|
|
106
|
+
stages: loaded.stages.map((stage) => ({
|
|
107
|
+
id: stage.id,
|
|
108
|
+
...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
|
|
109
|
+
})),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// skip invalid
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
listings.sort((a, b) => a.id.localeCompare(b.id));
|
|
117
|
+
return listings;
|
|
118
|
+
}
|
|
119
|
+
export async function listStages(cwd) {
|
|
120
|
+
const dir = path.join(cwd, "stages");
|
|
121
|
+
let entries;
|
|
122
|
+
try {
|
|
123
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
const usageByStage = await listPipelineUsageByStage(cwd);
|
|
129
|
+
const valid = [];
|
|
130
|
+
const broken = [];
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
133
|
+
continue;
|
|
134
|
+
const filePath = path.join(dir, entry.name);
|
|
135
|
+
const relPath = path.relative(cwd, filePath);
|
|
136
|
+
try {
|
|
137
|
+
const stage = await loadStage(filePath);
|
|
138
|
+
valid.push({
|
|
139
|
+
path: relPath,
|
|
140
|
+
id: stage.id,
|
|
141
|
+
used_by_pipeline_ids: [...(usageByStage.get(stage.id) ?? new Set())].sort(),
|
|
142
|
+
...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
147
|
+
const brokenRow = {
|
|
148
|
+
path: relPath,
|
|
149
|
+
error: message,
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
const raw = await readYamlObject(filePath);
|
|
153
|
+
if (typeof raw?.id === "string")
|
|
154
|
+
brokenRow.id = raw.id;
|
|
155
|
+
if (typeof raw?.model === "string")
|
|
156
|
+
brokenRow.model = raw.model;
|
|
157
|
+
const gateKinds = parseSafeGateKinds(raw?.gate_kinds);
|
|
158
|
+
if (gateKinds)
|
|
159
|
+
brokenRow.gate_kinds = gateKinds;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// keep path + error only when parsing itself fails
|
|
163
|
+
}
|
|
164
|
+
broken.push(brokenRow);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
valid.sort((a, b) => a.id.localeCompare(b.id));
|
|
168
|
+
broken.sort((a, b) => a.path.localeCompare(b.path));
|
|
169
|
+
return [...valid, ...broken];
|
|
170
|
+
}
|
|
171
|
+
export async function listModels(cwd) {
|
|
172
|
+
const dir = path.join(cwd, "stages");
|
|
173
|
+
const models = new Set(BAKED_MODEL_IDS);
|
|
174
|
+
let entries;
|
|
175
|
+
try {
|
|
176
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return [...models].sort((a, b) => a.localeCompare(b));
|
|
180
|
+
}
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
183
|
+
continue;
|
|
184
|
+
try {
|
|
185
|
+
const raw = await readYamlObject(path.join(dir, entry.name));
|
|
186
|
+
if (typeof raw?.model === "string" && raw.model.trim()) {
|
|
187
|
+
models.add(raw.model);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// ignore unreadable or invalid stage YAML
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return [...models].sort((a, b) => a.localeCompare(b));
|
|
195
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type PackageScope = "user" | "project";
|
|
2
|
+
export type ExtensionFileScope = "user" | "project" | "temporary";
|
|
3
|
+
export type PackageListing = {
|
|
4
|
+
source: string;
|
|
5
|
+
scope: PackageScope;
|
|
6
|
+
filtered: boolean;
|
|
7
|
+
installedPath?: string;
|
|
8
|
+
};
|
|
9
|
+
export type ExtensionFileListing = {
|
|
10
|
+
name: string;
|
|
11
|
+
path: string;
|
|
12
|
+
scope: ExtensionFileScope;
|
|
13
|
+
source: string;
|
|
14
|
+
origin: "package" | "top-level";
|
|
15
|
+
baseDir?: string;
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
};
|
|
18
|
+
export type ExtensionCatalog = {
|
|
19
|
+
packages: PackageListing[];
|
|
20
|
+
extensions: ExtensionFileListing[];
|
|
21
|
+
};
|
|
22
|
+
export type ListExtensionsOptions = {
|
|
23
|
+
cwd: string;
|
|
24
|
+
agentDir: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function listExtensions(options: ListExtensionsOptions): Promise<ExtensionCatalog>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { DefaultPackageManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
function listingName(filePath) {
|
|
4
|
+
return path.basename(filePath).replace(/\.(ts|js)$/i, "");
|
|
5
|
+
}
|
|
6
|
+
export async function listExtensions(options) {
|
|
7
|
+
const settingsManager = SettingsManager.create(options.cwd, options.agentDir, {
|
|
8
|
+
projectTrusted: true,
|
|
9
|
+
});
|
|
10
|
+
const packageManager = new DefaultPackageManager({
|
|
11
|
+
cwd: options.cwd,
|
|
12
|
+
agentDir: options.agentDir,
|
|
13
|
+
settingsManager,
|
|
14
|
+
});
|
|
15
|
+
const packages = packageManager.listConfiguredPackages().map((pkg) => ({
|
|
16
|
+
source: pkg.source,
|
|
17
|
+
scope: pkg.scope,
|
|
18
|
+
filtered: pkg.filtered,
|
|
19
|
+
...(pkg.installedPath !== undefined
|
|
20
|
+
? { installedPath: pkg.installedPath }
|
|
21
|
+
: {}),
|
|
22
|
+
}));
|
|
23
|
+
packages.sort((a, b) => {
|
|
24
|
+
const bySource = a.source.localeCompare(b.source);
|
|
25
|
+
if (bySource !== 0)
|
|
26
|
+
return bySource;
|
|
27
|
+
return a.scope.localeCompare(b.scope);
|
|
28
|
+
});
|
|
29
|
+
const resolved = await packageManager.resolve(async () => "skip");
|
|
30
|
+
const extensions = resolved.extensions
|
|
31
|
+
.filter((entry) => entry.enabled)
|
|
32
|
+
.map((entry) => ({
|
|
33
|
+
name: listingName(entry.path),
|
|
34
|
+
path: entry.path,
|
|
35
|
+
scope: entry.metadata.scope,
|
|
36
|
+
source: entry.metadata.source,
|
|
37
|
+
origin: entry.metadata.origin,
|
|
38
|
+
...(entry.metadata.baseDir !== undefined
|
|
39
|
+
? { baseDir: entry.metadata.baseDir }
|
|
40
|
+
: {}),
|
|
41
|
+
enabled: true,
|
|
42
|
+
}));
|
|
43
|
+
extensions.sort((a, b) => {
|
|
44
|
+
const byName = a.name.localeCompare(b.name);
|
|
45
|
+
if (byName !== 0)
|
|
46
|
+
return byName;
|
|
47
|
+
return a.path.localeCompare(b.path);
|
|
48
|
+
});
|
|
49
|
+
return { packages, extensions };
|
|
50
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type SkillScope = "user" | "project" | "temporary";
|
|
2
|
+
export type SkillListing = {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
filePath: string;
|
|
6
|
+
baseDir: string;
|
|
7
|
+
scope: SkillScope;
|
|
8
|
+
source: string;
|
|
9
|
+
disableModelInvocation: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type SkillDiagnostic = {
|
|
12
|
+
message: string;
|
|
13
|
+
path?: string;
|
|
14
|
+
};
|
|
15
|
+
export type SkillCatalog = {
|
|
16
|
+
skills: SkillListing[];
|
|
17
|
+
diagnostics: SkillDiagnostic[];
|
|
18
|
+
};
|
|
19
|
+
export type ListSkillsOptions = {
|
|
20
|
+
cwd: string;
|
|
21
|
+
agentDir: string;
|
|
22
|
+
};
|
|
23
|
+
export type ResolvedSkill = {
|
|
24
|
+
name: string;
|
|
25
|
+
filePath: string;
|
|
26
|
+
baseDir: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function resolveSkillByName(name: string, options: ListSkillsOptions): Promise<ResolvedSkill | undefined>;
|
|
29
|
+
export declare function listSkills(options: ListSkillsOptions): Promise<SkillCatalog>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { DefaultResourceLoader, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
function toListing(skill) {
|
|
4
|
+
return {
|
|
5
|
+
name: skill.name,
|
|
6
|
+
description: skill.description,
|
|
7
|
+
filePath: skill.filePath,
|
|
8
|
+
baseDir: skill.baseDir,
|
|
9
|
+
scope: skill.sourceInfo.scope,
|
|
10
|
+
source: skill.sourceInfo.source,
|
|
11
|
+
disableModelInvocation: skill.disableModelInvocation,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
async function openSkillCatalogLoader(options) {
|
|
15
|
+
const settingsManager = SettingsManager.create(options.cwd, options.agentDir, {
|
|
16
|
+
projectTrusted: true,
|
|
17
|
+
});
|
|
18
|
+
const loader = new DefaultResourceLoader({
|
|
19
|
+
cwd: options.cwd,
|
|
20
|
+
agentDir: options.agentDir,
|
|
21
|
+
settingsManager,
|
|
22
|
+
});
|
|
23
|
+
await loader.reload();
|
|
24
|
+
return loader;
|
|
25
|
+
}
|
|
26
|
+
export async function resolveSkillByName(name, options) {
|
|
27
|
+
const loader = await openSkillCatalogLoader(options);
|
|
28
|
+
const match = loader.getSkills().skills.find((skill) => skill.name === name);
|
|
29
|
+
if (!match)
|
|
30
|
+
return undefined;
|
|
31
|
+
try {
|
|
32
|
+
await access(match.filePath);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
name: match.name,
|
|
39
|
+
filePath: match.filePath,
|
|
40
|
+
baseDir: match.baseDir,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export async function listSkills(options) {
|
|
44
|
+
const loader = await openSkillCatalogLoader(options);
|
|
45
|
+
const { skills, diagnostics } = loader.getSkills();
|
|
46
|
+
return {
|
|
47
|
+
skills: skills
|
|
48
|
+
.map(toListing)
|
|
49
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
50
|
+
diagnostics: diagnostics.map((d) => ({
|
|
51
|
+
message: d.message,
|
|
52
|
+
...(d.path !== undefined ? { path: d.path } : {}),
|
|
53
|
+
})),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ValidationFindingCode } from "./validateCatalog.js";
|
|
2
|
+
export type TaskLoadCode = "task.invalid_shape" | "task.load_error";
|
|
3
|
+
export type LoadIssue = {
|
|
4
|
+
code: ValidationFindingCode;
|
|
5
|
+
message: string;
|
|
6
|
+
category: "pipeline";
|
|
7
|
+
pipelineId?: string;
|
|
8
|
+
} | {
|
|
9
|
+
code: ValidationFindingCode;
|
|
10
|
+
message: string;
|
|
11
|
+
category: "stage";
|
|
12
|
+
stageId?: string;
|
|
13
|
+
} | {
|
|
14
|
+
code: TaskLoadCode;
|
|
15
|
+
message: string;
|
|
16
|
+
category: "task";
|
|
17
|
+
taskId?: string;
|
|
18
|
+
};
|
|
19
|
+
export type LoadOutcome<T> = {
|
|
20
|
+
ok: true;
|
|
21
|
+
value: T;
|
|
22
|
+
} | {
|
|
23
|
+
ok: false;
|
|
24
|
+
issues: LoadIssue[];
|
|
25
|
+
};
|
|
26
|
+
export declare function loadFailure<T>(issues: LoadIssue[]): LoadOutcome<T>;
|
|
27
|
+
export declare function loadSuccess<T>(value: T): LoadOutcome<T>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { LoadedPipeline } from "../types/pipeline.js";
|
|
2
|
+
import { type LoadOutcome } from "./loadOutcome.js";
|
|
3
|
+
import type { ValidationFinding } from "./validateCatalog.js";
|
|
4
|
+
export type { LoadedPipeline } from "../types/pipeline.js";
|
|
5
|
+
export type { LoadIssue, LoadOutcome } from "./loadOutcome.js";
|
|
6
|
+
export type LoadPipelineValidatedResult = {
|
|
7
|
+
ok: true;
|
|
8
|
+
loaded: LoadedPipeline;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
findings: ValidationFinding[];
|
|
12
|
+
};
|
|
13
|
+
export declare function loadPipelineOutcome(nameOrPath: string, options?: {
|
|
14
|
+
cwd?: string;
|
|
15
|
+
stagesDir?: string;
|
|
16
|
+
}): Promise<LoadOutcome<LoadedPipeline>>;
|
|
17
|
+
export declare function loadPipelineValidated(nameOrPath: string, options?: {
|
|
18
|
+
cwd?: string;
|
|
19
|
+
stagesDir?: string;
|
|
20
|
+
validateStages?: boolean;
|
|
21
|
+
}): Promise<LoadPipelineValidatedResult>;
|
|
22
|
+
export declare function loadPipeline(nameOrPath: string, options?: {
|
|
23
|
+
cwd?: string;
|
|
24
|
+
stagesDir?: string;
|
|
25
|
+
}): Promise<LoadedPipeline>;
|