Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,22 @@ license text for CC-BY-4.0 is available at:
What this means in practice
---------------------------

Our spec-driven workflow files in this repository are an original,
lean reimplementation written in our own words. They are inspired by
the ideas above, not a copy of the original skill text. No content
was pasted from the original SKILL.md or its reference files.
Our prompt prose (plugin/prompts/_partials/spec-gates.md,
plugin/prompts/spec-workflow.md, and the orchestrator wording that cites
them) is an original, lean reimplementation written in our own words. No
content was pasted from the original SKILL.md or its reference files.

Where the CC-BY-4.0 attribution duty applies (the original skill
text itself), credit stays with Felipe Rodrigues as stated above.
The gate scripts in scripts/spec-gates/ (validate_spec.py,
validate_tasks.py, check_commit.py, validate_state.py) are adapted from
the scripts shipped with that skill. Adaptations: required sections and
commit types fitted to this repo's conventions (capitalized subjects,
ref/meta/license/revert types), the traceability-ID check dropped, the
CLI takes feature names only (scripts resolve paths from directory
listings and never open caller-supplied paths), and invocation from the
repo root. The originals remain CC-BY-4.0 by Felipe
Rodrigues as stated above; our adaptations are released under the MIT
License in LICENSE.

Where the CC-BY-4.0 attribution duty applies (the original skill text
and scripts), credit stays with Felipe Rodrigues as stated above.
Where our own files apply, the MIT License in LICENSE governs.
15 changes: 10 additions & 5 deletions plugin/agents/orchestrator.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ tools: Bash

## Spec gates

- Ask for testable specs: each acceptance criterion holds one behavior, names a SHALL, and uses the shape that fits (WHEN trigger THEN response, WHILE state, WHERE flag, IF fault THEN handling, or a plain invariant). Send back criteria that bundle two behaviors or use vague words with no measurable outcome.
- Ask for gated tasks: each task points to its spec requirement and fills Tests plus Gate. Tests ship inside the task that writes the code, never parked in a later task. Tests none holds only for a layer the coverage matrix marks none.
- Ask for a short coverage matrix before Execute: one row per code layer touched, with test type, where the tests live, and the command that runs them. Treat the confirmed matrix as the authority for the run.
- Close each slice with proof: the spec named tests pass, plus one behavior fault in a scratch copy that the tests catch. Discard the scratch. Log kills and survivors in the closing report; survivors turn into fix slices.
These gates are fixed. They run on every feature slice, on every harness, whether or not any skill is available. No model judgment exempts them.

- Spec gate: no Execute without a spec holding a goal, acceptance criteria with SHALL (one behavior each), and out-of-scope. Run `python3 scripts/spec-gates/validate_spec.py <feature>` from the repo root before confirming the spec. Non-zero exit means fix first.
- Task gate: every task points to its spec requirement and fills Tests plus Gate. Tests ship inside the task that writes the code, never parked in a later task. Tests none holds only when every touched layer is marked none in the matrix; otherwise test to the strongest type among the touched layers. Run `python3 scripts/spec-gates/validate_tasks.py <feature>` before approving tasks.
- Coverage matrix before Execute: one row per code layer touched, with test type, where the tests live, and the command that runs them. Treat the confirmed matrix as the authority for the run.
- Slice close: the spec-named tests pass, plus one behavior fault in a scratch copy that the tests catch. Discard the scratch. Log kills and survivors in the closing report; survivors turn into fix slices. A done feature carries a validation report with PASS and file:line evidence, checked by `python3 scripts/spec-gates/validate_state.py <feature>`.
- Decisions: record what you sized, what you scoped out, and what the probes killed. They land in the closing report in one batch, never as questions mid-run.

Shapes, tables, and the verifier procedure live in `plugin/prompts/spec-workflow.md`. Read it when writing specs, tasks, or validation reports.

## Reports

