Skip to content
Draft
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ When you pick a spec from the interactive menu, devloop uses your configured run

## Specs

A good spec is short, concrete, and verifiable. Start from [`skills/devloop-spec/references/spec-template.md`](skills/devloop-spec/references/spec-template.md). The bundled `devloop-spec` skill can also render a sibling HTML companion with [`skills/devloop-spec/scripts/render.sh`](skills/devloop-spec/scripts/render.sh).
A good spec is short, concrete, and verifiable. Start from [`skills/devloop-spec/references/spec-template.md`](skills/devloop-spec/references/spec-template.md).

Strict mode is on by default: specs need `## Acceptance criteria`, and reviews must pass both the spec gate and engineering quality gate.

## Skills

Devloop ships two agent skills, installed into `~/.claude/skills` and `~/.agents/skills`:

- [`devloop-spec`](skills/devloop-spec/SKILL.md) — turns a rough idea, notes, a URL, or an interview into one concrete, devloop-ready spec, with optional HTML rendering.
- [`devloop-spec`](skills/devloop-spec/SKILL.md) — turns a rough idea, notes, a URL, or an interview into one concrete, devloop-ready spec.
- [`devloop-review`](skills/devloop-review/SKILL.md) — judges each pass against the spec and engineering quality gates, returning ACCEPT, REJECT, or UNCLEAR with fix instructions.

## Runtime
Expand Down
147 changes: 22 additions & 125 deletions scripts/devloop_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,7 @@ equals() {
[[ "$actual" == "$expected" ]] || fail "$label expected [$expected], got [$actual]"
}

count_occurrences() {
local file="$1"
local needle="$2"
awk -v needle="$needle" '
{
line = $0
while ((idx = index(line, needle)) > 0) {
count++
line = substr(line, idx + length(needle))
}
}
END { print count + 0 }
' "$file"
}

bash -n "$REPO_ROOT/devloop" "$SCRIPTS_DIR/install.sh" "$SCRIPTS_DIR/uninstall.sh" "$SCRIPTS_DIR/skill_helpers.sh" "$SCRIPTS_DIR/release.sh" "$REMOTE_INSTALLER" "$REPO_ROOT/site/public/install" "$REPO_ROOT/skills/devloop-spec/scripts/render.sh"
bash -n "$REPO_ROOT/devloop" "$SCRIPTS_DIR/install.sh" "$SCRIPTS_DIR/uninstall.sh" "$SCRIPTS_DIR/skill_helpers.sh" "$SCRIPTS_DIR/release.sh" "$REMOTE_INSTALLER" "$REPO_ROOT/site/public/install"
ok "bash syntax"

DEVLOOP_LIB=1
Expand Down Expand Up @@ -99,13 +84,32 @@ contains "$readme_text" "cd devloop" "README source install"
contains "$readme_text" "./scripts/install.sh" "README source install"
contains "$readme_text" "\`devloop update\`" "README command table"
not_contains "$readme_text" "\`devloop $obsolete_update_command\`" "README command table"
not_contains "$readme_text" "render.py" "README Python spec renderer"
not_contains "$readme_text" "render.sh" "README spec renderer"
not_contains "$readme_text" "sibling HTML companion" "README spec renderer"
ok "README install docs"

skill_path="$("$REPO_ROOT/devloop" spec --skill-path)"
[[ "$skill_path" == "$REPO_ROOT/skills/devloop-spec/SKILL.md" ]] || fail "unexpected skill path: $skill_path"
contains "$("$REPO_ROOT/devloop" spec --print-skill)" "name: devloop-spec" "spec skill"
ok "spec skill path"

