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.
Files changed (164) hide show
  1. package/README.md +44 -14
  2. package/dist/agent/activity.d.ts +2 -0
  3. package/dist/agent/cursorProvider.d.ts +1 -10
  4. package/dist/agent/cursorProvider.js +18 -10
  5. package/dist/agent/fakeAgent.js +11 -7
  6. package/dist/agent/piAdapter.d.ts +2 -1
  7. package/dist/agent/piAdapter.js +28 -25
  8. package/dist/agent/port.d.ts +6 -1
  9. package/dist/agent/port.js +3 -0
  10. package/dist/cli/artifactCommand.d.ts +10 -0
  11. package/dist/cli/artifactCommand.js +120 -0
  12. package/dist/cli/envelopeCommand.d.ts +32 -0
  13. package/dist/cli/envelopeCommand.js +285 -0
  14. package/dist/cli/exportRunCommand.d.ts +10 -0
  15. package/dist/cli/exportRunCommand.js +150 -0
  16. package/dist/cli/handoffFormat.d.ts +22 -0
  17. package/dist/cli/handoffFormat.js +61 -0
  18. package/dist/cli/runCommand.d.ts +5 -1
  19. package/dist/cli/runCommand.js +36 -5
  20. package/dist/cli/runOutput.d.ts +4 -1
  21. package/dist/cli/runOutput.js +36 -8
  22. package/dist/cli/runsCommand.d.ts +22 -0
  23. package/dist/cli/runsCommand.js +591 -0
  24. package/dist/cli/skillsCommand.d.ts +16 -0
  25. package/dist/cli/skillsCommand.js +459 -0
  26. package/dist/cli/validateOutput.d.ts +2 -2
  27. package/dist/cli/validateOutput.js +2 -2
  28. package/dist/cli.js +110 -5
  29. package/dist/config/loadPipeline.js +5 -1
  30. package/dist/config/normalizePipelineStageEntry.d.ts +2 -0
  31. package/dist/config/normalizePipelineStageEntry.js +25 -1
  32. package/dist/config/pipelineStageKeys.js +1 -1
  33. package/dist/config/resolveForkEmitContext.d.ts +2 -1
  34. package/dist/config/resolveForkEmitContext.js +23 -1
  35. package/dist/config/resolvePipelineDag.js +36 -4
  36. package/dist/envelope/check.d.ts +2 -0
  37. package/dist/envelope/check.js +51 -0
  38. package/dist/envelope/cloneForks.d.ts +2 -0
  39. package/dist/envelope/cloneForks.js +62 -0
  40. package/dist/envelope/forkChoice.js +1 -1
  41. package/dist/index.d.ts +3 -1
  42. package/dist/index.js +3 -1
  43. package/dist/mcp/catalogTools.d.ts +3 -0
  44. package/dist/mcp/catalogTools.js +187 -0
  45. package/dist/mcp/controlTools.d.ts +3 -0
  46. package/dist/mcp/controlTools.js +147 -0
  47. package/dist/mcp/deps.d.ts +11 -0
  48. package/dist/mcp/deps.js +1 -0
  49. package/dist/mcp/projectRun.d.ts +1 -40
  50. package/dist/mcp/projectRun.js +1 -64
  51. package/dist/mcp/resources.d.ts +5 -0
  52. package/dist/mcp/resources.js +45 -0
  53. package/dist/mcp/server.d.ts +16 -3
  54. package/dist/mcp/server.js +159 -3
  55. package/dist/mcp/toolResults.d.ts +7 -0
  56. package/dist/mcp/toolResults.js +6 -0
  57. package/dist/mcp/tools.d.ts +2 -7
  58. package/dist/mcp/tools.js +52 -103
  59. package/dist/mcp/waitRun.d.ts +46 -0
  60. package/dist/mcp/waitRun.js +146 -0
  61. package/dist/mcp/waitingGates.d.ts +12 -0
  62. package/dist/mcp/waitingGates.js +83 -0
  63. package/dist/package-meta.d.ts +1 -0
  64. package/dist/package-meta.js +1 -0
  65. package/dist/projection/projectRun.d.ts +44 -0
  66. package/dist/projection/projectRun.js +71 -0
  67. package/dist/prompt/priorEnvelope.d.ts +1 -1
  68. package/dist/prompt/priorEnvelope.js +4 -1
  69. package/dist/runstore/paths.js +2 -0
  70. package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
  71. package/dist/runstore/pipelineDagSnapshot.js +90 -1
  72. package/dist/runstore/port.d.ts +13 -2
  73. package/dist/runstore/port.js +4 -1
  74. package/dist/runstore/runProjection.d.ts +1 -1
  75. package/dist/runstore/runProjection.js +14 -3
  76. package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
  77. package/dist/runstore/sqlite/SqliteRunStore.js +35 -3
  78. package/dist/runstore/stageInstanceId.d.ts +4 -0
  79. package/dist/runstore/stageInstanceId.js +23 -0
  80. package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
  81. package/dist/runstore/syntheticStageSnapshot.js +2 -1
  82. package/dist/runstore/trackProjection.js +28 -12
  83. package/dist/runstore/workspaceLayout.d.ts +1 -0
  84. package/dist/runstore/workspaceLayout.js +1 -1
  85. package/dist/runtime/cloneSchedule.d.ts +18 -0
  86. package/dist/runtime/cloneSchedule.js +233 -0
  87. package/dist/runtime/envelopeRouting.d.ts +2 -1
  88. package/dist/runtime/envelopeRouting.js +80 -13
  89. package/dist/runtime/pipelineScheduler.d.ts +6 -2
  90. package/dist/runtime/pipelineScheduler.js +166 -30
  91. package/dist/runtime/resumeReconstruct.js +8 -3
  92. package/dist/runtime/runChangeBus.d.ts +18 -0
  93. package/dist/runtime/runChangeBus.js +83 -0
  94. package/dist/runtime/runManager.d.ts +7 -0
  95. package/dist/runtime/runManager.js +31 -2
  96. package/dist/runtime/runRetryCoordinator.d.ts +2 -0
  97. package/dist/runtime/runRetryCoordinator.js +4 -1
  98. package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
  99. package/dist/runtime/stageAttemptBootstrap.js +17 -9
  100. package/dist/runtime/stageRunner.d.ts +1 -0
  101. package/dist/runtime/stageRunner.js +17 -15
  102. package/dist/runtime/stageWorker.js +11 -4
  103. package/dist/server/bootstrap.d.ts +32 -0
  104. package/dist/server/bootstrap.js +56 -0
  105. package/dist/server/createHttpHost.d.ts +34 -0
  106. package/dist/server/createHttpHost.js +70 -0
  107. package/dist/server/http.d.ts +6 -13
  108. package/dist/server/http.js +295 -374
  109. package/dist/server/mcpHost.d.ts +22 -0
  110. package/dist/server/mcpHost.js +21 -0
  111. package/dist/server/operatorResults.d.ts +13 -0
  112. package/dist/server/operatorResults.js +22 -0
  113. package/dist/tools/emitStageEnvelope.d.ts +11 -2
  114. package/dist/tools/emitStageEnvelope.js +22 -6
  115. package/dist/types/envelope.d.ts +2 -0
  116. package/dist/types/forkChoice.d.ts +22 -0
  117. package/dist/types/pipeline.d.ts +8 -0
  118. package/dist/ui/assets/{index-DefBlEvN.css → index-C3N7MXAC.css} +1 -1
  119. package/dist/ui/assets/index-CSoGDA3A.js +118 -0
  120. package/dist/ui/index.html +2 -2
  121. package/package.json +2 -1
  122. package/skills/install-suite.sh +107 -0
  123. package/skills/stageflow/SKILL.md +26 -0
  124. package/skills/stageflow/references/control-surface.md +28 -0
  125. package/skills/stageflow/scripts/detect-host.mjs +54 -0
  126. package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
  127. package/skills/stageflow-author/SKILL.md +58 -0
  128. package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
  129. package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
  130. package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
  131. package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
  132. package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
  133. package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
  134. package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
  135. package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
  136. package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
  137. package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
  138. package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
  139. package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
  140. package/skills/stageflow-author/references/catalog-mapping.md +131 -0
  141. package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
  142. package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
  143. package/skills/stageflow-author/references/validate-and-report.md +38 -0
  144. package/skills/stageflow-delegate/SKILL.md +18 -0
  145. package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
  146. package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
  147. package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
  148. package/skills/stageflow-run/SKILL.md +182 -0
  149. package/skills/stageflow-run/references/mcp-call.md +17 -0
  150. package/skills/stageflow-run/references/native-question-ui.md +78 -0
  151. package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
  152. package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
  153. package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
  154. package/skills/stageflow-session-capture/SKILL.md +94 -0
  155. package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
  156. package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
  157. package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
  158. package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
  159. package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
  160. package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
  161. package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
  162. package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
  163. package/skills/stageflow-setup/SKILL.md +110 -0
  164. package/dist/ui/assets/index-CFSzDZje.js +0 -118
