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.
Files changed (53) hide show
  1. package/README.ko.md +139 -101
  2. package/README.md +159 -121
  3. package/dist/src/check.js +7 -5
  4. package/dist/src/check.js.map +1 -1
  5. package/dist/src/cli.js +2 -0
  6. package/dist/src/cli.js.map +1 -1
  7. package/dist/src/config.js +16 -1
  8. package/dist/src/config.js.map +1 -1
  9. package/dist/src/dates.js +7 -0
  10. package/dist/src/dates.js.map +1 -1
  11. package/dist/src/graph.js +81 -14
  12. package/dist/src/graph.js.map +1 -1
  13. package/dist/src/init.js +24 -8
  14. package/dist/src/init.js.map +1 -1
  15. package/dist/src/markdown.js +155 -25
  16. package/dist/src/markdown.js.map +1 -1
  17. package/dist/src/messages.js +40 -15
  18. package/dist/src/messages.js.map +1 -1
  19. package/dist/src/model.js +2 -3
  20. package/dist/src/model.js.map +1 -1
  21. package/dist/src/report.js +1 -1
  22. package/dist/src/report.js.map +1 -1
  23. package/dist/src/rules/broken-link.js +4 -1
  24. package/dist/src/rules/broken-link.js.map +1 -1
  25. package/dist/src/rules/duplicate-policy.js +11 -2
  26. package/dist/src/rules/duplicate-policy.js.map +1 -1
  27. package/dist/src/rules/log-body.js +3 -3
  28. package/dist/src/rules/log-body.js.map +1 -1
  29. package/dist/src/rules/log-format.js +6 -0
  30. package/dist/src/rules/log-format.js.map +1 -1
  31. package/dist/src/rules/missing-backlink.js +4 -1
  32. package/dist/src/rules/missing-backlink.js.map +1 -1
  33. package/dist/src/rules/missing-section.js +14 -2
  34. package/dist/src/rules/missing-section.js.map +1 -1
  35. package/dist/src/rules/unreachable.js +1 -1
  36. package/dist/src/rules/unreachable.js.map +1 -1
  37. package/dist/src/rules/unterminated-comment.js +13 -0
  38. package/dist/src/rules/unterminated-comment.js.map +1 -0
  39. package/dist/src/skills.js +12 -1
  40. package/dist/src/skills.js.map +1 -1
  41. package/dist/src/templates/agents.js +2 -2
  42. package/dist/src/templates/agents.js.map +1 -1
  43. package/dist/src/templates/en.js +5 -5
  44. package/dist/src/templates/en.js.map +1 -1
  45. package/dist/src/templates/ko.js +1 -1
  46. package/dist/src/templates/ko.js.map +1 -1
  47. package/dist/src/templates/skills-en.js +16 -15
  48. package/dist/src/templates/skills-en.js.map +1 -1
  49. package/dist/src/templates/skills-ko.js +10 -9
  50. package/dist/src/templates/skills-ko.js.map +1 -1
  51. package/dist/src/version.js +7 -0
  52. package/dist/src/version.js.map +1 -0
  53. 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 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 fixes the procedures an agent follows to write, find, ask about and ingest them as skills.