spec_skill_text="$(cat "$REPO_ROOT/skills/devloop-spec/SKILL.md")"
spec_template_text="$(cat "$REPO_ROOT/skills/devloop-spec/references/spec-template.md")"
contains "$spec_skill_text" "ASCII" "spec skill ASCII diagrams"
contains "$spec_skill_text" "plain code fence" "spec skill plain diagram fence"
not_contains "$spec_skill_text" "mermaid" "spec skill Mermaid guidance"
not_contains "$spec_skill_text" "Mermaid" "spec skill Mermaid guidance"
not_contains "$spec_skill_text" "render.py" "spec skill Python renderer"
not_contains "$spec_skill_text" "python3" "spec skill Python renderer"
not_contains "$spec_skill_text" "render.sh" "spec skill renderer"
not_contains "$spec_skill_text" "HTML companion" "spec skill renderer"
contains "$spec_template_text" $'\n```\n current' "spec template plain ASCII fence"
contains "$spec_template_text" "Current behavior -> Implementation change -> Expected outcome" "spec template ASCII schematic"
not_contains "$spec_template_text" '```mermaid' "spec template Mermaid fence"
not_contains "$spec_template_text" "flowchart LR" "spec template Mermaid syntax"
ok "spec ASCII diagram guidance"

contains "$(cat "$REPO_ROOT/README.md")" "\`devloop --create-pr <spec.md>\`" "README PR mode"
contains "$(cat "$REPO_ROOT/README.md")" "maintain a draft PR (requires \`gh\`)" "README PR mode"
ok "README PR guidance"
Expand Down Expand Up @@ -149,113 +153,6 @@ ok "skill metadata"
work=$(mktemp -d "${TMPDIR:-/tmp}/devloop-test.XXXXXX")
trap 'rm -rf "$work"' EXIT

renderer_script="$REPO_ROOT/skills/devloop-spec/scripts/render.sh"
[[ -x "$renderer_script" ]] || fail "missing executable bash spec renderer"

regular_renderer_fixture="$work/spec-render-fixture.md"
cat > "$regular_renderer_fixture" <<'SPEC'
---
status: draft
type: feat
created: 2026-06-16
pr: null
---

# Renderer Fixture
Render the spec with light styling and robust escaping.

## Problem
Renderer regressions are hard to spot from markdown alone.

```markdown
## This is inside a code fence
- not a real section
</pre><script>alert("x")</script>&
```

The paragraph after the fenced heading still belongs to Problem.

## Outcome
The HTML companion renders the spec sections.

## Scope
- In: renderer fixture
- Out: browser automation

## Behavior
### Happy path
1. Run the bundled renderer.
2. HTML is written next to the markdown.

### Edge cases
- HTML-sensitive fenced content is escaped.

## Acceptance criteria
1. The generated HTML uses the light theme.

## Test plan
- Red: Not applicable for fixture.
- Green: `skills/devloop-spec/scripts/render.sh <fixture>`
- Full: `bash scripts/devloop_test.sh`
- Coverage: Not applicable for Bash fixture.

## Constraints
- Must: keep markdown canonical.
- Avoid: editing generated HTML by hand.
- Existing convention: derived files sit beside the source markdown.

## Notes
No gaps.
SPEC
if "$renderer_script" >/tmp/devloop-renderer-usage.out 2>&1; then
fail "spec renderer accepted missing argument"
fi
if "$renderer_script" "$regular_renderer_fixture" "$regular_renderer_fixture" >/tmp/devloop-renderer-usage.out 2>&1; then
fail "spec renderer accepted extra argument"
fi
renderer_output="$("$renderer_script" "$regular_renderer_fixture")"
[[ -f "$renderer_output" ]] || fail "spec renderer did not create HTML"
contains "$(cat "$renderer_output")" "--bg: #ffffff" "spec renderer light theme"
not_contains "$(cat "$renderer_output")" '<summary><span class="chev">▶</span>This is inside a code fence</summary>' "spec renderer fenced heading"
contains "$(cat "$renderer_output")" '&lt;/pre&gt;&lt;script&gt;alert(&quot;x&quot;)&lt;/script&gt;&amp;' "spec renderer escaped fenced HTML"
contains "$(cat "$renderer_output")" "The paragraph after the fenced heading still belongs to Problem." "spec renderer fenced content"
contains "$(cat "$renderer_output")" $'<details class="section open" open>\n <summary><span class="chev">▶</span>Acceptance criteria</summary>' "spec renderer acceptance open"
not_contains "$(cat "$renderer_output")" "mermaid.esm.min.mjs" "spec renderer without Mermaid"
expected_renderer_output="$(cd -P "$(dirname "$regular_renderer_fixture")" >/dev/null 2>&1 && pwd)/$(basename "${regular_renderer_fixture%.md}.html")"
equals "$renderer_output" "$expected_renderer_output" "spec renderer output path"

