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
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ These landed after phase 10, and all are documented in `design/orchestration.md`
**`internal/artifact` owns the grammars, and `internal/validate` consumes them.** The task grammar used to live in the validator; a reader that disagreed with the validator about what a task is would be worse than no reader. The parser now states facts about a line (`Methodologies`, `Loose`, `HasCitation`) and turning a fact into a finding stays in `validate` — which is also what lets `map` read a malformed artifact instead of refusing exactly the file a user most needs to inspect.

**No search engine.** The obvious reach for `find` is an inverted index; at 352KB and 94 artifacts a linear pass ranks the whole workspace in 55ms, and Tantivy or its kin would cost a CGO surface or a second binary against a stdlib-only `go.mod` and a six-platform cross-compile. What precision needed here was not a better index but a better *unit*: BM25 over addressable regions rather than lines, so a hit comes back as something `show` accepts. The seam is `artifact.Search` — it takes artifacts and returns hits, and nothing outside that file knows how it found them.
- **`caveman.md`, the register the agent answers in.** The output budget belongs to the code: prose about the work is written once and then carried in every later request of the session, so narration is the part of a long run that can be cut without losing a fact. It ships as a *rule* rather than a skill because it is on by default, and a default the model has to decide to load is not one — the cost is what every rule costs, since the harnesses that read `rules/` preload it. One level (ultra) rather than a dial, because three descriptions of the register are three things to keep true instead of one, and nobody turns the dial.

What it must never compress is the line that keeps it honest: artifacts under `specs/`, `plans/`, `docs/`, anything a validator parses or a shell runs, quoted output, commit and PR bodies, and questions asked of the user. A denser EARS line is a finding, not a saving.

**The language is the third kickoff answer.** `autonomy.md` asks it with the other two and it lands in the artifact's frontmatter as `lang: en|wenyan`, graded by `checkKickoffAs` on exactly the terms `autonomy` and `ci` are — checked when present, absent meaning the run predates the question. There is no `--lang` flag on `spec new` or `plan new`: it is the one answer that can arrive after the file exists, so `scc patch fm <artifact> lang=wenyan` is the whole path to it, and a value neither the rule nor the validator knows is rolled back like any other bad edit. `TestTheRuleOffersEveryKickoffAnswerThisAccepts` is what stops the rule and the validator from naming different values.
- **The four seeded `docs/` anchors** (`assets.Seeds`). `init` writes `glossary.md`, `stack.md`, `wiki/index.md`, and `wiki/changelog.md` — the knowledge base's only fixed-name documents, each holding the format its validator checks. A seed is written once and tracked nowhere: not in the manifest, not by `scc update`.