@@ -0,0 +1,459 @@
1
+ import { createHash } from "node:crypto";
2
+ import { spawnSync } from "node:child_process";
3
+ import { access, cp, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile, } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { inflateRawSync } from "node:zlib";
6
+ import path from "node:path";
7
+ import { findProjectRoot } from "../project/findProjectRoot.js";
8
+ export const SKILLS_USAGE = `Usage:
9
+ sf skills list
10
+ sf skills install --from-path <dir> [--skill-name <name>]
11
+ sf skills install --from-zip <url-or-path> [--skill-name <name>] [--checksum sha256:<hex>]`;
12
+ const defaultIo = {
13
+ log: (line) => console.log(line),
14
+ error: (line) => console.error(line),
15
+ fetch: globalThis.fetch.bind(globalThis),
16
+ runDoctor: (scriptPath, cwd) => {
17
+ const result = spawnSync(process.execPath, [scriptPath, "doctor"], {
18
+ cwd,
19
+ stdio: "pipe",
20
+ });
21
+ return result.status ?? 1;
22
+ },
23
+ };
24
+ function parseSkillsArgs(args) {
25
+ if (args.length === 0) {
26
+ return { help: false };
27
+ }
28
+ if (args[0] === "--help" || args[0] === "-h") {
29
+ return { help: true };
30
+ }
31
+ const subcommand = args[0];
32
+ if (subcommand !== "list" && subcommand !== "install") {
33
+ throw new Error(`Unknown skills subcommand: ${subcommand}`);
34
+ }
35
+ let fromPath;
36
+ let fromZip;
37
+ let skillName;
38
+ let checksum;
39
+ let help = false;
40
+ for (let i = 1; i < args.length; i++) {
41
+ const arg = args[i];
42
+ if (arg === "--help" || arg === "-h") {
43
+ help = true;
44
+ }
45
+ else if (arg === "--from-path") {
46
+ const value = args[++i];
47
+ if (value === undefined || value.length === 0) {
48
+ throw new Error("Missing value for --from-path");
49
+ }
50
+ fromPath = value;
51
+ }
52
+ else if (arg === "--from-zip") {
53
+ const value = args[++i];
54
+ if (value === undefined || value.length === 0) {
55
+ throw new Error("Missing value for --from-zip");
56
+ }
57
+ fromZip = value;
58
+ }
59
+ else if (arg === "--skill-name") {
60
+ const value = args[++i];
61
+ if (value === undefined || value.length === 0) {
62
+ throw new Error("Missing value for --skill-name");
63
+ }
64
+ skillName = value;
65
+ }
66
+ else if (arg === "--checksum") {
67
+ const value = args[++i];
68
+ if (value === undefined || value.length === 0) {
69
+ throw new Error("Missing value for --checksum");
70
+ }
71
+ checksum = value;
72
+ }
73
+ else if (arg.startsWith("-")) {
74
+ throw new Error(`Unknown flag: ${arg}`);
75
+ }
76
+ else {
77
+ throw new Error(`Unexpected argument: ${arg}`);
78
+ }
79
+ }
80
+ return {
81
+ help,
82
+ subcommand,
83
+ fromPath,
84
+ fromZip,
85
+ skillName,
86
+ checksum,
87
+ };
88
+ }
89
+ function skillsDirForRoot(projectRoot) {
90
+ return path.join(projectRoot, ".pi", "skills");
91
+ }
92
+ function shouldCopyRelative(rel) {
93
+ const normalized = rel.split(path.sep).join("/");
94
+ if (normalized === "" || normalized === ".") {
95
+ return true;
96
+ }
97
+ const segments = normalized.split("/");
98
+ if (segments.some((segment) => segment === "test" || segment === "node_modules")) {
99
+ return false;
100
+ }
101
+ if (segments[0] === "scripts") {
102
+ const base = segments[segments.length - 1] ?? "";
103
+ if (base.startsWith("generate-")) {
104
+ return false;
105
+ }
106
+ }
107
+ return true;
108
+ }
109
+ async function copySkillTree(sourceRoot, destRoot) {
110
+ await rm(destRoot, { recursive: true, force: true });
111
+ await mkdir(destRoot, { recursive: true });
112
+ await cp(sourceRoot, destRoot, {
113
+ recursive: true,
114
+ filter: (src) => {
115
+ const rel = path.relative(sourceRoot, src);
116
+ return shouldCopyRelative(rel);
117
+ },
118
+ });
119
+ }
120
+ function inferSkillNameFromPath(sourcePath) {
121
+ return path.basename(path.resolve(sourcePath));
122
+ }
123
+ function inferSkillNameFromZipRef(zipRef) {
124
+ const base = path.basename(zipRef.split("?")[0] ?? zipRef);
125
+ return base.replace(/\.zip$/i, "");
126
+ }
127
+ function parseChecksum(value) {
128
+ const match = /^sha256:([a-f0-9]{64})$/i.exec(value);
129
+ if (!match) {
130
+ throw new Error("--checksum must be sha256:<64 hex chars>");
131
+ }
132
+ return match[1].toLowerCase();
133
+ }
134
+ function sha256Hex(data) {
135
+ return createHash("sha256").update(data).digest("hex");
136
+ }
137
+ function isZipSlipEntry(name) {
138
+ const normalized = name.replace(/\\/g, "/");
139
+ if (path.posix.isAbsolute(normalized)) {
140
+ return true;
141
+ }
142
+ return normalized.split("/").some((segment) => segment === "..");
143
+ }
144
+ function findEndOfCentralDirectory(data) {
145
+ for (let i = data.length - 22; i >= 0; i--) {
146
+ if (data.readUInt32LE(i) === 0x06054b50) {
147
+ return i;
148
+ }
149
+ }
150
+ throw new Error("invalid zip archive: end of central directory not found");
151
+ }
152
+ function readZipEntries(data) {
153
+ const eocdOffset = findEndOfCentralDirectory(data);
154
+ const centralDirOffset = data.readUInt32LE(eocdOffset + 16);
155
+ const totalEntries = data.readUInt16LE(eocdOffset + 10);
156
+ const entries = [];
157
+ let offset = centralDirOffset;
158
+ for (let i = 0; i < totalEntries; i++) {
159
+ if (data.readUInt32LE(offset) !== 0x02014b50) {
160
+ throw new Error("invalid zip archive: central directory entry missing");
161
+ }
162
+ const compressionMethod = data.readUInt16LE(offset + 10);
163
+ const compressedSize = data.readUInt32LE(offset + 20);
164
+ const uncompressedSize = data.readUInt32LE(offset + 24);
165
+ const nameLength = data.readUInt16LE(offset + 28);
166
+ const extraLength = data.readUInt16LE(offset + 30);
167
+ const commentLength = data.readUInt16LE(offset + 32);
168
+ const localHeaderOffset = data.readUInt32LE(offset + 42);
169
+ const name = data.toString("utf8", offset + 46, offset + 46 + nameLength);
170
+ entries.push({
171
+ name,
172
+ compressionMethod,
173
+ compressedSize,
174
+ uncompressedSize,
175
+ localHeaderOffset,
176
+ });
177
+ offset += 46 + nameLength + extraLength + commentLength;
178
+ }
179
+ return entries;
180
+ }
181
+ async function extractZipBuffer(data, destDir) {
182
+ await mkdir(destDir, { recursive: true });
183
+ const entries = readZipEntries(data);
184
+ for (const entry of entries) {
185
+ if (entry.name.endsWith("/")) {
186
+ continue;
187
+ }
188
+ if (isZipSlipEntry(entry.name)) {
189
+ throw new Error("zip entry path rejected (zip-slip)");
190
+ }
191
+ const localOffset = entry.localHeaderOffset;
192
+ if (data.readUInt32LE(localOffset) !== 0x04034b50) {
193
+ throw new Error("invalid zip archive: local file header missing");
194
+ }
195
+ const nameLength = data.readUInt16LE(localOffset + 26);
196
+ const extraLength = data.readUInt16LE(localOffset + 28);
197
+ const dataOffset = localOffset + 30 + nameLength + extraLength;
198
+ const compressed = data.subarray(dataOffset, dataOffset + entry.compressedSize);
199
+ let content;
200
+ if (entry.compressionMethod === 0) {
201
+ content = compressed;
202
+ }
203
+ else if (entry.compressionMethod === 8) {
204
+ content = inflateRawSync(compressed);
205
+ }
206
+ else {
207
+ throw new Error(`unsupported zip compression method: ${entry.compressionMethod}`);
208
+ }
209
+ if (content.length !== entry.uncompressedSize) {
210
+ throw new Error("invalid zip archive: uncompressed size mismatch");
211
+ }
212
+ const outPath = path.join(destDir, entry.name);
213
+ await mkdir(path.dirname(outPath), { recursive: true });
214
+ await writeFile(outPath, content);
215
+ }
216
+ }
217
+ async function resolveZipBytes(zipRef, io) {
218
+ if (zipRef.startsWith("https://")) {
219
+ const response = await io.fetch(zipRef);
220
+ if (!response.ok) {
221
+ throw new Error(`failed to fetch zip: HTTP ${response.status}`);
222
+ }
223
+ const arrayBuffer = await response.arrayBuffer();
224
+ return Buffer.from(arrayBuffer);
225
+ }
226
+ if (/^https?:\/\//i.test(zipRef)) {
227
+ throw new Error("only HTTPS URLs are supported for --from-zip");
228
+ }
229
+ return readFile(path.resolve(zipRef));
230
+ }
231
+ async function pathExists(filePath) {
232
+ try {
233
+ await access(filePath);
234
+ return true;
235
+ }
236
+ catch {
237
+ return false;
238
+ }
239
+ }
240
+ async function findSkillRoot(extractRoot, skillName) {
241
+ const directBin = path.join(extractRoot, "bin", `${skillName}.mjs`);
242
+ if (await pathExists(directBin)) {
243
+ return extractRoot;
244
+ }
245
+ const nestedRoot = path.join(extractRoot, skillName);
246
+ const nestedBin = path.join(nestedRoot, "bin", `${skillName}.mjs`);
247
+ if (await pathExists(nestedBin)) {
248
+ return nestedRoot;
249
+ }
250
+ const found = await findSkillRootByBin(extractRoot, skillName);
251
+ if (found !== undefined) {
252
+ return found;
253
+ }
254
+ const bySkillMd = await findSkillRootBySkillMd(extractRoot);
255
+ if (bySkillMd !== undefined) {
256
+ return bySkillMd;
257
+ }
258
+ throw new Error(`could not locate skill root in extracted archive`);
259
+ }
260
+ async function findSkillRootByBin(dir, skillName) {
261
+ const entries = await readdir(dir, { withFileTypes: true });
262
+ for (const entry of entries) {
263
+ if (!entry.isDirectory()) {
264
+ continue;
265
+ }
266
+ const fullPath = path.join(dir, entry.name);
267
+ const binScript = path.join(fullPath, "bin", `${skillName}.mjs`);
268
+ if (await pathExists(binScript)) {
269
+ return fullPath;
270
+ }
271
+ const nested = await findSkillRootByBin(fullPath, skillName);
272
+ if (nested !== undefined) {
273
+ return nested;
274
+ }
275
+ }
276
+ return undefined;
277
+ }
278
+ async function findSkillRootBySkillMd(dir) {
279
+ const entries = await readdir(dir, { withFileTypes: true });
280
+ for (const entry of entries) {
281
+ const fullPath = path.join(dir, entry.name);
282
+ if (entry.isFile() && entry.name === "SKILL.md") {
283
+ return dir;
284
+ }
285
+ if (entry.isDirectory()) {
286
+ const nested = await findSkillRootBySkillMd(fullPath);
287
+ if (nested !== undefined) {
288
+ return nested;
289
+ }
290
+ }
291
+ }
292
+ return undefined;
293
+ }
294
+ async function resolveInstallSource(parsed, io) {
295
+ const hasPath = parsed.fromPath !== undefined;
296
+ const hasZip = parsed.fromZip !== undefined;
297
+ if (hasPath === hasZip) {
298
+ throw new Error("specify exactly one of --from-path or --from-zip");
299
+ }
300
+ if (parsed.fromPath !== undefined) {
301
+ const sourceRoot = path.resolve(parsed.fromPath);
302
+ try {
303
+ const sourceStat = await stat(sourceRoot);
304
+ if (!sourceStat.isDirectory()) {
305
+ throw new Error("source path not found");
306
+ }
307
+ }
308
+ catch {
309
+ throw new Error("source path not found");
310
+ }
311
+ const skillName = parsed.skillName ?? inferSkillNameFromPath(parsed.fromPath);
312
+ return { sourceRoot, skillName };
313
+ }
314
+ const zipRef = parsed.fromZip;
315
+ const zipBytes = await resolveZipBytes(zipRef, io);
316
+ if (parsed.checksum !== undefined) {
317
+ const expected = parseChecksum(parsed.checksum);
318
+ const actual = sha256Hex(zipBytes);
319
+ if (actual !== expected) {
320
+ throw new Error("checksum mismatch");
321
+ }
322
+ }
323
+ const extractDir = await mkdtemp(path.join(tmpdir(), "sf-skill-zip-"));
324
+ await extractZipBuffer(zipBytes, extractDir);
325
+ const skillName = parsed.skillName ?? inferSkillNameFromZipRef(zipRef);
326
+ const sourceRoot = await findSkillRoot(extractDir, skillName);
327
+ return {
328
+ sourceRoot,
329
+ skillName,
330
+ cleanup: async () => {
331
+ await rm(extractDir, { recursive: true, force: true });
332
+ },
333
+ };
334
+ }
335
+ async function listSkills(projectRoot, io) {
336
+ const skillsDir = skillsDirForRoot(projectRoot);
337
+ let names;
338
+ try {
339
+ names = await readdir(skillsDir);
340
+ }
341
+ catch (err) {
342
+ if (err.code === "ENOENT") {
343
+ return;
344
+ }
345
+ throw err;
346
+ }
347
+ for (const name of names.sort()) {
348
+ const skillDir = path.join(skillsDir, name);
349
+ const skillStat = await stat(skillDir);
350
+ if (!skillStat.isDirectory()) {
351
+ continue;
352
+ }
353
+ let version = "—";
354
+ try {
355
+ const pkgRaw = await readFile(path.join(skillDir, "package.json"), "utf8");
356
+ const pkg = JSON.parse(pkgRaw);
357
+ if (typeof pkg.version === "string" && pkg.version.length > 0) {
358
+ version = pkg.version;
359
+ }
360
+ }
361
+ catch {
362
+ // no package.json
363
+ }
364
+ io.log(`${name}\t${version}\tbin/${name}.mjs`);
365
+ }
366
+ }
367
+ async function installSkill(projectRoot, parsed, io) {
368
+ const resolved = await resolveInstallSource(parsed, io);
369
+ try {
370
+ const destRoot = path.join(skillsDirForRoot(projectRoot), resolved.skillName);
371
+ await copySkillTree(resolved.sourceRoot, destRoot);
372
+ const doctorScript = path.join(destRoot, "bin", `${resolved.skillName}.mjs`);
373
+ const doctorCode = io.runDoctor(doctorScript, projectRoot);
374
+ if (doctorCode !== 0) {
375
+ io.error("skill doctor failed");
376
+ return 1;
377
+ }
378
+ io.log(`Installed skill ${resolved.skillName} to ${destRoot}`);
379
+ return 0;
380
+ }
381
+ finally {
382
+ if (resolved.cleanup) {
383
+ await resolved.cleanup();
384
+ }
385
+ }
386
+ }
387
+ export async function runSkillsCommand(args, options = {}) {
388
+ const cwd = options.cwd ?? process.cwd();
389
+ const projectRoot = options.projectRoot ?? findProjectRoot(cwd) ?? path.resolve(cwd);
390
+ const io = { ...defaultIo, ...options.io };
391
+ try {
392
+ const parsed = parseSkillsArgs(args);
393
+ if (parsed.help) {
394
+ io.error(SKILLS_USAGE);
395
+ return 0;
396
+ }
397
+ if (parsed.subcommand === undefined) {
398
+ io.error(SKILLS_USAGE);
399
+ return 1;
400
+ }
401
+ if (parsed.subcommand === "list") {
402
+ await listSkills(projectRoot, io);
403
+ return 0;
404
+ }
405
+ return await installSkill(projectRoot, parsed, io);
406
+ }
407
+ catch (err) {
408
+ io.error(err instanceof Error ? err.message : String(err));
409
+ return 1;
410
+ }
411
+ }
412
+ export function createStoredZip(files) {
413
+ const localParts = [];
414
+ const centralParts = [];
415
+ let offset = 0;
416
+ for (const file of files) {
417
+ const nameBuf = Buffer.from(file.name, "utf8");
418
+ const content = Buffer.isBuffer(file.content)
419
+ ? file.content
420
+ : Buffer.from(file.content, "utf8");
421
+ const localHeader = Buffer.alloc(30);
422
+ localHeader.writeUInt32LE(0x04034b50, 0);
423
+ localHeader.writeUInt16LE(20, 4);
424
+ localHeader.writeUInt16LE(0, 6);
425
+ localHeader.writeUInt16LE(0, 8);
426
+ localHeader.writeUInt32LE(0, 14);
427
+ localHeader.writeUInt32LE(content.length, 18);
428
+ localHeader.writeUInt32LE(content.length, 22);
429
+ localHeader.writeUInt16LE(nameBuf.length, 26);
430
+ localHeader.writeUInt16LE(0, 28);
431
+ localParts.push(localHeader, nameBuf, content);
432
+ const central = Buffer.alloc(46);
433
+ central.writeUInt32LE(0x02014b50, 0);
434
+ central.writeUInt16LE(20, 4);
435
+ central.writeUInt16LE(20, 6);
436
+ central.writeUInt16LE(0, 8);
437
+ central.writeUInt16LE(0, 10);
438
+ central.writeUInt32LE(0, 16);
439
+ central.writeUInt32LE(content.length, 20);
440
+ central.writeUInt32LE(content.length, 24);
441
+ central.writeUInt16LE(nameBuf.length, 28);
442
+ central.writeUInt16LE(0, 30);
443
+ central.writeUInt16LE(0, 32);
444
+ central.writeUInt32LE(offset, 42);
445
+ centralParts.push(central, nameBuf);
446
+ offset += 30 + nameBuf.length + content.length;
447
+ }
448
+ const centralDir = Buffer.concat(centralParts);
449
+ const eocd = Buffer.alloc(22);
450
+ eocd.writeUInt32LE(0x06054b50, 0);
451
+ eocd.writeUInt16LE(0, 4);
452
+ eocd.writeUInt16LE(0, 6);
453
+ eocd.writeUInt16LE(files.length, 8);
454
+ eocd.writeUInt16LE(files.length, 10);
455
+ eocd.writeUInt32LE(centralDir.length, 12);
456
+ eocd.writeUInt32LE(offset, 16);
457
+ eocd.writeUInt16LE(0, 20);
458
+ return Buffer.concat([...localParts, centralDir, eocd]);
459
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ValidationResult } from "../config/validateCatalog.js";
2
- export declare const VALIDATION_SCOPE_LINE = "Scope: pipeline and stage YAML only (task, checkout, providers not checked).";
3
- export declare const VALIDATION_CHECKS = "pipeline and stage YAML only";
2
+ export declare const VALIDATION_SCOPE_LINE = "Scope: catalog YAML (pipelines, stages, and tasks as selected by flags). Provider auth and checkout paths are not checked.";
3
+ export declare const VALIDATION_CHECKS = "catalog YAML (pipelines, stages, tasks as selected by flags)";
4
4
  export declare function formatValidationHuman(result: ValidationResult, options?: {
5
5
  strict?: boolean;
6
6
  }): string;