mermaid_renderer_fixture="$work/spec-render-mermaid-fixture.md"
cat > "$mermaid_renderer_fixture" <<'SPEC'
---
status: draft
type: feat
created: 2026-06-16
pr: null
---

# Mermaid Renderer Fixture
Render Mermaid fences without requiring local dependencies.

```mermaid
flowchart LR
Modes["Gmail/Outlook | IMAP"] --> Result["Rendered HTML"]
Danger["</pre>"] --> Result
```

## Problem
Mermaid should render in the browser when diagrams are present.

## Acceptance criteria
1. The generated HTML imports Mermaid exactly once.
SPEC
mermaid_renderer_output="$("$renderer_script" "$mermaid_renderer_fixture")"
[[ -f "$mermaid_renderer_output" ]] || fail "Mermaid spec renderer did not create HTML"
contains "$(cat "$mermaid_renderer_output")" '<pre class="mermaid">' "spec renderer Mermaid pre"
contains "$(cat "$mermaid_renderer_output")" 'Modes[&quot;Gmail/Outlook | IMAP&quot;]' "spec renderer Mermaid pass-through"
contains "$(cat "$mermaid_renderer_output")" 'Danger[&quot;&lt;/pre&gt;&quot;]' "spec renderer Mermaid escaping"
equals "$(count_occurrences "$mermaid_renderer_output" "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs")" "1" "spec renderer Mermaid import count"
ok "spec renderer"

equals "$(sed -n '1p' "$REPO_ROOT/site/public/VERSION")" "$version" "site VERSION matches root VERSION"
ok "site version file"

Expand Down Expand Up @@ -1299,12 +1196,12 @@ PATH="$install_path" command -v gum >/dev/null 2>&1 || fail "installer did not m
PATH="$install_path" command -v fzf >/dev/null 2>&1 || fail "installer did not make fzf available"
[[ -f "$install_home/.agents/skills/devloop-spec/SKILL.md" ]] || fail "installer did not install Codex spec skill"
[[ -f "$install_home/.agents/skills/devloop-spec/references/spec-template.md" ]] || fail "installer did not install Codex spec template reference"
[[ -x "$install_home/.agents/skills/devloop-spec/scripts/render.sh" ]] || fail "installer did not install Codex spec renderer"
[[ ! -e "$install_home/.agents/skills/devloop-spec/scripts/render.sh" ]] || fail "installer installed removed Codex spec renderer"
[[ ! -e "$install_home/.agents/skills/devloop-spec/scripts/render.py" ]] || fail "installer installed removed Codex Python spec renderer"
[[ -f "$install_home/.agents/skills/devloop-review/SKILL.md" ]] || fail "installer did not install Codex review skill"
[[ -f "$install_home/.agents/skills/devloop-review/.devloop-checksum" ]] || fail "installer did not write Codex checksum"
[[ -f "$install_home/.claude/skills/devloop-spec/SKILL.md" ]] || fail "installer did not install Claude spec skill"
[[ -x "$install_home/.claude/skills/devloop-spec/scripts/render.sh" ]] || fail "installer did not install Claude spec renderer"
[[ ! -e "$install_home/.claude/skills/devloop-spec/scripts/render.sh" ]] || fail "installer installed removed Claude spec renderer"
[[ ! -e "$install_home/.claude/skills/devloop-spec/scripts/render.py" ]] || fail "installer installed removed Claude Python spec renderer"
[[ -f "$install_home/.claude/skills/devloop-review/SKILL.md" ]] || fail "installer did not install Claude review skill"
[[ -f "$install_home/.claude/skills/devloop-review/.devloop-checksum" ]] || fail "installer did not write Claude checksum"
Expand Down
33 changes: 10 additions & 23 deletions skills/devloop-spec/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ Produce exactly one implementation spec that conforms to the devloop standard. T