`scc` is a redesign of `csdd` (`github.com/protonspy/csdd`), narrowed to spec-driven development and deliberately leaner. When reaching for something from there, port the *decision*, not the file. Already decided against: a TUI, an embedded web dashboard, an MCP server, a devcontainer.
Expand Down
4 changes: 2 additions & 2 deletions internal/artifact/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ var (
// 1.2 a task, by its number
// R1.2 a requirement, by its id
// specs/foo/ a decomposition leaf, by the spec it names
// #notes | Notes a section, by anchor slug or by the title as written
// notes:7 the 7th paragraph of that section
// #risks | Risks a section, by anchor slug or by the title as written
// risks:2 the 2nd paragraph of that section
// L120-160 an explicit line range, the escape hatch
//
// None of them is a line number except the last, which is why an address survives an
Expand Down
26 changes: 25 additions & 1 deletion internal/assets/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,24 @@ import (
// 12: the entry file's layout block is one column across all three harnesses. The
// padding is computed from the profile rather than written into the template,
// because a run of spaces that lines up for `.codex/` is ragged for `.opencode/`.
const Version = "12"
// 13: caveman.md — the output budget belongs to the code, and narration is the part of
// a long run that can be cut without losing a fact. A rule rather than a skill because
// it is on by default, and a default the model has to choose to load is not one. One
// level (ultra) rather than three, because a dial nobody turns is three descriptions of
// the register to keep true instead of one. The language it answers in is the third
// kickoff question in autonomy.md, recorded as `lang:` beside the two that were already
// there — the register is a decision about the whole run, so it belongs where the run's
// other two decisions are and not in a preference asked again every session. In the
// same pass, three places a real workspace showed scc stating a mechanism exactly and
// leaving the judgment unsaid — where an agent fills the gap with the cheapest
// reading. The plan-run command said "read the plan" where the skill it invokes says
// map it; the review agents asked for surrounding code without ever saying the diff
// already held it; and the wiki skill explained how a slug resolves at the moment the
// page is being named, without saying the name has to name the concept.
// Also: the wiki's pages move to docs/wiki/pages/, so index.md and changelog.md are
// told apart from content by where they sit rather than by their names — which is what
// stopped any other .md dropped into wiki/ from becoming a page, and then an orphan.
const Version = "13"

// The embedded tree. "all:" so nothing is silently dropped for having a name the
// default embed pattern skips.
Expand Down Expand Up @@ -141,6 +158,7 @@ func Workspace(h paths.Harness) []File {
// The methodology. Every one of these is scc's own content: an upgrade should
// deliver improvements to them, so none is Owned.
for _, rule := range []string{
"caveman.md",
"routing.md",
"autonomy.md",
"methodology.md",
Expand Down Expand Up @@ -282,6 +300,11 @@ var WorkflowSkills = []string{"plan-run"}
// slash command are derived from this one list, so the two cannot drift apart, and a
// skill added to either half above reaches workspaces that already exist through
// `scc update` on the same terms as any other managed file.
//
// The register the agent answers in was briefly a skill here and is now caveman.md,
// because it is on by default: a skill nobody invokes does nothing, and one the model
// must decide to load is not a default. What it costs is what every rule costs — it is
// preloaded where the harness reads rules/ — and that is the price of it being on.
func Skills() []string {
return append(append([]string{}, KnowledgeSkills...), WorkflowSkills...)
}
Expand All @@ -306,6 +329,7 @@ func Dirs(h paths.Harness) []string {
paths.PlansSeg,
paths.DocsSeg,
path.Join(paths.DocsSeg, paths.WikiSeg),
path.Join(paths.DocsSeg, paths.WikiSeg, paths.WikiPagesSeg),
path.Join(paths.DocsSeg, paths.RawSeg),
path.Join(paths.DocsSeg, paths.ADRSeg),
path.Join(paths.DocsSeg, paths.CodewikiSeg),
Expand Down
14 changes: 10 additions & 4 deletions internal/assets/templates/agents/code-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ git diff main...HEAD # or the base branch the work targets
git diff --stat main...HEAD
```

Then read what the work was supposed to be: `specs/<feature>/requirements.md`,
`design.md`, `tasks.md`, or the checklist in `plans/<name>.md`. **The artifact is the
standard the code is held to** — not the implementation's apparent intent. Build, test
and lint commands are in `{{.Rules}}/project.md`.
**The diff is your source; the repository is not.** It already carries every changed
line, so re-reading a file to look at them buys nothing. Open a file only when the diff
is genuinely not enough to judge a change, only if the diff touches it, and only once —
a review that fetches the same source three times spent its context on what it was
handed. When the work is in a worktree, read that path, never the main checkout's copy.

Then read what the work was supposed to be, on the same terms: `scc map <artifact>` for
its shape and `scc map show <artifact> <address>` for the part you need. **The artifact
is the standard the code is held to** — not the implementation's apparent intent. Build,
test and lint commands are in `{{.Rules}}/project.md`.

## The five gates — run every one, in this order

Expand Down
4 changes: 4 additions & 0 deletions internal/assets/templates/agents/security-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ git diff --stat main...HEAD
Judge what the change makes *possible*, not what the codebase already was. Pre-existing
issues in untouched code are worth one line at the end, not the body of the review.

That scope is also your read budget. The diff carries the changed lines already: open a
file only to follow reachability the diff cannot show you, only if the diff touches it,
and once. In a worktree, read that path rather than the main checkout's copy.

## The method — four passes, in this order

**1 · Map what the change adds to the attack surface.** Before judging anything, list
Expand Down
12 changes: 9 additions & 3 deletions internal/assets/templates/artifacts/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ ci: {{.CI}}
is read from there). Never both — two records of one fact disagree, and the copy
is the one that goes stale.

The same rule decides order: the order is the order things are written in, and
nothing restates it in prose. A group that must come first is moved up the list.

Delete this comment. -->

## Why
Expand All @@ -36,7 +39,10 @@ ci: {{.CI}}
- [ ] 1.1 (Unit) <description>
- [ ] 1.2 (TDD) <description>

## Notes
<!-- There is no notes section, and that is deliberate. Order is the list above.
What was decided and why is an ADR under docs/adr/, cited from the item it
governs. What changed and what shipped is git, which is a record nobody has to
maintain and nobody can contradict.

<!-- Order, dependencies between leaves, anything a reader needs to not merge these
in the wrong sequence. -->
A constraint that is none of those — "cannot merge before the migration window" —
goes on the item's own line, where whoever reads that item will see it. -->
11 changes: 8 additions & 3 deletions internal/assets/templates/commands/scc-plan-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ Use the `plan-run` skill.

Plan, and how to run it: $ARGUMENTS

Read the plan and name the groups back, numbered and in order, before writing any
code. The order is the one thing the user can correct cheaply now and expensively
after three merges.
Map the plan — `scc map <plan>` — and name the groups back, numbered and in order,
before writing any code. The order is the one thing the user can correct cheaply now
and expensively after three merges.

**Map it; do not open it.** A real plan runs to tens of kilobytes, and opening one as
the first act of a run puts all of it in context for every turn of a loop that lasts
hours. `map` answers the question you actually have here — the sections, the leaves,
the task counts, what is still open — and `map show` fetches the one part it did not.

Then take every answer the line above already gave and ask only for what is left.
"Implement the whole plan, one PR at the end, delivered when CI is green" has settled
Expand Down
13 changes: 8 additions & 5 deletions internal/assets/templates/docs/wiki/index.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# Wiki

The entry point. Every page under `wiki/` has to be reachable from here — directly,
or through a page that is — because a page nothing links to is a page nobody will
find again.
The entry point. Every page under `wiki/pages/` has to be reachable from here —
directly, or through a page that is — because a page nothing links to is a page
nobody will find again.

Pages link to each other as `[[page-slug]]`, where the slug is the filename without
its extension. A link that resolves to no page is reported, and so is a page this
index cannot reach.
its extension and without its directory. A link that resolves to no page is reported,
and so is a page this index cannot reach.

This file and `changelog.md` live here rather than in `pages/`: they are the wiki's
fixed documents, not pages, and neither is ever an orphan.

## Pages

Expand Down
4 changes: 2 additions & 2 deletions internal/assets/templates/entry.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Nothing loads these for you. Open the file whose moment has arrived, and open it
in a new session: a rule you read yesterday is not a rule you have read.
{{- end}}

Triggered by where you are in the work:
`caveman.md` is always on: the register you answer in. The rest, by where you are:

- `autonomy.md` — at kickoff, before writing anything
- `routing.md` — work arrives and needs a vehicle: a spec, or a plan
Expand All @@ -39,7 +39,7 @@ Read the source when you are about to change it, not to find it.

**Plans and specs** — `scc map` · `map <artifact>` · `map tasks <artifact> --next` ·
`map find "<terms>"` · `map show <artifact> <address>` · `map trace`. An address is a
name — `1.2` `R1.2` `#notes` `notes:7` `specs/<feature>/` — never a line number.
name — `1.2` `R1.2` `#risks` `risks:2` `specs/<feature>/` — never a line number.

**Changing one** — `scc patch check <artifact> 1.2`, plus `task` `add` `append` `fm`. Not
an editor: it resolves the address, re-validates, and rolls back an edit that adds a
Expand Down
8 changes: 4 additions & 4 deletions internal/assets/templates/rules/artifacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ without loading it either.
a line number**, so it survives an edit above it:

```
1.2 a task #notes a section, by anchor slug
R1.2 a requirement notes:7 the 7th paragraph of that section
1.2 a task #risks a section, by anchor slug
R1.2 a requirement risks:2 the 2nd paragraph of that section
specs/foo/ a leaf L120-160 an explicit range, the escape hatch
```

`find` returns addresses, which is what makes the pair work: search, then `show` only
the hit. A long `## Notes` with no headings inside it is still navigable — `scc map
the hit. A long section with no headings inside it is still navigable — `scc map
blocks` indexes its paragraphs by their opening sentence. Read the file directly only
when the question is about *this exact text*: prose you are about to rewrite.

Expand All @@ -39,7 +39,7 @@ when the question is about *this exact text*: prose you are about to rewrite.
scc patch check <artifact> 1.1 1.2
scc patch task <artifact> 1.2 --text "…" --method TDD --req R1.1,R1.2
scc patch add <artifact> --section tasks --number 1.3 --method Unit --text "…"
scc patch append <artifact> '#notes' --text - reads stdin, for paragraphs
scc patch append <artifact> '#risks' --text - reads stdin, for paragraphs
scc patch fm <artifact> pr=per-plan
```

Expand Down
14 changes: 10 additions & 4 deletions internal/assets/templates/rules/autonomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,29 @@
The spec phases are **autonomous by default**: write requirements, design, and
tasks, then start implementing. Do not stop for approval at each phase.

But autonomy is the user's call, so **ask, once, before writing anything** — two
But autonomy is the user's call, so **ask, once, before writing anything** — three
questions, together, in the same breath:

1. **Run automatically, or gate each phase for review?**
2. **When the PR is open, wait for CI, or finish there?**
3. **Answer in English, or in 文言文?** Classical Chinese at maximum terseness —
particles (之/乃/為/其), verb before object, subject dropped. Say the cost: its
"80-90% reduction" counts **characters, not tokens**, and CJK spends more tokens per
character, so the real saving is smaller and unmeasured. Governs speech, not artifacts.

Record both answers in the artifact's frontmatter, then never ask again for this
piece of work:
Record the answers in the artifact's frontmatter (`requirements.md` for a spec),
then never ask again for this piece of work:

```yaml
---
autonomy: auto # or: gated
ci: wait # or: no-wait
lang: en # or: wenyan — omit to mirror the user
---
```

`scc spec new <feature> --autonomy=auto --ci=wait` writes them for you.
`scc spec new <feature> --autonomy=auto --ci=wait` writes the first two;
`scc patch fm <artifact> lang=wenyan` writes the third without opening the file.

Recording them is what makes the run reproducible from the file and what stops a
second session from re-asking. Ask in conversation rather than reading a flag,
Expand Down
55 changes: 55 additions & 0 deletions internal/assets/templates/rules/caveman.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Caveman — the register you answer in

You talk short. You do not think short. **Ultra, on by default**, in every response from
the first, and it does not lapse because the session got long. One level, no dial: the
only decision available is turning it off ("stop caveman" / "modo normal").

**Ultra.** Strip conjunctions where cause and effect stay unambiguous. One word where
one word is enough. State each fact once — a fact you already gave does not come back
as a summary.

> Inline obj prop, new ref, re-render. `useMemo`.

**The output budget belongs to the code.** What you write is not only an answer, it is
context every later request of the session carries — so prose about the work is paid on
every turn after the one that produced it. The diff is the part that had to exist.

Drop articles, filler (just, really, basically, simply), pleasantries (sure, certainly,
happy to), hedging. Fragments are the norm. No narration of tool calls, no decorative
tables, no emoji, no preamble announcing the answer before the answer.

**Never invent abbreviations** — not `cfg`, `impl`, `req`, `auth`. The tokenizer splits
an invented short form into the same pieces as the full word: the saving measures zero
and the reader still decodes it. Standard acronyms are fine — DB, API, HTTP, CI, PR.
**No causal arrows**: `→` is its own token, replacing a word that was also one. Both are
compression that measures as nothing and costs clarity, which is the one trade never
worth taking.

**Language is the kickoff answer** — `lang:` in the artifact's frontmatter, `en` or
`wenyan`. Absent, mirror the user: Portuguese in, Portuguese out, compressed.

**Never name the mode.** No announcement, no third-person tag, no full answer followed
by a short recap. The next answer being short is the whole confirmation.

## What never compresses

The line is who reads the bytes, not taste. Compressing something a validator parses, a
shell runs, or a person greps for is not compression — it is damage.

- **Artifacts** under `specs/`, `plans/`, `docs/`. EARS lines, task lines and headings
are graded by `scc validate`; a denser requirement is a finding, not a saving.
- **Code, commands, paths, identifiers, error strings** — byte for byte.
- **Quoted output**: an error, a finding, an exit code. Quote the shortest decisive line
rather than the whole log, and quote that line exactly.
- **Commit messages and PR bodies.** [delivery.md](delivery.md) needs the body to say
what changed, which spec, and how it was verified — read by a person months later
with none of your context.
- **Questions you ask.** A compressed question gets a wrong answer you pay for all run.

## Where it lifts

For that passage only, with no announcement either way, wherever a misread is expensive:
a security warning · confirming something irreversible · a multi-step sequence whose
order blurs without conjunctions · anywhere the compression itself introduced the
ambiguity · any question the user had to repeat, which is evidence the short answer
failed. Answer that one in full, then carry on.
Loading
Loading