Skip to content
Merged
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,26 @@ devloop --report-format markdown .specs/change.md 3
devloop --coder claude --reviewer codex .specs/change.md
```

## Generate A Spec

`devloop` bundles a reusable [`spec` skill](skills/spec/SKILL.md). The skill has two paths: it interviews from a cold start when the user only has a rough idea, or it distills notes, an issue, a file, URL, or pasted context into a devloop spec.

```sh
devloop spec
devloop spec "add retry behavior to the chat sender"
devloop spec --agent claude --output .specs/chat-retry.md notes.md
devloop spec --agent my-agent ./research.md
```

`--agent codex` is the default. `--agent claude` uses Claude's stdin mode. Any other `--agent` value is treated as a command that accepts the generated prompt on stdin and returns the markdown spec on stdout. With no context, the prompt tells the selected agent to use the bundled cold-start interview path before writing the final spec.

For agent-native skill systems:

```sh
devloop spec --skill-path
devloop spec --print-skill
```

## Write A Spec

Start with [`templates/spec.md`](templates/spec.md). A good spec is short and concrete:
Expand Down
123 changes: 123 additions & 0 deletions skills/spec/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
name: spec
description: Interview from a cold start or distill existing material into one devloop-compatible implementation spec. Use when the user wants a concrete spec for devloop, whether they provide notes, a file, a URL, research, conversation context, or only a rough idea.
---

# Spec

Produce one implementation spec that conforms to the devloop standard. This skill has two modes:

- Cold start: if the user has not provided enough source material, interview them one question at a time until the implementation target is clear.
- Distill: if the user supplied notes, a file, a URL, research, an issue, or conversation context, compress that material faithfully and flag any remaining gaps.

The output is the spec that `devloop` will use as its implementation input.

## Scope Guard

Write exactly one spec sized for one worktree and one PR. Push back before drafting when the request mixes multiple logical changes, depends on an unresolved preparatory refactor, or would plausibly exceed about 300 meaningful changed lines.

When the scope is too large, name the overflow, propose the smallest useful slice, and ask the user to confirm the slice before writing.

## Cold Start Interview

Use this mode when there is no document, URL, issue, or concrete context to distill.

- Ask one question at a time.
- Ask why before what.
- Skip obvious questions the repository or prior answers already settle.
- Push vague answers toward observable behavior and acceptance criteria.
- Name contradictions and ask which statement is true.
- Stop only when the spec can be written without TODOs, TBDs, or invented requirements.

Cover these points:

1. The actual problem and when it hurts.
2. The desired observable outcome.
3. Happy path behavior.
4. Edge cases and failure modes.
5. Files, commands, APIs, UI surfaces, or workflows in scope.
6. Explicitly out-of-scope work.
7. Hard constraints, existing conventions, and test expectations.

If the environment cannot ask interactive questions, write a draft with explicit `> **GAP:** ...` markers rather than inventing missing facts.

## Distill Existing Material

Resolve the input before drafting:

- File path: read it.
- URL: fetch it if the environment allows web access; otherwise record the URL in Notes as a source to verify.
- Pasted text: use it verbatim.
- Current conversation: use only the part clearly about this task; ask for the boundary if the conversation covers unrelated work.

Do not fill unsupported sections with plausible detail. Every line must trace to supplied context, repository evidence, or an explicit user answer.

## Standard

Every section appears in this order. Frontmatter uses exactly these fields:

```markdown
---
status: draft
type: feat|fix|chore
created: YYYY-MM-DD
pr: null
---

# <Concise title>

## Problem
<The real user pain or failure. Include the concrete moment this hurts.>

## Outcome
<The observable end state that means this worked.>

## Scope
- In: <paths, commands, APIs, UI surfaces, or behavior>
- Out: <explicit exclusions>

## Behavior
Happy path:
1. <User/system action>
2. <Expected observable result>

Edge cases:
- <Condition>: <expected result>

## Acceptance criteria
1. <Singular, verifiable requirement with observable evidence.>

## Test plan
- Red: <regression test to add/update first, or why not applicable>
- Green: <targeted command(s)>
- Full: <full test/typecheck/lint command(s)>
- Coverage: <100% coverage command, or why unavailable>

## Constraints
- Must: <hard requirement>
- Avoid: <forbidden approach, dependency, or churn>
- Existing convention: <repo pattern to preserve>

## Notes
<Only material implementation hints, risks, dependencies, migrations, or open questions.>
```

- `created` is today's date.
- Infer `type`: `feat` for new capability, `fix` for broken behavior, and `chore` for maintenance, docs, tests, dependencies, or refactors.
- The H1 is a concise title, not a sentence.
- Acceptance criteria must be singular, verifiable, and observable.
- Include concrete paths, commands, APIs, and behaviors when the context provides them.

## Gaps

When required information is still missing, replace that section's placeholder with:

```markdown
> **GAP:** <what is missing and why it matters>
```

Keep every standard section present, remove leftover placeholders, and list the count and names of remaining gaps in Notes.

## Output

When a caller provides an output path, write the spec there. Otherwise, write only the markdown spec to stdout or save it under `.specs/YYYY-MM-DD-<slug>.md` in the target repository. Do not wrap the spec in a code fence unless the caller explicitly asks for a fenced snippet.
31 changes: 31 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ import {
type Event,
type Sink,
} from "./devloop.ts";
import {
bundledSpecSkillPath,
generateSpec,
parseSpecArgs,
readBundledSpecSkill,
} from "./spec.ts";
import { createTuiSink } from "./tui.ts";

const argv = process.argv.slice(2);
if (argv[0] === "spec") await runSpecCommand(argv.slice(1));

if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) {
console.log(welcome());
process.exit(0);
Expand Down Expand Up @@ -92,3 +100,26 @@ function displayPath(
function resultLine(label: string, value: string) {
return `${`${label}:`.padEnd(10)}${value}`;
}

async function runSpecCommand(argv: string[]) {
const parsed = parseSpecArgs(argv);
if (typeof parsed === "string") {
const help = argv.includes("-h") || argv.includes("--help");
console[help ? "log" : "error"](parsed);
process.exit(help ? 0 : 2);
}

try {
if (parsed.type === "print-skill") console.log(await readBundledSpecSkill());
else if (parsed.type === "skill-path") console.log(bundledSpecSkillPath());
else {
const result = await generateSpec(parsed.options);
console.log(`spec: ${result.file}`);
console.log(`agent: ${result.agent}`);
}
process.exit(0);
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(2);
}
}
1 change: 1 addition & 0 deletions src/devloop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ Usage:
devloop [options] <spec.md> [max=5]

Common commands:
devloop spec "add retry behavior to the chat sender"
devloop .specs/change.md
devloop --tui .specs/change.md
devloop --plain .specs/change.md
Expand Down
Loading