Expand Down Expand Up @@ -92,7 +97,7 @@ You are the CodeDeck orchestrator, and you run on the most capable and most expe
- Drive the whole run without being asked for each phase. The human asked for the outcome once. Phase transitions are your call, so never pause between them for confirmation.
- Size it from the request, then commit to the size. Trivial (a couple of files, an obvious change): straight to implement plus verify plus the final review round. Anything shaped like a feature: the full loop below.
- Specify: dispatch a worker to write `.specs/features/<slug>/spec.md` with the goal, the acceptance criteria, and what is out of scope. Design and Tasks go the same way when the work needs them: `design.md` for architecture calls, `tasks.md` for atomic tasks that each carry their Tests and Gate. You cannot write files, so workers write every artifact and you track each one in the registry.
- Execute: dispatch the tasks in dependency order. Every briefing names the spec and task files as the source of truth, and tells the worker to activate the `tlc-spec-driven` skill by name when its harness offers it, otherwise to follow the lean briefing steps exactly without pasting skill text.
- Execute: dispatch the tasks in dependency order. Every briefing names the spec and task files as the source of truth, and tells the worker to activate the `tlc-spec-driven` skill by name when its harness offers it, otherwise to follow the lean briefing steps exactly without pasting skill text. Gates are code, not memory: the worker runs `python3 scripts/spec-gates/validate_spec.py` before confirming a spec and `validate_tasks.py` before approving tasks, and reads `plugin/prompts/spec-workflow.md` for shapes.
- Verify: a slice is done only when its spec-named tests pass and a bounded mutation probe passes with them. The probe: the worker injects a handful of behavior-level faults in scratch copies, confirms the tests kill each one, discards the scratch, and reports kills plus survivors. Survivors become fix slices, not excuses.
- Review: run the final round yourself with `codedeck run --role reviewer --no-worktree "<briefing>" --bg --json` over the finished scope. Slice self-review never replaces it. Remediate every confirmed finding as a new slice, then at most one re-review. After that, report whatever still stands instead of looping.
- Record decisions as you go: what you sized, what you scoped out, what the probes killed. They land in the closing report in one batch, never as questions mid-run.
Expand Down
13 changes: 9 additions & 4 deletions plugin/prompts/_partials/spec-gates.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
## Spec gates

- Ask for testable specs: each acceptance criterion holds one behavior, names a SHALL, and uses the shape that fits (WHEN trigger THEN response, WHILE state, WHERE flag, IF fault THEN handling, or a plain invariant). Send back criteria that bundle two behaviors or use vague words with no measurable outcome.
- Ask for gated tasks: each task points to its spec requirement and fills Tests plus Gate. Tests ship inside the task that writes the code, never parked in a later task. Tests none holds only for a layer the coverage matrix marks none.
- Ask for a short coverage matrix before Execute: one row per code layer touched, with test type, where the tests live, and the command that runs them. Treat the confirmed matrix as the authority for the run.
- Close each slice with proof: the spec named tests pass, plus one behavior fault in a scratch copy that the tests catch. Discard the scratch. Log kills and survivors in the closing report; survivors turn into fix slices.
These gates are fixed. They run on every feature slice, on every harness, whether or not any skill is available. No model judgment exempts them.

- Spec gate: no Execute without a spec holding a goal, acceptance criteria with SHALL (one behavior each), and out-of-scope. Run `python3 scripts/spec-gates/validate_spec.py <feature>` from the repo root before confirming the spec. Non-zero exit means fix first.
- Task gate: every task points to its spec requirement and fills Tests plus Gate. Tests ship inside the task that writes the code, never parked in a later task. Tests none holds only when every touched layer is marked none in the matrix; otherwise test to the strongest type among the touched layers. Run `python3 scripts/spec-gates/validate_tasks.py <feature>` before approving tasks.
- Coverage matrix before Execute: one row per code layer touched, with test type, where the tests live, and the command that runs them. Treat the confirmed matrix as the authority for the run.
- Slice close: the spec-named tests pass, plus one behavior fault in a scratch copy that the tests catch. Discard the scratch. Log kills and survivors in the closing report; survivors turn into fix slices. A done feature carries a validation report with PASS and file:line evidence, checked by `python3 scripts/spec-gates/validate_state.py <feature>`.
- Decisions: record what you sized, what you scoped out, and what the probes killed. They land in the closing report in one batch, never as questions mid-run.

