php artisan boost:add-skill monicahq/laravel-sabre
Save this content to: .claude/skills/speckit-companion-tasks/SKILL.md
## User Input
```text
$ARGUMENTS
```
<!-- speckit-companion:part speckit-hooks -->
## Pre-Execution Checks — stock spec-kit extension hooks
Companion runs **on top of** stock spec-kit, so a project's installed spec-kit **extensions** (git, and any others registered in `.specify/extensions.yml`) must still fire on a Companion run exactly as they do on a stock `/speckit.*` run. This is separate from Companion's own node-hooks (`.specify/companion.yml`): both fire. Like the rest of the pipeline, checking these hooks must **never fail the host command** — if anything is missing or malformed, skip silently and continue.
Let `<step>` be this command's phase: `specify`, `plan`, `tasks`, or `implement`.
**Before-hooks — run these *now*, before any of the work below.**
- Check whether `.specify/extensions.yml` exists in the project root. If it does not, skip silently — there are no hooks.
- If it exists, read it and look for entries under `hooks.before_<step>`. If the YAML cannot be parsed, skip hook checking silently and continue normally.
- Filter out hooks where `enabled` is explicitly `false`. A hook with no `enabled` field is enabled by default.
- Do **not** interpret or evaluate a hook's `condition` expression yourself: a hook with no `condition` (or a null/empty one) is executable; a hook with a non-empty `condition` is left to the HookExecutor — skip it here.
- For each executable hook, emit one block based on its `optional` flag:
- **Optional** (`optional: true`):
```
## Extension Hooks
**Optional Pre-Hook**: {extension}
Command: `/{command}`
Description: {description}
Prompt: {prompt}
To execute: `/{command}`
```
- **Mandatory** (`optional: false`):
```
## Extension Hooks
**Automatic Pre-Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}
Wait for the result of the hook command before proceeding to the Outline.
```
- If no before-hooks are registered, skip silently.
**After-hooks — run these once this command's work is fully reported, before handing off.**
- Re-check `.specify/extensions.yml`; if absent or unparseable, skip silently. Look under `hooks.after_<step>`, applying the same `enabled` / `condition` filtering as above.
- For each executable hook, emit one block:
- **Optional** (`optional: true`):
```
## Extension Hooks
**Optional Hook**: {extension}
Command: `/{command}`
Description: {description}
Prompt: {prompt}
To execute: `/{command}`
```
- **Mandatory** (`optional: false`):
```
## Extension Hooks
**Automatic Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}
```
- If no after-hooks are registered, skip silently.
For `specify`, branch creation is normally one of these `before_specify` hooks (the git extension); spec directory and file creation are always handled by the command body itself.
<!-- /speckit-companion:part speckit-hooks -->
## Outline
Produce a dependency-ordered task list organized **by user story** into phases (Setup → Foundational → one phase per story → Polish), so each story is an independently testable increment.
**Right-size this task list to the change.** Before drafting, read the recorded size from the spec's context — `.spec-context.json` → the `size` field (treat a missing value as `normal`). **Apply the budget to the step below, omitting anything it says to skip.**
- **`normal` or `oversized`** — produce the full phased task list exactly as the step describes. No trimming.
- **`simple`** — a small change needs the tasks, not the ceremony around them. Produce a **lean** list:
- **No baseline/setup task** for "run install/build to confirm green" — that is not real work.
- Group by phase still (Setup if any → Foundational → the work → Polish), but **drop the per-story `Goal` / `Independent Test` / `Checkpoint` blocks** — a small change ships in one pass, not as separate demoable slices.
- End with a **one-line** dependency note (what blocks what), **not** the full "Dependencies & Execution Order" + "Parallel Opportunities" prose.
- Keep every task line precise (the `T###`, the exact file, the requirement) — trim the framing, never the tasks themselves.
This budget governs the step that follows. Where it would produce something the budget skips, omit it.
1. Read `.specify/feature.json` for the feature directory, then record the **tasks START** so the step's duration begins now (the script stamps the real clock; do not hand-write tasks timing):
```bash
python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_directory> --step tasks --status tasking --kind start --by extension
```
Load `plan.md` and `spec.md` (required), plus `data-model.md`, `contracts/`, and `research.md` if present.
2. Create `<feature_directory>/tasks.md` organized **by user story**, so each story can be implemented, tested, and delivered as an independent increment. Use the line format `- [ ] **T###** [P?] [US#] Description · exact/file/path`:
- `[P]` marks a task that is **independent** of the others in its wave — a different file with no incomplete dependency, so it can be built in any order (or in parallel on a host that wants to).
- `[US#]` maps the task to a user story from the spec for traceability.
3. **Make the dependency structure explicit — group each phase's work into ordered waves, never a flat list.** A reader (human or agent) must see at a glance *which tasks are independent* and *where work has to wait*:
- A **wave** is a set of tasks that touch different files and don't depend on each other, so they can be built in any order. Head it with a line like `**Wave 1 — independent (different files):**` and tag each of its tasks `[P]`.
- Between waves, write an explicit join line — `**⟶ Wait for Wave 1 to finish, then:**` — before the tasks that depend on the previous wave. Those form the next wave (or run singly).
- A wave of one is fine — a single task, no `[P]`. Same-file or dependent tasks are **never** in the same wave. Group every genuinely-independent task of the phase into one wave, so the dependency boundaries are honest.
This wave layout is the execution map implement reads — it replaces the old scattered-`[P]` list. (Implement builds the tasks inline by default; the wave grouping documents the dependency order and tells a subagent-capable host which tasks *could* run together.)
4. Group the waves into phases, in this order:
- **Phase 1: Setup** — project structure, config, and tooling prerequisites shared by everything.
- **Phase 2: Foundational** — core infrastructure that BLOCKS all stories (shared models/types, providers, routing, persistence). No user-story work begins until this phase is done.
- **Phase 3 onward: one phase per user story**, in priority order (P1 first = the MVP slice). For each story: an optional `### Tests` block (include only when the spec or constitution asks for tests — write them to fail first), then `### Implementation` laid out as waves (foundation/models first, then the independent components/UI wave, then the integration wave), then a **Checkpoint** line stating the story is now independently functional and testable.
- **Final phase: Polish** — cross-cutting cleanup, docs, and validation against the spec's Success Criteria. **Single-owner validation:** by default this phase generates a task that runs the test/lint suites to validate against the Success Criteria. Skip that suite-run task ONLY when the project has explicitly handed validation to a post-implement hook — read `.specify/companion.yml` and look under `commands.implement.hooks.after.implement-exec` for a hook entry that carries the marker `owns: validation` (the project's explicit statement that this hook runs the Success-Criteria suites). Presence of a hook is NOT the signal — review, PR, and deploy hooks share this same anchor; only the `owns: validation` marker is. When a marked hook is present, emit a deferring task (`- [ ] **T###** Validate against Success Criteria — owned by the project's post-implement validation hook (no separate suite run)`) instead of a second run. With no marked hook (the common case, or `companion.yml` absent/malformed), Polish owns validation and generates the suite-run task as usual. Either way the suites run in exactly one place.
5. End with a **Dependencies & Execution Order** section: the phase dependencies (Setup → Foundational → stories → Polish) and a one-line restatement of each phase's waves (which wave blocks which). Each task names the concrete file it creates or edits.
6. **Capture the requirement→task map** so "which tasks cover FR-X?" is answerable from the context file (best-effort; one call per requirement; skip silently if `python3` is unavailable — the implement step fills each requirement's tests later). Carry each requirement's one-line text via `--title` so the requirement is captured as readable content, not just an id:
```bash
python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_directory> --coverage-req FR-001 --title "<the requirement's one-line text>" --tasks "T001,T004"
python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_directory> --step tasks --step-summary '{"summary": "<task count + phase shape in one line>"}'
```
**Output**: `<feature_directory>/tasks.md` organized by user story into dependency-ordered phases, each phase laid out as explicit waves with join points.
<!-- speckit-companion:part timing -->
## Timing — keep `.spec-context.json` honest
These rules apply to every Companion profile command. The extension records lifecycle timing with its own scripts wherever it can; these rules keep anything you append consistent with that and accurate for any dispatcher (terminal, IDE chat, or the GUI). The model is **finish-only**: each task and each substep records a *single* finish event, and its duration is the gap to the previous finish (or the step's start). Never a `start`+`complete` pair for a task or substep — a pair stamped at one instant is what produces `0s` ticks and bursts.
- **Never hand-edit `.spec-context.json`.** Record every finish by **running the writer script**, never by editing the JSON file yourself — a hand-authored edit is what corrupts the file (a duplicated `status` key). The script stamps the real clock, writes atomically, and is idempotent. The commands below are the only way you touch timing.
- **Self-close — clarify and analyze only.** When your own work for **clarify or analyze** ends, record the step finish (feature dir from `.specify/feature.json`):
```bash
python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_dir> --step <this step> --finish --by ai
```
`--finish` appends a single step-level complete and touches **nothing else** (it leaves `status`/`currentStep` to the lifecycle hooks). Do NOT self-close **specify, plan, tasks, or implement**: for each of those steps the extension stamps both boundaries (start and complete) itself — specify and implement from their own command bodies, plan and tasks from a body-recorded start plus the after-step hook's completion. A step-level `ai` complete there would land first, and because the completion append is idempotent (first writer wins), it would permanently block the hook's extension-stamped close and leave the step's duration untrusted.
- **Substeps — one finish each, via the script.** For each substep boundary (plan: `research`, `design`; tasks: `generate`), the moment that substep ends, run:
```bash
python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_dir> --step <step> --substep <name> --finish --by ai
```
One call per substep, each stamped with its own real clock at the moment it finishes — never two substeps in one batch, never a separate `start`. The delta between consecutive finishes is each substep's duration.
- **Implement — finishing a task *is* logging it (finish-only).** Recording a task's finish is the **closing action of that task**, done the instant its work is complete and before you start the next one — not a bookkeeping pass you batch at the end of a phase. The closing action is a single append (feature dir from `.specify/feature.json`):
```bash
python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_dir> --task <TaskID> --kind complete --by ai --did "<one-line summary of what this task did>" --files "<comma,separated,files,touched>" --append
```
`--append` writes **one line** to `.spec-context.events.jsonl` and does **not** read or rewrite the shared `.spec-context.json`, so it never hits the "read the file first" retry and **parallel workers can each append their own finish at the same time without contending** — the line carries its own real timestamp (`date -u` is stamped by the script). The `--did`/`--files` flags ride along so the Activity panel's Tasks card is populated from the script. **Do NOT hand-edit the `- [ ]` checkbox in `tasks.md`** — the script owns it: materialize flips it to `- [x]` from your appended finish, so a fanned-out subagent only appends and never touches the shared `tasks.md`. Do NOT hand-author per-task JSON and do NOT write a per-task `start`.
Then **fold the appended lines into `.spec-context.json` — per task, the moment each finish lands.** The fold is the second half of the task's closing action, run by the **MAIN agent only**, in the foreground, one task at a time — for your own task right after its append, and for a fanned-out worker's task the moment its result returns:
```bash
python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_dir> --materialize
```
`--materialize` is the one read-modify-write: it folds the finishes into the panel **and checks off the matching `tasks.md` boxes** for every journaled task, idempotently (re-folding never double-counts). The panel only sees folded finishes — the append log is not watched — so folding per task is what makes progress advance task by task instead of jumping in end-of-wave bursts. Workers never run `--materialize` (that would put two writers on the shared file); they only append, and the MAIN agent serializes every fold. Run it once more at each wave join as a backstop, and the end-of-step hook materializes anything left and fills any task you didn't journal. What's trustworthy here is the **per-task summary** (`did`/`files`) and the order tasks completed, plus the **step-level** start→complete span, which the scripts stamp exactly. The per-task *timestamps* are best-effort — they reflect when each finish was recorded, not a precisely measured duration; that's fine, the summaries are the point.
- **Never write the next step's start.** Only the next command appends the next step's start entry; writing it here makes the viewer render a phantom "Generating <next>…".
<!-- /speckit-companion:part timing -->
<!-- speckit-companion:part self-advance -->
## Self-advance — hand off to the next step
This is one step in the Companion pipeline. How the run continues depends on the environment you are running in; do not invoke a separate headless/deterministic run command for the everyday flow.
- **On an agentic CLI that keeps acting after a step finishes:** once this step's work is complete, read the Companion workflow definition (`speckit-extension/workflows/speckit-companion.workflow.yml`) to learn which step comes next, then continue into it on your own — dispatch the next step's `/speckit.companion.*` command and keep going through the pipeline.
- **Pause at every review gate.** Where the workflow marks a `gate` (e.g. review-spec, review-plan), stop and wait for approval rather than running past it. Only continue once the gate is approved.
- **Terminal step after implement.** After the implementation step finishes (and any commit step), the workflow's final step is `mark-complete`. Run it so the spec lands at `status: completed`. That step writes `completed` only through `write-context.py --mark-complete`, which refuses unless the spec is already `implemented` — never introduce a second completed-writer.
- **Degrade gracefully on a one-shot environment.** If your environment runs one step and then stops, the handoff simply does not fire: finish this step, record its progress, and stop. The run stays valid and resumable, and the next step is triggered manually (by the developer or the companion panel). Completion likewise stays a manual action there.
<!-- /speckit-companion:part self-advance -->
<!-- speckit-companion:part orchestrator -->
## Node hooks — run the project's `before`/`after` inserts
This command is assembled from ordered **nodes**. A project can attach its own work at the boundary *before* or *after* any node by declaring it in `.specify/companion.yml`. You are the runtime: read that file (if present) and run those hooks at the right moments. Like the rest of the pipeline, this must **never fail the host command** — degrade and continue.
**Find the hooks for this command.** Look up `commands.<this-command>.hooks` in `.specify/companion.yml`. It has two anchors, `before` and `after`, each keyed by a node id from this command's order. Run a node's `before` hooks immediately before that node's work, and its `after` hooks immediately after. When several hooks sit at one anchor, run them **top to bottom, in declared order**.
**Hook types:**
- `{ type: command, run: "<shell>" }` — run the shell command with your terminal/Bash tool, then continue. *If you have no terminal tool* (some chat-only providers), do not pretend to: report the command you would have run and continue.
- `{ type: prompt, text: "<instruction>" }` — treat the text as an inline instruction and act on it before moving on.
- `{ type: node, ref: <id> }` — read `.specify/companion/nodes/<id>.md` and carry out its body as if it were part of this command.
**Background hooks.** Any hook may add `background: true`. Kick it off and continue the pipeline immediately without waiting for it to finish — it must not hold the spec prisoner. Use it for slow, independent side-effects (a test run, a build, a notification): for a `command`, launch it detached (e.g. append `&` or use `nohup … &`); for a `node`/`prompt`, do its work without blocking the next step. Report its result whenever it lands, but never block on it. **Do not** mark a `background` hook on anything that writes `.spec-context.json` (the timing/capture calls): those are fast already and run a read-modify-write on the shared file, so two of them racing in the background can lose an update. Background is for side-effects, not bookkeeping.
**Failure handling (never abort the host command):**
- **No `.specify/companion.yml`** → there are no hooks; run the command exactly as written. Do not warn.
- **The file is malformed / unparseable** → ignore it, note one short warning, and run the shipped command unchanged.
- **A hook is anchored to a node that isn't in this run's order** (e.g. a recipe dropped it) → warn once and skip that anchor's hooks.
- **A `type: node` hook's `ref` file is missing** → this is a real misconfiguration: report it clearly and stop before doing damage, rather than silently skipping.
If a hook's own work fails (a `command` exits non-zero, a `node` can't complete), report it and — unless the failure clearly makes the rest unsafe — continue the pipeline. The host command's own output is never blocked by a hook.
<!-- /speckit-companion:part orchestrator -->
$ARGUMENTS
Companion runs on top of stock spec-kit, so a project's installed spec-kit extensions (git, and any others registered in .specify/extensions.yml) must still fire on a Companion run exactly as they do on a stock /speckit.* run. This is separate from Companion's own node-hooks (.specify/companion.yml): both fire. Like the rest of the pipeline, checking these hooks must never fail the host command — if anything is missing or malformed, skip silently and continue.
Let <step> be this command's phase: specify, plan, tasks, or implement.
Before-hooks — run these now, before any of the work below.
.specify/extensions.yml exists in the project root. If it does not, skip silently — there are no hooks.hooks.before_<step>. If the YAML cannot be parsed, skip hook checking silently and continue normally.enabled is explicitly false. A hook with no enabled field is enabled by default.condition expression yourself: a hook with no condition (or a null/empty one) is executable; a hook with a non-empty condition is left to the HookExecutor — skip it here.optional flag:
optional: true):
## Extension Hooks
Optional Pre-Hook: {extension}
Command: /{command}
Description: {description}
Prompt: {prompt}
To execute: `/{command}`
```
optional: false):
## Extension Hooks
**Automatic Pre-Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}
Wait for the result of the hook command before proceeding to the Outline.
After-hooks — run these once this command's work is fully reported, before handing off.
.specify/extensions.yml; if absent or unparseable, skip silently. Look under hooks.after_<step>, applying the same enabled / condition filtering as above.optional: true):
## Extension Hooks
**Optional Hook**: {extension}
Command: `/{command}`
Description: {description}
Prompt: {prompt}
To execute: `/{command}`
optional: false):
## Extension Hooks
**Automatic Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}
For specify, branch creation is normally one of these before_specify hooks (the git extension); spec directory and file creation are always handled by the command body itself.
Produce a dependency-ordered task list organized by user story into phases (Setup → Foundational → one phase per story → Polish), so each story is an independently testable increment.
Right-size this task list to the change. Before drafting, read the recorded size from the spec's context — .spec-context.json → the size field (treat a missing value as normal). Apply the budget to the step below, omitting anything it says to skip.
normal or oversized — produce the full phased task list exactly as the step describes. No trimming.simple — a small change needs the tasks, not the ceremony around them. Produce a lean list:
Goal / Independent Test / Checkpoint blocks — a small change ships in one pass, not as separate demoable slices.T###, the exact file, the requirement) — trim the framing, never the tasks themselves.This budget governs the step that follows. Where it would produce something the budget skips, omit it.
Read .specify/feature.json for the feature directory, then record the tasks START so the step's duration begins now (the script stamps the real clock; do not hand-write tasks timing):
python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_directory> --step tasks --status tasking --kind start --by extension
Load plan.md and spec.md (required), plus data-model.md, contracts/, and research.md if present.
Create <feature_directory>/tasks.md organized by user story, so each story can be implemented, tested, and delivered as an independent increment. Use the line format - [ ] **T###** [P?] [US#] Description · exact/file/path:
[P] marks a task that is independent of the others in its wave — a different file with no incomplete dependency, so it can be built in any order (or in parallel on a host that wants to).[US#] maps the task to a user story from the spec for traceability.Make the dependency structure explicit — group each phase's work into ordered waves, never a flat list. A reader (human or agent) must see at a glance which tasks are independent and where work has to wait:
**Wave 1 — independent (different files):** and tag each of its tasks [P].**⟶ Wait for Wave 1 to finish, then:** — before the tasks that depend on the previous wave. Those form the next wave (or run singly).[P]. Same-file or dependent tasks are never in the same wave. Group every genuinely-independent task of the phase into one wave, so the dependency boundaries are honest.
This wave layout is the execution map implement reads — it replaces the old scattered-[P] list. (Implement builds the tasks inline by default; the wave grouping documents the dependency order and tells a subagent-capable host which tasks could run together.)Group the waves into phases, in this order:
### Tests block (include only when the spec or constitution asks for tests — write them to fail first), then ### Implementation laid out as waves (foundation/models first, then the independent components/UI wave, then the integration wave), then a Checkpoint line stating the story is now independently functional and testable..specify/companion.yml and look under commands.implement.hooks.after.implement-exec for a hook entry that carries the marker owns: validation (the project's explicit statement that this hook runs the Success-Criteria suites). Presence of a hook is NOT the signal — review, PR, and deploy hooks share this same anchor; only the owns: validation marker is. When a marked hook is present, emit a deferring task (- [ ] **T###** Validate against Success Criteria — owned by the project's post-implement validation hook (no separate suite run)) instead of a second run. With no marked hook (the common case, or companion.yml absent/malformed), Polish owns validation and generates the suite-run task as usual. Either way the suites run in exactly one place.End with a Dependencies & Execution Order section: the phase dependencies (Setup → Foundational → stories → Polish) and a one-line restatement of each phase's waves (which wave blocks which). Each task names the concrete file it creates or edits.
Capture the requirement→task map so "which tasks cover FR-X?" is answerable from the context file (best-effort; one call per requirement; skip silently if python3 is unavailable — the implement step fills each requirement's tests later). Carry each requirement's one-line text via --title so the requirement is captured as readable content, not just an id:
python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_directory> --coverage-req FR-001 --title "<the requirement's one-line text>" --tasks "T001,T004"
python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_directory> --step tasks --step-summary '{"summary": "<task count + phase shape in one line>"}'
Output: <feature_directory>/tasks.md organized by user story into dependency-ordered phases, each phase laid out as explicit waves with join points.
.spec-context.json honestThese rules apply to every Companion profile command. The extension records lifecycle timing with its own scripts wherever it can; these rules keep anything you append consistent with that and accurate for any dispatcher (terminal, IDE chat, or the GUI). The model is finish-only: each task and each substep records a single finish event, and its duration is the gap to the previous finish (or the step's start). Never a start+complete pair for a task or substep — a pair stamped at one instant is what produces 0s ticks and bursts.
Never hand-edit .spec-context.json. Record every finish by running the writer script, never by editing the JSON file yourself — a hand-authored edit is what corrupts the file (a duplicated status key). The script stamps the real clock, writes atomically, and is idempotent. The commands below are the only way you touch timing.
Self-close — clarify and analyze only. When your own work for clarify or analyze ends, record the step finish (feature dir from .specify/feature.json):
python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_dir> --step <this step> --finish --by ai
--finish appends a single step-level complete and touches nothing else (it leaves status/currentStep to the lifecycle hooks). Do NOT self-close specify, plan, tasks, or implement: for each of those steps the extension stamps both boundaries (start and complete) itself — specify and implement from their own command bodies, plan and tasks from a body-recorded start plus the after-step hook's completion. A step-level ai complete there would land first, and because the completion append is idempotent (first writer wins), it would permanently block the hook's extension-stamped close and leave the step's duration untrusted.
research, design; tasks: generate), the moment that substep ends, run: python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_dir> --step <step> --substep <name> --finish --by ai
One call per substep, each stamped with its own real clock at the moment it finishes — never two substeps in one batch, never a separate start. The delta between consecutive finishes is each substep's duration.
.specify/feature.json): python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_dir> --task <TaskID> --kind complete --by ai --did "<one-line summary of what this task did>" --files "<comma,separated,files,touched>" --append
--append writes one line to .spec-context.events.jsonl and does not read or rewrite the shared .spec-context.json, so it never hits the "read the file first" retry and parallel workers can each append their own finish at the same time without contending — the line carries its own real timestamp (date -u is stamped by the script). The --did/--files flags ride along so the Activity panel's Tasks card is populated from the script. Do NOT hand-edit the - [ ] checkbox in tasks.md — the script owns it: materialize flips it to - [x] from your appended finish, so a fanned-out subagent only appends and never touches the shared tasks.md. Do NOT hand-author per-task JSON and do NOT write a per-task start.
Then fold the appended lines into .spec-context.json — per task, the moment each finish lands. The fold is the second half of the task's closing action, run by the MAIN agent only, in the foreground, one task at a time — for your own task right after its append, and for a fanned-out worker's task the moment its result returns:
python3 .specify/extensions/companion/scripts/write-context.py --feature-dir <feature_dir> --materialize
--materialize is the one read-modify-write: it folds the finishes into the panel and checks off the matching tasks.md boxes for every journaled task, idempotently (re-folding never double-counts). The panel only sees folded finishes — the append log is not watched — so folding per task is what makes progress advance task by task instead of jumping in end-of-wave bursts. Workers never run --materialize (that would put two writers on the shared file); they only append, and the MAIN agent serializes every fold. Run it once more at each wave join as a backstop, and the end-of-step hook materializes anything left and fills any task you didn't journal. What's trustworthy here is the per-task summary (did/files) and the order tasks completed, plus the step-level start→complete span, which the scripts stamp exactly. The per-task timestamps are best-effort — they reflect when each finish was recorded, not a precisely measured duration; that's fine, the summaries are the point.
This is one step in the Companion pipeline. How the run continues depends on the environment you are running in; do not invoke a separate headless/deterministic run command for the everyday flow.
speckit-extension/workflows/speckit-companion.workflow.yml) to learn which step comes next, then continue into it on your own — dispatch the next step's /speckit.companion.* command and keep going through the pipeline.gate (e.g. review-spec, review-plan), stop and wait for approval rather than running past it. Only continue once the gate is approved.mark-complete. Run it so the spec lands at status: completed. That step writes completed only through write-context.py --mark-complete, which refuses unless the spec is already implemented — never introduce a second completed-writer.before/after insertsThis command is assembled from ordered nodes. A project can attach its own work at the boundary before or after any node by declaring it in .specify/companion.yml. You are the runtime: read that file (if present) and run those hooks at the right moments. Like the rest of the pipeline, this must never fail the host command — degrade and continue.
Find the hooks for this command. Look up commands.<this-command>.hooks in .specify/companion.yml. It has two anchors, before and after, each keyed by a node id from this command's order. Run a node's before hooks immediately before that node's work, and its after hooks immediately after. When several hooks sit at one anchor, run them top to bottom, in declared order.
Hook types:
{ type: command, run: "<shell>" } — run the shell command with your terminal/Bash tool, then continue. If you have no terminal tool (some chat-only providers), do not pretend to: report the command you would have run and continue.{ type: prompt, text: "<instruction>" } — treat the text as an inline instruction and act on it before moving on.{ type: node, ref: <id> } — read .specify/companion/nodes/<id>.md and carry out its body as if it were part of this command.Background hooks. Any hook may add background: true. Kick it off and continue the pipeline immediately without waiting for it to finish — it must not hold the spec prisoner. Use it for slow, independent side-effects (a test run, a build, a notification): for a command, launch it detached (e.g. append & or use nohup … &); for a node/prompt, do its work without blocking the next step. Report its result whenever it lands, but never block on it. Do not mark a background hook on anything that writes .spec-context.json (the timing/capture calls): those are fast already and run a read-modify-write on the shared file, so two of them racing in the background can lose an update. Background is for side-effects, not bookkeeping.
Failure handling (never abort the host command):
.specify/companion.yml → there are no hooks; run the command exactly as written. Do not warn.type: node hook's ref file is missing → this is a real misconfiguration: report it clearly and stop before doing damage, rather than silently skipping.If a hook's own work fails (a command exits non-zero, a node can't complete), report it and — unless the failure clearly makes the rest unsafe — continue the pipeline. The host command's own output is never blocked by a hook.
How can I help you explore Laravel packages today?