@sledorze/cairn 0.1.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 (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +77950 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config.d.ts +20 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +168 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/core/Config.d.ts +80 -0
  12. package/dist/core/Config.d.ts.map +1 -0
  13. package/dist/core/Config.js +135 -0
  14. package/dist/core/Config.js.map +1 -0
  15. package/dist/core/DocSummaries.d.ts +42 -0
  16. package/dist/core/DocSummaries.d.ts.map +1 -0
  17. package/dist/core/DocSummaries.js +74 -0
  18. package/dist/core/DocSummaries.js.map +1 -0
  19. package/dist/core/MarkdownLinks.bench.d.ts +2 -0
  20. package/dist/core/MarkdownLinks.bench.d.ts.map +1 -0
  21. package/dist/core/MarkdownLinks.bench.js +81 -0
  22. package/dist/core/MarkdownLinks.bench.js.map +1 -0
  23. package/dist/core/MarkdownLinks.d.ts +51 -0
  24. package/dist/core/MarkdownLinks.d.ts.map +1 -0
  25. package/dist/core/MarkdownLinks.js +115 -0
  26. package/dist/core/MarkdownLinks.js.map +1 -0
  27. package/dist/core/SummaryTree.bench.d.ts +2 -0
  28. package/dist/core/SummaryTree.bench.d.ts.map +1 -0
  29. package/dist/core/SummaryTree.bench.js +70 -0
  30. package/dist/core/SummaryTree.bench.js.map +1 -0
  31. package/dist/core/SummaryTree.d.ts +45 -0
  32. package/dist/core/SummaryTree.d.ts.map +1 -0
  33. package/dist/core/SummaryTree.js +196 -0
  34. package/dist/core/SummaryTree.js.map +1 -0
  35. package/dist/core/glob.bench.d.ts +2 -0
  36. package/dist/core/glob.bench.d.ts.map +1 -0
  37. package/dist/core/glob.bench.js +62 -0
  38. package/dist/core/glob.bench.js.map +1 -0
  39. package/dist/core/glob.d.ts +7 -0
  40. package/dist/core/glob.d.ts.map +1 -0
  41. package/dist/core/glob.js +52 -0
  42. package/dist/core/glob.js.map +1 -0
  43. package/dist/core/paths.d.ts +3 -0
  44. package/dist/core/paths.d.ts.map +1 -0
  45. package/dist/core/paths.js +6 -0
  46. package/dist/core/paths.js.map +1 -0
  47. package/dist/index.d.ts +12 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +13 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/init/content.d.ts +3 -0
  52. package/dist/init/content.d.ts.map +1 -0
  53. package/dist/init/content.js +127 -0
  54. package/dist/init/content.js.map +1 -0
  55. package/dist/init/generate.d.ts +14 -0
  56. package/dist/init/generate.d.ts.map +1 -0
  57. package/dist/init/generate.js +121 -0
  58. package/dist/init/generate.js.map +1 -0
  59. package/dist/io/DocsFs.d.ts +30 -0
  60. package/dist/io/DocsFs.d.ts.map +1 -0
  61. package/dist/io/DocsFs.js +95 -0
  62. package/dist/io/DocsFs.js.map +1 -0
  63. package/dist/program/CheckLinks.d.ts +27 -0
  64. package/dist/program/CheckLinks.d.ts.map +1 -0
  65. package/dist/program/CheckLinks.js +122 -0
  66. package/dist/program/CheckLinks.js.map +1 -0
  67. package/dist/program/CheckSummaries.d.ts +38 -0
  68. package/dist/program/CheckSummaries.d.ts.map +1 -0
  69. package/dist/program/CheckSummaries.js +198 -0
  70. package/dist/program/CheckSummaries.js.map +1 -0
  71. package/dist/program/JsonReport.d.ts +14 -0
  72. package/dist/program/JsonReport.d.ts.map +1 -0
  73. package/dist/program/JsonReport.js +12 -0
  74. package/dist/program/JsonReport.js.map +1 -0
  75. package/dist/program/locale.d.ts +13 -0
  76. package/dist/program/locale.d.ts.map +1 -0
  77. package/dist/program/locale.js +8 -0
  78. package/dist/program/locale.js.map +1 -0
  79. package/package.json +78 -0
  80. package/schema/cairn.schema.json +77 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stéphane Le Dorze
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ <p align="center">
2
+ <img src="assets/logo.svg" width="120" height="120" alt="Cairn logo">
3
+ </p>
4
+
5
+ # @sledorze/cairn
6
+
7
+ Keep your documentation summaries honest. `cairn` enforces a hierarchical,
8
+ content-hashed summary tree over any docs folder — and fails CI when a summary drifts
9
+ out of date or a link goes dead.
10
+
11
+ ## The problem
12
+
13
+ Docs summaries rot. Someone edits `guide.md`, forgets to update `guide.summary.md`, and
14
+ the digest now lies. The usual fix — "regenerate summaries when the source is newer" —
15
+ compares modification times. But **git does not preserve mtimes**: after a clone or a CI
16
+ checkout, every file looks freshly written, so a time-based check silently passes on
17
+ summaries that are actually stale. The bug you meant to catch ships anyway.
18
+
19
+ `cairn` checks **content**, not clocks. Each summary embeds the SHA-256 of the
20
+ source it summarizes as its first line:
21
+
22
+ ```markdown
23
+ <!-- source-sha256: 3f9a…(64 hex) -->
24
+ ```
25
+
26
+ The checker recomputes the source hash and compares. Mismatch means stale, missing means
27
+ missing — and it behaves identically on your laptop and in CI, before and after a clone.
28
+
29
+ ## Install
30
+
31
+ ```sh
32
+ pnpm add -D @sledorze/cairn
33
+ ```
34
+
35
+ ## Quick start
36
+
37
+ ```sh
38
+ npx cairn check
39
+ ```
40
+
41
+ The round-trip when you touch a doc:
42
+
43
+ 1. **Edit** `docs/guide.md`.
44
+ 2. `npx cairn check` **flags it stale** — the source hash no longer matches the
45
+ stamp in `guide.summary.md`.
46
+ 3. **Write** the updated `guide.summary.md` (and update any parent `_SUMMARY.md`).
47
+ 4. **Stamp**: `npx cairn check --summaries-only --stamp` rewrites the
48
+ `source-sha256` lines bottom-up.
49
+ 5. **Check** again — `npx cairn check` exits 0. Green.
50
+
51
+ You author the prose; the tool verifies and stamps. It never invents content.
52
+
53
+ ### Commands
54
+
55
+ | Command | What it does |
56
+ | ----------------------------------------- | -------------------------------------------------------- |
57
+ | `cairn check` | Check summaries + links; exit 1 on any problem |
58
+ | `cairn check --summaries-only` | Check only summary freshness |
59
+ | `cairn check --links-only` | Check only Markdown links |
60
+ | `cairn check --links-only --fix` | Auto-repair unambiguous dead links |
61
+ | `cairn check --summaries-only --stamp` | Rewrite `source-sha256` of existing summaries, bottom-up |
62
+ | `cairn init --agent claude\|copilot\|all` | Scaffold agent guidance files |
63
+
64
+ ## The two summary kinds
65
+
66
+ **File summaries** — every Markdown file longer than the threshold (default 30 lines) gets
67
+ a sibling `X.summary.md`: a fast-to-read digest of the **current** content of `X.md`.
68
+ Front-load the thesis and the numbers; a reader should get the gist in ~10 seconds.
69
+
70
+ **Directory summaries** — every directory in scope gets a `_SUMMARY.md` that acts as a map.
71
+ It aggregates its direct docs (each doc's `.summary.md` if the doc is big, else the doc
72
+ itself) plus the `_SUMMARY.md` of each direct sub-directory, and it links to **every**
73
+ direct child file and sub-directory (the link-completeness rule).
74
+
75
+ ### Why bottom-up
76
+
77
+ A directory summary's hash is computed over a manifest of its children's hashes — a Merkle
78
+ tree. So summaries must be (re)written and stamped **leaves-first**: file summaries, then
79
+ directories deepest-first, then stamp. Stamp top-down and parents capture stale child
80
+ hashes. The `--stamp` command walks the tree in the right order for you.
81
+
82
+ ## Configuration
83
+
84
+ Drop a `.cairnrc.json` at the repo root (`cairn init` scaffolds one for you):
85
+
86
+ ```json
87
+ {
88
+ "$schema": "./node_modules/@sledorze/cairn/schema/cairn.schema.json",
89
+ "roots": ["docs/**"],
90
+ "thresholdLines": 30,
91
+ "naming": {
92
+ "dirSummary": "_SUMMARY.md",
93
+ "fileSummarySuffix": ".summary.md"
94
+ },
95
+ "checks": { "summaries": true, "links": true },
96
+ "requireDirSummaries": true,
97
+ "ignore": ["**/node_modules/**"],
98
+ "stampCommand": "npx cairn check --summaries-only --stamp",
99
+ "locale": "en"
100
+ }
101
+ ```
102
+
103
+ | Key | Meaning |
104
+ | -------------------------- | --------------------------------------------------------------------------------------- |
105
+ | `$schema` | JSON Schema URL for editor autocomplete/validation. Ignored by cairn. |
106
+ | `extends` | One or more config files to inherit from (see below) |
107
+ | `roots` | Documentation roots to scan (array; globs allowed). Default `docs/` |
108
+ | `thresholdLines` | Line count above which a file needs a `.summary.md`. Non-negative integer. Default `30` |
109
+ | `naming.dirSummary` | Directory summary filename. Default `_SUMMARY.md` |
110
+ | `naming.fileSummarySuffix` | Suffix for file summaries. Default `.summary.md` |
111
+ | `checks.summaries` | Enable summary freshness checking |
112
+ | `checks.links` | Enable Markdown link checking |
113
+ | `requireDirSummaries` | Require a `_SUMMARY.md` in every in-scope directory |
114
+ | `ignore` | Globs to exclude from scanning |
115
+ | `stampCommand` | Command agents should run to stamp hashes |
116
+ | `locale` | Prose locale for generated guidance: `en` or `fr` |
117
+
118
+ Config is validated strictly (via `effect/Schema`): an **unknown key or a wrong-typed
119
+ value fails loudly** with a file-scoped, actionable error, instead of being silently
120
+ ignored. A typo like `"thresholdLins"` is a bug you want caught, not a setting that
121
+ quietly reverts to the default.
122
+
123
+ ### Sharing config with `extends`
124
+
125
+ A `.cairnrc.json` (or any file it extends) can inherit from one or more base presets:
126
+
127
+ ```json
128
+ { "extends": "./base.cairnrc.json", "thresholdLines": 50 }
129
+ ```
130
+
131
+ `extends` accepts a single path or an array; presets are applied first (in order), then
132
+ the extending file's own fields win. Use it to share a base config across packages in a
133
+ monorepo, or to publish an org-wide preset as its own package.
134
+
135
+ ### Editor autocomplete
136
+
137
+ The `$schema` key (scaffolded by `cairn init`, generated from the same schema that
138
+ validates your config — see `schema/cairn.schema.json`) gives editors that understand
139
+ JSON Schema (VS Code, JetBrains, coc-json) inline docs, autocomplete, and a squiggle on
140
+ an invalid key — before you even run `cairn check`.
141
+
142
+ ## Multi-agent guidance
143
+
144
+ ```sh
145
+ npx cairn init --agent all
146
+ ```
147
+
148
+ `init` scaffolds the convention into whatever coding agents you use, so they follow it
149
+ automatically:
150
+
151
+ - **`.claude/rules/*.md`** — a path-scoped Claude rule (`paths:` frontmatter) loaded when an
152
+ agent touches your docs.
153
+ - **`CLAUDE.md`** — an `@AGENTS.md` import, upserted at the repo root. Claude Code
154
+ auto-loads `CLAUDE.md` at session start but never reads `AGENTS.md` on its own; without
155
+ this pointer the block below is invisible to it.
156
+ - **`.github/instructions/*.instructions.md`** — GitHub Copilot instructions with an
157
+ `applyTo:` glob.
158
+ - **`AGENTS.md`** — a block appended to the repo-wide agent guide.
159
+ - **`SKILL.md`** — the on-demand methodology for _writing good summaries_.
160
+
161
+ Pass `--agent claude`, `--agent copilot`, or `--agent all`.
162
+
163
+ ## CI usage
164
+
165
+ Run the check as a required status. It is fast and clone-safe by design:
166
+
167
+ ```yaml
168
+ - run: pnpm add -D @sledorze/cairn
169
+ - run: npx cairn check
170
+ ```
171
+
172
+ A missing, stale, or broken summary exits non-zero and blocks the merge. Because freshness
173
+ is content-hashed rather than mtime-based, the result is identical to what you saw locally —
174
+ no false greens after checkout.
175
+
176
+ ## Credits
177
+
178
+ Built on [Effect](https://effect.website).
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}