crumbs-spec 0.0.0-stage โ 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.ko.md +397 -0
- package/README.md +398 -2
- package/dist/src/check.js +72 -0
- package/dist/src/check.js.map +1 -0
- package/dist/src/cli.js +195 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config.js +151 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/dates.js +22 -0
- package/dist/src/dates.js.map +1 -0
- package/dist/src/graph.js +241 -0
- package/dist/src/graph.js.map +1 -0
- package/dist/src/headings.js +55 -0
- package/dist/src/headings.js.map +1 -0
- package/dist/src/init.js +114 -0
- package/dist/src/init.js.map +1 -0
- package/dist/src/log.js +65 -0
- package/dist/src/log.js.map +1 -0
- package/dist/src/markdown.js +323 -0
- package/dist/src/markdown.js.map +1 -0
- package/dist/src/messages.js +203 -0
- package/dist/src/messages.js.map +1 -0
- package/dist/src/model.js +4 -0
- package/dist/src/model.js.map +1 -0
- package/dist/src/report.js +14 -0
- package/dist/src/report.js.map +1 -0
- package/dist/src/rules/broken-anchor.js +23 -0
- package/dist/src/rules/broken-anchor.js.map +1 -0
- package/dist/src/rules/broken-link.js +21 -0
- package/dist/src/rules/broken-link.js.map +1 -0
- package/dist/src/rules/duplicate-policy.js +47 -0
- package/dist/src/rules/duplicate-policy.js.map +1 -0
- package/dist/src/rules/log-body.js +23 -0
- package/dist/src/rules/log-body.js.map +1 -0
- package/dist/src/rules/log-format.js +34 -0
- package/dist/src/rules/log-format.js.map +1 -0
- package/dist/src/rules/log-order.js +23 -0
- package/dist/src/rules/log-order.js.map +1 -0
- package/dist/src/rules/missing-backlink.js +42 -0
- package/dist/src/rules/missing-backlink.js.map +1 -0
- package/dist/src/rules/missing-code-path.js +16 -0
- package/dist/src/rules/missing-code-path.js.map +1 -0
- package/dist/src/rules/missing-flow.js +17 -0
- package/dist/src/rules/missing-flow.js.map +1 -0
- package/dist/src/rules/missing-section.js +31 -0
- package/dist/src/rules/missing-section.js.map +1 -0
- package/dist/src/rules/open-question.js +13 -0
- package/dist/src/rules/open-question.js.map +1 -0
- package/dist/src/rules/section-order.js +46 -0
- package/dist/src/rules/section-order.js.map +1 -0
- package/dist/src/rules/unreachable.js +27 -0
- package/dist/src/rules/unreachable.js.map +1 -0
- package/dist/src/rules/unterminated-comment.js +13 -0
- package/dist/src/rules/unterminated-comment.js.map +1 -0
- package/dist/src/skills.js +27 -0
- package/dist/src/skills.js.map +1 -0
- package/dist/src/templates/agents.js +54 -0
- package/dist/src/templates/agents.js.map +1 -0
- package/dist/src/templates/en.js +75 -0
- package/dist/src/templates/en.js.map +1 -0
- package/dist/src/templates/ko.js +75 -0
- package/dist/src/templates/ko.js.map +1 -0
- package/dist/src/templates/skills-en.js +260 -0
- package/dist/src/templates/skills-en.js.map +1 -0
- package/dist/src/templates/skills-ko.js +265 -0
- package/dist/src/templates/skills-ko.js.map +1 -0
- package/dist/src/version.js +7 -0
- package/dist/src/version.js.map +1 -0
- package/package.json +50 -4
package/README.md
CHANGED
|
@@ -1,3 +1,399 @@
|
|
|
1
|
-
#
|
|
1
|
+
# crumbs-spec ๐
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[ํ๊ตญ์ด](README.ko.md) ยท **English**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/Sienna94/crumbs-spec/actions/workflows/ci.yml) [](https://www.npmjs.com/package/crumbs-spec)
|
|
6
|
+
|
|
7
|
+
crumbs-spec leaves breadcrumbs (links) on every document so an agent never gets lost, and keeps a **living spec** that leads from the index to the detail document in one path. It describes how the repository behaves *right now* in responsibility-sized documents, checks with one command that those documents are properly linked, and ships the procedures an agent follows to write, find, ask about and ingest them as skills.
|
|
8
|
+
|
|
9
|
+
## Why we built it
|
|
10
|
+
|
|
11
|
+
While we ran dozens of spec documents with agents on a real app repository, the same three things kept happening.
|
|
12
|
+
|
|
13
|
+
1. Specs only survived as change units (per-feature `spec.md`, tickets), so months later there was no document that answered "what is the rule right now?".
|
|
14
|
+
2. Piling them up like a wiki put the same rule into several documents with slightly different wording, and nobody knew which one was right. A later benchmark had an agent find three such contradictions.
|
|
15
|
+
3. When agents read documents they leaned on folder structure and search, so they wandered into other areas or fell back to grep. One question cost eight documents.
|
|
16
|
+
|
|
17
|
+
"Write better documents" did not fix it. We needed **a tool that enforces structure by machine**. When every policy has exactly one owner, the link graph is checked by a machine, and the unknown is written as a question instead of a guess, agents and people reach the document by the same path. So we started scattering breadcrumbs.
|
|
18
|
+
|
|
19
|
+
## How it differs from other tools
|
|
20
|
+
|
|
21
|
+
Spec Kit is good at pre-implementation planning and LLM Wiki at accumulating sources. crumbs-spec fills the gap between them: **the current state that must survive after implementation**. It complements them rather than replacing them.
|
|
22
|
+
|
|
23
|
+
| | GitHub Spec Kit | OpenSpec | Karpathy's LLM Wiki | **crumbs-spec** |
|
|
24
|
+
|---|---|---|---|---|
|
|
25
|
+
| Document unit | One change (feature): `spec.md`, `plan.md`, `tasks.md` | Current-state spec + change deltas | Entity, concept and source-summary pages | **One responsibility (user flow)**. Each policy is owned by exactly one document |
|
|
26
|
+
| Point in time | Before implementation | Before and after | When a source is ingested | **Current behavior after implementation** |
|
|
27
|
+
| Existing code | States that it "does not infer" | Has onboarding, but no code-to-spec drafting | Targets sources, not code | **The crumbs-writer skill drafts from code and tests; the unknown becomes a `[needs confirmation]` question** |
|
|
28
|
+
| Navigation | Folders and file names | Folders and file names | Index first | **Index โ overview โ detail by links only**. No IDs, no front matter |
|
|
29
|
+
| Machine checks | Consistency across spec, plan and tasks | `openspec validate` (structure) and delta merge on archive | Orphan page and broken link lint (with LLM judgment) | **16 structural rules**: links, anchors, reachability, backlinks, duplicate policy names, evidence paths, section order, flow diagrams, log format, unterminated comments |
|
|
30
|
+
| Sync after implementation | The team picks a maintenance model | `archive`/`sync` for the change in hand | Feeds query results back into the wiki | **Agent rule ("update the owning document in the same change, even unasked") and a decision log** |
|
|
31
|
+
| Human readability | Prose | Prose | Prose | **Flow diagrams (Mermaid) and branch tables in the template** |
|
|
32
|
+
|
|
33
|
+
Kiro specs and BMAD sit close to Spec Kit (per-feature, before implementation) and are not listed separately; the Korean plan document compares all five.
|
|
34
|
+
|
|
35
|
+
From LLM Wiki we kept the raw/wiki/schema split, index-first navigation, the append-only log and human-supervised ingest. Three things are deliberately different: documents are organized by responsibility rather than entity (only concepts that several flows branch on get a page under `concepts/`), documents are maintained by agents but owned by people, and lint is deterministic with no LLM judgment. See section 1 of [docs/IMPLEMENTATION_PLAN.md](docs/IMPLEMENTATION_PLAN.md) (Korean) for the full comparison.
|
|
36
|
+
|
|
37
|
+
## What we cared about
|
|
38
|
+
|
|
39
|
+
- **A single source of truth for the current state.** No change directories. Documents explain "how it behaves now", not "what changed".
|
|
40
|
+
- **No guessing.** A rule not confirmed by code, tests or approved sources is written only as "right now it behaves like this" plus a `[needs confirmation]` (`[ํ์ธ ํ์]` with `lang: ko`) question. `check` reports one `open-question` warning per document that still has questions. Whether a statement is a guess is the skill's rule and the reviewer's job; `check` cannot tell.
|
|
41
|
+
- **One owner per policy, checked at the name level.** `check` rejects the same policy name in two documents (or twice in one). Two policies that say the same thing under different names are not detected; the writer skill's naming rules exist for that.
|
|
42
|
+
- **People-first prose.** No IDs, no status tables, no enforced grammar. Navigation lives in links and headings only.
|
|
43
|
+
- **Deterministic checks, human judgment.** `check` looks at structure only. Style, content and staleness are for skill guidance and people.
|
|
44
|
+
- **Decisions in one place.** When a question gets its answer, a `decision` entry goes into the log and the document points at it with a single dated link.
|
|
45
|
+
- **Two languages.** `lang` in `crumbs.json` selects the language of the templates, skills, messages and AGENTS block for the whole repository. It defaults to English (`en`); `ko` gives Korean. This README is the English guide.
|
|
46
|
+
|
|
47
|
+
## What happened when we used it (one run, self-reported)
|
|
48
|
+
|
|
49
|
+
Read this as an anecdote, not a benchmark. It was a single run with 18 questions, graded by the author, the questions were drawn from the existing spec system, the two corpora differ in size (38 documents against about 127), and the raw data comes from a private repository and cannot be published. The roadmap's example app and benchmark runner exist to replace it.
|
|
50
|
+
|
|
51
|
+
We rewrote three areas of a real app repository (about 40,000 lines of code) with the crumbs-writer skill without showing it any existing spec document. Then we asked the same 18 questions of the existing spec system and the crumbs system and let an agent answer from documents only.
|
|
52
|
+
|
|
53
|
+
**Cost of writing from zero** (one agent per domain, three in parallel)
|
|
54
|
+
|
|
55
|
+
| Domain | Code | Documents | Policies (confirmed / open questions) | Agent tokens | Writing time |
|
|
56
|
+
|---|---|---|---|---|---|
|
|
57
|
+
| Payment | ~20k lines | 19 | 100 (5 / 95) | 570k | 1 h 7 min |
|
|
58
|
+
| Viewer | ~23k lines | 40 | 194 (29 / 165) | 850k | 1 h 7 min |
|
|
59
|
+
| Auth | ~10k lines | 35 | 195 (64 / 131) | 790k | 1 h 13 min |
|
|
60
|
+
|
|
61
|
+
Writing time is the actual working time from the agent transcripts' timestamps, excluding idle gaps longer than ten minutes. With the three domains in parallel everything finished in about 1 h 15 min. In our own review of the output we found no invented policy, and the six places where the existing spec disagreed with the code were written as the code behaves; that review was done by the author, not independently. The documents were later regrouped by user flow into 38.
|
|
62
|
+
|
|
63
|
+
**Navigation cost on the same 18 questions** (documents only; reading code was forbidden)
|
|
64
|
+
|
|
65
|
+
crumbs-spec answered fewer questions correctly (15 against 17) and read fewer files and tokens.
|
|
66
|
+
|
|
67
|
+
| | Existing spec system | crumbs-spec |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| Correct answers | 17 / 18 (1 partial) | 15 / 18 (3 partial) |
|
|
70
|
+
| Files read in total | 70 (avg 3.9, max 8) | 64 (avg 3.6, max 6) |
|
|
71
|
+
| Fell back to grep | 3 | 1 |
|
|
72
|
+
| Tokens in total | 2.32M | 1.89M (โ18%) |
|
|
73
|
+
|
|
74
|
+
The existing system's partial answer came from failing to find the owning document; the three crumbs partials all reached the owning document in 3โ5 files but the writer had dropped details such as screen numbers and channel branches. We added a skill rule for this ("do not drop values that carry a condition"); the benchmark has not been re-run. Note the bias: the questions were drawn from the existing specs, which favors the existing system.
|
|
75
|
+
|
|
76
|
+
## Install and quick start
|
|
77
|
+
|
|
78
|
+
Node.js 20 or later, no runtime dependencies.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npx crumbs-spec init --skills all # document structure + the four agent skills
|
|
82
|
+
npx crumbs-spec init # document structure only
|
|
83
|
+
npx crumbs-spec check # check the link graph and structure
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Korean repositories: pass `--lang ko`.
|
|
87
|
+
|
|
88
|
+
`init` creates these files and never overwrites one that exists. With `--skills all` it also writes eight SKILL.md files, and in AGENTS.md it only ever replaces its own block between `<!-- crumbs:start -->` and `<!-- crumbs:end -->`.
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
crumbs.json configuration
|
|
92
|
+
docs/specs/index.md index (the entrance)
|
|
93
|
+
docs/specs/log.md append-only log
|
|
94
|
+
docs/specs/templates/overview.md overview template
|
|
95
|
+
docs/specs/templates/detail.md detail template
|
|
96
|
+
docs/raw/README.md raw sources directory (read-only)
|
|
97
|
+
AGENTS.md crumbs block (agent rules)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If `crumbs.json` already exists, `init` keeps its settings and ignores `--spec-dir` and `--lang` (it says so). The last line of `init` is the next command to run: `npx crumbs-spec check`.
|
|
101
|
+
|
|
102
|
+
The package is on npm as [`crumbs-spec`](https://www.npmjs.com/package/crumbs-spec). To run it from a clone instead, use `npm run build` and then `node dist/src/cli.js <command>`.
|
|
103
|
+
|
|
104
|
+
## Adding it to an existing project
|
|
105
|
+
|
|
106
|
+
The order for a repository that already has code and documents. Start with one domain instead of moving everything at once.
|
|
107
|
+
|
|
108
|
+
1. **Run `init` at the repository root.** If your spec root lives elsewhere, pass `--spec-dir` (for example `docs/crumbs`). Existing files are left alone; if `AGENTS.md` already exists, only the block between `<!-- crumbs:start -->` and `<!-- crumbs:end -->` is inserted.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx crumbs-spec init --spec-dir docs/crumbs --skills claude
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
2. **Let the agent read the rules.** Claude Code only reads the crumbs block if `CLAUDE.md` contains the line `@AGENTS.md`. Skills are found automatically under `.claude/skills/` (Claude Code) and `.agents/skills/` (Codex).
|
|
115
|
+
|
|
116
|
+
3. **Do not migrate existing documents.** The writer treats code and tests as the only sources and does not read existing wikis, design notes or READMEs unless you name them as sources, because there is no way to know whether they still match the code. Copy the ones worth using as evidence (plans, contracts, decision records) into `docs/raw/` and ingest them one at a time with `crumbs-ingest`.
|
|
117
|
+
|
|
118
|
+
4. **Write the first domain from code.** Pick the area people ask about most and hand it to the writer's "document existing code" mode. The `[needs confirmation]` questions in the result become the list of things a person has to decide for that area.
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
Document the code under src/billing/ with crumbs-writer in "document existing code" mode.
|
|
122
|
+
The domain directory is docs/crumbs/billing/. Leave unconfirmed policies as questions and finish the whole thing.
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
5. **Put `check` in CI.** Fail only on errors (broken links and anchors, unreachable documents, missing backlinks, missing code paths, duplicate policy names) and ignore warnings. Markdown under the spec root that is not a crumbs document yet goes into `exclude` in `crumbs.json`: entries are paths relative to the spec root, an entry ending in `/` excludes that directory, there are no glob patterns, and `check` tells you when a document has none of the required sections.
|
|
126
|
+
|
|
127
|
+
6. **Change the document in the same change as the code.** The AGENTS block tells the agent to "update the owning document in the same change, even when nobody asked", so every PR brings `check` and a log entry with it. The index fills in as domains are added one by one.
|
|
128
|
+
|
|
129
|
+
If you already have a spec system, try asking both systems the same questions. Files read and correct answers show at once which one is easier to navigate.
|
|
130
|
+
|
|
131
|
+
## How a spec gets written
|
|
132
|
+
|
|
133
|
+
The order the writer skill follows in "document existing code" mode. In "reflect a change" mode, steps 1โ2 become "find the owning document whose evidence names the changed code path" and the rest is the same.
|
|
134
|
+
|
|
135
|
+
```mermaid
|
|
136
|
+
flowchart TD
|
|
137
|
+
A["Read the index and templates"] --> B["Follow code and tests from the entry point"]
|
|
138
|
+
B --> C["Draw the responsibility boundary: one user flow = one document"]
|
|
139
|
+
C --> D["Write the Current behavior section, steps as ### subheadings"]
|
|
140
|
+
D --> E{"Is the rule confirmed by a test"}
|
|
141
|
+
E -->|yes| F["Write it as a policy"]
|
|
142
|
+
E -->|no| G["Statement of current behavior + [needs confirmation] question"]
|
|
143
|
+
F --> H["Flow diagram and branch tables"]
|
|
144
|
+
G --> H
|
|
145
|
+
H --> I["Evidence paths and mutual related-document links"]
|
|
146
|
+
I --> J["Update the overview list and the index"]
|
|
147
|
+
J --> K["crumbs check"]
|
|
148
|
+
K -->|errors| I
|
|
149
|
+
K -->|clean| L["log add: result, reason, document"]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
When an answer arrives, a person answers the questions produced by `crumbs-interview`, and the writer turns the question policy into a confirmed one, leaving a `decision` log entry and a decision link. When new material arrives, `crumbs-ingest` finds the affected documents, gets confirmation and then applies it.
|
|
153
|
+
|
|
154
|
+
## Document model
|
|
155
|
+
|
|
156
|
+
There are four roles. Navigation always starts at the index.
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
docs/specs/index.md index: domain list + log link
|
|
160
|
+
docs/specs/<domain>/overview.md overview: scope, flow diagram, document list, terms
|
|
161
|
+
docs/specs/<domain>/<topic>.md detail: one user flow
|
|
162
|
+
docs/specs/log.md log: what was done, in order
|
|
163
|
+
docs/specs/concepts/ concepts shared by several domains (optional)
|
|
164
|
+
docs/raw/ immutable sources such as contracts and plans
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The sections of a detail document are fixed. Headings follow `lang` (English by default, Korean with `lang: ko`) and can be renamed through `headings` in `crumbs.json`. With `lang: ko` the headings are ๋ชฉ์ , ํ์ฌ ๋์, ํ๋ฆ, ์ ์ฑ
, ๊ทผ๊ฑฐ, ํ์ธ ํ์ ์ฌํญ, ๊ด๋ จ ๋ฌธ์.
|
|
168
|
+
|
|
169
|
+
| Section | Content |
|
|
170
|
+
|---|---|
|
|
171
|
+
| Purpose | The one responsibility this document owns and what it does not cover |
|
|
172
|
+
| Current behavior | As the user experiences it, in the present tense; three or more steps become `###` subheadings |
|
|
173
|
+
| Flow | A Mermaid diagram for two or more steps; a table when there are two or more comparison axes |
|
|
174
|
+
| Policies | `### policy name` (unique across the repository) and the rule; if unconfirmed, a statement of current behavior followed by a `[needs confirmation]` question |
|
|
175
|
+
| Evidence | Backticked paths that contain a `/`, each with a description; `check` verifies the path exists |
|
|
176
|
+
| Open questions | `- [needs confirmation] [policy name](#anchor) โ question` |
|
|
177
|
+
| Related | Link to the overview and neighboring documents; backlinks must be mutual |
|
|
178
|
+
|
|
179
|
+
Example (`docs/specs/shopping/cart.md`):
|
|
180
|
+
|
|
181
|
+
````markdown
|
|
182
|
+
# Cart
|
|
183
|
+
<!-- Read ../index.md first. After changing this document, add an entry to ../log.md -->
|
|
184
|
+
|
|
185
|
+
## Purpose
|
|
186
|
+
Owns the flow from adding an item to changing its quantity and emptying the cart. Coupon application is owned by [Coupon discount](coupon-discount.md).
|
|
187
|
+
|
|
188
|
+
## Current behavior
|
|
189
|
+
- When the user adds the same item again, the app adds the quantities.
|
|
190
|
+
- When the quantity would exceed 99, the app shows "You can add up to 99 of this item." and does not add it.
|
|
191
|
+
|
|
192
|
+
## Flow
|
|
193
|
+
```mermaid
|
|
194
|
+
flowchart TD
|
|
195
|
+
A["Add item"] --> B{"Already in cart?"}
|
|
196
|
+
B -->|yes| C["Add quantities"]
|
|
197
|
+
B -->|no| D["New line"]
|
|
198
|
+
C --> E{"More than 99?"}
|
|
199
|
+
E -->|yes| F["Show notice, do not add"]
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Policies
|
|
203
|
+
### Quantity cap
|
|
204
|
+
A cart line holds at most 99 of one item.
|
|
205
|
+
|
|
206
|
+
### Minimum order amount
|
|
207
|
+
Right now the checkout button is disabled when the total is under 10,000 KRW.
|
|
208
|
+
[needs confirmation] Where does the 10,000 KRW threshold come from?
|
|
209
|
+
|
|
210
|
+
## Evidence
|
|
211
|
+
- `src/cart/cart.ts:12-48` โ adding and quantity merge
|
|
212
|
+
- `test/cart/cart.test.ts` โ quantity cap
|
|
213
|
+
|
|
214
|
+
## Open questions
|
|
215
|
+
- [needs confirmation] [Minimum order amount](#minimum-order-amount) โ Where does the 10,000 KRW threshold come from?
|
|
216
|
+
|
|
217
|
+
## Related
|
|
218
|
+
- [Shopping overview](overview.md)
|
|
219
|
+
- [Coupon discount](coupon-discount.md)
|
|
220
|
+
````
|
|
221
|
+
|
|
222
|
+
The overview's "Terms" section is a list of aliases linked to the owning policy (`- Subscription pass(flat-rate pass) โ [Pass active check](pass.md#pass-active-check)`). A term that is only a definition stops there; only concepts that several flows branch on (channel, currency, member state) get an owning document under `concepts/`.
|
|
223
|
+
|
|
224
|
+
## Commands
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npx crumbs-spec init [--spec-dir <dir>] [--lang <en|ko>] [--skills <claude|codex|all>]
|
|
228
|
+
npx crumbs-spec check [--format text|json] [--config <path>]
|
|
229
|
+
npx crumbs-spec log add --kind <ingest|query|lint|drift|change|decision> "<title>" [--body "<body>"]
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Common options are `--root <dir>` (repository root, default: current directory), `--lang <en|ko>` (language for `init`; errors raised before `crumbs.json` is read; default `en`), `-h`/`--help` and `-v`/`--version`. Configuration lives in `crumbs.json` at the repository root (`specDir`, `index`, `overview`, `log`, `lang`, `headings`, `exclude`, `codeRoot`, `rawDir`).
|
|
233
|
+
|
|
234
|
+
`check` prints one line per finding as `file:line severity rule: message`, and a summary last.
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
docs/specs/shopping/cart.md:34 error broken-anchor: anchor not found: coupon-discount.md#application-order
|
|
238
|
+
docs/specs/shopping/cart.md:20 warning open-question: 1 open question(s)
|
|
239
|
+
|
|
240
|
+
crumbs check: 4 documents, 1 error, 1 warning
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Supported Markdown.** Headings must be ATX (`#` to `######`); setext headings are not recognized. Inline links and reference links (`[text][label]`, and `[label][]` with a `[label]: path` definition) are checked; a shortcut reference such as `[label]` alone is not a link. Heading anchors follow github-slugger: each space becomes `-`, punctuation other than `-` and `_` is dropped, and nothing is collapsed, so `### Quantity & limits` becomes `#quantity--limits`. Symlinked files and directories under the spec root are followed. An evidence path must contain a `/`, so a repository-root file is written `./package.json`; line suffixes such as `:12`, `:12-48`, `:12:5` and `:12-48,60-70` are accepted. Entries in `exclude` are paths relative to the spec root, and an entry ending in `/` excludes that directory. Every finding carries a `line` (1 when there is no better position), so JSON output always includes it.
|
|
244
|
+
|
|
245
|
+
### Check rules
|
|
246
|
+
|
|
247
|
+
| Rule | Severity | What it catches |
|
|
248
|
+
|---|---|---|
|
|
249
|
+
| `index-missing` | error | The index document is missing (no other rule runs) |
|
|
250
|
+
| `missing-title` | error | The first heading is not an H1 |
|
|
251
|
+
| `broken-link` | error | The link target file does not exist, or its spelling differs from the file on disk |
|
|
252
|
+
| `broken-anchor` | error | The link points to a heading that does not exist |
|
|
253
|
+
| `unreachable` | error | A document that cannot be reached from the index by links |
|
|
254
|
+
| `missing-section` | error | A required section for the role is missing |
|
|
255
|
+
| `missing-backlink` | error | No link back to the parent (overview or index), or a detail AโB link without BโA |
|
|
256
|
+
| `missing-code-path` | error | An evidence code path does not exist |
|
|
257
|
+
| `duplicate-policy` | error | The same policy name appears in two documents, or twice in one |
|
|
258
|
+
| `log-format` | error | A log entry is not in the form `## [YYYY-MM-DD] kind \| title`, or the date is more than a day in the future |
|
|
259
|
+
| `unterminated-comment` | error | An HTML comment is opened and never closed, hiding the rest of the document |
|
|
260
|
+
| `open-question` | warning | One warning per document that still has open questions (the count is the number of questions) |
|
|
261
|
+
| `log-order` | warning | Log dates are out of order |
|
|
262
|
+
| `missing-flow` | warning | Current behavior has six or more steps but no diagram or table |
|
|
263
|
+
| `section-order` | warning | Sections are not in template order |
|
|
264
|
+
| `log-body` | warning | A `decision` entry has no document link |
|
|
265
|
+
|
|
266
|
+
## Log
|
|
267
|
+
|
|
268
|
+
The log is appended in one place (`docs/specs/log.md`) in chronological order. The title is **one sentence about the user outcome**, not the task name, and the body has three lines.
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
npx crumbs-spec log add --kind change "Cart quantity cap raised to 99" \
|
|
272
|
+
--body "Result: a cart line holds up to 99. Reason: request SHOP-42. Document: [Quantity cap](shopping/cart.md#quantity-cap)"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`log add` refuses titles or bodies that contain `<!--`.
|
|
276
|
+
|
|
277
|
+
When a `[needs confirmation]` question gets its answer, add a `decision` entry and put one decision link at the end of the confirmed policy. Instead of change-history tables inside documents, decisions accumulate in the log and documents only point at them. The slug drops the brackets, `|`, comma and colon, so the `|` leaves a double dash in the anchor.
|
|
278
|
+
|
|
279
|
+
```markdown
|
|
280
|
+
## [2026-11-02] decision | Minimum order amount: 10,000 KRW comes from the shipping fee policy
|
|
281
|
+
Result: the checkout button is disabled below 10,000 KRW. Reason: 2026-11-02 planning review. Document: [Minimum order amount](shopping/cart.md#minimum-order-amount)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
```markdown
|
|
285
|
+
### Minimum order amount
|
|
286
|
+
The checkout button is disabled when the total is under 10,000 KRW. (decision: [2026-11-02](../log.md#2026-11-02-decision--minimum-order-amount-10000-krw-comes-from-the-shipping-fee-policy))
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
The kinds are `ingest` (a source was applied), `query` (an answer fed back), `lint`, `drift`, `change` (a document changed) and `decision` (a question was settled).
|
|
290
|
+
|
|
291
|
+
## Skills
|
|
292
|
+
|
|
293
|
+
`init --skills` installs the procedures an agent follows into `.claude/skills/<name>/SKILL.md` (Claude Code) and `.agents/skills/<name>/SKILL.md` (Codex). An existing `SKILL.md` is never overwritten; delete it and run `init --skills` again to upgrade. Claude Code reads the crumbs block only when `CLAUDE.md` contains `@AGENTS.md`.
|
|
294
|
+
|
|
295
|
+
Each generated SKILL.md starts with a comment naming the crumbs-spec version that produced it. Skills and templates follow the package version: a minor release may change their text, a patch release does not. To upgrade, delete the file and run `init --skills` again.
|
|
296
|
+
|
|
297
|
+
| Skill | What it does |
|
|
298
|
+
|---|---|
|
|
299
|
+
| `crumbs-writer` | Writes or updates documents in two modes: "reflect a change" (changed code โ owning document) and "document existing code" (first draft from code and tests only). Carries the rules for responsibility size, style, question format, flow diagrams and concept pages |
|
|
300
|
+
| `crumbs-finder` | Finds the answer index โ overview โ detail, cites the source, and feeds good answers back as `query` log entries |
|
|
301
|
+
| `crumbs-interview` | Turns the unconfirmed policies a request touches into questions and stops |
|
|
302
|
+
| `crumbs-ingest` | Reads new material under `docs/raw/`, finds the affected documents, applies them after confirmation and leaves an `ingest` entry |
|
|
303
|
+
|
|
304
|
+
## Usage examples
|
|
305
|
+
|
|
306
|
+
### 1. Document existing code for the first time
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
Document the code under src/cart/ with crumbs-writer in "document existing code" mode.
|
|
310
|
+
The domain directory is docs/specs/shopping/. Leave unconfirmed policies as questions and finish the whole thing.
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
The agent reads the index and templates, follows code and tests from the entry point, and produces `shopping/overview.md` plus the detail documents. Rules confirmed by tests become policies; rules that exist only in code become "right now โฆ" statements with `[needs confirmation]` questions. It ends by running `check`, clearing errors and adding a log entry.
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
crumbs check: 5 documents, 0 errors, 3 warnings
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The three warnings are the three documents that still have open questions. Skimming their "Open questions" sections is enough to see what has to be decided.
|
|
320
|
+
|
|
321
|
+
### 2. Reflect a code change
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
I raised the cart quantity limit from 99 to 199. Update the docs.
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
The writer finds the owning document with `grep -rl 'src/cart/cart.ts' docs/specs` (the evidence section names the path), fixes the current-behavior section and the `### Quantity cap` policy, updates the value in the flow diagram, and logs `change | Cart quantity cap raised to 199`. If two documents look like owners it stops and asks which one is.
|
|
328
|
+
|
|
329
|
+
### 3. Ask about what is unknown and record the answer as a decision
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
I want to change the coupon stacking rule. What do I need to confirm first?
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
The interview skill lists the policies the request touches, separates those confirmed by code, tests or sources from those that are not, and turns the unconfirmed ones into questions. Once you answer, the writer rewrites the policy as confirmed and leaves a `decision` log entry and a decision link.
|
|
336
|
+
|
|
337
|
+
### 4. Ingest a source document
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
Apply docs/raw/coupon-policy-v3.pdf.
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
The ingest skill reads the source, finds the affected documents through the index, backlinks and evidence, and shows the list of intended changes first. Only after confirmation does it edit the owning documents and add an `ingest` entry. There is no automatic merge.
|
|
344
|
+
|
|
345
|
+
### 5. Find an answer
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
How many items can go into the cart?
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
The finder goes index โ shopping overview โ cart document and answers with `### Quantity cap` as the source. For term or concept questions it checks the terms section of `concepts/overview.md` first. If the documents do not answer, it says "not in the documents" and names the closest one.
|
|
352
|
+
|
|
353
|
+
### 6. Check in CI
|
|
354
|
+
|
|
355
|
+
A GitHub Actions job that fails on errors:
|
|
356
|
+
|
|
357
|
+
```yaml
|
|
358
|
+
jobs:
|
|
359
|
+
specs:
|
|
360
|
+
runs-on: ubuntu-latest
|
|
361
|
+
steps:
|
|
362
|
+
- uses: actions/checkout@v4
|
|
363
|
+
- uses: actions/setup-node@v4
|
|
364
|
+
with: { node-version: 20 }
|
|
365
|
+
- run: npx crumbs-spec check --format json > crumbs-report.json
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Exit code 1 means there are errors. Warnings (`open-question`, `missing-flow` and so on) do not fail the build.
|
|
369
|
+
|
|
370
|
+
## Exit codes
|
|
371
|
+
|
|
372
|
+
| Code | Meaning |
|
|
373
|
+
|---|---|
|
|
374
|
+
| 0 | `check`: no errors (warnings allowed). `init` and `log add`: done |
|
|
375
|
+
| 1 | `check`: at least one error |
|
|
376
|
+
| 2 | Usage error, configuration error or unexpected I/O error |
|
|
377
|
+
|
|
378
|
+
## What we left out
|
|
379
|
+
|
|
380
|
+
- Requirement IDs, front matter and enforced grammar such as EARS. Navigation lives in links and headings only.
|
|
381
|
+
- Change-history tables inside documents and one file per decision. Decisions go into the single log and documents point at them.
|
|
382
|
+
- Definition-only term pages. The overview's terms section is enough.
|
|
383
|
+
- Style and content judgment in `check`. Style is handled by the skill guidance and the markdownlint sample under [docs/lint/](docs/lint/README.md).
|
|
384
|
+
|
|
385
|
+
## Tests and compatibility
|
|
386
|
+
|
|
387
|
+
`npm test` runs the node:test suite on Linux and macOS in CI (see `.github/workflows/ci.yml`). Changes are listed in [CHANGELOG.md](CHANGELOG.md). Until 1.0, a minor version may change check rules, templates and skill text; a patch version only fixes bugs.
|
|
388
|
+
|
|
389
|
+
## Roadmap
|
|
390
|
+
|
|
391
|
+
| Step | Content | Status |
|
|
392
|
+
|---|---|---|
|
|
393
|
+
| 1 | `init`, `check`, templates, AGENTS block | done |
|
|
394
|
+
| 2 | Four skills, `log add`, flow section, responsibility-size rules, terms and concept pages, decision log | done |
|
|
395
|
+
| 3 | `crumbs reverse <path>` โ a command that drafts from code (the writer skill does this today) | planned |
|
|
396
|
+
| 4 | `crumbs drift --base <ref>` โ finds documents whose evidence changed in a diff | planned |
|
|
397
|
+
| 5 | Example app and a navigation-cost benchmark runner | planned |
|
|
398
|
+
|
|
399
|
+
The `lang` setting (`en` by default, `ko` for Korean) selects the language of the templates, skills and messages, so one repository uses one language.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { loadRepoConfig } from './config.js';
|
|
2
|
+
import { buildGraph, relPath } from './graph.js';
|
|
3
|
+
import { MESSAGES } from './messages.js';
|
|
4
|
+
import { brokenAnchor } from './rules/broken-anchor.js';
|
|
5
|
+
import { brokenLink } from './rules/broken-link.js';
|
|
6
|
+
import { duplicatePolicy } from './rules/duplicate-policy.js';
|
|
7
|
+
import { logBody } from './rules/log-body.js';
|
|
8
|
+
import { logFormat } from './rules/log-format.js';
|
|
9
|
+
import { logOrder } from './rules/log-order.js';
|
|
10
|
+
import { missingBacklink } from './rules/missing-backlink.js';
|
|
11
|
+
import { missingCodePath } from './rules/missing-code-path.js';
|
|
12
|
+
import { missingFlow } from './rules/missing-flow.js';
|
|
13
|
+
import { missingSection } from './rules/missing-section.js';
|
|
14
|
+
import { openQuestion } from './rules/open-question.js';
|
|
15
|
+
import { sectionOrder } from './rules/section-order.js';
|
|
16
|
+
import { unreachable } from './rules/unreachable.js';
|
|
17
|
+
import { unterminatedComment } from './rules/unterminated-comment.js';
|
|
18
|
+
import { finding } from './model.js';
|
|
19
|
+
// ๋ชจ๋ ๋ฌธ์๋ ์ฒซ ์ ๋ชฉ์ด H1์ด์ด์ผ ํ๋ค. ์ ๋ชฉ์ด ์๋ ๋ฌธ์๋ 1์ค์ ๊ฐ๋ฆฌํจ๋ค.
|
|
20
|
+
function missingTitle(graph) {
|
|
21
|
+
return graph.docs
|
|
22
|
+
.filter((doc) => doc.headings[0]?.level !== 1)
|
|
23
|
+
.map((doc) => finding('missing-title', 'error', doc.file, MESSAGES[graph.lang].h1Title, doc.headings[0]?.line ?? 1));
|
|
24
|
+
}
|
|
25
|
+
function checkGraph(graph) {
|
|
26
|
+
if (graph.index === undefined) {
|
|
27
|
+
const indexPath = relPath(graph.root, graph.indexAbs);
|
|
28
|
+
// ์ธ๋ฑ์ค๊ฐ ์์ผ๋ฉด ๋ค๋ฅธ ๊ท์น์ ๋๋ฆฌ์ง ์๋๋ค.
|
|
29
|
+
return [finding('index-missing', 'error', indexPath, MESSAGES[graph.lang].indexMissing(indexPath), 1)];
|
|
30
|
+
}
|
|
31
|
+
return [
|
|
32
|
+
...missingTitle(graph),
|
|
33
|
+
...unterminatedComment(graph),
|
|
34
|
+
...brokenLink(graph),
|
|
35
|
+
...brokenAnchor(graph),
|
|
36
|
+
...unreachable(graph),
|
|
37
|
+
...missingSection(graph),
|
|
38
|
+
...missingBacklink(graph),
|
|
39
|
+
...missingCodePath(graph),
|
|
40
|
+
...duplicatePolicy(graph),
|
|
41
|
+
...logFormat(graph),
|
|
42
|
+
...logOrder(graph),
|
|
43
|
+
...logBody(graph),
|
|
44
|
+
...sectionOrder(graph),
|
|
45
|
+
...missingFlow(graph),
|
|
46
|
+
...openQuestion(graph),
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
const compareText = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
50
|
+
// file, line, rule, message ์์ผ๋ก ์ ๋ ฌํ๋ค.
|
|
51
|
+
function sortFindings(findings) {
|
|
52
|
+
return [...findings].sort((a, b) => compareText(a.file, b.file) ||
|
|
53
|
+
a.line - b.line ||
|
|
54
|
+
compareText(a.rule, b.rule) ||
|
|
55
|
+
compareText(a.message, b.message));
|
|
56
|
+
}
|
|
57
|
+
export function runCheck(root, config) {
|
|
58
|
+
const graph = buildGraph(root, config);
|
|
59
|
+
const findings = sortFindings(checkGraph(graph));
|
|
60
|
+
const errors = findings.filter((finding) => finding.severity === 'error').length;
|
|
61
|
+
return {
|
|
62
|
+
version: 1,
|
|
63
|
+
ok: errors === 0,
|
|
64
|
+
summary: { documents: graph.docs.length, errors, warnings: findings.length - errors },
|
|
65
|
+
findings,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// configPath๊ฐ ์์ผ๋ฉด ์ ์ฅ์ ๋ฃจํธ์ crumbs.json์ ์ฝ๋๋ค. fallback์ ์ค์ ์ ์ฝ๊ธฐ ์ ์ค๋ฅ์ ์ธ์ด๋ค(--lang ๋๋ en).
|
|
69
|
+
export function checkRepo(root, configPath, fallback) {
|
|
70
|
+
return runCheck(root, loadRepoConfig(root, configPath, fallback));
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/check.ts"],"names":[],"mappings":"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"}
|