@@ -1,6 +1,6 @@
1
1
  import { effectiveSeverity } from "../config/validateCatalog.js";
2
- export const VALIDATION_SCOPE_LINE = "Scope: pipeline and stage YAML only (task, checkout, providers not checked).";
3
- export const VALIDATION_CHECKS = "pipeline and stage YAML only";
2
+ export const VALIDATION_SCOPE_LINE = "Scope: catalog YAML (pipelines, stages, and tasks as selected by flags). Provider auth and checkout paths are not checked.";
3
+ export const VALIDATION_CHECKS = "catalog YAML (pipelines, stages, tasks as selected by flags)";
4
4
  function effectiveSeverityRank(finding, strict) {
5
5
  return effectiveSeverity(finding, strict) === "error" ? 0 : 1;
6
6
  }
package/dist/cli.js CHANGED
@@ -4,27 +4,52 @@ import { realpathSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { PiAgentAdapter } from "./agent/piAdapter.js";
7
+ import { ARTIFACT_USAGE, runArtifactCommand } from "./cli/artifactCommand.js";
8
+ import { ENVELOPE_USAGE, runEnvelopeCommand, } from "./cli/envelopeCommand.js";
9
+ import { EXPORT_RUN_USAGE, runExportRunCommand, } from "./cli/exportRunCommand.js";
7
10
  import { INIT_USAGE, runInitCommand } from "./cli/initCommand.js";
8
11
  import { PROVIDERS_USAGE, runProvidersCommand } from "./cli/providersCommand.js";
9
12
  import { RUN_USAGE, runRunCommand } from "./cli/runCommand.js";
13
+ import { RUNS_USAGE, runRunsCommand } from "./cli/runsCommand.js";
10
14
  import { resolveOperatorCatalog } from "./cli/operatorCatalog.js";
15
+ import { SKILLS_USAGE, runSkillsCommand } from "./cli/skillsCommand.js";
11
16
  import { VALIDATE_USAGE, runValidateCommand } from "./cli/validateCommand.js";
12
17
  import { createRunStore } from "./runstore/createStore.js";
13
18
  import { resolveStageflowContext } from "./project/resolveStageflowContext.js";
14
19
  import { exitForOutcome, runStageWorker } from "./runtime/stageWorker.js";
15
20
  import { SF_STAGE_WORKER } from "./runtime/stageWorkerProtocol.js";
16
21
  import { DEFAULT_PORT, startUiServer } from "./server/http.js";
22
+ import { startMcpServer } from "./server/mcpHost.js";
23
+ import { resolveMcpStateless } from "./mcp/server.js";
24
+ import { PACKAGE_VERSION } from "./package-meta.js";
17
25
  const USAGE = `Usage:
18
26
  sf init
19
- sf run --task <path> --pipeline <path> [--checkout <path>] [--json] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]
27
+ sf run --task <path> --pipeline <path> [--checkout <path>] [--json] [--include stages] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]
20
28
  sf validate [--pipeline <path>] [--task <path>] [--strict] [--json]
21
- sf ui [--port ${DEFAULT_PORT}]
29
+ sf artifact read --run <runId> --path <relPath> [--out <file>]
30
+ sf envelope get --run <runId> --stage <stageId> [--json] [--from <sf-run.json>] [--detect-stage <id>] [--format envelope|handoff]
31
+ sf export-run --run <runId> [--from <sf-run.json>] [--out <file>]
32
+ sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]
33
+ sf runs show --run <runId> [--from <sf-run.json>] [--json]
34
+ sf runs waiting [--run <runId>] [--json]
35
+ sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]
36
+ sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]
37
+ sf runs retry --run <runId> --stage <stageId> [--json]
38
+ sf runs abandon --run <runId> --stage <stageId> [--json]
39
+ sf runs rerun --run <runId> [--json]
40
+ sf ui [--port ${DEFAULT_PORT}] [--mcp-stateless]
41
+ sf mcp [--port ${DEFAULT_PORT}] [--mcp-stateless]
22
42
  sf providers list
23
43
  sf providers status [--provider <id>]
24
44
  sf providers detect
25
45
  sf providers source [get | set <pi_home|sf_owned>]
26
46
  sf providers login <providerId> [--type api_key|oauth] [--api-key-env <VAR>]
27
47
  sf providers logout <providerId>
48
+ sf skills list
49
+ sf skills install --from-path <dir> [--skill-name <name>]
50
+ sf skills install --from-zip <url-or-path> [--skill-name <name>] [--checksum sha256:<hex>]
51
+ sf --version
52
+ sf -V
28
53
  sf --help
29
54
 
30
55
  Stageflow (sf) runs YAML-defined automatic stage pipelines.
@@ -37,7 +62,17 @@ ${RUN_USAGE}
37
62
 
38
63
  ${VALIDATE_USAGE}
39
64
 
40
- ${PROVIDERS_USAGE}`;
65
+ ${ARTIFACT_USAGE}
66
+
67
+ ${ENVELOPE_USAGE}
68
+
69
+ ${EXPORT_RUN_USAGE}
70
+
71
+ ${RUNS_USAGE}
72
+
73
+ ${PROVIDERS_USAGE}
74
+
75
+ ${SKILLS_USAGE}`;
41
76
  function parseArgs(argv) {
42
77
  const args = argv.slice(2);
43
78
  if (args.length === 0) {
@@ -47,11 +82,22 @@ function parseArgs(argv) {
47
82
  args.length === 1) {
48
83
  return { help: true };
49
84
  }
85
+ if (args[0] === "--version" || args[0] === "-V") {
86
+ if (args.length > 1) {
87
+ throw new Error(`Unexpected argument: ${args[1]}`);
88
+ }
89
+ return { help: false, version: true };
90
+ }
50
91
  const command = args[0];
51
92
  if (command === "providers" ||
52
93
  command === "validate" ||
53
94
  command === "run" ||
54
- command === "init") {
95
+ command === "init" ||
96
+ command === "artifact" ||
97
+ command === "envelope" ||
98
+ command === "export-run" ||
99
+ command === "runs" ||
100
+ command === "skills") {
55
101
  return { help: false, command };
56
102
  }
57
103
  if (args.includes("--help") || args.includes("-h")) {
@@ -61,6 +107,7 @@ function parseArgs(argv) {
61
107
  let pipeline;
62
108
  let port;
63
109
  let checkout;
110
+ let mcpStateless = false;
64
111
  for (let i = 1; i < args.length; i++) {
65
112
  if (args[i] === "--task") {
66
113
  task = args[++i];
@@ -82,8 +129,11 @@ function parseArgs(argv) {
82
129
  throw new Error(`Invalid --port: ${raw}`);
83
130
  }
84
131
  }
132
+ else if (args[i] === "--mcp-stateless") {
133
+ mcpStateless = true;
134
+ }
85
135
  }
86
- return { help: false, command, task, pipeline, port, checkout };
136
+ return { help: false, command, task, pipeline, port, checkout, mcpStateless };
87
137
  }
88
138
  export function parseRunStageArgs(argv) {
89
139
  let runId;
@@ -211,6 +261,10 @@ async function main(argv) {
211
261
  console.log(USAGE);
212
262
  return argv.slice(2).length === 0 ? 1 : 0;
213
263
  }
264
+ if (parsed.version) {
265
+ console.log(PACKAGE_VERSION);
266
+ return 0;
267
+ }
214
268
  const ctx = await resolveStageflowContext(process.cwd());
215
269
  if (parsed.command === "init") {
216
270
  return runInitCommand(argv.slice(3), { cwd: ctx.invocationCwd });
@@ -225,17 +279,52 @@ async function main(argv) {
225
279
  isGitProject: ctx.isGitProject,
226
280
  });
227
281
  }
282
+ if (parsed.command === "artifact") {
283
+ return runArtifactCommand(argv.slice(3), {
284
+ cwd: ctx.invocationCwd,
285
+ projectRoot: ctx.projectRoot,
286
+ });
287
+ }
288
+ if (parsed.command === "envelope") {
289
+ return runEnvelopeCommand(argv.slice(3), {
290
+ cwd: ctx.invocationCwd,
291
+ projectRoot: ctx.projectRoot,
292
+ });
293
+ }
294
+ if (parsed.command === "export-run") {
295
+ return runExportRunCommand(argv.slice(3), {
296
+ cwd: ctx.invocationCwd,
297
+ projectRoot: ctx.projectRoot,
298
+ });
299
+ }
300
+ if (parsed.command === "runs") {
301
+ return runRunsCommand(argv.slice(3), {
302
+ cwd: ctx.invocationCwd,
303
+ projectRoot: ctx.projectRoot,
304
+ isGitProject: ctx.isGitProject,
305
+ });
306
+ }
228
307
  if (parsed.command === "providers") {
229
308
  return runProvidersCommand(argv.slice(3), ctx.invocationCwd);
230
309
  }
310
+ if (parsed.command === "skills") {
311
+ return runSkillsCommand(argv.slice(3), {
312
+ cwd: ctx.invocationCwd,
313
+ projectRoot: ctx.projectRoot,
314
+ });
315
+ }
231
316
  const store = createRunStore({ rootDir: ctx.projectRoot });
232
317
  if (parsed.command === "ui") {
318
+ const mcpStateless = resolveMcpStateless({
319
+ mcpStateless: parsed.mcpStateless,
320
+ });
233
321
  const { url, mcpUrl } = await startUiServer({
234
322
  agent: new PiAgentAdapter(),
235
323
  store,
236
324
  cwd: ctx.invocationCwd,
237
325
  rootDir: ctx.projectRoot,
238
326
  port: parsed.port,
327
+ mcpStateless,
239
328
  });
240
329
  console.log(`Operator console: ${url}`);
241
330
  console.log(`MCP endpoint: ${mcpUrl}`);
@@ -243,6 +332,22 @@ async function main(argv) {
243
332
  await new Promise(() => undefined);
244
333
  return 0;
245
334
  }
335
+ if (parsed.command === "mcp") {
336
+ const mcpStateless = resolveMcpStateless({
337
+ mcpStateless: parsed.mcpStateless,
338
+ });
339
+ const { mcpUrl } = await startMcpServer({
340
+ agent: new PiAgentAdapter(),
341
+ store,
342
+ cwd: ctx.invocationCwd,
343
+ rootDir: ctx.projectRoot,
344
+ port: parsed.port,
345
+ mcpStateless,
346
+ });
347
+ console.log(`MCP endpoint: ${mcpUrl}`);
348
+ await new Promise(() => undefined);
349
+ return 0;
350
+ }
246
351
  if (parsed.command === "internal") {
247
352
  const subArgs = argv.slice(3);
248
353
  const subcommand = subArgs[0];
@@ -96,7 +96,11 @@ async function loadPipelineFromPath(pipelinePath) {
96
96
  },
97
97
  ]);
98
98
  }
99
- stages.push(stageOutcome.value);
99
+ const stage = {
100
+ ...stageOutcome.value,
101
+ ...(entry.skill !== undefined ? { skill: entry.skill } : {}),
102
+ };
103
+ stages.push(stage);
100
104
  stageSources[stageId] = { kind: "file", path: entry.body.absolutePath };
101
105
  }
102
106
  const pipeline = {