From 8074219af2ba4f59573e08eea54cff3908d2a243 Mon Sep 17 00:00:00 2001 From: Siddharth Kapoor Date: Fri, 10 Jul 2026 08:17:20 -0400 Subject: [PATCH 1/8] feat(flow): materialize run-state into local run instance Move mutable run-state out of the flow definition into a local run instance under .codevoyant/runs// (progress.md + context.md), so flow definitions stay read-only templates and global flows are no longer clobbered by concurrent or cross-project runs. - flow-dir.md: add Run instance section defining RUN_DIR - go.md: seed/read progress.md + context.md from RUN_DIR; never mutate the definition's flow.md - status.md: prefer run instance progress/Status, fall back to definition - list.md: note that live progress lives in the run instance --- skills/flow/references/flow-dir.md | 24 +++++++++++++++++++ skills/flow/references/workflows/go.md | 28 ++++++++++++++-------- skills/flow/references/workflows/list.md | 2 ++ skills/flow/references/workflows/status.md | 23 +++++++++++------- 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/skills/flow/references/flow-dir.md b/skills/flow/references/flow-dir.md index 2d29cff..0cfcb05 100644 --- a/skills/flow/references/flow-dir.md +++ b/skills/flow/references/flow-dir.md @@ -83,3 +83,27 @@ Set `FLOW_DIR` to the resolved `{FLOWS_DIR}/{slug}/` and use it for the rest of ``` FLOW_DIR = {FLOWS_DIR}/{slug}/ # global if --global, else local ``` + +## Run instance (mutable run-state — for `go`, `status`, `doctor`) + +A flow **definition** (`{FLOWS_DIR}/{slug}/` with `flow.md` + `implementation/step-N.md`) is a **read-only template**. `go` must never mutate it — mutating a *global* definition clobbers the shared template and lets concurrent/other-project runs overwrite each other's state. + +All mutable run-state lives in a **run instance**, which is **always local to the current project**, regardless of whether the definition is local or global: + +``` +.codevoyant/runs/{slug}/ + progress.md # a copy of the definition's Steps checklist; the ONLY place [ ] → [x] is flipped + context.md # the accumulating handoff log (persisted for resume) +``` + +Resolve it the same way in every workflow that runs or inspects a flow: + +```bash +RUNS_DIR=".codevoyant/runs" # always local — never under $HOME, even for a global definition +RUN_DIR="$RUNS_DIR/{slug}" # {slug} is the resolved definition's directory name +``` + +- `{slug}` is the directory name of the resolved definition (from `FLOW_DIR`), so the run instance is stable whether the definition was found locally or globally. +- Step **implementations** are always read from the definition (`FLOW_DIR/implementation/step-N.md`), never copied into the run instance — only the checklist (`progress.md`) and the handoff log (`context.md`) are instance-local. +- Create on demand: `mkdir -p "$RUN_DIR"` (safe if it already exists). +- A run instance whose **definition is global** is expected and normal — it is not corruption or drift. diff --git a/skills/flow/references/workflows/go.md b/skills/flow/references/workflows/go.md index db394fb..56077f2 100644 --- a/skills/flow/references/workflows/go.md +++ b/skills/flow/references/workflows/go.md @@ -22,13 +22,21 @@ Build the parameter map `PARAMS`: - `input` = `INPUT` (the joined free text), if any - each `--set key=value` → `PARAMS[key] = value` -## Step 0.5: Resolve the flow location +## Step 0.5: Resolve the flow definition and local run instance -Resolve `FLOW_DIR` per `references/flow-dir.md` (local-first, then global; `--global` forces global only). If not found in any scope, error: "Flow '{FLOW_NAME}' not found (looked in local and global). Run /flow new {FLOW_NAME} first." +Resolve `FLOW_DIR` (the **definition** — read-only) per `references/flow-dir.md` (local-first, then global; `--global` forces global only). If not found in any scope, error: "Flow '{FLOW_NAME}' not found (looked in local and global). Run /flow new {FLOW_NAME} first." -## Step 1: Read flow.md and resolve parameters +Then resolve the **run instance** per `references/flow-dir.md` → *Run instance*: `RUN_DIR=".codevoyant/runs/{slug}/"` where `{slug}` is the definition's directory name. The run instance is **always local**, even when the definition is global. `mkdir -p "$RUN_DIR"`. -Read `FLOW_DIR/flow.md`. Parse the Steps checklist and the Parameters section. +**The definition is a template — this workflow never writes to `FLOW_DIR`.** All mutable run-state (`progress.md`, `context.md`) is written under `RUN_DIR` only. + +## Step 1: Read the definition, seed the run instance, resolve parameters + +Read the **definition** `FLOW_DIR/flow.md` (read-only). Parse the Steps checklist and the Parameters section. + +**Seed the run instance's progress file.** The mutable checklist lives at `RUN_DIR/progress.md`, never in the definition: +- **First run** (`RUN_DIR/progress.md` does not exist): copy the definition's `## Steps` checklist verbatim into `RUN_DIR/progress.md` (keep the `N. [ ] {{placeholder}}` lines exactly, all unchecked). Prepend a one-line header `# Run progress: {slug}` and a `Status: Active` line so `status` and `doctor` can read it. +- **Resume** (`RUN_DIR/progress.md` exists): use it as the source of truth for which steps are already `[x]` — do NOT re-seed from the definition. **Resolve every `{{placeholder}}` used anywhere in the steps:** 1. Scan all step commands for `{{name}}` tokens; collect the unique set `NEEDED`. @@ -37,9 +45,9 @@ Read `FLOW_DIR/flow.md`. Parse the Steps checklist and the Parameters section. - Else → **prompt the user once** for the value, using AskUserQuestion (free-text via Other): question `Value for {{name}}?`, header `Parameter`, and (for `{{input}}`) show the flow's Parameters description as help. Store the answer in `PARAMS[name]`. 3. Do not proceed with any unresolved token. If the user dismisses a prompt, abort: "Cancelled — {{name}} is required to run this flow." -Collect the pending steps: all lines matching `N. [ ] ...` (unchecked), in order. If none are pending, report "Flow '{FLOW_NAME}' is already complete." and exit. +Collect the pending steps: all lines in `RUN_DIR/progress.md` matching `N. [ ] ...` (unchecked), in order. If none are pending, report "Flow '{FLOW_NAME}' is already complete." and exit. -Initialize the **flow context** accumulator `CONTEXT`. **On resume:** if `FLOW_DIR/context.md` exists (an earlier, interrupted run), load it into `CONTEXT` so the remaining steps keep the handoffs from steps already marked `[x]`. Otherwise start empty. (Without this, a resumed flow loses e.g. the PR number a completed `pr open` step produced.) +Initialize the **flow context** accumulator `CONTEXT`. **On resume:** if `RUN_DIR/context.md` exists (an earlier, interrupted run), load it into `CONTEXT` so the remaining steps keep the handoffs from steps already marked `[x]`. Otherwise start empty. (Without this, a resumed flow loses e.g. the PR number a completed `pr open` step produced.) ## Step 2: Execute steps sequentially @@ -78,19 +86,19 @@ For each pending step in order: ``` [step {N} · {RESOLVED_COMMAND}] → {handoff or one-line summary} ``` - Keep `CONTEXT` terse — it is injected into every later step, so it must stay a short bulleted log, not full transcripts. **Persist it:** write the accumulated `CONTEXT` to `FLOW_DIR/context.md` so an interrupted flow can resume with it (see Step 1). + Keep `CONTEXT` terse — it is injected into every later step, so it must stay a short bulleted log, not full transcripts. **Persist it:** write the accumulated `CONTEXT` to `RUN_DIR/context.md` (the local run instance — **never** beside the definition) so an interrupted flow can resume with it (see Step 1). -6. Update `FLOW_DIR/flow.md` — change this step's `[ ]` to `[x]` (keep the original `{{placeholder}}` text in flow.md; only the run used resolved values). Leave `Status` as `Active` until all steps complete. +6. Update `RUN_DIR/progress.md` — change this step's `[ ]` to `[x]` (keep the original `{{placeholder}}` text; only the run used resolved values). **Never modify the definition's `FLOW_DIR/flow.md`** — it stays a pristine template. Leave `Status` in `progress.md` as `Active` until all steps complete. 7. Report: `✓ Step {N} complete.` 8. Proceed to next pending step. -**On step failure:** if a subagent reports it could not complete (blocking error), stop the loop, leave the step unchecked, and report which step failed and why. `CONTEXT` is already persisted to `FLOW_DIR/context.md`, so re-running `/flow go` resumes from this step with prior context intact. Do not run later steps — they likely depend on this one's context. +**On step failure:** if a subagent reports it could not complete (blocking error), stop the loop, leave the step unchecked in `RUN_DIR/progress.md`, and report which step failed and why. `CONTEXT` is already persisted to `RUN_DIR/context.md`, so re-running `/flow go` resumes from this step with prior context intact. Do not run later steps — they likely depend on this one's context. ## Step 3: Final report -After all steps are complete, update `Status` in `FLOW_DIR/flow.md` to `Complete` and remove `FLOW_DIR/context.md` (a fresh run starts clean). +After all steps are complete, update `Status` in `RUN_DIR/progress.md` to `Complete` and remove `RUN_DIR/context.md` (a fresh run starts clean). Leave the definition's `FLOW_DIR/flow.md` untouched — it was never mutated. Report: ``` diff --git a/skills/flow/references/workflows/list.md b/skills/flow/references/workflows/list.md index dad8ff7..ead22a8 100644 --- a/skills/flow/references/workflows/list.md +++ b/skills/flow/references/workflows/list.md @@ -31,6 +31,8 @@ For each flow found, read its `flow.md` and extract: - **step counts** — `done` = number of `[x]` lines, `total` = total step lines - **parameters** — the token names from the Parameters section (or "—") +> **Note:** `list` reads step counts from each flow **definition**, so a freshly defined flow shows `0/N`. Live per-run progress is not in the definition — it lives in the local run instance `.codevoyant/runs/{slug}/progress.md` (see `references/flow-dir.md` → *Run instance*). Use `/flow status ` to see a run's actual progress. + ## Step 2: Render the table Print a table grouped by scope. If a scope has no flows, show a `(none)` row for it. diff --git a/skills/flow/references/workflows/status.md b/skills/flow/references/workflows/status.md index d6f72e5..5f13dcc 100644 --- a/skills/flow/references/workflows/status.md +++ b/skills/flow/references/workflows/status.md @@ -11,17 +11,24 @@ FLOW_NAME = first non-flag positional arg (required) If `FLOW_NAME` is missing, error: "Usage: /flow status [--global]. A flow name is required." -Resolve `FLOW_DIR` per `references/flow-dir.md` (local-first, then global; `--global` forces global). If not found in any scope, error: "Flow '{FLOW_NAME}' not found (looked in local and global). Run /flow new {FLOW_NAME} first." +Resolve the **definition** `FLOW_DIR` per `references/flow-dir.md` (local-first, then global; `--global` forces global). If not found in any scope, error: "Flow '{FLOW_NAME}' not found (looked in local and global). Run /flow new {FLOW_NAME} first." -## Step 1: Read and display flow.md +Then resolve the **run instance** per `references/flow-dir.md` → *Run instance*: `RUN_DIR=".codevoyant/runs/{slug}/"` (always local). If `RUN_DIR/progress.md` exists, this run's live checkbox/Status state lives there; otherwise the flow has not been run locally and status reflects the definition (all steps pending). -Read `FLOW_DIR/flow.md`. Extract and print: +## Step 1: Read and display flow state -1. **Flow title** (from the `# Flow: {TITLE}` heading) -2. **Scope** (local or global — from the resolved location / Metadata) -3. **Status** (from the Metadata section) -4. **Parameters** (from the Parameters section, if any) -5. **Steps checklist** — print each step line as-is, preserving `[ ]` or `[x]` markers and any `{{placeholders}}` +Read the **definition** `FLOW_DIR/flow.md` for the title, scope, and Parameters. For the live checklist and Status, prefer the **run instance**: + +- If `RUN_DIR/progress.md` exists → read the **Steps checklist** and **Status** from it (this is the current run's progress). Note `(run instance: .codevoyant/runs/{slug})` in the output. +- Otherwise → read the checklist and Status from the definition `FLOW_DIR/flow.md` (all steps pending; the flow has not been run locally). + +Extract and print: + +1. **Flow title** (from the definition's `# Flow: {TITLE}` heading) +2. **Scope** (local or global — the definition's resolved location / Metadata) +3. **Status** (from the run instance if present, else the definition's Metadata) +4. **Parameters** (from the definition's Parameters section, if any) +5. **Steps checklist** — print each step line as-is from the chosen source, preserving `[ ]` or `[x]` markers and any `{{placeholders}}` Then print a summary line: From 8bda526de3b2c7bfb3f6360b62bd00dedce8312b Mon Sep 17 00:00:00 2001 From: Siddharth Kapoor Date: Fri, 10 Jul 2026 08:18:50 -0400 Subject: [PATCH 2/8] feat(flow): add flow doctor diagnose/--fix workflow Add references/workflows/doctor.md: diagnoses (default) and repairs (--fix) one flow or every flow across both scopes. Runs six checks (clobber, stale context, orphaned worktree/branch, step-file drift, schema drift, placeholder coherence) as PASS/WARN/FAIL, and five heals under --fix. Honors the critical guard that a legitimately-interrupted context.md matching its own steps is never deleted. Built against the Phase 1 run-instance layout: a global definition paired with a local run instance is treated as normal, not drift. --- skills/flow/references/workflows/doctor.md | 108 +++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 skills/flow/references/workflows/doctor.md diff --git a/skills/flow/references/workflows/doctor.md b/skills/flow/references/workflows/doctor.md new file mode 100644 index 0000000..016255d --- /dev/null +++ b/skills/flow/references/workflows/doctor.md @@ -0,0 +1,108 @@ +# Workflow: flow doctor + +Diagnose — and with `--fix`, repair — the health of one flow or every flow across both scopes. Dry-run by default: it reports and changes nothing unless `--fix` is passed. + +A flow has two parts (see `references/flow-dir.md`): a read-only **definition** (`flow.md` + `implementation/step-N.md`, local or global) and a local **run instance** (`.codevoyant/runs/{slug}/progress.md` + `context.md`). Doctor checks both and understands that a run instance whose definition is global is **normal**, not corruption. + +## Step 0: Parse arguments + +``` +--fix → apply repairs (default: diagnose only, change nothing) +--global / -g → target ONLY the global scope (~/.codevoyant/flows); see references/flow-dir.md +FLOW_NAME → first non-flag positional arg (OPTIONAL). If omitted, scan ALL flows in scope. +``` + +Strip `--fix` and `--global`/`-g` before reading the positional name. + +`FIX=false`; set `FIX=true` if `--fix` is present. + +## Step 0.5: Resolve the target set + +- **A name was given:** resolve the single **definition** `FLOW_DIR` per `references/flow-dir.md` (local-first, then global; `--global` forces global). If not found in any scope, error: "Flow '{FLOW_NAME}' not found (looked in local and global). Run /flow new {FLOW_NAME} first." The target set is that one flow. +- **No name:** enumerate every flow definition in scope, exactly as `list.md` Step 1 does: + ```bash + # local (skip if --global) + [ -d .codevoyant/flows ] && for d in .codevoyant/flows/*/; do [ -f "$d/flow.md" ] && echo "local $d"; done + # global (always, unless --local semantics — here --global restricts to this line) + [ -d "$HOME/.codevoyant/flows" ] && for d in "$HOME"/.codevoyant/flows/*/; do [ -f "$d/flow.md" ] && echo "global $d"; done + ``` + If `--global` was passed, enumerate only the global line. The target set is every definition found. If none, report "No flows found. Create one: /flow new ..." and exit. + +For each target definition, also resolve its **run instance**: `RUN_DIR=".codevoyant/runs/{slug}/"` (always local; `{slug}` = the definition's directory name). The run instance may not exist (flow never run here) — that is fine. + +Also determine a **legacy context path**: `FLOW_DIR/context.md`. Pre-Phase-1 runs wrote `context.md` beside the definition; doctor inspects it too. For the checks below, `CONTEXT_FILE` = `RUN_DIR/context.md` if it exists, else `FLOW_DIR/context.md` if it exists, else none. A `context.md` found **beside a global definition** (`FLOW_DIR/context.md` under `~/.codevoyant/flows`) is itself abnormal — flag it under check 1/2 as legacy clobber. + +## Step 1: Run the checks (per flow) + +For each flow in the target set, read the definition `flow.md`, the definition's `implementation/` directory, the run-instance `progress.md` (if any), and `CONTEXT_FILE` (if any). Evaluate each check and record `PASS` / `WARN` / `FAIL` with a one-line reason. + +**Check 1 — Cross-run clobber (FAIL).** Only applies if a `context.md` exists. Extract from the flow's own step commands (in `flow.md` / `progress.md`) the identifiers this flow is expected to produce/consume — the skill verbs and any literal slugs. Then scan `context.md`'s handoff lines for a branch name, spec slug, or worktree path. If `context.md` references a branch / spec-slug / worktree that is **inconsistent** with this flow's own steps (e.g. a flow whose steps are `/spec new --branch {{prompt}}` … but whose `context.md` handoffs name an unrelated `go-rust-odin-templates` run that this flow's current parameters/steps would not produce) → `FAIL: context.md references '{X}' unrelated to this flow's steps — two runs likely shared one state file`. If the `context.md` handoffs are consistent with the flow's steps → this check is `PASS` (a legitimately-interrupted run). A `CONTEXT_FILE` located beside a **global** definition is inconsistent by construction (global definitions must not hold run-state) → `FAIL: legacy context.md beside global definition`. + +**Check 2 — Stale context (WARN/FAIL).** If a `context.md` exists AND the governing Status (run-instance `progress.md` Status if present, else definition `flow.md` Status) is `Complete` → `FAIL: context.md present but Status=Complete (should have been removed on completion)`. Else if no `context.md` → `PASS`. + +**Check 3 — Orphaned worktree/branch (WARN).** For every worktree path and branch name mentioned in `CONTEXT_FILE` handoffs: +- worktree path → check it exists on disk (`test -d {path}`). +- branch name → check it exists in git (`git rev-parse --verify --quiet {branch}` or `git worktree list`). +If a referenced worktree/branch no longer exists → `WARN: context references worktree/branch '{X}' that no longer exists`. If none referenced or all exist → `PASS`. + +**Check 4 — Step-file drift (FAIL).** Count step lines in the definition `flow.md` `## Steps` (`N. [ ] ...` / `N. [x] ...`), call it `S`. Count `implementation/step-N.md` files in the definition, call it `F`. If `S != F` → `FAIL: {S} step lines but {F} step files (missing: {...} / extra: {...})`. Else `PASS`. (Compare against the DEFINITION only — `progress.md` is a checklist copy, not a step-file source, so it is never counted here.) + +**Check 5 — Schema drift (WARN/FAIL).** Validate the definition `flow.md` against `references/flow-template.md`: +- Required Metadata fields: `Slug`, `Scope`, `Created`, `Status`. +- Required sections: `## Parameters`, `## Steps`. +Missing any → `FAIL: flow.md missing {section/field}`. Also spot-check that each `implementation/step-N.md` has the current shape from `references/step-template.md` (`## Flow context`, `## Agent prompt`); a step file missing these → `WARN: step-{N}.md on an obsolete template shape`. All present → `PASS`. + +**Check 6 — Placeholder coherence (WARN).** Collect `{{token}}` set used across the definition's step commands = `USED`. Collect the tokens declared in the `## Parameters` section = `DECLARED`. If `USED - DECLARED` non-empty → `WARN: steps use undeclared parameter(s): {...}`. If `DECLARED - USED` non-empty → `WARN: declared but unused parameter(s): {...}`. If they match (or both empty / `_none_`) → `PASS`. + +## Step 2: Apply heals (only when `FIX=true`) + +Diagnose-only (`FIX=false`): skip this step entirely — never write anything. + +When `FIX=true`, for each flow, apply the heals its checks warrant. **State each repair before applying it** (print `→ {what} …` then `✓ {result}`). Never touch a global **definition**'s `flow.md`/step files except the conservative schema migration in heal 4 (which is explicitly a definition repair) — never rewrite a definition's checkboxes. + +**Heal A — Remove clobbered/stale `context.md`.** +Delete `CONTEXT_FILE` **only** when: +- Check 1 FAILED (inconsistent with this flow's own steps, including a legacy `context.md` beside a global definition), OR +- Check 2 FAILED (Status=Complete). + +**CRITICAL GUARD — never delete a legitimately-interrupted context.** If Check 1 PASSED (the `context.md` handoffs ARE consistent with this flow's steps) AND Status is not `Complete`, the `context.md` is the **resume payload** that `go.md` loads on resume — do NOT delete it. This distinction is the whole point of the check: only clobbered or completed contexts are removed; a matching, mid-run context is preserved. If neither condition holds, report `context.md preserved (legitimate interrupted run — resume payload)` and skip the delete. + +**Heal B — Reset Status Active → Complete.** In the governing checklist (`RUN_DIR/progress.md` if it exists, else the definition `flow.md` — but only reset the definition's Status if the flow has no run instance, since a definition should stay a template), if every step line is `[x]` but Status is `Active`, set Status to `Complete`. Prefer fixing the run instance. + +**Heal C — Regenerate missing `implementation/step-N.md` stubs.** For each step line in the definition `flow.md` that has no corresponding `implementation/step-N.md`, generate a stub from `references/step-template.md` filled with `{N}`, the step command (placeholders verbatim), `{flow-name}` = slug, `{total}` = step count. Report each generated stub. If there are **extra** step files (more files than step lines) or the mapping is otherwise ambiguous, do NOT delete anything — report `count mismatch requires manual review: {details}`. + +**Heal D — Migrate old-schema `flow.md` to the current template.** Conservatively backfill only the missing sections/fields identified in Check 5, preserving all existing content: add absent Metadata fields (with best-effort values — `Slug` from dir name, `Scope` from location, `Created`/`Status` left as `unknown`/`Active` if not derivable), and add an empty `## Parameters` (`_none_`) or `## Steps` section only if entirely absent. Never reorder or remove existing content. Report exactly which sections were backfilled. + +**Heal E — Prune deleted-worktree references from `context.md`.** For each worktree/branch flagged orphaned in Check 3, remove or annotate that reference in `CONTEXT_FILE` (strip the dead `worktree=`/`branch=` token from the handoff line, keep the rest). Report each pruned reference. (Skip if Heal A already deleted the file.) + +## Step 3: Report + +Reuse the reporting style of `list.md`/`status.md`. Print a per-flow block, then a summary. + +Per-flow block: +``` +{scope} {slug} ({def: local|global}, run instance: {present|none}) + [PASS] clobber — + [FAIL] stale-context context.md present but Status=Complete + [WARN] orphan context references branch 'feat/x' that no longer exists + [PASS] step-files 9 steps, 9 files + [PASS] schema — + [WARN] placeholders declared but unused: {{env}} +``` + +Summary: +``` +Doctor summary: {N} flow(s) checked · {P} PASS · {W} WARN · {F} FAIL +``` + +Under `--fix`, append exactly what was repaired: +``` +Repairs applied: + {slug}: removed clobbered context.md; regenerated step-3.md + {slug}: no repairs (all checks pass / nothing safely auto-fixable) +``` + +Diagnose-only footer: +``` +Run /flow doctor {name if given} --fix to apply the repairs above. +``` From c70fb07e1e09b1236263e686a095e20cc889068d Mon Sep 17 00:00:00 2001 From: Siddharth Kapoor Date: Fri, 10 Jul 2026 08:20:46 -0400 Subject: [PATCH 3/8] docs(flow): wire doctor verb and document run-instance split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose the flow doctor workflow and the definition-vs-run-instance model across the dispatcher, help, and public docs. - SKILL.md: add fix/diagnose/check → doctor aliases, --fix pass-through note, and doctor row in the workflow index - help.md: add the doctor usage line, doctor aliases, and a run-instance storage note - docs/skills/flow.md: add a doctor section and a definition-vs-run-instance note under go --- docs/skills/flow.md | 17 +++++++++++++++++ skills/flow/SKILL.md | 20 +++++++++++++------- skills/flow/references/workflows/help.md | 17 +++++++++++++---- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/docs/skills/flow.md b/docs/skills/flow.md index 9f76d7e..40deaea 100644 --- a/docs/skills/flow.md +++ b/docs/skills/flow.md @@ -45,6 +45,8 @@ Each step's key outputs (PR numbers, plan names, file paths) are captured and th Run all pending steps sequentially as blocking subagents. Resumes from the first incomplete step if re-run after interruption. Resolves the flow locally first, then globally; pass `--global` to force the global copy. +The flow's own directory is a read-only **definition** (its steps and per-step implementations). A run never mutates it: the run's checkbox progress and accumulating context are materialized into a local **run instance** at `.codevoyant/runs//` (`progress.md` + `context.md`). This keeps a global flow a pristine, reusable template — running it from any project writes progress only to that project's run instance, so concurrent or cross-project runs never clobber each other. Resume reads the local run instance; `/flow status` shows its progress. + ```bash /flow go auth-refactor # execute all pending steps /flow go ship "add OAuth login" # bind free text to {{input}} @@ -71,6 +73,21 @@ Print the `flow.md` checklist with current step status, scope, and parameters. /flow status ship --global # inspect a global flow ``` +### doctor — diagnose and repair flows + +Check flows for corruption and, with `--fix`, repair what is safe to repair. Diagnose-only by default. With no name, it scans every flow in both scopes. + +```bash +/flow doctor # diagnose all flows (local + global), change nothing +/flow doctor autospec # diagnose one flow +/flow doctor autospec --fix # apply safe repairs +/flow doctor autospec --fix --global # target the global copy +``` + +Checks (reported PASS/WARN/FAIL per flow): cross-run **clobber** (a `context.md` referencing an unrelated run's branch/slug/worktree), **stale** context (present although Status is Complete), **orphaned** worktree/branch (referenced but gone), **step-file drift** (step lines ≠ `step-N.md` files), **schema drift** (missing template sections), and **placeholder coherence** (undeclared or unused `{{tokens}}`). + +Repairs (`--fix`, each announced before it runs): remove a clobbered or stale `context.md` — but **never** a legitimately-interrupted one that matches the flow's own steps (that is the resume payload); reset Status `Active → Complete` when all steps are done; regenerate missing `step-N.md` stubs; conservatively migrate an old-schema `flow.md` to the current template; and prune references to deleted worktrees from `context.md`. + ### save — create a composite skill Turn a completed flow into a reusable skill scaffolded via `/skill new`. The generated skill, when invoked, spins up a fresh flow instance from the saved steps and runs it end-to-end. diff --git a/skills/flow/SKILL.md b/skills/flow/SKILL.md index c93f132..7cf5e02 100644 --- a/skills/flow/SKILL.md +++ b/skills/flow/SKILL.md @@ -30,11 +30,14 @@ Aliases: "run" → go "exec" → go "start" → go - "ls" → list - "show" → status - "review" → status - "export" → save - "publish" → save + "ls" → list + "show" → status + "review" → status + "export" → save + "publish" → save + "fix" → doctor + "diagnose" → doctor + "check" → doctor Dispatch to: references/workflows/{VERB}.md ``` @@ -43,6 +46,8 @@ The `--global` / `-g` flag (store or read a flow under `~/.codevoyant/flows` ins Any flag other than the flow-control flags (`--global`/`-g`, and `--set` for `go`) is **not** dropped: `references/flow-dir.md` collects it into `PASSTHROUGH_FLAGS`, and the `new`/`go` workflows forward it to the step commands. This is how `--branch feature/x` reaches the skills a flow runs (e.g. so `spec new` works on a separate branch). +The `--fix` flag (used by `doctor` to apply repairs instead of only diagnosing) is likewise **not** a verb — pass it through unchanged to the workflow. + ## Workflow index | Verb | File | Purpose | @@ -50,14 +55,15 @@ Any flag other than the flow-control flags (`--global`/`-g`, and `--set` for `go | new | `references/workflows/new.md` | Define a new flow (create flow.md + step files) | | go | `references/workflows/go.md` | Execute pending steps sequentially as blocking subagents | | list | `references/workflows/list.md` | List all flows (local and global) | -| status | `references/workflows/status.md` | Print flow.md checklist state | +| status | `references/workflows/status.md` | Print flow checklist state (from the local run instance if present) | +| doctor | `references/workflows/doctor.md` | Diagnose (and with `--fix` repair) broken flows across both scopes | | save | `references/workflows/save.md` | Turn a flow into a reusable composite skill via /skill new | | help | `references/workflows/help.md` | Usage reference | ## Instructions 1. Extract VERB from the user's message (first non-flag positional argument after "flow"). -2. Apply aliases (run/exec/start → go; ls → list; show/review → status; export/publish → save). +2. Apply aliases (run/exec/start → go; ls → list; show/review → status; export/publish → save; fix/diagnose/check → doctor). 3. If VERB is empty or unrecognized, default to `help`. 4. Read and execute the corresponding workflow file from `references/workflows/{VERB}.md`. 5. Pass all remaining arguments (including any `--global`/`-g` flag and any other unrecognized flags such as `--branch`) to the workflow unchanged, **as a preserved argv array** — each original argument stays one element, so multi-word step strings (`/spec new {{objective}}`) and quoted flag values (`feature="add OAuth"`) survive intact. The workflow iterates this argv (`"$@"`) to parse flow-control flags and bucket the rest into `PASSTHROUGH_FLAGS` via `references/flow-dir.md`; it must never flatten the args into a single string and re-split them. diff --git a/skills/flow/references/workflows/help.md b/skills/flow/references/workflows/help.md index eddda02..1e8aba3 100644 --- a/skills/flow/references/workflows/help.md +++ b/skills/flow/references/workflows/help.md @@ -12,14 +12,16 @@ flow — chain skill commands into a named, reusable pipeline — run pending steps sequentially /flow list [--global | --local] — list all flows (local + global) /flow status [--global] — show checklist state + /flow doctor [name] [--fix] [--global] — diagnose (or --fix repair) broken flows /flow save --skill [--global] — turn a flow into a reusable skill /flow help — this message Verb aliases: - run, exec, start → go - ls → list - show, review → status - export, publish → save + run, exec, start → go + ls → list + show, review → status + fix, diagnose, check → doctor + export, publish → save Storage: local (default) .codevoyant/flows// — this project only @@ -31,6 +33,13 @@ Forwarding flags to steps: /flow new ship "/spec new {{input}}" "/spec go" --branch feature/x (bakes it in) /flow go ship "add OAuth" --branch feature/x (one-off run) +Definitions vs. run instances: + A flow's directory is a read-only DEFINITION (steps + implementations). Running a + flow never mutates it — progress + context for a run live in a local RUN INSTANCE: + .codevoyant/runs// (progress.md + context.md) + so a global flow stays a pristine template and concurrent runs never clobber each + other. /flow status reads the run instance; /flow doctor cleans up when they don't. + Parameters (dynamic input): Put {{placeholders}} in any step. At run time: • bare text after the name binds to {{input}} From e81a15d3d17d1d383bd40cae1639270ad6dddf31 Mon Sep 17 00:00:00 2001 From: Siddharth Kapoor Date: Fri, 10 Jul 2026 08:32:38 -0400 Subject: [PATCH 4/8] feat(flow): persist resolved run identity in run instance go.md now writes a run.md identity file into the run instance capturing the resolved slug/branch/spec-slug/worktree, backfilling identifiers as steps produce them. This gives /flow doctor a concrete anchor to compare context.md against, since the definition and progress.md only ever hold {{placeholders}}. Documents run.md in flow-dir.md and help.md. --- skills/flow/references/flow-dir.md | 3 +++ skills/flow/references/workflows/go.md | 18 +++++++++++++++++- skills/flow/references/workflows/help.md | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/skills/flow/references/flow-dir.md b/skills/flow/references/flow-dir.md index 0cfcb05..a6f6179 100644 --- a/skills/flow/references/flow-dir.md +++ b/skills/flow/references/flow-dir.md @@ -92,10 +92,13 @@ All mutable run-state lives in a **run instance**, which is **always local to th ``` .codevoyant/runs/{slug}/ + run.md # this run's resolved identity (slug, definition, branch/spec-slug/worktree) — written at go start progress.md # a copy of the definition's Steps checklist; the ONLY place [ ] → [x] is flipped context.md # the accumulating handoff log (persisted for resume) ``` +`run.md` is the run instance's **identity record**. Because the definition and `progress.md` only ever hold `{{placeholders}}`, the resolved branch / spec-slug / worktree of a real run live nowhere in the definition — `run.md` (and `context.md`'s handoffs) are the only place they exist. `doctor` reads `run.md` as the authoritative "what is this run" anchor to distinguish a legitimately-interrupted `context.md` from one clobbered by a different run. + Resolve it the same way in every workflow that runs or inspects a flow: ```bash diff --git a/skills/flow/references/workflows/go.md b/skills/flow/references/workflows/go.md index 56077f2..ab67536 100644 --- a/skills/flow/references/workflows/go.md +++ b/skills/flow/references/workflows/go.md @@ -38,6 +38,20 @@ Read the **definition** `FLOW_DIR/flow.md` (read-only). Parse the Steps checklis - **First run** (`RUN_DIR/progress.md` does not exist): copy the definition's `## Steps` checklist verbatim into `RUN_DIR/progress.md` (keep the `N. [ ] {{placeholder}}` lines exactly, all unchecked). Prepend a one-line header `# Run progress: {slug}` and a `Status: Active` line so `status` and `doctor` can read it. - **Resume** (`RUN_DIR/progress.md` exists): use it as the source of truth for which steps are already `[x]` — do NOT re-seed from the definition. +**Write the run instance's identity file.** The definition's step text only ever holds `{{placeholders}}`, so the resolved identity of *this* run must be recorded explicitly — otherwise nothing downstream (notably `/flow doctor`) can tell a legitimately-interrupted run apart from a foreign run that clobbered the state file. On **first run**, write `RUN_DIR/run.md`: +``` +# Run identity: {slug} +slug: {slug} +definition: {FLOW_DIR} # absolute or scope-qualified path to the definition +scope: {local|global} # scope of the definition +started: {ISO timestamp} +# resolved run identifiers (appended as steps produce them — see Step 2.5): +branch: +spec-slug: +worktree: +``` +This file is the **authoritative record of what this run is**. `doctor` compares `context.md`'s handoff identifiers against `run.md` (not against placeholder step text) to decide whether a `context.md` belongs to this run or was clobbered by another. On **resume**, leave an existing `run.md` in place (do not overwrite the recorded identity); only backfill empty fields if later steps resolve them. + **Resolve every `{{placeholder}}` used anywhere in the steps:** 1. Scan all step commands for `{{name}}` tokens; collect the unique set `NEEDED`. 2. For each token in `NEEDED`: @@ -88,6 +102,8 @@ For each pending step in order: ``` Keep `CONTEXT` terse — it is injected into every later step, so it must stay a short bulleted log, not full transcripts. **Persist it:** write the accumulated `CONTEXT` to `RUN_DIR/context.md` (the local run instance — **never** beside the definition) so an interrupted flow can resume with it (see Step 1). + **Backfill the run identity.** If this handoff resolved a concrete `branch`, `spec-slug`, or `worktree` (e.g. a `spec new`/`pr open` step reporting `branch=…`, `slug=…`, `worktree=…`), write those values into the matching empty fields of `RUN_DIR/run.md`. Only fill fields that are still empty — never rewrite an already-recorded identifier (the first value a run commits to is its identity; a later differing value would be the clobber `doctor` looks for). This keeps `run.md` the concrete, resolved anchor `doctor` compares `context.md` against. + 6. Update `RUN_DIR/progress.md` — change this step's `[ ]` to `[x]` (keep the original `{{placeholder}}` text; only the run used resolved values). **Never modify the definition's `FLOW_DIR/flow.md`** — it stays a pristine template. Leave `Status` in `progress.md` as `Active` until all steps complete. 7. Report: `✓ Step {N} complete.` @@ -98,7 +114,7 @@ For each pending step in order: ## Step 3: Final report -After all steps are complete, update `Status` in `RUN_DIR/progress.md` to `Complete` and remove `RUN_DIR/context.md` (a fresh run starts clean). Leave the definition's `FLOW_DIR/flow.md` untouched — it was never mutated. +After all steps are complete, update `Status` in `RUN_DIR/progress.md` to `Complete` and remove `RUN_DIR/context.md` (a fresh run starts clean). Leave `RUN_DIR/run.md` in place — it is the completed run's identity record and is harmless once `context.md` is gone (a subsequent first run re-seeds it in Step 1). Leave the definition's `FLOW_DIR/flow.md` untouched — it was never mutated. Report: ``` diff --git a/skills/flow/references/workflows/help.md b/skills/flow/references/workflows/help.md index 1e8aba3..3678a84 100644 --- a/skills/flow/references/workflows/help.md +++ b/skills/flow/references/workflows/help.md @@ -36,7 +36,7 @@ Forwarding flags to steps: Definitions vs. run instances: A flow's directory is a read-only DEFINITION (steps + implementations). Running a flow never mutates it — progress + context for a run live in a local RUN INSTANCE: - .codevoyant/runs// (progress.md + context.md) + .codevoyant/runs// (run.md + progress.md + context.md) so a global flow stays a pristine template and concurrent runs never clobber each other. /flow status reads the run instance; /flow doctor cleans up when they don't. From b1b8e5dd6eb69f6a65b5629dd4b62b7ea57e3bc2 Mon Sep 17 00:00:00 2001 From: Siddharth Kapoor Date: Fri, 10 Jul 2026 08:32:52 -0400 Subject: [PATCH 5/8] fix(flow): base doctor clobber guard on run identity, delete only on positive signal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check 1 now compares context.md against the run's recorded identity in run.md instead of the placeholder-bearing step text, so a legitimately interrupted run is no longer misread as unrelated. Heal A deletes a context.md ONLY on a positive clobber signal (identifier differs from run.md, legacy context beside a global definition, or Status=Complete) and preserves on uncertainty or match — inverting the previous delete-unless-PASS bias that risked destroying live resume state. Also inspect both RUN_DIR/context.md and a legacy FLOW_DIR/context.md independently rather than first-match, so an old clobber beside the definition still gets cleaned, and announce any deletion under ~/.codevoyant/flows (global scope) before acting. --- skills/flow/references/workflows/doctor.md | 39 ++++++++++++++++------ 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/skills/flow/references/workflows/doctor.md b/skills/flow/references/workflows/doctor.md index 016255d..832ce65 100644 --- a/skills/flow/references/workflows/doctor.md +++ b/skills/flow/references/workflows/doctor.md @@ -2,7 +2,7 @@ Diagnose — and with `--fix`, repair — the health of one flow or every flow across both scopes. Dry-run by default: it reports and changes nothing unless `--fix` is passed. -A flow has two parts (see `references/flow-dir.md`): a read-only **definition** (`flow.md` + `implementation/step-N.md`, local or global) and a local **run instance** (`.codevoyant/runs/{slug}/progress.md` + `context.md`). Doctor checks both and understands that a run instance whose definition is global is **normal**, not corruption. +A flow has two parts (see `references/flow-dir.md`): a read-only **definition** (`flow.md` + `implementation/step-N.md`, local or global) and a local **run instance** (`.codevoyant/runs/{slug}/run.md` + `progress.md` + `context.md`). `run.md` records the run's resolved identity (slug, branch, spec-slug, worktree) and is the concrete anchor doctor uses to tell a legitimately-interrupted `context.md` from a clobbered one. Doctor checks both parts and understands that a run instance whose definition is global is **normal**, not corruption. ## Step 0: Parse arguments @@ -30,17 +30,29 @@ Strip `--fix` and `--global`/`-g` before reading the positional name. For each target definition, also resolve its **run instance**: `RUN_DIR=".codevoyant/runs/{slug}/"` (always local; `{slug}` = the definition's directory name). The run instance may not exist (flow never run here) — that is fine. -Also determine a **legacy context path**: `FLOW_DIR/context.md`. Pre-Phase-1 runs wrote `context.md` beside the definition; doctor inspects it too. For the checks below, `CONTEXT_FILE` = `RUN_DIR/context.md` if it exists, else `FLOW_DIR/context.md` if it exists, else none. A `context.md` found **beside a global definition** (`FLOW_DIR/context.md` under `~/.codevoyant/flows`) is itself abnormal — flag it under check 1/2 as legacy clobber. +**Load the run identity.** If `RUN_DIR/run.md` exists (written by `go.md` when the run started — see `references/flow-dir.md` → *Run instance*), read it into `RUN_IDENTITY`: the recorded `slug`, and any resolved `branch` / `spec-slug` / `worktree` fields (empty fields count as unknown). `RUN_IDENTITY` is the **authoritative record of what this run is** — the concrete anchor for Check 1, because the definition and `progress.md` only ever hold `{{placeholders}}`. If `run.md` is absent (e.g. a run that predates the identity file, or a legacy context beside the definition), `RUN_IDENTITY` is unavailable — treat that as "can't determine" everywhere below (which biases toward preserve). + +**Inspect both context locations independently — do NOT first-match.** Two `context.md` files can coexist: the current run instance's (`RUN_DIR/context.md`) and a lingering legacy one beside the definition (`FLOW_DIR/context.md`, written by pre-run-instance runs). If we picked only the first that exists, a legacy clobber sitting beside the definition would never be inspected or cleaned. So build a list `CONTEXT_FILES` of **every** path that exists: +- `RUN_DIR/context.md` (if present) — checked against `RUN_IDENTITY`. +- `FLOW_DIR/context.md` (if present) — a legacy context. A legacy `context.md` beside a **global** definition (`FLOW_DIR` under `~/.codevoyant/flows`) is abnormal by construction — global definitions must never hold run-state — so it is always a clobber candidate under Check 1. + +Run the checks below over each entry in `CONTEXT_FILES` (a flow with both gets both inspected). ## Step 1: Run the checks (per flow) -For each flow in the target set, read the definition `flow.md`, the definition's `implementation/` directory, the run-instance `progress.md` (if any), and `CONTEXT_FILE` (if any). Evaluate each check and record `PASS` / `WARN` / `FAIL` with a one-line reason. +For each flow in the target set, read the definition `flow.md`, the definition's `implementation/` directory, the run-instance `progress.md` (if any), `RUN_IDENTITY` (from `run.md`, if any), and each entry in `CONTEXT_FILES` (if any). Evaluate each check and record `PASS` / `WARN` / `FAIL` with a one-line reason. Where a check inspects a context file, apply it to every entry in `CONTEXT_FILES`. + +**Check 1 — Cross-run clobber (FAIL only on a positive clobber signal).** Only applies to a `context.md` that exists. Compare the context against the run's **recorded identity** (`RUN_IDENTITY` from `run.md`) — NOT against the flow's step text, which only ever holds `{{placeholders}}` and so has nothing concrete to match a real run against. -**Check 1 — Cross-run clobber (FAIL).** Only applies if a `context.md` exists. Extract from the flow's own step commands (in `flow.md` / `progress.md`) the identifiers this flow is expected to produce/consume — the skill verbs and any literal slugs. Then scan `context.md`'s handoff lines for a branch name, spec slug, or worktree path. If `context.md` references a branch / spec-slug / worktree that is **inconsistent** with this flow's own steps (e.g. a flow whose steps are `/spec new --branch {{prompt}}` … but whose `context.md` handoffs name an unrelated `go-rust-odin-templates` run that this flow's current parameters/steps would not produce) → `FAIL: context.md references '{X}' unrelated to this flow's steps — two runs likely shared one state file`. If the `context.md` handoffs are consistent with the flow's steps → this check is `PASS` (a legitimately-interrupted run). A `CONTEXT_FILE` located beside a **global** definition is inconsistent by construction (global definitions must not hold run-state) → `FAIL: legacy context.md beside global definition`. +Scan the `context.md` handoff lines for concrete identifiers: `branch=`, spec `slug=`, `worktree=` values. Then: +- **Positive clobber → FAIL.** `RUN_IDENTITY` is available AND records a concrete `branch` / `spec-slug` / `worktree`, AND the `context.md` names a *different* value for that same identifier (e.g. `run.md` says `branch: feat/auth` but `context.md` handoffs name `go-rust-odin-templates`) → `FAIL: context.md names '{X}' but this run's identity is '{Y}' — two runs likely shared one state file`. This is the only signal that a foreign run clobbered the state. +- **Legacy context beside a global definition → FAIL.** A `FLOW_DIR/context.md` under `~/.codevoyant/flows` is a clobber by construction (global definitions must never hold run-state), regardless of identity → `FAIL: legacy context.md beside global definition`. +- **Matches identity → PASS.** The context's identifiers agree with `RUN_IDENTITY` (or the run has committed to no conflicting identifier yet) → `PASS` (a legitimately-interrupted run — the resume payload). +- **Can't determine → PASS (preserve).** No `run.md` / no recorded identity, or `context.md` carries no comparable identifier → there is **no positive clobber signal**, so do NOT flag it. Record `PASS: no clobber signal (identity unavailable — preserved)`. Uncertainty must never escalate to a delete. **Check 2 — Stale context (WARN/FAIL).** If a `context.md` exists AND the governing Status (run-instance `progress.md` Status if present, else definition `flow.md` Status) is `Complete` → `FAIL: context.md present but Status=Complete (should have been removed on completion)`. Else if no `context.md` → `PASS`. -**Check 3 — Orphaned worktree/branch (WARN).** For every worktree path and branch name mentioned in `CONTEXT_FILE` handoffs: +**Check 3 — Orphaned worktree/branch (WARN).** For every worktree path and branch name mentioned in any `CONTEXT_FILES` handoff: - worktree path → check it exists on disk (`test -d {path}`). - branch name → check it exists in git (`git rev-parse --verify --quiet {branch}` or `git worktree list`). If a referenced worktree/branch no longer exists → `WARN: context references worktree/branch '{X}' that no longer exists`. If none referenced or all exist → `PASS`. @@ -60,12 +72,19 @@ Diagnose-only (`FIX=false`): skip this step entirely — never write anything. When `FIX=true`, for each flow, apply the heals its checks warrant. **State each repair before applying it** (print `→ {what} …` then `✓ {result}`). Never touch a global **definition**'s `flow.md`/step files except the conservative schema migration in heal 4 (which is explicitly a definition repair) — never rewrite a definition's checkboxes. -**Heal A — Remove clobbered/stale `context.md`.** -Delete `CONTEXT_FILE` **only** when: -- Check 1 FAILED (inconsistent with this flow's own steps, including a legacy `context.md` beside a global definition), OR +**Heal A — Remove clobbered/stale `context.md`.** Applied per file across `CONTEXT_FILES`. +Delete a `context.md` **only** when there is a positive signal that it is safe to remove: +- Check 1 FAILED with a **positive clobber signal** — its identifiers differ from `RUN_IDENTITY`, or it is a legacy `context.md` beside a global definition, OR - Check 2 FAILED (Status=Complete). -**CRITICAL GUARD — never delete a legitimately-interrupted context.** If Check 1 PASSED (the `context.md` handoffs ARE consistent with this flow's steps) AND Status is not `Complete`, the `context.md` is the **resume payload** that `go.md` loads on resume — do NOT delete it. This distinction is the whole point of the check: only clobbered or completed contexts are removed; a matching, mid-run context is preserved. If neither condition holds, report `context.md preserved (legitimate interrupted run — resume payload)` and skip the delete. +**CRITICAL GUARD — the bias is PRESERVE; delete only on a positive signal.** The default for any `context.md` is to keep it: it may be the **resume payload** that `go.md` loads on resume, and deleting a live one is unrecoverable data loss. Therefore: +- Delete **only** when a check above produced a positive delete signal (a concrete identity mismatch, a legacy global-scope context, or Status=Complete). +- **Preserve on uncertainty.** If Check 1 could not determine a clobber — no `run.md` / no recorded identity, or no comparable identifier in the context — that is *not* a delete signal. Never delete "just in case." Report `context.md preserved (no positive clobber signal — resume payload may be live)` and skip. +- **Preserve on match.** If the context's identifiers agree with `RUN_IDENTITY` and Status is not `Complete`, report `context.md preserved (matches run identity — legitimate interrupted run)` and skip. + +A doctor that occasionally leaves a stale file is fine; one that occasionally deletes a live resume payload is not. + +**Announce global-scope deletions explicitly.** Deleting a `FLOW_DIR/context.md` under `~/.codevoyant/flows` is the one `--fix` path that writes into global scope (`$HOME`). Before removing it, print an explicit notice — `⚠ Deleting a file under ~/.codevoyant/flows (global scope): {path}` — then `→`/`✓` as usual, so a user running `doctor --fix` locally is never surprised by a `$HOME` mutation. **Heal B — Reset Status Active → Complete.** In the governing checklist (`RUN_DIR/progress.md` if it exists, else the definition `flow.md` — but only reset the definition's Status if the flow has no run instance, since a definition should stay a template), if every step line is `[x]` but Status is `Active`, set Status to `Complete`. Prefer fixing the run instance. @@ -73,7 +92,7 @@ Delete `CONTEXT_FILE` **only** when: **Heal D — Migrate old-schema `flow.md` to the current template.** Conservatively backfill only the missing sections/fields identified in Check 5, preserving all existing content: add absent Metadata fields (with best-effort values — `Slug` from dir name, `Scope` from location, `Created`/`Status` left as `unknown`/`Active` if not derivable), and add an empty `## Parameters` (`_none_`) or `## Steps` section only if entirely absent. Never reorder or remove existing content. Report exactly which sections were backfilled. -**Heal E — Prune deleted-worktree references from `context.md`.** For each worktree/branch flagged orphaned in Check 3, remove or annotate that reference in `CONTEXT_FILE` (strip the dead `worktree=`/`branch=` token from the handoff line, keep the rest). Report each pruned reference. (Skip if Heal A already deleted the file.) +**Heal E — Prune deleted-worktree references from `context.md`.** For each worktree/branch flagged orphaned in Check 3, remove or annotate that reference in the owning file from `CONTEXT_FILES` (strip the dead `worktree=`/`branch=` token from the handoff line, keep the rest). Report each pruned reference. (Skip any file Heal A already deleted.) ## Step 3: Report From 51f175baf916cc372800ee832a34d714df0e1582 Mon Sep 17 00:00:00 2001 From: Siddharth Kapoor Date: Fri, 10 Jul 2026 08:32:58 -0400 Subject: [PATCH 6/8] docs(flow): document run.md identity and identity-based doctor guard Update the flow docs to describe the run.md identity file in the run instance and the revised doctor behaviour: clobber detection anchored on run identity, delete only on a positive signal, both context locations inspected, and explicit announcement of global-scope deletions. --- docs/skills/flow.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/skills/flow.md b/docs/skills/flow.md index 40deaea..d622cd4 100644 --- a/docs/skills/flow.md +++ b/docs/skills/flow.md @@ -45,7 +45,7 @@ Each step's key outputs (PR numbers, plan names, file paths) are captured and th Run all pending steps sequentially as blocking subagents. Resumes from the first incomplete step if re-run after interruption. Resolves the flow locally first, then globally; pass `--global` to force the global copy. -The flow's own directory is a read-only **definition** (its steps and per-step implementations). A run never mutates it: the run's checkbox progress and accumulating context are materialized into a local **run instance** at `.codevoyant/runs//` (`progress.md` + `context.md`). This keeps a global flow a pristine, reusable template — running it from any project writes progress only to that project's run instance, so concurrent or cross-project runs never clobber each other. Resume reads the local run instance; `/flow status` shows its progress. +The flow's own directory is a read-only **definition** (its steps and per-step implementations). A run never mutates it: the run's identity, checkbox progress, and accumulating context are materialized into a local **run instance** at `.codevoyant/runs//` (`run.md` + `progress.md` + `context.md`). `run.md` records the resolved identity of the run (slug, definition, and the branch/spec-slug/worktree it produces) — the concrete anchor `/flow doctor` uses to tell a live interrupted run apart from a clobbered one, since the definition itself only holds `{{placeholders}}`. This keeps a global flow a pristine, reusable template — running it from any project writes progress only to that project's run instance, so concurrent or cross-project runs never clobber each other. Resume reads the local run instance; `/flow status` shows its progress. ```bash /flow go auth-refactor # execute all pending steps @@ -84,9 +84,9 @@ Check flows for corruption and, with `--fix`, repair what is safe to repair. Dia /flow doctor autospec --fix --global # target the global copy ``` -Checks (reported PASS/WARN/FAIL per flow): cross-run **clobber** (a `context.md` referencing an unrelated run's branch/slug/worktree), **stale** context (present although Status is Complete), **orphaned** worktree/branch (referenced but gone), **step-file drift** (step lines ≠ `step-N.md` files), **schema drift** (missing template sections), and **placeholder coherence** (undeclared or unused `{{tokens}}`). +Checks (reported PASS/WARN/FAIL per flow): cross-run **clobber** (a `context.md` naming a branch/spec-slug/worktree that differs from this run's recorded identity in `run.md`), **stale** context (present although Status is Complete), **orphaned** worktree/branch (referenced but gone), **step-file drift** (step lines ≠ `step-N.md` files), **schema drift** (missing template sections), and **placeholder coherence** (undeclared or unused `{{tokens}}`). Doctor also inspects a legacy `context.md` sitting beside the definition (pre-run-instance layout), independently of any run-instance context. -Repairs (`--fix`, each announced before it runs): remove a clobbered or stale `context.md` — but **never** a legitimately-interrupted one that matches the flow's own steps (that is the resume payload); reset Status `Active → Complete` when all steps are done; regenerate missing `step-N.md` stubs; conservatively migrate an old-schema `flow.md` to the current template; and prune references to deleted worktrees from `context.md`. +Repairs (`--fix`, each announced before it runs): remove a `context.md` **only** on a positive clobber signal (its identifiers differ from `run.md`'s) or when Status is Complete — a context that matches the run's identity, or one whose identity can't be determined, is **preserved** (it may be the resume payload); reset Status `Active → Complete` when all steps are done; regenerate missing `step-N.md` stubs; conservatively migrate an old-schema `flow.md` to the current template; and prune references to deleted worktrees from `context.md`. Deleting a file under `~/.codevoyant/flows` (global scope) is announced explicitly before it happens. ### save — create a composite skill From f364210c8b7c51c7c813318a11cfa3b010586785 Mon Sep 17 00:00:00 2001 From: Siddharth Kapoor Date: Fri, 10 Jul 2026 12:57:21 -0400 Subject: [PATCH 7/8] fix(flow): let a completed flow run re-run cleanly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 3 left progress.md on disk (all [x], Status=Complete) and only removed context.md, but Step 1's resume branch was keyed on progress.md existence — so a second /flow go of a finished flow took Resume, found zero pending steps, and exited 'already complete'. A completed run could never re-run. Step 1 now classifies the run instance into three states: first run (no progress.md), completed run (all [x] or Status=Complete — archive the stale instance to RUN_DIR/archive and re-seed a clean run), and resume (partially complete — preserved, never wiped). Step 3's wording now matches the mechanism. Genuinely in-progress runs still resume with their context intact. --- skills/flow/references/workflows/go.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/skills/flow/references/workflows/go.md b/skills/flow/references/workflows/go.md index ab67536..8a53592 100644 --- a/skills/flow/references/workflows/go.md +++ b/skills/flow/references/workflows/go.md @@ -34,21 +34,22 @@ Then resolve the **run instance** per `references/flow-dir.md` → *Run instance Read the **definition** `FLOW_DIR/flow.md` (read-only). Parse the Steps checklist and the Parameters section. -**Seed the run instance's progress file.** The mutable checklist lives at `RUN_DIR/progress.md`, never in the definition: +**Seed the run instance's progress file.** The mutable checklist lives at `RUN_DIR/progress.md`, never in the definition. Classify the existing run instance into one of three states and act accordingly: - **First run** (`RUN_DIR/progress.md` does not exist): copy the definition's `## Steps` checklist verbatim into `RUN_DIR/progress.md` (keep the `N. [ ] {{placeholder}}` lines exactly, all unchecked). Prepend a one-line header `# Run progress: {slug}` and a `Status: Active` line so `status` and `doctor` can read it. -- **Resume** (`RUN_DIR/progress.md` exists): use it as the source of truth for which steps are already `[x]` — do NOT re-seed from the definition. +- **Completed run → re-seed** (`RUN_DIR/progress.md` exists AND every step line is `[x]`, or its `Status` is `Complete`): the previous run of this flow finished. A finished run must be able to run again cleanly, so treat this as a fresh start, not a resume. **Archive the stale run instance, then re-seed as a first run:** move any existing `RUN_DIR/progress.md`, `RUN_DIR/context.md`, and `RUN_DIR/run.md` into `RUN_DIR/archive/{completed-timestamp}/` (best-effort — create the dir, `mv` what exists; if archiving fails, delete them instead so the re-seed is clean), then re-run the **First run** seeding above to write a new all-unchecked `progress.md` (Status: Active) and, below, a new `run.md`. Do NOT load the old `context.md` into `CONTEXT` — a re-run starts clean. +- **Resume** (`RUN_DIR/progress.md` exists AND is only partially complete — at least one `[ ]` line remains AND Status is not `Complete`): a genuinely in-progress run was interrupted. Use it as the source of truth for which steps are already `[x]` — do NOT re-seed from the definition, and do NOT wipe it. -**Write the run instance's identity file.** The definition's step text only ever holds `{{placeholders}}`, so the resolved identity of *this* run must be recorded explicitly — otherwise nothing downstream (notably `/flow doctor`) can tell a legitimately-interrupted run apart from a foreign run that clobbered the state file. On **first run**, write `RUN_DIR/run.md`: +**Write the run instance's identity file.** The definition's step text only ever holds `{{placeholders}}`, so the resolved identity of *this* run must be recorded explicitly — otherwise nothing downstream (notably `/flow doctor`) can tell a legitimately-interrupted run apart from a foreign run that clobbered the state file. On **first run** (and on a **completed → re-seed**, which archived the old `run.md` above and now starts fresh), write `RUN_DIR/run.md`: ``` # Run identity: {slug} -slug: {slug} +slug: {slug} # the FLOW's own slug (definition dir name) — never overwritten definition: {FLOW_DIR} # absolute or scope-qualified path to the definition scope: {local|global} # scope of the definition started: {ISO timestamp} -# resolved run identifiers (appended as steps produce them — see Step 2.5): -branch: -spec-slug: -worktree: +# resolved run identifiers (backfilled as steps produce them — see Step 2.5): +branch: # ← handoff branch= +spec-slug: # ← handoff slug= (the resolved spec slug; distinct from the flow slug above) +worktree: # ← handoff worktree= ``` This file is the **authoritative record of what this run is**. `doctor` compares `context.md`'s handoff identifiers against `run.md` (not against placeholder step text) to decide whether a `context.md` belongs to this run or was clobbered by another. On **resume**, leave an existing `run.md` in place (do not overwrite the recorded identity); only backfill empty fields if later steps resolve them. @@ -59,9 +60,9 @@ This file is the **authoritative record of what this run is**. `doctor` compares - Else → **prompt the user once** for the value, using AskUserQuestion (free-text via Other): question `Value for {{name}}?`, header `Parameter`, and (for `{{input}}`) show the flow's Parameters description as help. Store the answer in `PARAMS[name]`. 3. Do not proceed with any unresolved token. If the user dismisses a prompt, abort: "Cancelled — {{name}} is required to run this flow." -Collect the pending steps: all lines in `RUN_DIR/progress.md` matching `N. [ ] ...` (unchecked), in order. If none are pending, report "Flow '{FLOW_NAME}' is already complete." and exit. +Collect the pending steps: all lines in `RUN_DIR/progress.md` matching `N. [ ] ...` (unchecked), in order. A completed run was already re-seeded above (so its steps are all unchecked again and there will be pending steps); reaching zero pending here therefore means a resumed instance whose remaining steps are all done — report "Flow '{FLOW_NAME}' is already complete." and exit. -Initialize the **flow context** accumulator `CONTEXT`. **On resume:** if `RUN_DIR/context.md` exists (an earlier, interrupted run), load it into `CONTEXT` so the remaining steps keep the handoffs from steps already marked `[x]`. Otherwise start empty. (Without this, a resumed flow loses e.g. the PR number a completed `pr open` step produced.) +Initialize the **flow context** accumulator `CONTEXT`. **On resume (partial run only):** if `RUN_DIR/context.md` exists (an earlier, interrupted run that was NOT re-seeded above), load it into `CONTEXT` so the remaining steps keep the handoffs from steps already marked `[x]`. Otherwise start empty (a first run, or a completed → re-seed, which archived the old context and starts clean). (Without this, a resumed flow loses e.g. the PR number a completed `pr open` step produced.) ## Step 2: Execute steps sequentially @@ -102,7 +103,7 @@ For each pending step in order: ``` Keep `CONTEXT` terse — it is injected into every later step, so it must stay a short bulleted log, not full transcripts. **Persist it:** write the accumulated `CONTEXT` to `RUN_DIR/context.md` (the local run instance — **never** beside the definition) so an interrupted flow can resume with it (see Step 1). - **Backfill the run identity.** If this handoff resolved a concrete `branch`, `spec-slug`, or `worktree` (e.g. a `spec new`/`pr open` step reporting `branch=…`, `slug=…`, `worktree=…`), write those values into the matching empty fields of `RUN_DIR/run.md`. Only fill fields that are still empty — never rewrite an already-recorded identifier (the first value a run commits to is its identity; a later differing value would be the clobber `doctor` looks for). This keeps `run.md` the concrete, resolved anchor `doctor` compares `context.md` against. + **Backfill the run identity.** If this handoff resolved a concrete `branch`, spec slug, or `worktree` (e.g. a `spec new`/`pr open` step reporting `branch=…`, `slug=…`, `worktree=…`), write those values into the matching empty fields of `RUN_DIR/run.md`. **Field mapping — be exact:** a handoff `slug=` (the resolved *spec* slug from a `spec new`/`spec go` step) maps to `run.md`'s `spec-slug:` field, **never** to the top-level `slug:` (which is the flow's own slug, already populated at first run and never overwritten); handoff `branch=` → `branch:`; handoff `worktree=` → `worktree:`. Only fill fields that are still empty — never rewrite an already-recorded identifier (the first value a run commits to is its identity; a later differing value would be the clobber `doctor` looks for). This keeps `run.md` the concrete, resolved anchor `doctor` compares `context.md` against. 6. Update `RUN_DIR/progress.md` — change this step's `[ ]` to `[x]` (keep the original `{{placeholder}}` text; only the run used resolved values). **Never modify the definition's `FLOW_DIR/flow.md`** — it stays a pristine template. Leave `Status` in `progress.md` as `Active` until all steps complete. @@ -114,7 +115,7 @@ For each pending step in order: ## Step 3: Final report -After all steps are complete, update `Status` in `RUN_DIR/progress.md` to `Complete` and remove `RUN_DIR/context.md` (a fresh run starts clean). Leave `RUN_DIR/run.md` in place — it is the completed run's identity record and is harmless once `context.md` is gone (a subsequent first run re-seeds it in Step 1). Leave the definition's `FLOW_DIR/flow.md` untouched — it was never mutated. +After all steps are complete, update `Status` in `RUN_DIR/progress.md` to `Complete` and remove `RUN_DIR/context.md` (a fresh run starts clean). Leave `RUN_DIR/progress.md` (now all `[x]`, `Status: Complete`) and `RUN_DIR/run.md` in place — together they are the completed run's record. This finished state is **not** a dead end: because `progress.md` survives, the next `/flow go` of this flow hits Step 1's **completed run → re-seed** branch (all `[x]` / `Status: Complete`), which archives this instance and seeds a clean new run — so a completed flow can always be run again. Leave the definition's `FLOW_DIR/flow.md` untouched — it was never mutated. Report: ``` From 8967bd29e860d50c69eb851687be2f8594f27d03 Mon Sep 17 00:00:00 2001 From: Siddharth Kapoor Date: Fri, 10 Jul 2026 12:57:35 -0400 Subject: [PATCH 8/8] docs(flow): reconcile run-identity field naming across go/doctor/flow-dir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A step handoff reports slug= (the resolved spec slug), but run.md has two slug-like fields — the flow's own slug: and spec-slug: — so the backfill target and doctor's comparison field were ambiguous. Pin one canonical mapping everywhere: handoff slug= -> run.md spec-slug: (never the flow slug:), branch= -> branch:, worktree= -> worktree:. go.md (backfill), doctor.md (Check 1), and flow-dir.md now spell out and share the same field names, so Check 1's identity match is reliable. --- skills/flow/references/flow-dir.md | 7 +++++++ skills/flow/references/workflows/doctor.md | 9 +++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/skills/flow/references/flow-dir.md b/skills/flow/references/flow-dir.md index a6f6179..f68449b 100644 --- a/skills/flow/references/flow-dir.md +++ b/skills/flow/references/flow-dir.md @@ -99,6 +99,13 @@ All mutable run-state lives in a **run instance**, which is **always local to th `run.md` is the run instance's **identity record**. Because the definition and `progress.md` only ever hold `{{placeholders}}`, the resolved branch / spec-slug / worktree of a real run live nowhere in the definition — `run.md` (and `context.md`'s handoffs) are the only place they exist. `doctor` reads `run.md` as the authoritative "what is this run" anchor to distinguish a legitimately-interrupted `context.md` from one clobbered by a different run. +`run.md` uses these field names, and **`go` (backfill) and `doctor` (Check 1) must use the same ones** — one canonical name per identifier, no synonyms: + +- `slug:` — the **flow's** own slug (the definition directory name); set at first run, never overwritten. +- `branch:` — receives a handoff `branch=` value. +- `spec-slug:` — receives a handoff `slug=` value (the resolved **spec** slug from a `spec new`/`spec go` step). Note the handoff token is `slug=` but the recorded field is `spec-slug:` precisely so it is never confused with the flow `slug:` above. +- `worktree:` — receives a handoff `worktree=` value. + Resolve it the same way in every workflow that runs or inspects a flow: ```bash diff --git a/skills/flow/references/workflows/doctor.md b/skills/flow/references/workflows/doctor.md index 832ce65..9a78967 100644 --- a/skills/flow/references/workflows/doctor.md +++ b/skills/flow/references/workflows/doctor.md @@ -44,8 +44,13 @@ For each flow in the target set, read the definition `flow.md`, the definition's **Check 1 — Cross-run clobber (FAIL only on a positive clobber signal).** Only applies to a `context.md` that exists. Compare the context against the run's **recorded identity** (`RUN_IDENTITY` from `run.md`) — NOT against the flow's step text, which only ever holds `{{placeholders}}` and so has nothing concrete to match a real run against. -Scan the `context.md` handoff lines for concrete identifiers: `branch=`, spec `slug=`, `worktree=` values. Then: -- **Positive clobber → FAIL.** `RUN_IDENTITY` is available AND records a concrete `branch` / `spec-slug` / `worktree`, AND the `context.md` names a *different* value for that same identifier (e.g. `run.md` says `branch: feat/auth` but `context.md` handoffs name `go-rust-odin-templates`) → `FAIL: context.md names '{X}' but this run's identity is '{Y}' — two runs likely shared one state file`. This is the only signal that a foreign run clobbered the state. +Scan the `context.md` handoff lines for concrete identifiers and map each to the `run.md` field it must be compared against — **use the same field names go.md writes** (see go.md Step 1's `run.md` template and Step 2.5's backfill mapping): +- handoff `branch=` ↔ `run.md` `branch:` +- handoff `slug=` (the resolved *spec* slug) ↔ `run.md` `spec-slug:` — **never** the flow's own top-level `slug:` +- handoff `worktree=` ↔ `run.md` `worktree:` + +Then: +- **Positive clobber → FAIL.** `RUN_IDENTITY` is available AND records a concrete `branch` / `spec-slug` / `worktree`, AND the `context.md` names a *different* value for that same identifier (comparing each handoff token to its mapped `run.md` field above — e.g. `run.md` says `branch: feat/auth` but a `context.md` handoff says `branch=go-rust-odin-templates`, or `run.md` `spec-slug: add-oauth` vs a handoff `slug=teardown-migrator`) → `FAIL: context.md names '{X}' but this run's identity is '{Y}' — two runs likely shared one state file`. This is the only signal that a foreign run clobbered the state. - **Legacy context beside a global definition → FAIL.** A `FLOW_DIR/context.md` under `~/.codevoyant/flows` is a clobber by construction (global definitions must never hold run-state), regardless of identity → `FAIL: legacy context.md beside global definition`. - **Matches identity → PASS.** The context's identifiers agree with `RUN_IDENTITY` (or the run has committed to no conflicting identifier yet) → `PASS` (a legitimately-interrupted run — the resume payload). - **Can't determine → PASS (preserve).** No `run.md` / no recorded identity, or `context.md` carries no comparable identifier → there is **no positive clobber signal**, so do NOT flag it. Record `PASS: no clobber signal (identity unavailable — preserved)`. Uncertainty must never escalate to a delete.