-
Notifications
You must be signed in to change notification settings - Fork 1
Research/spok run cli #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
0xjgv
wants to merge
2
commits into
main
Choose a base branch
from
research/spok-run-cli
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,171 +1,43 @@ | ||
| --- | ||
| name: spok-flow | ||
| description: end-to-end problem validation → research → design → plan → implement → review → commit workflow for a single chunk, with native or hybrid model routing and an optional post-commit self-learn gate. Driven by spok-apply. | ||
| description: end-to-end problem validation → research → design → plan → implement → review → commit workflow for a single chunk, executed by delegating the whole loop to the `spok run` CLI command, with native or hybrid model routing. Driven by spok-apply. | ||
| argument-hint: [hybrid] <task-dir> (absolute path to a pre-staged chunk directory containing ticket.md) | ||
| version: 0.9.0 | ||
| version: 1.0.0 | ||
| --- | ||
| # Flow Instructions | ||
|
|
||
| ## 0. Receive Pre-Staged Task Directory | ||
| ## 1. Receive the task directory | ||
|
|
||
| `spok-apply` has already created the task directory and written `ticket.md` for the chunk to execute. The argument to this skill is the **absolute path** to that directory (e.g. `spok/changes/<change-slug>/.flow/<chunk-slug>/`). A leading `hybrid` token selects the built-in Claude + Codex execution profile; remove that token before resolving the task directory. | ||
| `spok-apply` has already created the task directory and written `ticket.md` for the chunk to execute. The argument to this skill is the **absolute path** to that directory (e.g. `spok/changes/<change-slug>/.flow/<chunk-slug>/`). A leading `hybrid` token selects the built-in Claude + Codex execution profile; remove that token before resolving the path. | ||
|
|
||
| > Verify the directory exists and contains `ticket.md` using the **Read** tool. Do NOT recreate the directory or overwrite `ticket.md`. | ||
| Verify the directory exists and contains `ticket.md` using the **Read** tool. If `ticket.md` is missing, halt and report back — `spok-apply` is responsible for staging it. Do NOT recreate the directory or overwrite `ticket.md`. | ||
|
|
||
| If `ticket.md` is missing, halt and report back — `spok-apply` is responsible for staging it. | ||
| ## 2. Run the flow | ||
|
|
||
| ## 1. Deterministic Control Loop | ||
|
|
||
| The `spok` CLI owns the inner flow sequence and resume state. Do not choose, skip, reorder, or rename steps yourself. | ||
|
|
||
| For a hybrid invocation, prefix every `spok flow status`, `spok flow next`, and | ||
| `spok flow complete` command with `SPOK_FLOW_PROFILE=hybrid`. For a default | ||
| invocation, run the commands without that environment variable. An existing | ||
| workflow state owns its persisted profile; if the requested profile conflicts, | ||
| surface the CLI blocker exactly. | ||
|
|
||
| Run: | ||
| Execute the flow in the foreground with the host's shell tool: | ||
|
|
||
| ```bash | ||
| spok flow status "<task-dir>" --json | ||
| spok run "<task-dir>" --json | ||
| ``` | ||
|
|
||
| Hybrid equivalent: | ||
| When the `hybrid` token was present, append `--profile hybrid`: | ||
|
|
||
| ```bash | ||
| SPOK_FLOW_PROFILE=hybrid spok flow status "<task-dir>" --json | ||
| spok run "<task-dir>" --json --profile hybrid | ||
| ``` | ||
|
|
||
| If it returns `state: "blocked"`, halt and report the `reason` exactly, applying the design-review clause in step 6 when it matches. | ||
|
|
||
| Then repeat this loop until the CLI returns `state: "complete"`: | ||
|
|
||
| 1. Run: | ||
|
|
||
| ```bash | ||
| spok flow next "<task-dir>" --json | ||
| ``` | ||
|
|
||
| For hybrid execution, use: | ||
|
|
||
| ```bash | ||
| SPOK_FLOW_PROFILE=hybrid spok flow next "<task-dir>" --json | ||
| ``` | ||
|
|
||
| 2. If `next` returns `state: "blocked"`, halt and report the `reason` exactly, applying the design-review clause in step 6 when it matches. If it returns `state: "complete"`, return success to `spok-apply`. | ||
|
|
||
| 3. Read the returned `step` object: | ||
| - `id` is the workflow step id. | ||
| - `skill` is the exact skill to invoke. | ||
| - `runner` is the exact tool that must execute the step: `claude` or `codex`. | ||
| - `model` is the exact model to pass to that runner. | ||
| - `effort` is present when the step carries a reasoning-effort hint; relay it to the selected runner when present. | ||
| - `argument` is the exact argument to pass to that skill. | ||
| - `expectedOutput` is present for file-producing steps. | ||
| - `prompt` is the **complete subagent prompt**, composed by the CLI. It already | ||
| carries the skill invocation, the return contract, any step-specific clause, | ||
| and the repository rules from `spok/MEMORY.md`. Do not rewrite, summarize, or | ||
| add to it. | ||
|
|
||
| If the response carries `memoryWarning` or `workRootWarning`, surface it to the user once and continue. | ||
|
|
||
| 4. Dispatch the step through `step.runner`. | ||
|
|
||
| Detect the active harness once: a non-empty `CODEX_HOME` means `codex`; | ||
| otherwise it is `claude`. | ||
|
|
||
| - When `step.runner` matches the active harness, delegate in the foreground | ||
| through the current host's native subagent mechanism to the | ||
| host-owned `general-purpose` agent. Pass `model: <step.model>`, (when | ||
| present) `effort: <step.effort>`, and `<step.prompt>` **verbatim** as the | ||
| prompt. | ||
| - When `step.runner` is `codex` from another harness, first verify `codex` is | ||
| on `PATH`, then run `codex exec` sequentially in the foreground. Use | ||
| `--ephemeral`, `--dangerously-bypass-hook-trust`, `--cd <project-root>`, | ||
| `--model <step.model>`, `--sandbox workspace-write`, and, when | ||
| `step.effort` is present, `-c model_reasoning_effort="<step.effort>"`. | ||
| Pass `<step.prompt>` **verbatim** on stdin with `-`; do not interpolate it | ||
| into a shell command. | ||
| - When `step.runner` is `claude` from another harness, first verify `claude` | ||
| is on `PATH`, then run `claude -p` sequentially in the foreground. Use | ||
| `--no-session-persistence`, `--model <step.model>`, | ||
| `--permission-mode auto`, text output, and, when `step.effort` is present, | ||
| `--effort <step.effort>`. Pass `<step.prompt>` **verbatim** on stdin; do not | ||
| interpolate it into a shell command. | ||
|
|
||
| Resolve `<project-root>` with `git -C "<task-dir>" rev-parse --show-toplevel`. | ||
| Use `--dangerously-bypass-hook-trust` to run enabled hooks without an | ||
| interactive trust prompt; it does not enable disabled hooks or relax the | ||
| sandbox. Do not use `--dangerously-bypass-approvals-and-sandbox`. If the | ||
| executable is missing, authentication fails, or the child exits nonzero, | ||
| report the error and halt. Do not call `spok flow complete`; leaving the | ||
| current step ready makes the run safely resumable after the tool is fixed. | ||
|
|
||
| Run every path **sequentially in the foreground** because each step depends | ||
| on the previous step's validated artifact or recorded result. Do not invoke | ||
| the step skill inline: process isolation keeps each step's context bounded. | ||
|
|
||
| 5. Record completion with the CLI. | ||
|
|
||
| A `--summary` is recorded permanently in `workflow-state.json`. Do not relay a verification claim that cannot be attributed to a command that ran during the step. If the subagent reports "lint clean" or "tests pass" without naming the command it ran, drop the claim from the summary rather than passing it through — the CLI checks only that the summary is non-empty, so you are the last check on it. | ||
| - File-producing steps (the CLI verifies `expectedOutput` exists and is non-empty): | ||
|
|
||
| ```bash | ||
| spok flow complete "<task-dir>" --step "<id>" --json | ||
| ``` | ||
|
|
||
| Prefix this command with `SPOK_FLOW_PROFILE=hybrid` for a hybrid run, as | ||
| described above. | ||
|
|
||
| - `validate` additionally has its recorded verdict read from `validation.md` by the CLI: `PASS` completes the step. A `FAIL` with repair attempts remaining is a *successful* completion that routes to a `repair` step and then back to `validate` — the CLI may return `validate` more than once; complete every occurrence with the same bare `--step validate`. When repair attempts are exhausted and the verdict is still `FAIL`, `complete` (and subsequent `next`/`status`) return `state: "blocked"` with an exhausted-repair reason: report it exactly as returned — do not retry the step and do not edit `validation.md` to unblock it. An unreadable verdict blocks as before. | ||
|
|
||
| - `implement`, `simplify`, and `repair` (dispatched like any other step when the CLI returns it): | ||
|
|
||
| ```bash | ||
| spok flow complete "<task-dir>" --step "<id>" --summary "<summary>" --json | ||
| ``` | ||
|
|
||
| The `implement` prompt requires the subagent to end its reply with a | ||
| `Work root: <absolute path>` line naming the repository it edited. Pass that | ||
| path through so the commit step is told where the changes live: | ||
|
|
||
| ```bash | ||
| spok flow complete "<task-dir>" --step "implement" --summary "<summary>" --work-root "<absolute-path>" --json | ||
| ``` | ||
|
|
||
| Pass `--work-root` on `simplify` or `repair` too when that subagent reports a | ||
| different repository. If the subagent reported no work root, omit the flag — | ||
| the CLI degrades to unsteered commit discovery and warns. Never invent the | ||
| path. A path that is relative or does not exist blocks the completion. | ||
|
|
||
| - `commit`: | ||
|
|
||
| ```bash | ||
| spok flow complete "<task-dir>" --step "commit" --commit "<commit-sha>" --summary "<summary>" --json | ||
| ``` | ||
|
|
||
| When a work root was recorded, the CLI resolves the SHA in that repository | ||
| and blocks when it is not a commit reachable from `HEAD` there. Report such | ||
| a block exactly; do not retry with a different SHA. | ||
|
|
||
| - `self-learn` is an optional file-producing advisory gate returned only when | ||
| project config enables `flow.self_learn: true`. Complete it like any other | ||
| file-producing step. Its findings do not fail or amend the commit. | ||
| Do not background the command and do not run anything in parallel with it. Harness progress streams on stderr; stdout carries one JSONL event per line, each with `schemaVersion: 1`. | ||
|
|
||
| 6. If `complete` returns `state: "blocked"`, halt and report the `reason` exactly. When the `reason` starts with `Step design-review recorded a FAIL verdict`, read `<task-dir>/design-review.md` and present its `## Human Decisions Required` section verbatim alongside the reason, then halt — do not retry the step and do not edit `design-review.md` to unblock it. When the `reason` starts with `Step design-review has no readable verdict`, read `<task-dir>/design-review.md` if it exists and present its `## Human Decisions Required` section verbatim alongside the reason if that section is present, then halt and report as a generic blocked state. | ||
| ## 3. Relay the outcome | ||
|
|
||
| Do not restate or assume the step order — `spok flow next` is the only source of truth. | ||
| Do not derive or override runner or model routing inside this skill — `spok flow next --json` is the source of truth, including `step.runner`, `step.model`, and `step.effort`. | ||
| In plain terms: spok flow next --json is the source of truth for model routing. | ||
| Step-specific instructions — including the `implement` no-commit rule — are composed | ||
| into `step.prompt` by the CLI. Do not restate them. | ||
| Report the run's result from its exit code and final JSONL event. Relay what the CLI reported; never invent or soften an outcome. | ||
|
|
||
| <guidance> | ||
| ## Important guidelines | ||
| - **Exit 0** (`complete` event): report success to `spok-apply`, including the recorded commit when the event carries a `commit`. | ||
| - **Exit 2** (`blocked` event): report the event's `reason` verbatim. When the event carries `humanDecisions`, present that `## Human Decisions Required` content verbatim alongside the reason. Do not retry, and do not edit artifacts to unblock the flow. | ||
| - **Exit 3** (`step_failed` event): report the failure. Re-invoking the same command resumes at the same step. | ||
| - **Exit 1 or a signal code (130/143)**: report the error or cancellation verbatim. | ||
| - **A `warning` event**, at any point in the stream: surface its `message` to the user once and continue; a warning does not stop the run. | ||
|
|
||
| - Raise questions or concerns about objectives, design, or plan to the user at any time using the **AskUserQuestion** tool. | ||
| - Run step subagents **sequentially in the foreground** because each step depends on the previous step's validated artifact or recorded result. | ||
| - Let `spok flow next` choose the next step. Let `spok flow complete` validate step completion. | ||
| - Use a **TaskList** to track the steps and their status. | ||
| ## Guardrails | ||
|
|
||
| </guidance> | ||
| `spok run` owns all execution semantics: step order, routing, models, prompt composition, harness dispatch, and completion recording. Do not choose, skip, reorder, or re-route steps; do not drive the flow state machine directly; do not construct harness invocations; do not restate routing, models, or step prompts. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -18,12 +18,15 @@ import { | |||||
| flowCompleteCommand, | ||||||
| flowNextCommand, | ||||||
| flowStatusCommand, | ||||||
| isFlowProfile, | ||||||
| runCommand, | ||||||
| DEFAULT_SCHEMA, | ||||||
| type StatusOptions, | ||||||
| type InstructionsOptions, | ||||||
| type NewChangeOptions, | ||||||
| type FlowCommandOptions, | ||||||
| type FlowCompleteCommandOptions, | ||||||
| type RunCommandOptions, | ||||||
| } from '../commands/workflow/index.js'; | ||||||
| import { maybeShowTelemetryNotice, trackCommand, trackTelemetryEvent, shutdown } from '../telemetry/index.js'; | ||||||
| import { collectCliSignals } from './signals.js'; | ||||||
|
|
@@ -97,6 +100,9 @@ const COMMAND_VISIBILITY: Record<string, CommandVisibility> = { | |||||
| instructions: 'skill', | ||||||
| new: 'skill', | ||||||
| 'new change': 'skill', | ||||||
| // Documented under User-Facing Commands in docs/cli.md; humans and external | ||||||
| // orchestrators invoke it directly, not only skills. | ||||||
| run: 'user', | ||||||
| flow: 'internal', | ||||||
| 'flow status': 'internal', | ||||||
| 'flow next': 'internal', | ||||||
|
|
@@ -592,6 +598,31 @@ flowCmd | |||||
| } | ||||||
| }); | ||||||
|
|
||||||
| program | ||||||
| .command('run <task-dir>') | ||||||
| .description('Drive the deterministic flow to completion by dispatching each step to its harness') | ||||||
| .option('--profile <profile>', 'Flow profile: claude, codex, or hybrid') | ||||||
| .option('--json', 'Output as JSON') | ||||||
| .action(async (taskDir: string, options: RunCommandOptions) => { | ||||||
| // An unrecognized profile is a usage error, caught before the state machine | ||||||
| // could report it as a blocker and exit 2. | ||||||
| if (options.profile && !isFlowProfile(options.profile)) { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: When callers pass an explicitly empty Prompt for AI agents
Suggested change
|
||||||
| if (!options.json) console.log(); | ||||||
| ora().fail( | ||||||
| `Error: Invalid --profile value: ${options.profile}. Expected claude, codex, or hybrid.` | ||||||
| ); | ||||||
| process.exit(1); | ||||||
| } | ||||||
|
|
||||||
| try { | ||||||
| process.exitCode = await runCommand(taskDir, options); | ||||||
| } catch (error) { | ||||||
| if (!options.json) console.log(); | ||||||
| ora().fail(`Error: ${(error as Error).message}`); | ||||||
| process.exit(1); | ||||||
| } | ||||||
| }); | ||||||
|
|
||||||
| const newCmd = program.command('new').description('Create new items'); | ||||||
|
|
||||||
| newCmd | ||||||
|
|
||||||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: When
flow.self_learnis invalid,spok runsilently disables the setting becauseCONFIG_WARNING_COMMANDSomits the new command. Addrunto that set so the human-facing driver reports the same configuration diagnostics as the other flow commands.Prompt for AI agents