crumbs-spec 0.1.0 → 0.3.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/README.ko.md +139 -101
- package/README.md +159 -121
- package/dist/src/check.js +7 -5
- package/dist/src/check.js.map +1 -1
- package/dist/src/cli.js +2 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.js +16 -1
- package/dist/src/config.js.map +1 -1
- package/dist/src/dates.js +7 -0
- package/dist/src/dates.js.map +1 -1
- package/dist/src/graph.js +81 -14
- package/dist/src/graph.js.map +1 -1
- package/dist/src/init.js +24 -8
- package/dist/src/init.js.map +1 -1
- package/dist/src/markdown.js +155 -25
- package/dist/src/markdown.js.map +1 -1
- package/dist/src/messages.js +40 -15
- package/dist/src/messages.js.map +1 -1
- package/dist/src/model.js +2 -3
- package/dist/src/model.js.map +1 -1
- package/dist/src/report.js +1 -1
- package/dist/src/report.js.map +1 -1
- package/dist/src/rules/broken-link.js +4 -1
- package/dist/src/rules/broken-link.js.map +1 -1
- package/dist/src/rules/duplicate-policy.js +11 -2
- package/dist/src/rules/duplicate-policy.js.map +1 -1
- package/dist/src/rules/log-body.js +3 -3
- package/dist/src/rules/log-body.js.map +1 -1
- package/dist/src/rules/log-format.js +6 -0
- package/dist/src/rules/log-format.js.map +1 -1
- package/dist/src/rules/missing-backlink.js +4 -1
- package/dist/src/rules/missing-backlink.js.map +1 -1
- package/dist/src/rules/missing-section.js +14 -2
- package/dist/src/rules/missing-section.js.map +1 -1
- package/dist/src/rules/unreachable.js +1 -1
- package/dist/src/rules/unreachable.js.map +1 -1
- package/dist/src/rules/unterminated-comment.js +13 -0
- package/dist/src/rules/unterminated-comment.js.map +1 -0
- package/dist/src/skills.js +12 -1
- package/dist/src/skills.js.map +1 -1
- package/dist/src/templates/agents.js +2 -2
- package/dist/src/templates/agents.js.map +1 -1
- package/dist/src/templates/en.js +5 -5
- package/dist/src/templates/en.js.map +1 -1
- package/dist/src/templates/ko.js +1 -1
- package/dist/src/templates/ko.js.map +1 -1
- package/dist/src/templates/skills-en.js +16 -15
- package/dist/src/templates/skills-en.js.map +1 -1
- package/dist/src/templates/skills-ko.js +10 -9
- package/dist/src/templates/skills-ko.js.map +1 -1
- package/dist/src/version.js +7 -0
- package/dist/src/version.js.map +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,68 +2,77 @@
|
|
|
2
2
|
|
|
3
3
|
[한국어](README.ko.md) · **English**
|
|
4
4
|
|
|
5
|
-
crumbs-spec
|
|
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.
|
|
6
8
|
|
|
7
9
|
## Why we built it
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
While we ran dozens of spec documents with agents on a real app repository, the same three things kept happening.
|
|
10
12
|
|
|
11
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?".
|
|
12
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.
|
|
13
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.
|
|
14
16
|
|
|
15
|
-
"Write better documents" did not fix it. We needed **a tool that enforces structure by machine**.
|
|
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.
|
|
16
18
|
|
|
17
19
|
## How it differs from other tools
|
|
18
20
|
|
|
19
|
-
Spec Kit is good at pre-implementation planning and LLM Wiki at accumulating sources. crumbs-spec
|
|
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.
|
|
20
22
|
|
|
21
23
|
| | GitHub Spec Kit | OpenSpec | Karpathy's LLM Wiki | **crumbs-spec** |
|
|
22
24
|
|---|---|---|---|---|
|
|
23
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 |
|
|
24
|
-
| Point in time | Before implementation | Before and after | When a source is ingested | **Current
|
|
25
|
-
| Existing code | States that it "does not infer" | Has onboarding, but no code-to-spec drafting | Targets sources, not code | **
|
|
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** |
|
|
26
28
|
| Navigation | Folders and file names | Folders and file names | Index first | **Index → overview → detail by links only**. No IDs, no front matter |
|
|
27
|
-
| Machine checks | Consistency across spec, plan and tasks |
|
|
28
|
-
| 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")
|
|
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** |
|
|
29
31
|
| Human readability | Prose | Prose | Prose | **Flow diagrams (Mermaid) and branch tables in the template** |
|
|
30
|
-
| Measurement | None | None | None | **Compares navigation cost of two document systems with the same questions** |
|
|
31
32
|
|
|
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.
|
|
33
36
|
|
|
34
37
|
## What we cared about
|
|
35
38
|
|
|
36
39
|
- **A single source of truth for the current state.** No change directories. Documents explain "how it behaves now", not "what changed".
|
|
37
|
-
- **No guessing.** A rule not confirmed by code, tests or approved sources is written only as "right now it behaves like this" plus a `[확인 필요]`
|
|
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.
|
|
38
42
|
- **People-first prose.** No IDs, no status tables, no enforced grammar. Navigation lives in links and headings only.
|
|
39
|
-
- **Deterministic checks, human
|
|
43
|
+
- **Deterministic checks, human judgment.** `check` looks at structure only. Style, content and staleness are for skill guidance and people.
|
|
40
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.
|
|
41
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.
|
|
42
46
|
|
|
43
|
-
##
|
|
47
|
+
## Does it work on code it has never seen? (RealWorld experiment)
|
|
44
48
|
|
|
45
|
-
We
|
|
49
|
+
We ran the writer skill on two open-source backends of the [RealWorld](https://github.com/realworld-apps/realworld) "Conduit" API and graded the documents against the project's official API test suite. The generated documents, prompts, ground truth and every grading row are published in [crumbs-spec-conduit](https://github.com/Sienna94/crumbs-spec-conduit).
|
|
46
50
|
|
|
47
|
-
**
|
|
51
|
+
- **Setup.** Two pinned implementations of the same API: [Nitro + Prisma](https://github.com/realworld-apps/nitro-prisma-zod-realworld-example-app) (TypeScript) and [Django Ninja](https://github.com/c4ffein/realworld-django-ninja) (Python). One Opus 5.5 agent per implementation wrote the documents from code and tests only. The official spec repository was not in the experiment repository while they wrote; it was added in a later commit.
|
|
52
|
+
- **Ground truth.** Each request in the official Hurl suite, at the version both implementations pin, is one row: 130 rows after dropping setup and cleanup requests.
|
|
53
|
+
- **Grading.** A different model (Sonnet 5.5) graded every row. `correct` means the documents state the status and every essential asserted value, `partial` that one is left out, `wrong` that the documents contradict it, `missing` that no document covers it. It also checked every policy against the code.
|
|
48
54
|
|
|
49
|
-
|
|
|
50
|
-
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
55
|
+
| | Nitro + Prisma | Django Ninja |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| Code / tests in the repository | ~1,400 lines / 2 unit test files | ~2,300 lines / endpoint tests for each app |
|
|
58
|
+
| Documents / policies | 28 / 51 | 26 / 91 |
|
|
59
|
+
| Policies confirmed by tests / left as questions | 6 / 45 | 50 / 41 |
|
|
60
|
+
| Suite rows correct | **125 / 130** | **116 / 130** |
|
|
61
|
+
| Suite rows partial / wrong / missing | 5 / 0 / 0 | 14 / 0 / 0 |
|
|
62
|
+
| Invented policies (code does not do it) | 0 | 0 |
|
|
63
|
+
| Policies partly overstated | 1 | 6 |
|
|
64
|
+
| Writer tokens / wall time | 165k / 2 h 40 min | 202k / 2 h 47 min |
|
|
54
65
|
|
|
55
|
-
|
|
66
|
+
- **Nothing contradicted the suite and nothing was invented.** Every miss is a detail left out. Nitro's five partials are `null` inputs the documents never mention and the order of tags. Twelve of Django's fourteen come from one honest question: the app's error handler and its tests disagree on the 401 body, so the writer stated both and asked.
|
|
67
|
+
- **Statements under a question were mostly right.** 12 Nitro rows and 32 Django rows were graded correct on a statement still marked `[needs confirmation]`. "This is what it does now" plus a question kept the documents useful without claiming more than the tests prove.
|
|
68
|
+
- **Fewer tests, more questions.** Nitro has two unit test files, so 45 of its 51 policies stayed questions. The suite answers 29 of them, which is the list a maintainer would confirm first.
|
|
69
|
+
- **The writers raised what no suite row tests.** As questions: Django's article list ignores the token and reads the session user, and its `createUser` command looks users up by a field a migration renamed. The Nitro documents also say plainly that the email format is not checked, which a newer version of the suite does test.
|
|
56
70
|
|
|
57
|
-
|
|
71
|
+
Limits: one run per implementation and one writer model. The grader is also a model; the author checked that every cited anchor exists and spot-checked rows. RealWorld is well known, so the writer may have seen it in training even without the spec in the repository. The suite asserts only part of what the documents say: 20 of Nitro's and 41 of Django's policies were checked against the code alone. The Django grader ran the code with unpinned dependencies, so some of its "partly" verdicts may depend on library versions.
|
|
58
72
|
|
|
59
|
-
|
|
60
|
-
|---|---|---|
|
|
61
|
-
| Correct answers | 17 / 18 (1 partial) | 15 / 18 (3 partial) |
|
|
62
|
-
| Files read in total | 70 (avg 3.9, max 8) | 64 (avg 3.6, max 6) |
|
|
63
|
-
| Fell back to grep | 3 | 1 |
|
|
64
|
-
| Tokens in total | 2.32M | 1.89M (−18%) |
|
|
73
|
+
### An earlier run on a private app (anecdote)
|
|
65
74
|
|
|
66
|
-
|
|
75
|
+
Before this we rewrote three areas of a private app (about 40,000 lines) and asked the existing spec system and crumbs the same 18 questions, documents only. crumbs answered 15 against 17, read 64 files against 70 and used 18% fewer tokens. Its three partial answers were details the writer had dropped, which led to the skill rule "do not drop values that carry a condition". The author graded it, the questions came from the existing specs, and the data cannot be published, so read it as an anecdote. The navigation comparison has not been repeated on public code yet.
|
|
67
76
|
|
|
68
77
|
## Install and quick start
|
|
69
78
|
|
|
@@ -77,7 +86,7 @@ npx crumbs-spec check # check the link graph and structure
|
|
|
77
86
|
|
|
78
87
|
Korean repositories: pass `--lang ko`.
|
|
79
88
|
|
|
80
|
-
`init` creates these
|
|
89
|
+
`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 -->`.
|
|
81
90
|
|
|
82
91
|
```
|
|
83
92
|
crumbs.json configuration
|
|
@@ -89,7 +98,9 @@ docs/raw/README.md raw sources directory (read-only)
|
|
|
89
98
|
AGENTS.md crumbs block (agent rules)
|
|
90
99
|
```
|
|
91
100
|
|
|
92
|
-
|
|
101
|
+
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`.
|
|
102
|
+
|
|
103
|
+
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>`.
|
|
93
104
|
|
|
94
105
|
## Adding it to an existing project
|
|
95
106
|
|
|
@@ -101,22 +112,22 @@ The order for a repository that already has code and documents. Start with one d
|
|
|
101
112
|
npx crumbs-spec init --spec-dir docs/crumbs --skills claude
|
|
102
113
|
```
|
|
103
114
|
|
|
104
|
-
2. **Let the agent read the rules.** Claude Code
|
|
115
|
+
2. **Let the agent read the rules.** Codex and Claude Code (v2.1.277 or later) read `AGENTS.md` on their own. In a repository that also has a `CLAUDE.md` (or `.claude/CLAUDE.md` or `CLAUDE.local.md`), Claude Code reads only that file, so add the line `@AGENTS.md` to it to pull in the crumbs block. `init` prints a guide line in that case. Skills are found automatically under `.claude/skills/` (Claude Code) and `.agents/skills/` (Codex).
|
|
105
116
|
|
|
106
117
|
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`.
|
|
107
118
|
|
|
108
|
-
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 `[
|
|
119
|
+
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.
|
|
109
120
|
|
|
110
121
|
```
|
|
111
122
|
Document the code under src/billing/ with crumbs-writer in "document existing code" mode.
|
|
112
123
|
The domain directory is docs/crumbs/billing/. Leave unconfirmed policies as questions and finish the whole thing.
|
|
113
124
|
```
|
|
114
125
|
|
|
115
|
-
5. **Put `check` in CI.** Fail only on errors (
|
|
126
|
+
5. **Put `check` in CI.** Fail only on errors (every rule marked error in the check rules table below) 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.
|
|
116
127
|
|
|
117
128
|
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.
|
|
118
129
|
|
|
119
|
-
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
|
+
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.
|
|
120
131
|
|
|
121
132
|
## How a spec gets written
|
|
122
133
|
|
|
@@ -126,10 +137,10 @@ The order the writer skill follows in "document existing code" mode. In "reflect
|
|
|
126
137
|
flowchart TD
|
|
127
138
|
A["Read the index and templates"] --> B["Follow code and tests from the entry point"]
|
|
128
139
|
B --> C["Draw the responsibility boundary: one user flow = one document"]
|
|
129
|
-
C --> D["Write
|
|
140
|
+
C --> D["Write the Current behavior section, steps as ### subheadings"]
|
|
130
141
|
D --> E{"Is the rule confirmed by a test"}
|
|
131
142
|
E -->|yes| F["Write it as a policy"]
|
|
132
|
-
E -->|no| G["Statement of current
|
|
143
|
+
E -->|no| G["Statement of current behavior + [needs confirmation] question"]
|
|
133
144
|
F --> H["Flow diagram and branch tables"]
|
|
134
145
|
G --> H
|
|
135
146
|
H --> I["Evidence paths and mutual related-document links"]
|
|
@@ -154,62 +165,57 @@ docs/specs/concepts/ concepts shared by several domains (optional)
|
|
|
154
165
|
docs/raw/ immutable sources such as contracts and plans
|
|
155
166
|
```
|
|
156
167
|
|
|
157
|
-
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`.
|
|
168
|
+
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 목적, 현재 동작, 흐름, 정책, 근거, 확인 필요 사항, 관련 문서.
|
|
158
169
|
|
|
159
170
|
| Section | Content |
|
|
160
171
|
|---|---|
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
|
|
|
172
|
+
| Purpose | The one responsibility this document owns and what it does not cover |
|
|
173
|
+
| Current behavior | As the user experiences it, in the present tense; three or more steps become `###` subheadings |
|
|
174
|
+
| Flow | A Mermaid diagram for two or more steps; a table when there are two or more comparison axes |
|
|
175
|
+
| Policies | `### policy name` (unique across the repository) and the rule; if unconfirmed, a statement of current behavior followed by a `[needs confirmation]` question |
|
|
176
|
+
| Evidence | Backticked paths that contain a `/`, each with a description; `check` verifies the path exists |
|
|
177
|
+
| Open questions | `- [needs confirmation] [policy name](#anchor) — question` |
|
|
178
|
+
| Related | Link to the overview and neighboring documents; backlinks must be mutual |
|
|
168
179
|
|
|
169
|
-
Example (
|
|
180
|
+
Example: a detail document the writer produced from the Nitro code in [the RealWorld experiment](#does-it-work-on-code-it-has-never-seen-realworld-experiment), unedited ([source](https://github.com/Sienna94/crumbs-spec-conduit/blob/main/nitro/docs/specs/api/articles/tag-list.md)). Usage example 3 below answers its question and adds a flow diagram.
|
|
170
181
|
|
|
171
182
|
````markdown
|
|
172
|
-
#
|
|
173
|
-
<!--
|
|
174
|
-
|
|
175
|
-
##
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
##
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
[
|
|
199
|
-
|
|
200
|
-
##
|
|
201
|
-
-
|
|
202
|
-
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
- [확인 필요] [최소 주문 금액](#최소-주문-금액) — 10,000원이라는 기준의 근거는 무엇인가?
|
|
206
|
-
|
|
207
|
-
## 관련 문서
|
|
208
|
-
- [쇼핑 개요](overview.md)
|
|
209
|
-
- [쿠폰 할인](쿠폰-할인.md)
|
|
183
|
+
# Tag list
|
|
184
|
+
<!-- Read ../../index.md first. After changing this document, add an entry to ../../log.md -->
|
|
185
|
+
|
|
186
|
+
## Purpose
|
|
187
|
+
This document owns GET /api/tags, which returns the most used tag names. How tags are attached to articles is owned by [Article creation](article-create.md#article-tag-name-handling). The endpoint allows anonymous viewers as described in [Authentication token](../authentication-token.md), and the list is the same for anonymous and signed-in viewers.
|
|
188
|
+
|
|
189
|
+
## Current behavior
|
|
190
|
+
- The server considers only tags that at least one article uses.
|
|
191
|
+
- The server sorts those tags by the number of articles that use them, most used first.
|
|
192
|
+
- The server returns at most 10 tag names.
|
|
193
|
+
- The server responds with status 200 and {"tags": ["<name>"…]}.
|
|
194
|
+
- If no article uses a tag, the list is empty.
|
|
195
|
+
|
|
196
|
+
## Flow
|
|
197
|
+
None
|
|
198
|
+
|
|
199
|
+
## Policies
|
|
200
|
+
### Popular tag list
|
|
201
|
+
The tag list holds at most 10 names of tags used by at least one article, sorted by the number of articles that use each tag, most used first. The order of tags used by the same number of articles is not defined, and the list has no pagination. No test covers this rule.
|
|
202
|
+
[needs confirmation] What is the basis of the 10-tag limit, and what should order tags with the same usage count?
|
|
203
|
+
|
|
204
|
+
## Evidence
|
|
205
|
+
- `server/routes/api/tags/index.get.ts` — selects tags used by at least one article, sorts them by usage and returns at most 10 names
|
|
206
|
+
- `prisma/schema.prisma` — unique tag names and the relation between tags and articles
|
|
207
|
+
|
|
208
|
+
## Open questions
|
|
209
|
+
- [needs confirmation] [Popular tag list](#popular-tag-list) — What is the basis of the 10-tag limit, and what should order tags with the same usage count?
|
|
210
|
+
|
|
211
|
+
## Related
|
|
212
|
+
- [Articles API overview](overview.md)
|
|
213
|
+
- [Article creation](article-create.md)
|
|
214
|
+
- [Article update](article-update.md)
|
|
215
|
+
- [Article deletion](article-delete.md)
|
|
210
216
|
````
|
|
211
217
|
|
|
212
|
-
The overview's
|
|
218
|
+
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/`.
|
|
213
219
|
|
|
214
220
|
## Commands
|
|
215
221
|
|
|
@@ -224,57 +230,72 @@ Common options are `--root <dir>` (repository root, default: current directory),
|
|
|
224
230
|
`check` prints one line per finding as `file:line severity rule: message`, and a summary last.
|
|
225
231
|
|
|
226
232
|
```
|
|
227
|
-
docs/specs/
|
|
228
|
-
docs/specs/
|
|
229
|
-
|
|
230
|
-
crumbs check:
|
|
233
|
+
docs/specs/api/articles/tag-list.md:31 error broken-link: link target not found: ../log.md#2026-11-03-decision--popular-tag-list-most-used-first-stays-ties-are-undefined-10-tags-are-cut-after-sorting-and-every-viewer-gets-the-same-unpaginated-list
|
|
234
|
+
docs/specs/api/articles/article-delete.md:44 warning open-question: 1 open question(s)
|
|
235
|
+
…
|
|
236
|
+
crumbs check: 28 documents, 1 error, 20 warnings
|
|
231
237
|
```
|
|
232
238
|
|
|
239
|
+
This is the check from usage example 3 before the writer fixed its decision link; the other 19 warnings are left out.
|
|
240
|
+
|
|
241
|
+
**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. Evidence paths are resolved against `codeRoot` (the repository root by default), except paths under `rawDir`, which are always resolved against the repository root. 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.
|
|
242
|
+
|
|
233
243
|
### Check rules
|
|
234
244
|
|
|
235
245
|
| Rule | Severity | What it catches |
|
|
236
246
|
|---|---|---|
|
|
237
247
|
| `index-missing` | error | The index document is missing (no other rule runs) |
|
|
238
248
|
| `missing-title` | error | The first heading is not an H1 |
|
|
239
|
-
| `broken-link`
|
|
249
|
+
| `broken-link` | error | The link target file does not exist, or its spelling differs from the file on disk |
|
|
250
|
+
| `broken-anchor` | error | The link points to a heading that does not exist |
|
|
240
251
|
| `unreachable` | error | A document that cannot be reached from the index by links |
|
|
241
252
|
| `missing-section` | error | A required section for the role is missing |
|
|
242
253
|
| `missing-backlink` | error | No link back to the parent (overview or index), or a detail A→B link without B→A |
|
|
243
254
|
| `missing-code-path` | error | An evidence code path does not exist |
|
|
244
|
-
| `duplicate-policy` | error | The same policy name appears in two documents |
|
|
245
|
-
| `log-format` | error | A log entry is not in the form `## [YYYY-MM-DD] kind \| title
|
|
246
|
-
| `
|
|
255
|
+
| `duplicate-policy` | error | The same policy name appears in two documents, or twice in one |
|
|
256
|
+
| `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 |
|
|
257
|
+
| `unterminated-comment` | error | An HTML comment is opened and never closed, hiding the rest of the document |
|
|
258
|
+
| `open-question` | warning | One warning per document that still has open questions (the count is the number of questions) |
|
|
247
259
|
| `log-order` | warning | Log dates are out of order |
|
|
248
|
-
| `missing-flow` | warning | Current
|
|
260
|
+
| `missing-flow` | warning | Current behavior has six or more steps but no diagram or table |
|
|
249
261
|
| `section-order` | warning | Sections are not in template order |
|
|
250
262
|
| `log-body` | warning | A `decision` entry has no document link |
|
|
251
263
|
|
|
252
264
|
## Log
|
|
253
265
|
|
|
254
|
-
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.
|
|
266
|
+
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. `log add` writes the entry and refuses titles or bodies that contain `<!--`.
|
|
255
267
|
|
|
256
268
|
```bash
|
|
257
|
-
npx crumbs-spec log add --kind change "
|
|
258
|
-
--body "
|
|
269
|
+
npx crumbs-spec log add --kind change "<one sentence about the user outcome>" \
|
|
270
|
+
--body "Result: <what changed>. Reason: <why>. Document: [<policy>](<domain>/<document>.md#<anchor>)"
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
The entry written in usage example 5:
|
|
274
|
+
|
|
275
|
+
```markdown
|
|
276
|
+
## [2026-11-03] change | A malformed email on registration, login and account update is rejected with 422 and is invalid
|
|
277
|
+
Result: a malformed email on registration, login and account update gets status 422 with {"errors": {"email": ["is invalid"]}}; 3 api documents updated and 1 open question remains about accepted address forms. Reason: code change in server/schemas/user.schema.ts, reported by the user. Document: [Email format](api/users/registration.md#email-format)
|
|
259
278
|
```
|
|
260
279
|
|
|
261
|
-
When a `[
|
|
280
|
+
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, `|`, commas and colons, so the `|` leaves a double dash in the anchor. From usage example 3:
|
|
262
281
|
|
|
263
282
|
```markdown
|
|
264
|
-
## [2026-11-
|
|
265
|
-
|
|
283
|
+
## [2026-11-03] decision | Popular tag list: most used first stays, ties are undefined, 10 tags are cut after sorting, and every viewer gets the same unpaginated list
|
|
284
|
+
Result: GET /api/tags keeps the most-used-first order, leaves the order of equally used tags undefined, applies the 10-tag limit after sorting, and returns the same list without pagination to every viewer. Reason: answers from the maintainer; existing clients may rely on most-used-first and the limit keeps the tag sidebar short. Document: [Popular tag list](api/articles/tag-list.md#popular-tag-list)
|
|
266
285
|
```
|
|
267
286
|
|
|
268
287
|
```markdown
|
|
269
|
-
###
|
|
270
|
-
|
|
288
|
+
### Popular tag list
|
|
289
|
+
The tag list holds at most 10 names of tags used by at least one article, sorted by the number of articles that use each tag, most used first, and the server applies the 10-tag limit after sorting. … (decision: [2026-11-03](../../log.md#2026-11-03-decision--popular-tag-list-most-used-first-stays-ties-are-undefined-10-tags-are-cut-after-sorting-and-every-viewer-gets-the-same-unpaginated-list))
|
|
271
290
|
```
|
|
272
291
|
|
|
273
292
|
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).
|
|
274
293
|
|
|
275
294
|
## Skills
|
|
276
295
|
|
|
277
|
-
`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
|
|
296
|
+
`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. When Claude Code reads the crumbs block is described in step 2 of [Adding it to an existing project](#adding-it-to-an-existing-project).
|
|
297
|
+
|
|
298
|
+
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.
|
|
278
299
|
|
|
279
300
|
| Skill | What it does |
|
|
280
301
|
|---|---|
|
|
@@ -285,57 +306,70 @@ The kinds are `ingest` (a source was applied), `query` (an answer fed back), `li
|
|
|
285
306
|
|
|
286
307
|
## Usage examples
|
|
287
308
|
|
|
309
|
+
Every example below was actually run on the Nitro backend from the experiment, in the [`usage/`](https://github.com/Sienna94/crumbs-spec-conduit/tree/main/usage) workspace of crumbs-spec-conduit. Example 1 is the experiment's Opus 5.5 writer. Examples 2–5 are a Claude Sonnet 5.5 agent that got only the request, read `AGENTS.md` and picked the skill itself; the author answered its questions as the maintainer would. Each example that changed files is one commit, linked below.
|
|
310
|
+
|
|
288
311
|
### 1. Document existing code for the first time
|
|
289
312
|
|
|
290
313
|
```
|
|
291
|
-
Document the code under
|
|
292
|
-
|
|
314
|
+
Document the code under ../vendor/nitro with crumbs-writer in "document existing code" mode,
|
|
315
|
+
from code and tests only. Leave unconfirmed policies as questions and finish the whole thing.
|
|
293
316
|
```
|
|
294
317
|
|
|
295
|
-
|
|
318
|
+
This is a shortened form of the experiment's [writer prompt](https://github.com/Sienna94/crumbs-spec-conduit/blob/main/eval/prompts/writer.md). The writer followed the code from the route handlers, split the API into four subdirectories and produced 21 detail documents with their overviews. The repository has only two unit test files, so 6 policies came out confirmed by tests and 45 as `[needs confirmation]` questions. It ended with `check` and a log entry ([commit](https://github.com/Sienna94/crumbs-spec-conduit/commit/572b0db)).
|
|
296
319
|
|
|
297
320
|
```
|
|
298
|
-
crumbs check:
|
|
321
|
+
crumbs check: 28 documents, 0 errors, 21 warnings
|
|
299
322
|
```
|
|
300
323
|
|
|
301
|
-
The warnings are the
|
|
324
|
+
The 21 warnings are the 21 detail documents that still have open questions. Their "Open questions" sections are the list of things the maintainer has to decide.
|
|
302
325
|
|
|
303
|
-
### 2.
|
|
326
|
+
### 2. Find an answer
|
|
304
327
|
|
|
305
328
|
```
|
|
306
|
-
|
|
329
|
+
How many tags does GET /api/tags return, and in what order?
|
|
307
330
|
```
|
|
308
331
|
|
|
309
|
-
The
|
|
332
|
+
The finder went index → API overview → articles overview → [tag-list.md](https://github.com/Sienna94/crumbs-spec-conduit/blob/main/nitro/docs/specs/api/articles/tag-list.md), four spec documents and no code. It answered: at most 10 names, most used first, only tags that at least one article uses, no pagination. It also said that two points are still `[needs confirmation]`: the order of tags with the same count and the basis of the limit. No file changed.
|
|
310
333
|
|
|
311
334
|
### 3. Ask about what is unknown and record the answer as a decision
|
|
312
335
|
|
|
313
336
|
```
|
|
314
|
-
I want to change the
|
|
337
|
+
I want to change how the tag list is ordered. What do I need to confirm first?
|
|
315
338
|
```
|
|
316
339
|
|
|
317
|
-
The interview skill
|
|
340
|
+
The interview skill listed the eight policies the request touches, marked three as confirmed by code, and turned the rest into seven questions, from "does 'tag list' mean GET /api/tags or the tagList inside articles" to "do existing clients depend on the current order". It stopped without editing anything. The answer: only GET /api/tags; keep most used first; ties stay undefined on purpose; the 10-tag limit stays and applies after sorting. The writer then confirmed the policy and added a `decision` entry and the decision link (see [Log](#log)). `check` caught two things on the way: the decision link pointed at `../log.md` instead of `../../log.md` (`broken-link`), and current behavior grew to seven steps without a diagram (`missing-flow`), so the writer added one ([commit](https://github.com/Sienna94/crumbs-spec-conduit/commit/7b86052)).
|
|
318
341
|
|
|
319
342
|
### 4. Ingest a source document
|
|
320
343
|
|
|
321
344
|
```
|
|
322
|
-
Apply docs/raw/
|
|
345
|
+
The upstream API spec changed. Apply docs/raw/realworld-b8a37f1-validate-email-format.diff.
|
|
323
346
|
```
|
|
324
347
|
|
|
325
|
-
The ingest skill
|
|
348
|
+
The source is the real upstream commit that added email-format validation to the RealWorld spec. The ingest skill read it, found the registration, login and account-update documents through the index and listed four claims. Then it stopped on a conflict: the source expects 422 "is invalid" for a malformed email, while the documents and the code say the format is not checked. Asked whether the source or the code wins, we chose the code, because documents describe current behavior. The skill wrote one `Email format` policy in the registration document with the current behavior and a question that cites the upstream spec, added one linking sentence to the login and update documents, and logged an `ingest` entry ([commit](https://github.com/Sienna94/crumbs-spec-conduit/commit/130946a)). Nothing was edited before the change list was confirmed.
|
|
326
349
|
|
|
327
|
-
### 5.
|
|
350
|
+
### 5. Reflect a code change
|
|
328
351
|
|
|
329
352
|
```
|
|
330
|
-
|
|
353
|
+
I added email format validation to registration, login and account update
|
|
354
|
+
(server/schemas/user.schema.ts, not committed yet). A malformed email now gets 422
|
|
355
|
+
with {"errors":{"email":["is invalid"]}}. Update the docs.
|
|
331
356
|
```
|
|
332
357
|
|
|
333
|
-
The
|
|
358
|
+
The writer found the owning document with `grep -rl 'user.schema' docs/specs`, since all three documents name that file under Evidence. It rewrote `Email format` as a confirmed policy, which closed the question from example 4, and updated the linking sentences in login and update and two flow diagrams. What the code did not settle, which addresses count as valid and whether a blank email also reports "is invalid", became a new question instead of a guess; a blank email does get both messages. The code and the documents are one commit ([commit](https://github.com/Sienna94/crumbs-spec-conduit/commit/32fd2f3)).
|
|
334
359
|
|
|
335
360
|
### 6. Check in CI
|
|
336
361
|
|
|
362
|
+
A GitHub Actions job that fails on errors:
|
|
363
|
+
|
|
337
364
|
```yaml
|
|
338
|
-
|
|
365
|
+
jobs:
|
|
366
|
+
specs:
|
|
367
|
+
runs-on: ubuntu-latest
|
|
368
|
+
steps:
|
|
369
|
+
- uses: actions/checkout@v4
|
|
370
|
+
- uses: actions/setup-node@v4
|
|
371
|
+
with: { node-version: 20 }
|
|
372
|
+
- run: npx crumbs-spec check --format json > crumbs-report.json
|
|
339
373
|
```
|
|
340
374
|
|
|
341
375
|
Exit code 1 means there are errors. Warnings (`open-question`, `missing-flow` and so on) do not fail the build.
|
|
@@ -353,7 +387,11 @@ Exit code 1 means there are errors. Warnings (`open-question`, `missing-flow` an
|
|
|
353
387
|
- Requirement IDs, front matter and enforced grammar such as EARS. Navigation lives in links and headings only.
|
|
354
388
|
- Change-history tables inside documents and one file per decision. Decisions go into the single log and documents point at them.
|
|
355
389
|
- Definition-only term pages. The overview's terms section is enough.
|
|
356
|
-
- Style and content
|
|
390
|
+
- Style and content judgment in `check`. Style is handled by the skill guidance and the markdownlint sample under [docs/lint/](docs/lint/README.md).
|
|
391
|
+
|
|
392
|
+
## Tests and compatibility
|
|
393
|
+
|
|
394
|
+
`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.
|
|
357
395
|
|
|
358
396
|
## Roadmap
|
|
359
397
|
|
|
@@ -363,6 +401,6 @@ Exit code 1 means there are errors. Warnings (`open-question`, `missing-flow` an
|
|
|
363
401
|
| 2 | Four skills, `log add`, flow section, responsibility-size rules, terms and concept pages, decision log | done |
|
|
364
402
|
| 3 | `crumbs reverse <path>` — a command that drafts from code (the writer skill does this today) | planned |
|
|
365
403
|
| 4 | `crumbs drift --base <ref>` — finds documents whose evidence changed in a diff | planned |
|
|
366
|
-
| 5 |
|
|
404
|
+
| 5 | Public example ([crumbs-spec-conduit](https://github.com/Sienna94/crumbs-spec-conduit)) and a navigation-cost benchmark runner | example done, runner planned |
|
|
367
405
|
|
|
368
406
|
The `lang` setting (`en` by default, `ko` for Korean) selects the language of the templates, skills and messages, so one repository uses one language.
|
package/dist/src/check.js
CHANGED
|
@@ -14,21 +14,23 @@ import { missingSection } from './rules/missing-section.js';
|
|
|
14
14
|
import { openQuestion } from './rules/open-question.js';
|
|
15
15
|
import { sectionOrder } from './rules/section-order.js';
|
|
16
16
|
import { unreachable } from './rules/unreachable.js';
|
|
17
|
+
import { unterminatedComment } from './rules/unterminated-comment.js';
|
|
17
18
|
import { finding } from './model.js';
|
|
18
|
-
// 모든 문서는 첫 제목이 H1이어야 한다.
|
|
19
|
+
// 모든 문서는 첫 제목이 H1이어야 한다. 제목이 없는 문서는 1줄을 가리킨다.
|
|
19
20
|
function missingTitle(graph) {
|
|
20
21
|
return graph.docs
|
|
21
22
|
.filter((doc) => doc.headings[0]?.level !== 1)
|
|
22
|
-
.map((doc) => finding('missing-title', 'error', doc.file, MESSAGES[graph.lang].h1Title, doc.headings[0]?.line));
|
|
23
|
+
.map((doc) => finding('missing-title', 'error', doc.file, MESSAGES[graph.lang].h1Title, doc.headings[0]?.line ?? 1));
|
|
23
24
|
}
|
|
24
25
|
function checkGraph(graph) {
|
|
25
26
|
if (graph.index === undefined) {
|
|
26
27
|
const indexPath = relPath(graph.root, graph.indexAbs);
|
|
27
28
|
// 인덱스가 없으면 다른 규칙은 돌리지 않는다.
|
|
28
|
-
return [finding('index-missing', 'error', indexPath, MESSAGES[graph.lang].indexMissing(indexPath))];
|
|
29
|
+
return [finding('index-missing', 'error', indexPath, MESSAGES[graph.lang].indexMissing(indexPath), 1)];
|
|
29
30
|
}
|
|
30
31
|
return [
|
|
31
32
|
...missingTitle(graph),
|
|
33
|
+
...unterminatedComment(graph),
|
|
32
34
|
...brokenLink(graph),
|
|
33
35
|
...brokenAnchor(graph),
|
|
34
36
|
...unreachable(graph),
|
|
@@ -45,10 +47,10 @@ function checkGraph(graph) {
|
|
|
45
47
|
];
|
|
46
48
|
}
|
|
47
49
|
const compareText = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
48
|
-
// file, line
|
|
50
|
+
// file, line, rule, message 순으로 정렬한다.
|
|
49
51
|
function sortFindings(findings) {
|
|
50
52
|
return [...findings].sort((a, b) => compareText(a.file, b.file) ||
|
|
51
|
-
|
|
53
|
+
a.line - b.line ||
|
|
52
54
|
compareText(a.rule, b.rule) ||
|
|
53
55
|
compareText(a.message, b.message));
|
|
54
56
|
}
|
package/dist/src/check.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,OAAO,EAAc,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,OAAO,EAA0C,MAAM,YAAY,CAAC;AAE7E,
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/check.ts"],"names":[],"mappings":"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"}
|
package/dist/src/cli.js
CHANGED
|
@@ -116,6 +116,8 @@ function logAdd(root, values, args, lang) {
|
|
|
116
116
|
const body = values.body === undefined ? undefined : normalizeLogBody(values.body);
|
|
117
117
|
if (body !== undefined && hasHeadingLine(body))
|
|
118
118
|
throw new UsageError(m.bodyHeading, lang);
|
|
119
|
+
if (rawTitle.includes('<!--') || (body ?? '').includes('<!--'))
|
|
120
|
+
throw new UsageError(m.commentForbidden, lang);
|
|
119
121
|
const config = loadRepoConfig(root, values.config, lang);
|
|
120
122
|
assertLogDocument(root, config);
|
|
121
123
|
rejectForeignOptions(values, LOG_ADD_OPTIONS, config.lang);
|