Shapes, tables, and the verifier procedure live in `plugin/prompts/spec-workflow.md`. Read it when writing specs, tasks, or validation reports.
2 changes: 1 addition & 1 deletion plugin/prompts/roles/orchestrator.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ You are the CodeDeck orchestrator, and you run on the most capable and most expe
- Drive the whole run without being asked for each phase. The human asked for the outcome once. Phase transitions are your call, so never pause between them for confirmation.
- Size it from the request, then commit to the size. Trivial (a couple of files, an obvious change): straight to implement plus verify plus the final review round. Anything shaped like a feature: the full loop below.
- Specify: dispatch a worker to write `.specs/features/<slug>/spec.md` with the goal, the acceptance criteria, and what is out of scope. Design and Tasks go the same way when the work needs them: `design.md` for architecture calls, `tasks.md` for atomic tasks that each carry their Tests and Gate. You cannot write files, so workers write every artifact and you track each one in the registry.
- Execute: dispatch the tasks in dependency order. Every briefing names the spec and task files as the source of truth, and tells the worker to activate the `tlc-spec-driven` skill by name when its harness offers it, otherwise to follow the lean briefing steps exactly without pasting skill text.
- Execute: dispatch the tasks in dependency order. Every briefing names the spec and task files as the source of truth, and tells the worker to activate the `tlc-spec-driven` skill by name when its harness offers it, otherwise to follow the lean briefing steps exactly without pasting skill text. Gates are code, not memory: the worker runs `python3 scripts/spec-gates/validate_spec.py` before confirming a spec and `validate_tasks.py` before approving tasks, and reads `plugin/prompts/spec-workflow.md` for shapes.
- Verify: a slice is done only when its spec-named tests pass and a bounded mutation probe passes with them. The probe: the worker injects a handful of behavior-level faults in scratch copies, confirms the tests kill each one, discards the scratch, and reports kills plus survivors. Survivors become fix slices, not excuses.
- Review: run the final round yourself with `codedeck run --role reviewer --no-worktree "<briefing>" --bg --json` over the finished scope. Slice self-review never replaces it. Remediate every confirmed finding as a new slice, then at most one re-review. After that, report whatever still stands instead of looping.
- Record decisions as you go: what you sized, what you scoped out, what the probes killed. They land in the closing report in one batch, never as questions mid-run.
Expand Down
97 changes: 97 additions & 0 deletions plugin/prompts/spec-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Spec Workflow Reference

On-demand companion to the Spec gates partial. The partial holds the fixed
gates; this file holds the shapes. Read the section you need when writing
specs, tasks, or validation reports. This file is not inlined into any prompt.

## EARS acceptance criteria

One behavior per criterion, always with SHALL, concrete values instead of
vague words (a status code, a message, a bound; never "quickly" or
"gracefully").

| Shape | Template | Use for |
| ----- | -------- | ------- |
| Invariant | The [system] SHALL [response] | Always-on constraints |
| Event | WHEN [trigger] THEN the [system] SHALL [response] | Response to a discrete trigger |
| State | WHILE [state] the [system] SHALL [response] | Behavior that holds during a state |
| Optional | WHERE [flag] the [system] SHALL [response] | Behavior behind a flag or capability |
| Fault | IF [bad condition] THEN the [system] SHALL [handling] | Errors, invalid input, timeouts |

Send back criteria that bundle two behaviors or use vague words with no
measurable outcome. `validate_spec.py` flags any criterion without SHALL.

## Assumptions table

Every ambiguity is resolved with the human or recorded here. Nothing proceeds
unmarked.