Use this skill for both cold-start interviews and distilling existing material. Do not hand off to a separate interview or spec-writing skill.

The markdown spec is the source of truth that `devloop` will use as implementation input. If a file is written and the environment supports it, also render the optional HTML companion.
The markdown spec is the source of truth that `devloop` will use as implementation input. Author diagrams as ASCII art inside plain code fences so terminal readers, GitHub, and plain text show the same schematic.

Available resources:

- `references/spec-template.md`: read when drafting or validating the spec shape.
- `scripts/render.sh`: run after writing a markdown spec to create a sibling HTML companion.

## Scope Guard

Expand Down Expand Up @@ -70,7 +69,7 @@ If the spec depends on something cheap to verify, verify it rather than assertin

Read `references/spec-template.md` when creating the draft. Every section appears in this order. Frontmatter uses exactly these fields:

```markdown
````markdown
---
status: draft
type: feat|fix|chore
Expand All @@ -81,10 +80,10 @@ pr: null
# <Concise title>
<One-sentence subtitle that names the implementation slice and why it matters.>

```mermaid
flowchart LR
Current["Current behavior"] --> Change["Implementation change"]
Change --> Result["Expected outcome"]
```
current change result
------- ------ ------
Current behavior -> Implementation change -> Expected outcome
```

## Problem
Expand Down Expand Up @@ -121,14 +120,14 @@ flowchart LR

## 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.
- The subtitle is a plain one-sentence line directly under the H1.
- The Mermaid schematic is optional but preferred when it clarifies architecture, data flow, ownership, or before/after behavior. Omit it if it would be decorative or speculative.
- In Mermaid flowchart node labels, quote labels containing `|`. Use `Node["A | B"]`, not `Node[A | B]`.
- The ASCII schematic is optional but preferred when it clarifies architecture, data flow, ownership, or before/after behavior. Omit it if it would be decorative or speculative.
- Put schematics inside a plain code fence: three backticks on a line by themselves, ASCII diagram lines, then three closing backticks.
- Under `## Behavior`, use `### Happy path` and `### Edge cases` H3 headings, not plain `Happy path:` labels.
- Acceptance criteria must be singular, verifiable, and observable.
- Include concrete paths, commands, APIs, and behaviors when the context provides them.
Expand Down Expand Up @@ -157,18 +156,6 @@ Do not hard-code personal paths. Use this precedence:

Do not wrap the spec in a code fence unless the caller explicitly asks for a fenced snippet.

## HTML Companion

When a markdown spec is written to a file, render the interactive HTML companion if the bundled script is available:

```bash
scripts/render.sh <path-to-spec.md>
```

Run the command from the skill directory, or resolve `scripts/render.sh` relative to this skill's `SKILL.md`. The script writes `<path-to-spec>.html` next to the markdown.

If Mermaid fails in the browser, fix the markdown source and rerun the renderer. If rendering cannot run in the current environment, keep the markdown spec and say HTML was not generated.

## Signoff

After writing, report the spec path, HTML path if generated, inferred `type`, acceptance criteria, and remaining gaps. Offer `devloop --create-pr <spec path>` as the next handoff when a file path exists.
After writing, report the spec path, inferred `type`, acceptance criteria, and remaining gaps. Offer `devloop --create-pr <spec path>` as the next handoff when a file path exists.
8 changes: 4 additions & 4 deletions skills/devloop-spec/references/spec-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ pr: null
# <Concise title>
<One-sentence subtitle that names the implementation slice and why it matters.>

```mermaid
flowchart LR
Current["Current behavior"] --> Change["Implementation change"]
Change --> Result["Expected outcome"]
```
current change result
------- ------ ------
Current behavior -> Implementation change -> Expected outcome
```

## Problem
Expand Down
Loading
Loading