stageflow 0.2.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -14
- package/dist/agent/activity.d.ts +12 -1
- package/dist/agent/activity.js +37 -2
- package/dist/agent/activityObserver.d.ts +5 -1
- package/dist/agent/activityObserver.js +174 -11
- package/dist/agent/cursorProvider.d.ts +1 -10
- package/dist/agent/cursorProvider.js +18 -10
- package/dist/agent/fakeAgent.js +15 -7
- package/dist/agent/piAdapter.d.ts +18 -1
- package/dist/agent/piAdapter.js +115 -45
- package/dist/agent/port.d.ts +7 -0
- package/dist/agent/port.js +3 -0
- package/dist/agent/providerAuth.js +12 -7
- package/dist/catalog/displayCatalogPath.d.ts +12 -0
- package/dist/catalog/displayCatalogPath.js +28 -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/initCommand.d.ts +9 -0
- package/dist/cli/initCommand.js +71 -0
- package/dist/cli/initTemplates.d.ts +3 -0
- package/dist/cli/initTemplates.js +19 -0
- package/dist/cli/operatorCatalog.d.ts +10 -0
- package/dist/cli/operatorCatalog.js +16 -0
- package/dist/cli/runCommand.d.ts +7 -1
- package/dist/cli/runCommand.js +83 -11
- package/dist/cli/runOutput.d.ts +4 -1
- package/dist/cli/runOutput.js +36 -8
- package/dist/cli/skillsCommand.d.ts +16 -0
- package/dist/cli/skillsCommand.js +459 -0
- package/dist/cli/validateCommand.d.ts +1 -1
- package/dist/cli/validateCommand.js +20 -3
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +128 -22
- package/dist/config/browseCatalog.d.ts +45 -0
- package/dist/config/browseCatalog.js +170 -0
- package/dist/config/createPipeline.d.ts +12 -3
- package/dist/config/createPipeline.js +292 -100
- package/dist/config/createStage.d.ts +10 -4
- package/dist/config/createStage.js +44 -14
- package/dist/config/listConfig.d.ts +14 -19
- package/dist/config/listConfig.js +26 -191
- package/dist/config/listModelsFromManifest.d.ts +2 -0
- package/dist/config/listModelsFromManifest.js +4 -0
- package/dist/config/loadOutcome.d.ts +4 -0
- package/dist/config/loadPipeline.d.ts +1 -15
- package/dist/config/loadPipeline.js +88 -142
- package/dist/config/loadStage.d.ts +4 -0
- package/dist/config/loadStage.js +60 -32
- package/dist/config/loadStageflowManifest.d.ts +5 -0
- package/dist/config/loadStageflowManifest.js +157 -0
- package/dist/config/mergePipelineIncludes.d.ts +9 -0
- package/dist/config/mergePipelineIncludes.js +141 -0
- package/dist/config/normalizePipelineStageEntry.d.ts +18 -0
- package/dist/config/normalizePipelineStageEntry.js +210 -0
- package/dist/config/pipelineStageKeys.d.ts +4 -0
- package/dist/config/pipelineStageKeys.js +14 -0
- package/dist/config/resolveCatalogContext.d.ts +13 -0
- package/dist/config/resolveCatalogContext.js +13 -0
- package/dist/config/resolveForkEmitContext.d.ts +4 -0
- package/dist/config/resolveForkEmitContext.js +34 -0
- package/dist/config/resolvePipelineDag.d.ts +5 -1
- package/dist/config/resolvePipelineDag.js +86 -36
- package/dist/config/scanCatalogPaths.d.ts +4 -0
- package/dist/config/scanCatalogPaths.js +83 -0
- package/dist/config/validateCatalog.d.ts +22 -4
- package/dist/config/validateCatalog.js +169 -132
- package/dist/envelope/check.d.ts +2 -0
- package/dist/envelope/check.js +58 -0
- package/dist/envelope/cloneForks.d.ts +2 -0
- package/dist/envelope/cloneForks.js +62 -0
- package/dist/envelope/forkChoice.d.ts +3 -0
- package/dist/envelope/forkChoice.js +47 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- 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 -37
- package/dist/mcp/projectRun.js +1 -57
- 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 -86
- 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/project/findProjectRoot.d.ts +2 -0
- package/dist/project/findProjectRoot.js +51 -0
- package/dist/project/globalHome.d.ts +2 -0
- package/dist/project/globalHome.js +19 -0
- package/dist/project/resolveProjectContext.d.ts +9 -0
- package/dist/project/resolveProjectContext.js +38 -0
- package/dist/project/resolveStageflowContext.d.ts +11 -0
- package/dist/project/resolveStageflowContext.js +66 -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/normalizeCatalogPath.d.ts +1 -0
- package/dist/runstore/normalizeCatalogPath.js +4 -0
- 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 +22 -2
- package/dist/runstore/port.js +4 -1
- package/dist/runstore/runProjection.d.ts +1 -1
- package/dist/runstore/runProjection.js +25 -4
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
- package/dist/runstore/sqlite/SqliteRunStore.js +65 -7
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +4 -1
- 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/credentialBinding.d.ts +8 -6
- package/dist/runtime/credentialBinding.js +18 -24
- package/dist/runtime/envelopeRouting.d.ts +2 -1
- package/dist/runtime/envelopeRouting.js +80 -13
- package/dist/runtime/pipelineRunner.d.ts +3 -0
- package/dist/runtime/pipelineRunner.js +15 -1
- package/dist/runtime/pipelineScheduler.d.ts +8 -2
- package/dist/runtime/pipelineScheduler.js +215 -39
- package/dist/runtime/reloadRunCatalog.d.ts +5 -0
- package/dist/runtime/reloadRunCatalog.js +44 -0
- package/dist/runtime/resumeReconstruct.d.ts +1 -0
- package/dist/runtime/resumeReconstruct.js +31 -10
- package/dist/runtime/runChangeBus.d.ts +18 -0
- package/dist/runtime/runChangeBus.js +83 -0
- package/dist/runtime/runManager.d.ts +4 -0
- package/dist/runtime/runManager.js +36 -16
- package/dist/runtime/settingsFile.d.ts +12 -0
- package/dist/runtime/settingsFile.js +87 -6
- package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
- package/dist/runtime/stageAttemptBootstrap.js +18 -7
- package/dist/runtime/stageProcessLauncher.js +3 -0
- package/dist/runtime/stageRecovery.js +3 -1
- package/dist/runtime/stageRunner.d.ts +1 -0
- package/dist/runtime/stageRunner.js +17 -15
- package/dist/runtime/stageWorker.js +13 -10
- 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 +297 -352
- 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 +12 -1
- package/dist/tools/emitStageEnvelope.js +27 -1
- package/dist/types/envelope.d.ts +3 -0
- package/dist/types/forkChoice.d.ts +30 -0
- package/dist/types/forkChoice.js +1 -0
- package/dist/types/pipeline.d.ts +59 -2
- package/dist/types/stageflowManifest.d.ts +23 -0
- package/dist/types/stageflowManifest.js +1 -0
- package/dist/ui/assets/{index-DCsDopak.css → index-C3N7MXAC.css} +1 -1
- package/dist/ui/assets/index-CSoGDA3A.js +118 -0
- package/dist/ui/index.html +3 -2
- package/dist/ui/stageflow-icon.svg +12 -0
- package/package.json +8 -5
- package/skills/install-suite.sh +107 -0
- package/skills/stageflow/SKILL.md +26 -0
- package/skills/stageflow/references/control-surface.md +26 -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 +151 -0
- package/skills/stageflow-run/references/mcp-call.md +17 -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-Cry0Tpfx.js +0 -118
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Catalog authoring
|
|
2
|
+
|
|
3
|
+
Write pipeline and stage YAML with the native Write tool. Confirm with `sf validate --pipeline <path> --strict`. Do not call `createPipeline` / `createStage` or any MCP catalog-write tool. `stageflow-author` is expected to follow this same shape; this file does not define that skill.
|
|
4
|
+
|
|
5
|
+
## Catalog roots
|
|
6
|
+
|
|
7
|
+
Read `stageflow.yaml` at the project root (git top-level when `git rev-parse --show-toplevel` succeeds, otherwise the current directory). Write new files under a declared `catalog.pipelines` root. A new subdirectory under an existing root is enough — catalog scan is recursive. If the manifest is missing or unreadable, stop and name `stageflow-setup`. Do not scaffold the manifest.
|
|
8
|
+
|
|
9
|
+
Resolve collision-safe ids with `scripts/resolve-catalog-id.mjs` (`--text`, `--dir` = the write directory, `--kind pipeline|stage`). Print is `{"id":"..."}`.
|
|
10
|
+
|
|
11
|
+
## External stage file
|
|
12
|
+
|
|
13
|
+
Required fields: `id`, `system_prompt`, `model`. Filename stem must match `id` (`research.yaml` → `id: research`).
|
|
14
|
+
|
|
15
|
+
Every `system_prompt` ends with a mandatory `emit_stage_envelope` footer, for example:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
When finished, call emit_stage_envelope once with status, summary, artifacts,
|
|
19
|
+
and a payload the next stage can use.
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Omit `gate_kinds`. A human-review checkpoint from the source session stays as prose in the stage approach, not a HITL gate.
|
|
23
|
+
|
|
24
|
+
Worked example: `assets/example-pipeline/research.yaml` and `assets/example-pipeline/implement.yaml`.
|
|
25
|
+
|
|
26
|
+
## Pipeline file
|
|
27
|
+
|
|
28
|
+
`id` plus `stages:` entries with `id`, `uses:` (path relative to the pipeline file), and `needs:` for every non-root stage. Linear chain: each stage `needs` the previous id.
|
|
29
|
+
|
|
30
|
+
Worked example: `assets/example-pipeline/example.pipeline.yaml`.
|
|
31
|
+
|
|
32
|
+
Write stages and the pipeline only. `stageflow-run` owns the throwaway task at run time.
|
|
33
|
+
|
|
34
|
+
## Validate
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
sf validate --pipeline <pipeline-path> --strict
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
On failure, delete the files this invocation wrote and report the finding. Do not leave a half-written catalog.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Transcript sources
|
|
2
|
+
|
|
3
|
+
Best-effort lookup for a past session when the human did not pass a file or paste. Current-session capture uses in-context history and does not read a file.
|
|
4
|
+
|
|
5
|
+
## Probe (one pass)
|
|
6
|
+
|
|
7
|
+
`scripts/locate-session-transcript.mjs` walks these stores and returns the newest `.jsonl` by mtime.
|
|
8
|
+
|
|
9
|
+
| Source | Path under `--home` | Scope |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Claude Code | `.claude/projects/<cwd-encoded>/**/*.jsonl` | cwd-encoded: replace `/`, `\`, and `:` in `--cwd` with `-` |
|
|
12
|
+
| Codex | `.codex/sessions/**/rollout-*.jsonl` | global date tree (`YYYY/MM/DD`) |
|
|
13
|
+
| Pi | `.pi/agent/sessions/**/*.jsonl` | global |
|
|
14
|
+
|
|
15
|
+
`--path <file>` skips probing: readable file → `{"ok":true,"path":"...","source":"explicit"}`; otherwise `{"ok":false,"reason":"unreadable"}`.
|
|
16
|
+
|
|
17
|
+
No match → `{"ok":false,"reason":"not_found"}`. Show a found path to the human before extracting.
|
|
18
|
+
|
|
19
|
+
## Unsupported stores
|
|
20
|
+
|
|
21
|
+
Cursor (`~/.cursor/chats/**/store.db`) and OpenCode (`~/.local/share/opencode/opencode.db`) are undocumented SQLite. Do not open or parse them. Those harnesses resolve as `not_found` unless the human points at a file or pastes text.
|
|
22
|
+
|
|
23
|
+
## Point-or-paste
|
|
24
|
+
|
|
25
|
+
When lookup returns `not_found` or `unreadable`, ask the human for a file path or pasted history and use that directly.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
function parseArgs(argv) {
|
|
6
|
+
let sfBin = "sf";
|
|
7
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
8
|
+
const arg = argv[i];
|
|
9
|
+
if (arg === "--sf-bin") {
|
|
10
|
+
const value = argv[i + 1];
|
|
11
|
+
if (!value) {
|
|
12
|
+
console.error("check-provider-gate: --sf-bin requires a path");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
sfBin = value;
|
|
16
|
+
i += 1;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return { sfBin };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function print(payload, code) {
|
|
23
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
24
|
+
process.exit(code);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { sfBin } = parseArgs(process.argv.slice(2));
|
|
28
|
+
const result = spawnSync(sfBin, ["providers", "status"], { encoding: "utf8" });
|
|
29
|
+
|
|
30
|
+
if (result.error || result.status === null) {
|
|
31
|
+
print({ ok: false, reason: "sf_not_found" }, 1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const lines = (result.stdout ?? "").split(/\r?\n/);
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
if (line.length === 0) continue;
|
|
37
|
+
const fields = line.split("\t");
|
|
38
|
+
if (fields[1] === "configured") {
|
|
39
|
+
print({ ok: true }, 0);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
print({ ok: false, reason: "no_provider_configured" }, 1);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { accessSync, constants, readdirSync, statSync } from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
function parseArgs(argv) {
|
|
8
|
+
let explicitPath;
|
|
9
|
+
let home = os.homedir();
|
|
10
|
+
let cwd = process.cwd();
|
|
11
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
12
|
+
const arg = argv[i];
|
|
13
|
+
const value = argv[i + 1];
|
|
14
|
+
if (arg === "--path") {
|
|
15
|
+
if (!value) {
|
|
16
|
+
console.error("locate-session-transcript: --path requires a file");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
explicitPath = value;
|
|
20
|
+
i += 1;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (arg === "--home") {
|
|
24
|
+
if (!value) {
|
|
25
|
+
console.error("locate-session-transcript: --home requires a directory");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
home = value;
|
|
29
|
+
i += 1;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (arg === "--cwd") {
|
|
33
|
+
if (!value) {
|
|
34
|
+
console.error("locate-session-transcript: --cwd requires a directory");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
cwd = value;
|
|
38
|
+
i += 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { explicitPath, home, cwd };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function print(payload, code) {
|
|
45
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
46
|
+
process.exit(code);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isReadableFile(filePath) {
|
|
50
|
+
try {
|
|
51
|
+
accessSync(filePath, constants.R_OK);
|
|
52
|
+
return statSync(filePath).isFile();
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function encodeClaudeProject(cwd) {
|
|
59
|
+
return cwd.replace(/[/\\:]/g, "-");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function walkFiles(dir) {
|
|
63
|
+
let entries;
|
|
64
|
+
try {
|
|
65
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const files = [];
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
const full = path.join(dir, entry.name);
|
|
72
|
+
if (entry.isDirectory()) {
|
|
73
|
+
files.push(...walkFiles(full));
|
|
74
|
+
} else if (entry.isFile()) {
|
|
75
|
+
files.push(full);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return files;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function newestMatch(dir, accept) {
|
|
82
|
+
let best;
|
|
83
|
+
for (const filePath of walkFiles(dir)) {
|
|
84
|
+
if (!accept(filePath)) continue;
|
|
85
|
+
let mtimeMs;
|
|
86
|
+
try {
|
|
87
|
+
mtimeMs = statSync(filePath).mtimeMs;
|
|
88
|
+
} catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!best || mtimeMs > best.mtimeMs) {
|
|
92
|
+
best = { path: filePath, mtimeMs };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return best;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { explicitPath, home, cwd } = parseArgs(process.argv.slice(2));
|
|
99
|
+
|
|
100
|
+
if (explicitPath !== undefined) {
|
|
101
|
+
if (!isReadableFile(explicitPath)) {
|
|
102
|
+
print({ ok: false, reason: "unreadable" }, 1);
|
|
103
|
+
}
|
|
104
|
+
print({ ok: true, path: explicitPath, source: "explicit" }, 0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const probes = [
|
|
108
|
+
{
|
|
109
|
+
source: "claude-code",
|
|
110
|
+
dir: path.join(home, ".claude", "projects", encodeClaudeProject(cwd)),
|
|
111
|
+
accept: (filePath) => filePath.endsWith(".jsonl"),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
source: "codex",
|
|
115
|
+
dir: path.join(home, ".codex", "sessions"),
|
|
116
|
+
accept: (filePath) => {
|
|
117
|
+
const base = path.basename(filePath);
|
|
118
|
+
return base.startsWith("rollout-") && base.endsWith(".jsonl");
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
source: "pi",
|
|
123
|
+
dir: path.join(home, ".pi", "agent", "sessions"),
|
|
124
|
+
accept: (filePath) => filePath.endsWith(".jsonl"),
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
let winner;
|
|
129
|
+
for (const probe of probes) {
|
|
130
|
+
const match = newestMatch(probe.dir, probe.accept);
|
|
131
|
+
if (!match) continue;
|
|
132
|
+
if (!winner || match.mtimeMs > winner.mtimeMs) {
|
|
133
|
+
winner = { path: match.path, mtimeMs: match.mtimeMs, source: probe.source };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!winner) {
|
|
138
|
+
print({ ok: false, reason: "not_found" }, 1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
print({ ok: true, path: winner.path, source: winner.source }, 0);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
const ID_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
7
|
+
const TOP_LEVEL_ID = /^id:\s*(?:"([^"]+)"|'([^']+)'|([^\s#]+))\s*(?:#.*)?$/m;
|
|
8
|
+
const KINDS = new Set(["pipeline", "stage"]);
|
|
9
|
+
const MAX_LEN = 64;
|
|
10
|
+
|
|
11
|
+
function parseArgs(argv) {
|
|
12
|
+
let text;
|
|
13
|
+
let dir;
|
|
14
|
+
let kind;
|
|
15
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
16
|
+
const arg = argv[i];
|
|
17
|
+
const value = argv[i + 1];
|
|
18
|
+
if (arg === "--text") {
|
|
19
|
+
text = value;
|
|
20
|
+
i += 1;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (arg === "--dir") {
|
|
24
|
+
dir = value;
|
|
25
|
+
i += 1;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (arg === "--kind") {
|
|
29
|
+
kind = value;
|
|
30
|
+
i += 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { text, dir, kind };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function trimToMax(value) {
|
|
37
|
+
let out = value;
|
|
38
|
+
if (out.length > MAX_LEN) {
|
|
39
|
+
out = out.slice(0, MAX_LEN);
|
|
40
|
+
}
|
|
41
|
+
out = out.replace(/-+$/g, "");
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function slugify(text) {
|
|
46
|
+
let slug = text
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
49
|
+
.replace(/-+/g, "-")
|
|
50
|
+
.replace(/^-+|-+$/g, "");
|
|
51
|
+
if (/^[0-9]/.test(slug)) {
|
|
52
|
+
slug = `x${slug}`;
|
|
53
|
+
}
|
|
54
|
+
if (!slug) {
|
|
55
|
+
slug = "x";
|
|
56
|
+
}
|
|
57
|
+
slug = trimToMax(slug);
|
|
58
|
+
if (!ID_PATTERN.test(slug) || slug.length === 0) {
|
|
59
|
+
slug = "x";
|
|
60
|
+
}
|
|
61
|
+
return slug;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function readTopLevelId(filePath) {
|
|
65
|
+
let raw;
|
|
66
|
+
try {
|
|
67
|
+
raw = readFileSync(filePath, "utf8");
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const match = raw.match(TOP_LEVEL_ID);
|
|
72
|
+
if (!match) return null;
|
|
73
|
+
return match[1] ?? match[2] ?? match[3] ?? null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function existingIds(dir) {
|
|
77
|
+
const ids = new Set();
|
|
78
|
+
let entries;
|
|
79
|
+
try {
|
|
80
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
81
|
+
} catch {
|
|
82
|
+
return ids;
|
|
83
|
+
}
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if (!entry.isFile()) continue;
|
|
86
|
+
const name = entry.name;
|
|
87
|
+
if (!name.endsWith(".yaml") && !name.endsWith(".yml")) continue;
|
|
88
|
+
const id = readTopLevelId(path.join(dir, name));
|
|
89
|
+
if (id) ids.add(id);
|
|
90
|
+
}
|
|
91
|
+
return ids;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function uniqueId(base, taken) {
|
|
95
|
+
if (!taken.has(base)) return base;
|
|
96
|
+
for (let n = 2; n < 10_000; n += 1) {
|
|
97
|
+
const suffix = `-${n}`;
|
|
98
|
+
const candidate = trimToMax(`${base.slice(0, MAX_LEN - suffix.length)}${suffix}`);
|
|
99
|
+
if (ID_PATTERN.test(candidate) && !taken.has(candidate)) {
|
|
100
|
+
return candidate;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return `${base}-x`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const { text, dir, kind } = parseArgs(process.argv.slice(2));
|
|
107
|
+
|
|
108
|
+
if (!KINDS.has(kind)) {
|
|
109
|
+
console.error("resolve-catalog-id: --kind must be pipeline or stage");
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (typeof text !== "string" || text.length === 0 || typeof dir !== "string") {
|
|
114
|
+
console.error("resolve-catalog-id: --text, --dir, and --kind are required");
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const id = uniqueId(slugify(text), existingIds(dir));
|
|
119
|
+
process.stdout.write(`${JSON.stringify({ id })}\n`);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stageflow-setup
|
|
3
|
+
description: >-
|
|
4
|
+
Installs Stageflow, scaffolds a starter catalog with sf init, then hard-stops
|
|
5
|
+
for provider login. Use when this project has no Stageflow install, sf is not
|
|
6
|
+
found, there is no catalog, first time running Stageflow here, the user asks
|
|
7
|
+
to set up Stageflow, or a provider is not logged in. Install, scaffold, and
|
|
8
|
+
provider-gate only — does not author pipelines or stages, capture a session,
|
|
9
|
+
or manage an MCP host.
|
|
10
|
+
disable-model-invocation: true
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Stageflow setup
|
|
14
|
+
|
|
15
|
+
Install the CLI, scaffold a catalog, then hard-stop until a provider is configured. Report `ready to author` or `blocked: <reason>`.
|
|
16
|
+
|
|
17
|
+
Author pipelines and stages with `stageflow-author`. Capture a chat or past session with `stageflow-session-capture`. Start, watch, or answer a run with `stageflow-run`. Codify a repeating pattern with `stageflow-delegate`.
|
|
18
|
+
|
|
19
|
+
This job does not author catalog YAML, capture a session, or manage an MCP host. Its surface is the `sf` CLI. Talking jobs cite [`../stageflow/references/control-surface.md`](../stageflow/references/control-surface.md) for MCP-vs-CLI; do not run the host probe in that file from this job.
|
|
20
|
+
|
|
21
|
+
## Detect-before-mutate
|
|
22
|
+
|
|
23
|
+
Re-derive readiness every invocation. Do not write a setup-complete marker.
|
|
24
|
+
|
|
25
|
+
Set `SF=sf`. After an EACCES fallback this invocation, set `SF='npx stageflow'` and use that prefix for every later CLI call.
|
|
26
|
+
|
|
27
|
+
Probe, in order:
|
|
28
|
+
|
|
29
|
+
1. `sf --version` — success means the CLI is on PATH. (After EACCES this invocation, use `$SF --version` instead.)
|
|
30
|
+
2. `stageflow.yaml` exists at the catalog root: git top-level when `git rev-parse --show-toplevel` succeeds, otherwise the current directory.
|
|
31
|
+
3. `$SF providers status` — a provider counts only when a row's second column is `configured`.
|
|
32
|
+
|
|
33
|
+
**Done when all three pass:** print `ready to author` and stop. Do not run `npm i -g stageflow` or `sf init`.
|
|
34
|
+
|
|
35
|
+
If any probe fails, repair only the failed phases below, in order. A failure prints `blocked: <reason>` and stops — do not guess the next phase.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
Skip this phase when probe 1 passed.
|
|
40
|
+
|
|
41
|
+
1. Run `node --version`. **Done when** the major version is `>=20`. If Node is missing or older, stop:
|
|
42
|
+
|
|
43
|
+
`blocked: node: Node >=20 is required. Install Node 20 or newer, then re-invoke stageflow-setup.`
|
|
44
|
+
|
|
45
|
+
Do not install or switch Node versions.
|
|
46
|
+
|
|
47
|
+
2. Run `npm i -g stageflow`. Never `sudo`.
|
|
48
|
+
|
|
49
|
+
- Success → keep `SF=sf`. **Done when** `sf --version` prints a version.
|
|
50
|
+
- `EACCES` or a permission error → set `SF='npx stageflow'`. Use that prefix for the rest of this invocation only. Tell the operator a later invocation will re-check the global install. Then continue to Scaffold.
|
|
51
|
+
- Any other error (network, registry) → stop. Do not fall back to `npx`.
|
|
52
|
+
|
|
53
|
+
`blocked: install: npm i -g stageflow failed. Command: npm i -g stageflow. Error: <verbatim>`
|
|
54
|
+
|
|
55
|
+
## Scaffold
|
|
56
|
+
|
|
57
|
+
Skip `$SF init` when probe 2 passed.
|
|
58
|
+
|
|
59
|
+
1. From the consumer project directory, run `$SF init` only when `stageflow.yaml` is missing. `sf init` creates `stageflow.yaml`, `pipelines/hello.pipeline.yaml`, and `tasks/hello.task.yaml`, and skips any path that already exists. Create catalog files only with this command.
|
|
60
|
+
|
|
61
|
+
2. After `$SF init`, run `$SF validate --strict`. Print findings verbatim. **Done when** exit code is `0`. If it fails:
|
|
62
|
+
|
|
63
|
+
`blocked: scaffold: sf validate --strict failed. <verbatim findings>`
|
|
64
|
+
|
|
65
|
+
3. If `git rev-parse --show-toplevel` fails after a create, tell the operator the catalog is not git-tracked. Do not run `git init`.
|
|
66
|
+
|
|
67
|
+
## Provider hard-stop
|
|
68
|
+
|
|
69
|
+
Run this phase only after Install and Scaffold have succeeded or were already satisfied. Never print `ready to author` while no provider is `configured`.
|
|
70
|
+
|
|
71
|
+
1. Run `$SF providers status`. **Done when** any row is `configured` — go to Completion. If a non-interactive login just succeeded, name that provider id.
|
|
72
|
+
|
|
73
|
+
2. If none are configured, run `$SF providers list`. For each id (first column), test whether the environment has `<ID>_API_KEY` with `ID` uppercased (`anthropic` → `ANTHROPIC_API_KEY`). Test presence only — do not read or print the value.
|
|
74
|
+
|
|
75
|
+
3. On the first match, run:
|
|
76
|
+
|
|
77
|
+
`$SF providers login <id> --type api_key --api-key-env <VAR>`
|
|
78
|
+
|
|
79
|
+
Re-run `$SF providers status`. If that id is `configured`, go to Completion and name the provider. If the login fails, print the error verbatim, then continue to the walkthrough.
|
|
80
|
+
|
|
81
|
+
4. On no match, a failed login, or a session that cannot complete OAuth (no attached terminal, CI-shaped), stop. Print the commands; do not start a browser or device flow.
|
|
82
|
+
|
|
83
|
+
`blocked: provider: no provider configured.`
|
|
84
|
+
|
|
85
|
+
Then print these commands for the human (substitute real ids from `list`):
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
$SF providers list
|
|
89
|
+
$SF providers login <id> --type api_key --api-key-env <ID>_API_KEY
|
|
90
|
+
$SF providers login <id> --type oauth
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Point at `docs/providers.md` for `pi_home` vs `sf_owned`. Leave the storage mode to the human.
|
|
94
|
+
|
|
95
|
+
## Completion
|
|
96
|
+
|
|
97
|
+
First line is exactly one of:
|
|
98
|
+
|
|
99
|
+
- `ready to author` — CLI present, catalog present, at least one provider `configured`.
|
|
100
|
+
- `blocked: <reason>` — name the phase (`node`, `install`, `scaffold`, or `provider`) and the next command.
|
|
101
|
+
|
|
102
|
+
If a non-interactive login just wired a provider, add a following line:
|
|
103
|
+
|
|
104
|
+
`provider: <id>`
|
|
105
|
+
|
|
106
|
+
If this invocation used the `npx` fallback, add a following line:
|
|
107
|
+
|
|
108
|
+
`CLI: npx stageflow (EACCES on npm i -g stageflow; this session only — a later invocation will re-check the global install)`
|
|
109
|
+
|
|
110
|
+
A later job calls this skill as preflight. It does not re-implement the provider gate.
|