5
+ [![CI](https://github.com/Sienna94/crumbs-spec/actions/workflows/ci.yml/badge.svg)](https://github.com/Sienna94/crumbs-spec/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/crumbs-spec)](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
- Running dozens of spec documents together with agents on a real app repository, the same three things kept happening.
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**. Give every policy exactly one owner, check the link graph, and make the unknown a question instead of a guess, and both agents and people reach the document by the same path. So we started scattering breadcrumbs.
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 takes the gap between them: **the current state that must survive after implementation**. It complements them rather than replacing them.
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 behaviour after implementation** |
25
- | Existing code | States that it "does not infer" | Has onboarding, but no code-to-spec drafting | Targets sources, not code | **Drafts from code and tests; the unknown becomes a `[확인 필요]` question** |
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 | Delta merge | Orphan page and broken link lint (with LLM judgement) | **15 deterministic rules**: links, anchors, reachability, backlinks, duplicate policies, evidence paths, section order, flow diagrams, log format |
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") + decision log + planned `drift`** |
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
- 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 organised 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 judgement. See section 1 of [docs/IMPLEMENTATION_PLAN.md](docs/IMPLEMENTATION_PLAN.md) (Korean) for the full comparison.
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 `[확인 필요]` (needs confirmation) question. `check` counts the questions for you.
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 judgement.** `check` looks at structure only. Style, content and staleness are for skill guidance and people.
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
- ## What happened when we used it
47
+ ## Does it work on code it has never seen? (RealWorld experiment)
44
48
 
45
- 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.
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
- **Cost of writing from zero** (one agent per domain, three in parallel)
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
- | Domain | Code | Documents | Policies | Confirmed by tests / questions | Agent tokens | Writing time |
50
- |---|---|---|---|---|---|---|
51
- | Payment | ~20k lines | 19 | 100 | 5 / 95 | 570k | 1 h 7 min |
52
- | Viewer | ~23k lines | 40 | 194 | 29 / 165 | 850k | 1 h 7 min |
53
- | Auth | ~10k lines | 35 | 195 | 64 / 131 | 790k | 1 h 13 min |
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
- 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. Not one policy was invented, and all six places where the existing spec disagreed with the code were written as the code behaves. The documents were later regrouped by user flow into 38.
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
- **Navigation cost on the same 18 questions** (documents only; reading code was forbidden)
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
- | | Existing spec system | crumbs-spec |
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
- 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. That cause is now fixed by a skill rule ("do not drop values that carry a condition"). Note the bias: the questions were drawn from the existing specs, which favours the existing system.
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 seven files and never overwrites an existing one.
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
- Until the package is on npm, run it from the repository with `npm run build` and then `node dist/src/cli.js <command>`.
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 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
+ 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 `[확인 필요]` questions in the result become the list of things a person has to decide for that area.
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 (broken links and anchors, unreachable documents, missing backlinks, missing code paths, duplicate policy names) and leave warnings alone. Directories under the spec root that are not crumbs documents yet go into `exclude` in `crumbs.json`.
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 (the step-5 benchmark runner will automate this).
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 현재 동작 (current behaviour), steps as ### subheadings"]
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 behaviour + 확인 필요 question"]
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
- | 목적 (purpose) | The one responsibility this document owns and what it does not cover |
162
- | 현재 동작 (current behaviour) | As the user experiences it, in the present tense; three or more steps become `###` subheadings |
163
- | 흐름 (flow) | A Mermaid diagram for two or more steps; a table when there are two or more comparison axes |
164
- | 정책 (policies) | `### policy name` (unique across the repository) and the rule; if unconfirmed, a statement of current behaviour followed by a `[확인 필요]` question |
165
- | 근거 (evidence) | Backticked code paths with a description; `check` verifies the path exists |
166
- | 확인 필요 사항 (open questions) | `- [확인 필요] [policy name](#anchor) — question` |
167
- | 관련 문서 (related) | Link to the overview and neighbouring documents; backlinks must be mutual |
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 (`docs/specs/shopping/장바구니.md`, "cart"):
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
- <!-- ../index.md를 먼저 읽는다. 이 문서를 바꾼 뒤 ../log.md에 항목을 추가한다 -->
174
-
175
- ## 목적
176
- 사용자가 상품을 담고 수량을 바꾸고 비울 때까지의 흐름을 맡는다. 쿠폰 적용은 [쿠폰 할인](쿠폰-할인.md)이 맡는다.
177
-
178
- ## 현재 동작
179
- - 사용자가 같은 상품을 다시 담으면 앱은 수량을 더한다.
180
- - 수량이 99를 넘으면 앱은 "최대 99개까지 담을 수 있습니다." 안내를 보이고 담지 않는다.
181
-
182
- ## 흐름
183
- ```mermaid
184
- flowchart TD
185
- A["상품 담기"] --> B{"같은 상품이 있나"}
186
- B -->|예| C["수량 합산"]
187
- B -->|아니오| D["새 줄 추가"]
188
- C --> E{"99 초과"}
189
- E -->|예| F["안내 후 담지 않음"]
190
- ```
191
-
192
- ## 정책
193
- ### 수량 상한
194
- 장바구니 한 줄의 수량은 99 이하다.
195
-
196
- ### 최소 주문 금액
197
- 지금은 합계가 10,000원 미만이면 결제 버튼을 비활성화한다.
198
- [확인 필요] 10,000원이라는 기준의 근거는 무엇인가?
199
-
200
- ## 근거
201
- - `src/cart/cart.ts:12-48` — 담기와 수량 합산
202
- - `test/cart/cart.test.ts` — 수량 상한
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 `## 용어` (terms) section is a list of aliases linked to the owning policy (`- 정액권(정액제) — [정액권 이용 중 판정](정액권.md#정액권-이용-중-판정)`). 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/`.
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/shopping/장바구니.md:34 error broken-anchor: 앵커가 없음: 쿠폰-할인.md#적용-순서
228
- docs/specs/shopping/장바구니.md:20 warning open-question: 미해결 질문 표시 2개
229
-
230
- crumbs check: 문서 4개, 오류 1개, 경고 1개
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` / `broken-anchor` | error | The link target file or heading does not exist |
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
- | `open-question` | warning | Number of `[확인 필요]` markers in a document |
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 behaviour has six or more steps but no diagram or table |
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 "장바구니 수량 상한을 99로 올림" \
258
- --body "결과: 한 줄에 99개까지 담을 수 있다. 이유: 요청 SHOP-42. 문서: [수량 상한](shopping/장바구니.md#수량-상한)"
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 `[확인 필요]` 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.
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-02] decision | 최소 주문 금액: 10,000원은 배송비 정책에서 온 값이다
265
- 결과: 합계 10,000원 미만이면 결제 버튼이 비활성이다. 이유: 2026-11-02 기획 확인. 문서: [최소 주문 금액](shopping/장바구니.md#최소-주문-금액)
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
- 합계가 10,000원 미만이면 결제 버튼을 비활성화한다. (결정: [2026-11-02](../log.md#2026-11-02-decision-최소-주문-금액-10000원은-배송비-정책에서-온-값이다))
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 only when `CLAUDE.md` contains `@AGENTS.md`.
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 src/cart/ with crumbs-writer in "document existing code" mode.
292
- The domain directory is docs/specs/shopping/. Leave unconfirmed policies as questions and finish the whole thing.
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
- 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 `[확인 필요]` questions. It ends by running `check`, clearing errors and adding a log entry.
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: 문서 5개, 오류 0개, 경고 3개
321
+ crumbs check: 28 documents, 0 errors, 21 warnings
299
322
  ```
300
323
 
301
- The warnings are the questions left behind. Skimming the open-questions section is enough to see what has to be decided.
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. Reflect a code change
326
+ ### 2. Find an answer
304
327
 
305
328
  ```
306
- I raised the cart quantity limit from 99 to 199. Update the docs.
329
+ How many tags does GET /api/tags return, and in what order?
307
330
  ```
308
331
 
309
- The writer finds the owning document with `grep -rl 'src/cart/cart.ts' docs/specs` (the evidence section names the path), fixes the current-behaviour section and the `### 수량 상한` policy, updates the value in the flow diagram, and logs `change | 장바구니 수량 상한을 199로 올림`. If two documents look like owners it stops and asks which one is.
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 coupon stacking rule. What do I need to confirm first?
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 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.
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/coupon-policy-v3.pdf.
345
+ The upstream API spec changed. Apply docs/raw/realworld-b8a37f1-validate-email-format.diff.
323
346
  ```
324
347
 
325
- 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.
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. Find an answer
350
+ ### 5. Reflect a code change
328
351
 
329
352
  ```
330
- How many items can go into the cart?
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 finder goes index → shopping overview → cart document and answers with `### 수량 상한` 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.
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
- - run: npx crumbs-spec check --format json > crumbs-report.json
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 judgement in `check`. Style is handled by the skill guidance and the markdownlint sample under [docs/lint/](docs/lint/README.md).
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 | Example app and a navigation-cost benchmark runner | planned |
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(없는 것은 마지막), rule, message 순으로 정렬한다.
50
+ // file, line, rule, message 순으로 정렬한다.
49
51
  function sortFindings(findings) {
50
52
  return [...findings].sort((a, b) => compareText(a.file, b.file) ||
51
- (a.line ?? Number.MAX_SAFE_INTEGER) - (b.line ?? Number.MAX_SAFE_INTEGER) ||
53
+ a.line - b.line ||
52
54
  compareText(a.rule, b.rule) ||
53
55
  compareText(a.message, b.message));
54
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/check.ts"],"names":[],"mappings":"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"}
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);