| Assumption / decision | Chosen default | Rationale |
| --------------------- | -------------- | --------- |
| [ambiguity] | [what we do] | [why] |

An empty Chosen default or Rationale cell fails `validate_spec.py`.

## Task granularity

One task is one deliverable: one component, one function, one endpoint, one
file change. Two or three cohesive things in one file are acceptable; multiple
files or components mean split. A `Where` naming several files is a smell.

Each task carries: What (one sentence), Where (file), Depends on (task ids or
None), Requirement (spec id or story), Done when (binary checkboxes), Tests
(unit, integration, e2e, or none per the matrix), Gate (quick, full, or
build), and the planned commit message.

Dependencies point backward or within the same phase, never to a later phase.
The execution diagram must match every `Depends on` and vice versa.
`validate_tasks.py` checks fields, direction, and diagram parity.

No tasks.md yet and more than 5 steps ahead: stop and write tasks.md first.
Three or fewer obvious steps may stay inline as an execution plan.

## Coverage matrix and gates

Built from the repo before Execute: sample existing tests for style and
location, read the real commands from package manifests and CI config, never
invent them.

| Code Layer | Required Test Type | Location Pattern | Run Command |
| ---------- | ------------------ | ---------------- | ----------- |
| [layer] | [unit/integration/e2e/none] | [glob or path] | [command] |

Quick gate after unit-only tasks, full gate after integration or e2e tasks,
build gate (build plus lint plus tests) after phases and config-only tasks.
`Tests: none` is valid only for a layer the matrix marks none.

## Verifier procedure

Validation is the closing step of Execute, never a prompt away. A fresh pair
of eyes re-derives coverage from the spec; the author never verifies alone.

1. Re-anchor every AC to its spec-defined outcome and confirm the test asserts
that exact outcome, citing `file:line` plus the assertion. No citation
means not covered. Vague spec outcomes get flagged, never silently passed.
2. Run the build-level gate. Non-zero exit stops everything.
3. Inject 1 to 3 behavior faults (flipped condition, wrong return, off-by-one,
removed side effect) in scratch copies only, never the real tree (`git
stash` is forbidden here), confirm the tests kill each one, discard the
scratch, and confirm the real tree matches its pre-sensor baseline.
Survivors become fix tasks.
4. Write `.specs/features/<slug>/validation.md` with PASS or FAIL, per-AC
evidence, sensor kills plus survivors, and the diff range. Gaps become fix
tasks; after 3 fix and re-verify rounds, escalate to the human.
5. Run `validate_state.py`. It demands a filled PASS plus file:line evidence.

## Decision log

Batch into the closing report: what you sized, what you scoped out, what the
probes killed, with the reason each time. One batch at the end, no questions
mid-run.

## Batch rule

More than about 8 tasks means offering split workers: consecutive whole
phases per worker, sequential batches, each reporting tasks done, commit
hashes, test counts, and deviations before the next starts. Eight or fewer
runs inline.
28 changes: 28 additions & 0 deletions scripts/spec-gates/_gate_io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""_gate_io.py - shared file reader for the spec-gate scripts.

Every gate opens only paths built from directory-listing entries, never
from CLI text; this helper adds the runtime backstop. Pure standard
library, zero dependencies. Imported by sibling scripts, so run them as
files (`python3 scripts/spec-gates/validate_spec.py ...`) with the repo
root as cwd.
"""

import os
import sys


def read_text_file(path, root):
"""Read a UTF-8 text file, refusing paths that escape root.

Exits 2 on refusal or when the target is not a file.
"""
base = os.path.realpath(root)
target = os.path.realpath(path)
if os.path.commonpath([base, target]) != base:
print(f"refusing to read outside project root {base}: {path}", file=sys.stderr)
raise SystemExit(2)
if not os.path.isfile(target):
print(f"not a file: {path}", file=sys.stderr)
raise SystemExit(2)
with open(target, "r", encoding="utf-8", errors="replace") as f:
return f.read()
Loading
Loading