@sledorze/cairn 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.md +69 -18
- package/dist/cli.js +69378 -70958
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -10
- package/dist/config.js.map +1 -1
- package/dist/core/Config.d.ts +22 -22
- package/dist/core/Config.d.ts.map +1 -1
- package/dist/core/Config.js +33 -33
- package/dist/core/Config.js.map +1 -1
- package/dist/core/glob.bench.js +1 -1
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/hashing.d.ts +3 -0
- package/dist/core/hashing.d.ts.map +1 -0
- package/dist/core/hashing.js +16 -0
- package/dist/core/hashing.js.map +1 -0
- package/dist/core/links/Anchors.d.ts +30 -0
- package/dist/core/links/Anchors.d.ts.map +1 -0
- package/dist/core/links/Anchors.js +119 -0
- package/dist/core/links/Anchors.js.map +1 -0
- package/dist/core/links/MarkdownLinks.bench.d.ts.map +1 -0
- package/dist/core/{MarkdownLinks.bench.js → links/MarkdownLinks.bench.js} +1 -1
- package/dist/core/links/MarkdownLinks.bench.js.map +1 -0
- package/dist/core/links/MarkdownLinks.d.ts +121 -0
- package/dist/core/links/MarkdownLinks.d.ts.map +1 -0
- package/dist/core/links/MarkdownLinks.js +228 -0
- package/dist/core/links/MarkdownLinks.js.map +1 -0
- package/dist/core/links/RefStore.d.ts +32 -0
- package/dist/core/links/RefStore.d.ts.map +1 -0
- package/dist/core/links/RefStore.js +60 -0
- package/dist/core/links/RefStore.js.map +1 -0
- package/dist/core/links/markdownFences.d.ts +10 -0
- package/dist/core/links/markdownFences.d.ts.map +1 -0
- package/dist/core/links/markdownFences.js +51 -0
- package/dist/core/links/markdownFences.js.map +1 -0
- package/dist/core/paths.d.ts +8 -0
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +13 -0
- package/dist/core/paths.js.map +1 -1
- package/dist/core/sidecar.d.ts +45 -0
- package/dist/core/sidecar.d.ts.map +1 -0
- package/dist/core/sidecar.js +83 -0
- package/dist/core/sidecar.js.map +1 -0
- package/dist/core/{DocSummaries.d.ts → summaries/DocSummaries.d.ts} +13 -3
- package/dist/core/summaries/DocSummaries.d.ts.map +1 -0
- package/dist/core/{DocSummaries.js → summaries/DocSummaries.js} +31 -11
- package/dist/core/summaries/DocSummaries.js.map +1 -0
- package/dist/core/summaries/StampStore.d.ts +19 -0
- package/dist/core/summaries/StampStore.d.ts.map +1 -0
- package/dist/core/summaries/StampStore.js +34 -0
- package/dist/core/summaries/StampStore.js.map +1 -0
- package/dist/core/summaries/SummaryTree.bench.d.ts.map +1 -0
- package/dist/core/{SummaryTree.bench.js → summaries/SummaryTree.bench.js} +24 -14
- package/dist/core/summaries/SummaryTree.bench.js.map +1 -0
- package/dist/core/{SummaryTree.d.ts → summaries/SummaryTree.d.ts} +10 -1
- package/dist/core/summaries/SummaryTree.d.ts.map +1 -0
- package/dist/core/{SummaryTree.js → summaries/SummaryTree.js} +35 -9
- package/dist/core/summaries/SummaryTree.js.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/init/content.d.ts +2 -2
- package/dist/init/content.d.ts.map +1 -1
- package/dist/init/content.js +45 -15
- package/dist/init/content.js.map +1 -1
- package/dist/init/generate.d.ts +1 -1
- package/dist/init/generate.d.ts.map +1 -1
- package/dist/init/generate.js +1 -1
- package/dist/io/DocsFs.d.ts +2 -3
- package/dist/io/DocsFs.d.ts.map +1 -1
- package/dist/io/DocsFs.js +15 -5
- package/dist/io/DocsFs.js.map +1 -1
- package/dist/program/JsonReport.d.ts +2 -2
- package/dist/program/JsonReport.d.ts.map +1 -1
- package/dist/program/JsonReport.js +2 -2
- package/dist/program/JsonReport.js.map +1 -1
- package/dist/program/{CheckLinks.d.ts → links/CheckLinks.d.ts} +9 -4
- package/dist/program/links/CheckLinks.d.ts.map +1 -0
- package/dist/program/links/CheckLinks.js +243 -0
- package/dist/program/links/CheckLinks.js.map +1 -0
- package/dist/program/links/CheckRefs.d.ts +47 -0
- package/dist/program/links/CheckRefs.d.ts.map +1 -0
- package/dist/program/links/CheckRefs.js +151 -0
- package/dist/program/links/CheckRefs.js.map +1 -0
- package/dist/program/locale.d.ts.map +1 -1
- package/dist/program/summaries/CheckSummaries.bench.d.ts +2 -0
- package/dist/program/summaries/CheckSummaries.bench.d.ts.map +1 -0
- package/dist/program/summaries/CheckSummaries.bench.js +94 -0
- package/dist/program/summaries/CheckSummaries.bench.js.map +1 -0
- package/dist/program/summaries/CheckSummaries.d.ts +58 -0
- package/dist/program/summaries/CheckSummaries.d.ts.map +1 -0
- package/dist/program/summaries/CheckSummaries.js +302 -0
- package/dist/program/summaries/CheckSummaries.js.map +1 -0
- package/package.json +7 -8
- package/schema/cairn.schema.json +35 -38
- package/dist/core/DocSummaries.d.ts.map +0 -1
- package/dist/core/DocSummaries.js.map +0 -1
- package/dist/core/MarkdownLinks.bench.d.ts.map +0 -1
- package/dist/core/MarkdownLinks.bench.js.map +0 -1
- package/dist/core/MarkdownLinks.d.ts +0 -51
- package/dist/core/MarkdownLinks.d.ts.map +0 -1
- package/dist/core/MarkdownLinks.js +0 -115
- package/dist/core/MarkdownLinks.js.map +0 -1
- package/dist/core/SummaryTree.bench.d.ts.map +0 -1
- package/dist/core/SummaryTree.bench.js.map +0 -1
- package/dist/core/SummaryTree.d.ts.map +0 -1
- package/dist/core/SummaryTree.js.map +0 -1
- package/dist/program/CheckLinks.d.ts.map +0 -1
- package/dist/program/CheckLinks.js +0 -122
- package/dist/program/CheckLinks.js.map +0 -1
- package/dist/program/CheckSummaries.d.ts +0 -38
- package/dist/program/CheckSummaries.d.ts.map +0 -1
- package/dist/program/CheckSummaries.js +0 -198
- package/dist/program/CheckSummaries.js.map +0 -1
- /package/dist/core/{MarkdownLinks.bench.d.ts → links/MarkdownLinks.bench.d.ts} +0 -0
- /package/dist/core/{SummaryTree.bench.d.ts → summaries/SummaryTree.bench.d.ts} +0 -0
package/dist/init/content.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const CONVENTION_BODY = "# Documentation summary convention\n\nThis repo enforces a **hierarchical, content-hashed documentation summary** tree.\nCI runs `cairn check` and **fails the merge** if any summary is missing,\nstale, or a link is broken. Treat green `check` as a hard requirement, not a nicety.\n\n## The invariant\n\n1. **File summaries** \u2014 every Markdown file longer than the threshold (default 30\n lines) has a sibling `X.summary.md`: a fast-to-read digest of the CURRENT content\n of `X.md`.\n2. **Directory summaries** \u2014 every in-scope directory has a `_SUMMARY.md` that\n aggregates its direct docs (each doc's `.summary.md` if the doc is big, else the\n doc itself) plus the `_SUMMARY.md` of each direct sub-directory. It links to\n **every** direct child file and sub-directory (link-completeness).\n3. **Freshness by content hash** \u2014 each summary's
|
|
2
|
-
export declare const SKILL_BODY = "# Writing good documentation summaries\n\nUse this when you author or refresh the summary tree that `cairn`\nenforces. It covers *how* to write summaries that are worth reading, and the exact\nmechanical order to (re)generate them so `check` goes green in one pass.\n\n## Two kinds of summary \u2014 do not conflate them\n\n**A file summary (`X.summary.md`) condenses ONE document.**\nGoal: a reader grasps what `X.md` says in ~10 seconds.\n\n- Faithful to the source \u2014 never invent, never contradict, never add claims the doc\n doesn't make. It is a digest, not commentary.\n- Front-load the thesis and the hard numbers. Lead with the conclusion, the decision,\n the metric \u2014 not the background.\n- Bullet-dense, no fluff. Drop hedging, transitions, and restated headings.\n- If the source changes, the summary changes. A summary that no longer matches its\n source is a bug the checker will catch.\n\n**A directory summary (`_SUMMARY.md`) is a MAP, not a digest.**\nGoal: a reader knows what lives in this directory and where to go next.\n\n- A short orientation paragraph (1-3 sentences): what this directory is about.\n- Then one line per direct child \u2014 file **and** sub-directory \u2014 each with a Markdown\n link and a few-word hook describing what's behind it.\n- For a big doc, link and hook its `.summary.md`; for a small doc, link the doc itself.\n- For a sub-directory, link its `_SUMMARY.md`.\n- **Link-completeness:** every direct child must appear as a link. A missing link fails\n `check`. When you add or remove a child, update the parent's `_SUMMARY.md`.\n\n## Why leaves-first \u2014 the Merkle mental model\n\nEach summary is stamped
|
|
1
|
+
export declare const CONVENTION_BODY = "# Documentation summary convention\n\nThis repo enforces a **hierarchical, content-hashed documentation summary** tree.\nCI runs `cairn check` and **fails the merge** if any summary is missing,\nstale, or a link is broken. Treat green `check` as a hard requirement, not a nicety.\n\n## The invariant\n\n1. **File summaries** \u2014 every Markdown file longer than the threshold (default 30\n lines) has a sibling `X.summary.md`: a fast-to-read digest of the CURRENT content\n of `X.md`.\n2. **Directory summaries** \u2014 every in-scope directory has a `_SUMMARY.md` that\n aggregates its direct docs (each doc's `.summary.md` if the doc is big, else the\n doc itself) plus the `_SUMMARY.md` of each direct sub-directory. It links to\n **every** direct child file and sub-directory (link-completeness).\n3. **Freshness by content hash, tracked OUTSIDE your docs** \u2014 each summary's hash is\n recorded in a hidden sidecar under `.cairn/`, one JSON file mirroring each summary's\n path (e.g. `.cairn/docs/a.summary.md.json`). The checker recomputes the source hash\n and compares it to the sidecar; mismatch = stale, absent = missing. This survives git\n clone and CI (mtime does not), and it means the tracking system leaves **zero bytes**\n in the docs you write \u2014 no stamp comment to see, ignore, or accidentally hand-edit.\n Commit `.cairn/` alongside your docs; it's not gitignored.\n4. **Bottom-up in one pass** \u2014 a directory summary hashes a manifest of its children's\n hashes (a Merkle tree), so (re)write leaves-first: file summaries, then directories\n deepest-first, then stamp.\n5. **Deletions are caught too** \u2014 a sidecar left behind with no matching doc (its source\n was deleted or renamed) is flagged as a deleted-source stamp; `--prune` removes both\n the leftover summary and its sidecar.\n\n## Upgrading from an older cairn (legacy `<!-- source-sha256 -->` stamp)\n\n**Nothing special to do \u2014 do not go looking for a migration step.** If a summary still\ncarries the old in-content `<!-- source-sha256: ... -->` comment, the ordinary stamp\ncommand (`npx cairn check --summaries-only --stamp`) strips it and writes the\n`.cairn/` sidecar in the same run, automatically. There is no separate command to\ndiscover or remember: whatever `stampCommand` this repo already runs already does it.\n(`--migrate-stamps` also exists, purely as an optional explicit/reportable alias for\nthe same self-healing behaviour \u2014 never required.)\n\n## Workflow when you edit docs\n\nWhen you create or edit any doc:\n\n1. If the doc is longer than the threshold, create or update its `X.summary.md` to\n reflect the new content.\n2. Update the `_SUMMARY.md` of every affected directory, walking **up** the tree\n leaves-first, and keep a link to every child file and sub-directory.\n3. Run the stamp command to (re)write the sidecar hashes under `.cairn/` bottom-up:\n `npx cairn check --summaries-only --stamp`.\n4. Run `npx cairn check` and ensure it exits 0 (green) before you finish.\n5. Commit your doc changes **together with** the `.cairn/` sidecar changes \u2014 a doc\n edit without its matching sidecar update is exactly what `check` is designed to catch.\n\n## Commands\n\n- `npx cairn check` \u2014 check summaries + links (exit 1 on any problem).\n- `npx cairn check --summaries-only` / `--links-only`.\n- `npx cairn check --links-only --fix` \u2014 auto-repair unambiguous dead links.\n- `npx cairn check --summaries-only --stamp` \u2014 write the `.cairn/` sidecar hash of\n EXISTING summaries bottom-up. It does **not** author prose; you write the content,\n then stamp.\n- `npx cairn check --prune` \u2014 delete orphan summaries and orphan `.cairn/` sidecars\n (source doc deleted, renamed, or below threshold).\n- `npx cairn check --migrate-stamps` \u2014 optional: the same self-healing `--stamp`\n already does for a legacy in-content stamp, as its own named/reported step. Never\n required.\n\nYou author the prose. The tool only verifies and stamps \u2014 and it never touches your prose to do it.\n";
|
|
2
|
+
export declare const SKILL_BODY = "# Writing good documentation summaries\n\nUse this when you author or refresh the summary tree that `cairn`\nenforces. It covers *how* to write summaries that are worth reading, and the exact\nmechanical order to (re)generate them so `check` goes green in one pass.\n\n## Two kinds of summary \u2014 do not conflate them\n\n**A file summary (`X.summary.md`) condenses ONE document.**\nGoal: a reader grasps what `X.md` says in ~10 seconds.\n\n- Faithful to the source \u2014 never invent, never contradict, never add claims the doc\n doesn't make. It is a digest, not commentary.\n- Front-load the thesis and the hard numbers. Lead with the conclusion, the decision,\n the metric \u2014 not the background.\n- Bullet-dense, no fluff. Drop hedging, transitions, and restated headings.\n- If the source changes, the summary changes. A summary that no longer matches its\n source is a bug the checker will catch.\n\n**A directory summary (`_SUMMARY.md`) is a MAP, not a digest.**\nGoal: a reader knows what lives in this directory and where to go next.\n\n- A short orientation paragraph (1-3 sentences): what this directory is about.\n- Then one line per direct child \u2014 file **and** sub-directory \u2014 each with a Markdown\n link and a few-word hook describing what's behind it.\n- For a big doc, link and hook its `.summary.md`; for a small doc, link the doc itself.\n- For a sub-directory, link its `_SUMMARY.md`.\n- **Link-completeness:** every direct child must appear as a link. A missing link fails\n `check`. When you add or remove a child, update the parent's `_SUMMARY.md`.\n\n## Why leaves-first \u2014 the Merkle mental model\n\nEach summary's hash is stamped into a hidden sidecar under `.cairn/` (never into the\nsummary's own content \u2014 that's what keeps the docs you write free of tool bytes). A\ndirectory summary's source is a **manifest of its children's hashes**, so a child's\nhash must be settled before its parent can be stamped. Think of it as a Merkle tree:\nchange a leaf and every hash on the path to the root must be recomputed. If you stamp\ntop-down, parents capture stale child hashes and `check` stays red.\n\n## The bottom-up procedure\n\n1. **Author leaves first.** For every doc over the threshold, write/refresh its\n `X.summary.md`. Get the prose right before touching any directory.\n2. **Author directories deepest-first.** Walk from the deepest directories up to the\n roots. For each, write its `_SUMMARY.md`: orientation paragraph, then a linked line\n for every direct child (child `.summary.md` or doc, and each sub-dir's `_SUMMARY.md`).\n3. **Stamp mechanically.** Run `npx cairn check --summaries-only --stamp`.\n It rewrites every `.cairn/` sidecar hash bottom-up. **Never hand-edit a sidecar** \u2014 it\n is computed, not authored; a hand-typed hash is always wrong.\n4. **Verify.** Run `npx cairn check` and confirm exit 0.\n\n## Tiny examples\n\nA **file summary** (`guides/getting-started.summary.md`) \u2014 pure prose, no stamp inside it:\n\n```markdown\n# Getting started \u2014 summary\n\n- Install as a dev dependency, then run the init command.\n- Configure via a single rc file; every option has a sensible default.\n- First run scaffolds an example and prints the next command to run.\n```\n\nIts hash lives in `.cairn/guides/getting-started.summary.md.json` (stamped by the tool):\n\n```json\n{\"sha256\":\"0000...(stamped by the tool)\",\"version\":1}\n```\n\nA **directory summary** (`guides/_SUMMARY.md`) \u2014 also stamp-free:\n\n```markdown\n# Guides\n\nHow-to guides for everyday tasks, in reading order.\n\n- [getting-started](./getting-started.summary.md) \u2014 install, configure, first run\n- [configuration](./configuration.md) \u2014 every rc option, with its default\n- [advanced/](./advanced/_SUMMARY.md) \u2014 recipes for larger setups\n```\n\nKeep summaries short, keep links complete, stamp last, verify green.\n";
|
|
3
3
|
//# sourceMappingURL=content.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/init/content.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/init/content.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,eAAe,kgIAoE3B,CAAA;AAED,eAAO,MAAM,UAAU,i1HAkFtB,CAAA"}
|
package/dist/init/content.js
CHANGED
|
@@ -17,12 +17,29 @@ stale, or a link is broken. Treat green \`check\` as a hard requirement, not a n
|
|
|
17
17
|
aggregates its direct docs (each doc's \`.summary.md\` if the doc is big, else the
|
|
18
18
|
doc itself) plus the \`_SUMMARY.md\` of each direct sub-directory. It links to
|
|
19
19
|
**every** direct child file and sub-directory (link-completeness).
|
|
20
|
-
3. **Freshness by content hash** — each summary's
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
3. **Freshness by content hash, tracked OUTSIDE your docs** — each summary's hash is
|
|
21
|
+
recorded in a hidden sidecar under \`.cairn/\`, one JSON file mirroring each summary's
|
|
22
|
+
path (e.g. \`.cairn/docs/a.summary.md.json\`). The checker recomputes the source hash
|
|
23
|
+
and compares it to the sidecar; mismatch = stale, absent = missing. This survives git
|
|
24
|
+
clone and CI (mtime does not), and it means the tracking system leaves **zero bytes**
|
|
25
|
+
in the docs you write — no stamp comment to see, ignore, or accidentally hand-edit.
|
|
26
|
+
Commit \`.cairn/\` alongside your docs; it's not gitignored.
|
|
23
27
|
4. **Bottom-up in one pass** — a directory summary hashes a manifest of its children's
|
|
24
28
|
hashes (a Merkle tree), so (re)write leaves-first: file summaries, then directories
|
|
25
29
|
deepest-first, then stamp.
|
|
30
|
+
5. **Deletions are caught too** — a sidecar left behind with no matching doc (its source
|
|
31
|
+
was deleted or renamed) is flagged as a deleted-source stamp; \`--prune\` removes both
|
|
32
|
+
the leftover summary and its sidecar.
|
|
33
|
+
|
|
34
|
+
## Upgrading from an older cairn (legacy \`<!-- source-sha256 -->\` stamp)
|
|
35
|
+
|
|
36
|
+
**Nothing special to do — do not go looking for a migration step.** If a summary still
|
|
37
|
+
carries the old in-content \`<!-- source-sha256: ... -->\` comment, the ordinary stamp
|
|
38
|
+
command (\`npx cairn check --summaries-only --stamp\`) strips it and writes the
|
|
39
|
+
\`.cairn/\` sidecar in the same run, automatically. There is no separate command to
|
|
40
|
+
discover or remember: whatever \`stampCommand\` this repo already runs already does it.
|
|
41
|
+
(\`--migrate-stamps\` also exists, purely as an optional explicit/reportable alias for
|
|
42
|
+
the same self-healing behaviour — never required.)
|
|
26
43
|
|
|
27
44
|
## Workflow when you edit docs
|
|
28
45
|
|
|
@@ -32,19 +49,27 @@ When you create or edit any doc:
|
|
|
32
49
|
reflect the new content.
|
|
33
50
|
2. Update the \`_SUMMARY.md\` of every affected directory, walking **up** the tree
|
|
34
51
|
leaves-first, and keep a link to every child file and sub-directory.
|
|
35
|
-
3. Run the stamp command to (re)write the
|
|
52
|
+
3. Run the stamp command to (re)write the sidecar hashes under \`.cairn/\` bottom-up:
|
|
36
53
|
\`npx cairn check --summaries-only --stamp\`.
|
|
37
54
|
4. Run \`npx cairn check\` and ensure it exits 0 (green) before you finish.
|
|
55
|
+
5. Commit your doc changes **together with** the \`.cairn/\` sidecar changes — a doc
|
|
56
|
+
edit without its matching sidecar update is exactly what \`check\` is designed to catch.
|
|
38
57
|
|
|
39
58
|
## Commands
|
|
40
59
|
|
|
41
60
|
- \`npx cairn check\` — check summaries + links (exit 1 on any problem).
|
|
42
61
|
- \`npx cairn check --summaries-only\` / \`--links-only\`.
|
|
43
62
|
- \`npx cairn check --links-only --fix\` — auto-repair unambiguous dead links.
|
|
44
|
-
- \`npx cairn check --summaries-only --stamp\` — write
|
|
45
|
-
summaries bottom-up. It does **not** author prose; you write the content,
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
- \`npx cairn check --summaries-only --stamp\` — write the \`.cairn/\` sidecar hash of
|
|
64
|
+
EXISTING summaries bottom-up. It does **not** author prose; you write the content,
|
|
65
|
+
then stamp.
|
|
66
|
+
- \`npx cairn check --prune\` — delete orphan summaries and orphan \`.cairn/\` sidecars
|
|
67
|
+
(source doc deleted, renamed, or below threshold).
|
|
68
|
+
- \`npx cairn check --migrate-stamps\` — optional: the same self-healing \`--stamp\`
|
|
69
|
+
already does for a legacy in-content stamp, as its own named/reported step. Never
|
|
70
|
+
required.
|
|
71
|
+
|
|
72
|
+
You author the prose. The tool only verifies and stamps — and it never touches your prose to do it.
|
|
48
73
|
`;
|
|
49
74
|
export const SKILL_BODY = `# Writing good documentation summaries
|
|
50
75
|
|
|
@@ -78,7 +103,8 @@ Goal: a reader knows what lives in this directory and where to go next.
|
|
|
78
103
|
|
|
79
104
|
## Why leaves-first — the Merkle mental model
|
|
80
105
|
|
|
81
|
-
Each summary is stamped
|
|
106
|
+
Each summary's hash is stamped into a hidden sidecar under \`.cairn/\` (never into the
|
|
107
|
+
summary's own content — that's what keeps the docs you write free of tool bytes). A
|
|
82
108
|
directory summary's source is a **manifest of its children's hashes**, so a child's
|
|
83
109
|
hash must be settled before its parent can be stamped. Think of it as a Merkle tree:
|
|
84
110
|
change a leaf and every hash on the path to the root must be recomputed. If you stamp
|
|
@@ -92,16 +118,15 @@ top-down, parents capture stale child hashes and \`check\` stays red.
|
|
|
92
118
|
roots. For each, write its \`_SUMMARY.md\`: orientation paragraph, then a linked line
|
|
93
119
|
for every direct child (child \`.summary.md\` or doc, and each sub-dir's \`_SUMMARY.md\`).
|
|
94
120
|
3. **Stamp mechanically.** Run \`npx cairn check --summaries-only --stamp\`.
|
|
95
|
-
It rewrites every
|
|
96
|
-
computed, not authored; a hand-typed hash is always wrong.
|
|
121
|
+
It rewrites every \`.cairn/\` sidecar hash bottom-up. **Never hand-edit a sidecar** — it
|
|
122
|
+
is computed, not authored; a hand-typed hash is always wrong.
|
|
97
123
|
4. **Verify.** Run \`npx cairn check\` and confirm exit 0.
|
|
98
124
|
|
|
99
125
|
## Tiny examples
|
|
100
126
|
|
|
101
|
-
A **file summary** (\`guides/getting-started.summary.md\`):
|
|
127
|
+
A **file summary** (\`guides/getting-started.summary.md\`) — pure prose, no stamp inside it:
|
|
102
128
|
|
|
103
129
|
\`\`\`markdown
|
|
104
|
-
<!-- source-sha256: 0000...(stamped by the tool) -->
|
|
105
130
|
# Getting started — summary
|
|
106
131
|
|
|
107
132
|
- Install as a dev dependency, then run the init command.
|
|
@@ -109,10 +134,15 @@ A **file summary** (\`guides/getting-started.summary.md\`):
|
|
|
109
134
|
- First run scaffolds an example and prints the next command to run.
|
|
110
135
|
\`\`\`
|
|
111
136
|
|
|
112
|
-
|
|
137
|
+
Its hash lives in \`.cairn/guides/getting-started.summary.md.json\` (stamped by the tool):
|
|
138
|
+
|
|
139
|
+
\`\`\`json
|
|
140
|
+
{"sha256":"0000...(stamped by the tool)","version":1}
|
|
141
|
+
\`\`\`
|
|
142
|
+
|
|
143
|
+
A **directory summary** (\`guides/_SUMMARY.md\`) — also stamp-free:
|
|
113
144
|
|
|
114
145
|
\`\`\`markdown
|
|
115
|
-
<!-- source-sha256: 0000...(stamped by the tool) -->
|
|
116
146
|
# Guides
|
|
117
147
|
|
|
118
148
|
How-to guides for everyday tasks, in reading order.
|
package/dist/init/content.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/init/content.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,oEAAoE;AACpE,4EAA4E;AAC5E,iCAAiC;AAEjC,MAAM,CAAC,MAAM,eAAe,GAAG
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/init/content.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,oEAAoE;AACpE,4EAA4E;AAC5E,iCAAiC;AAEjC,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoE9B,CAAA;AAED,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFzB,CAAA"}
|
package/dist/init/generate.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const AGENT_TARGETS: readonly [
|
|
1
|
+
export declare const AGENT_TARGETS: readonly ['agents', 'all', 'claude', 'copilot', 'opencode'];
|
|
2
2
|
export type AgentTarget = (typeof AGENT_TARGETS)[number];
|
|
3
3
|
export interface InitArgs {
|
|
4
4
|
readonly agent: AgentTarget;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/init/generate.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/init/generate.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,aAAa,YAAI,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAU,CAAA;AACxF,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAExD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CACpC;AA2FD,4EAA4E;AAC5E,eAAO,MAAM,OAAO,0BAA2B,QAAQ,KAAG,UA6BzD,CAAA"}
|
package/dist/init/generate.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// Plus a starter .cairnrc.json (only when absent).
|
|
13
13
|
import * as fs from 'node:fs';
|
|
14
14
|
import * as path from 'node:path';
|
|
15
|
-
import { CONVENTION_BODY, SKILL_BODY } from
|
|
15
|
+
import { CONVENTION_BODY, SKILL_BODY } from './content.js';
|
|
16
16
|
// Single source of truth for valid `--agent` values, so the CLI's choice list
|
|
17
17
|
// (src/cli.ts) can never drift from what `runInit` actually understands.
|
|
18
18
|
export const AGENT_TARGETS = ['agents', 'all', 'claude', 'copilot', 'opencode'];
|
package/dist/io/DocsFs.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { FileSystem, Path } from '
|
|
2
|
-
import { Context, Effect, Layer } from 'effect';
|
|
1
|
+
import { Context, Effect, FileSystem, Layer, Path } from 'effect';
|
|
3
2
|
export interface FileStat {
|
|
4
3
|
readonly mtimeMs: number;
|
|
5
4
|
readonly sizeBytes: number;
|
|
@@ -12,7 +11,7 @@ export interface DocsFsService {
|
|
|
12
11
|
readonly stat: (abs: string) => Effect.Effect<FileStat>;
|
|
13
12
|
readonly writeFile: (abs: string, content: string) => Effect.Effect<void>;
|
|
14
13
|
}
|
|
15
|
-
declare const DocsFs_base: Context.
|
|
14
|
+
declare const DocsFs_base: Context.ServiceClass<DocsFs, "DocsFs", DocsFsService>;
|
|
16
15
|
export declare class DocsFs extends DocsFs_base {
|
|
17
16
|
}
|
|
18
17
|
/** Live implementation bound to the Node filesystem. */
|
package/dist/io/DocsFs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocsFs.d.ts","sourceRoot":"","sources":["../../src/io/DocsFs.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"DocsFs.d.ts","sourceRoot":"","sources":["../../src/io/DocsFs.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAU,IAAI,EAAE,MAAM,QAAQ,CAAA;AAIzE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACzD,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACxD,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,SAAS,MAAM,EAAE,CAAC,CAAA;IAClF,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACzD,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACvD,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CAC1E;;AAED,qBAAa,MAAO,SAAQ,WAAkD;CAAG;AAEjF,wDAAwD;AACxD,eAAO,MAAM,UAAU,+DAwEtB,CAAA;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,UAAW,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CA2ClF,CAAA"}
|
package/dist/io/DocsFs.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
// Filesystem capability for the docs checkers, expressed as an Effect service
|
|
2
2
|
// so programs stay testable. `DocsFsLive` binds it to the real Node platform
|
|
3
3
|
// (via @effect/platform-node); `makeTestDocsFs` provides an in-memory layer.
|
|
4
|
-
import { FileSystem, Path } from '
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
export class DocsFs extends Context.Tag('DocsFs')() {
|
|
4
|
+
import { Context, Effect, FileSystem, Layer, Option, Path } from 'effect';
|
|
5
|
+
import { toPosix } from '../core/paths.js';
|
|
6
|
+
export class DocsFs extends Context.Service()('DocsFs') {
|
|
8
7
|
}
|
|
9
8
|
/** Live implementation bound to the Node filesystem. */
|
|
10
9
|
export const DocsFsLive = Layer.effect(DocsFs, Effect.gen(function* () {
|
|
@@ -42,13 +41,24 @@ export const DocsFsLive = Layer.effect(DocsFs, Effect.gen(function* () {
|
|
|
42
41
|
mtimeMs: Option.match(info.mtime, { onNone: () => 0, onSome: (d) => d.getTime() }),
|
|
43
42
|
sizeBytes: Number(info.size),
|
|
44
43
|
})), Effect.orDie);
|
|
44
|
+
// Every existing caller wrote into a directory that was already there (a doc's
|
|
45
|
+
// own directory, always present since the doc itself lives in it). That
|
|
46
|
+
// invariant broke the moment sidecar writes started targeting a brand-new
|
|
47
|
+
// `.cairn/**` tree (StampStore.ts / CheckSummaries.ts) — the FIRST sidecar
|
|
48
|
+
// under a given directory has no directory to write into yet. `recursive:
|
|
49
|
+
// true` makes this a no-op when the directory already exists, so it's safe
|
|
50
|
+
// to do unconditionally rather than only for `.cairn/**` paths.
|
|
51
|
+
const writeFile = (abs, content) => Effect.gen(function* () {
|
|
52
|
+
yield* fs.makeDirectory(path.dirname(abs), { recursive: true });
|
|
53
|
+
yield* fs.writeFileString(abs, content);
|
|
54
|
+
}).pipe(Effect.orDie);
|
|
45
55
|
return {
|
|
46
56
|
deleteFile: (abs) => fs.remove(abs).pipe(Effect.orDie),
|
|
47
57
|
exists: (abs) => fs.exists(abs).pipe(Effect.orDie),
|
|
48
58
|
listFiles,
|
|
49
59
|
readFile: (abs) => fs.readFileString(abs).pipe(Effect.orDie),
|
|
50
60
|
stat,
|
|
51
|
-
writeFile
|
|
61
|
+
writeFile,
|
|
52
62
|
};
|
|
53
63
|
}));
|
|
54
64
|
/**
|
package/dist/io/DocsFs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocsFs.js","sourceRoot":"","sources":["../../src/io/DocsFs.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6EAA6E;AAC7E,6EAA6E;AAE7E,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"DocsFs.js","sourceRoot":"","sources":["../../src/io/DocsFs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { SummaryPlan } from '../core/SummaryTree.ts';
|
|
2
|
-
import type { LinkCheckResult } from './CheckLinks.ts';
|
|
1
|
+
import type { SummaryPlan } from '../core/summaries/SummaryTree.ts';
|
|
2
|
+
import type { LinkCheckResult } from './links/CheckLinks.ts';
|
|
3
3
|
export interface JsonReport {
|
|
4
4
|
readonly exitCode: number;
|
|
5
5
|
readonly links: LinkCheckResult | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JsonReport.d.ts","sourceRoot":"","sources":["../../src/program/JsonReport.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"JsonReport.d.ts","sourceRoot":"","sources":["../../src/program/JsonReport.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAI5D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAA;IACtC,QAAQ,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAA;IACtC,QAAQ,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;CACvC;AAED,sGAAsG;AACtG,eAAO,MAAM,eAAe,yBAA0B,cAAc,KAAG,UAItE,CAAA"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// `--json`: a single combined, machine-readable result for `cairn check`.
|
|
2
2
|
// Wraps the already-structured `SummaryPlan`/`LinkCheckResult` — no new
|
|
3
3
|
// computation, just packaging + the same exit-code rules as the text report.
|
|
4
|
-
import { linkExitCode } from
|
|
5
|
-
import { summaryExitCode } from
|
|
4
|
+
import { linkExitCode } from './links/CheckLinks.js';
|
|
5
|
+
import { summaryExitCode } from './summaries/CheckSummaries.js';
|
|
6
6
|
/** `summaries`/`links` are `null` when that check was skipped (`--links-only`/`--summaries-only`). */
|
|
7
7
|
export const buildJsonReport = ({ links, summaries }) => {
|
|
8
8
|
const summaryCode = summaries === null ? 0 : summaryExitCode(summaries);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JsonReport.js","sourceRoot":"","sources":["../../src/program/JsonReport.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,wEAAwE;AACxE,6EAA6E;AAI7E,OAAO,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"JsonReport.js","sourceRoot":"","sources":["../../src/program/JsonReport.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,wEAAwE;AACxE,6EAA6E;AAI7E,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAa/D,sGAAsG;AACtG,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAAE,KAAK,EAAE,SAAS,EAAkB,EAAc,EAAE;IAClF,MAAM,WAAW,GAAG,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,SAAS,CAAC,CAAA;IACvE,MAAM,QAAQ,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAA;IACzD,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;AACxE,CAAC,CAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect } from 'effect';
|
|
2
|
-
import type { BrokenLink } from '
|
|
3
|
-
import { DocsFs } from '
|
|
4
|
-
import type { Locale } from '
|
|
2
|
+
import type { BrokenLink } from '../../core/links/MarkdownLinks.ts';
|
|
3
|
+
import { DocsFs } from '../../io/DocsFs.ts';
|
|
4
|
+
import type { Locale } from '../locale.ts';
|
|
5
5
|
export interface FileBroken {
|
|
6
6
|
readonly file: string;
|
|
7
7
|
readonly links: readonly BrokenLink[];
|
|
@@ -12,6 +12,11 @@ export interface LinkCheckResult {
|
|
|
12
12
|
readonly fixed: number;
|
|
13
13
|
}
|
|
14
14
|
export interface CheckLinksArgs {
|
|
15
|
+
/** The repository checkout root — the hard boundary a target outside
|
|
16
|
+
* `roots` may still be verified within (issue #39's security requirement:
|
|
17
|
+
* nothing outside `base` is ever stat'd, so cairn can't be turned into a
|
|
18
|
+
* filesystem-existence oracle by an untrusted PR's link targets). */
|
|
19
|
+
readonly base: string;
|
|
15
20
|
readonly fix: boolean;
|
|
16
21
|
readonly ignore?: readonly string[];
|
|
17
22
|
readonly roots: readonly string[];
|
|
@@ -23,5 +28,5 @@ export interface LinkReportOptions {
|
|
|
23
28
|
export declare const linkExitCode: (result: LinkCheckResult) => number;
|
|
24
29
|
/** Human-readable report lines (pure, so it can be unit-tested). */
|
|
25
30
|
export declare const formatLinkReport: (result: LinkCheckResult, options?: LinkReportOptions) => string[];
|
|
26
|
-
export declare const checkLinks: ({ fix, ignore, roots, }: CheckLinksArgs) => Effect.Effect<LinkCheckResult, never, DocsFs>;
|
|
31
|
+
export declare const checkLinks: ({ base, fix, ignore, roots, }: CheckLinksArgs) => Effect.Effect<LinkCheckResult, never, DocsFs>;
|
|
27
32
|
//# sourceMappingURL=CheckLinks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CheckLinks.d.ts","sourceRoot":"","sources":["../../../src/program/links/CheckLinks.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAS/B,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,mCAAmC,CAAA;AAKjF,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAM1C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAA;CACtC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAA;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B;;;yEAGqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AA+CD,kDAAkD;AAClD,eAAO,MAAM,YAAY,WAAY,eAAe,KAAG,MAA4C,CAAA;AAEnG,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,WAAY,eAAe,YAAW,iBAAiB,KAAQ,MAAM,EA0BjG,CAAA;AAwHD,eAAO,MAAM,UAAU,kCAKpB,cAAc,KAAG,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,CAiF5D,CAAA"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// Effect program: scan Markdown files for dead relative links and, when
|
|
2
|
+
// `fix` is set, auto-repair the unambiguous ones. Pure link logic lives in
|
|
3
|
+
// ../../core/links/MarkdownLinks.ts; filesystem access goes through the DocsFs service.
|
|
4
|
+
import * as nodePath from 'node:path';
|
|
5
|
+
import { Effect } from 'effect';
|
|
6
|
+
import { describeAnchors, extractAnchors, isValidLineAnchor, normalizeAnchor, parseLineAnchor, } from '../../core/links/Anchors.js';
|
|
7
|
+
import { buildBasenameIndex, checkContent, stripCode, suggestFix } from '../../core/links/MarkdownLinks.js';
|
|
8
|
+
import { matchesAny } from '../../core/glob.js';
|
|
9
|
+
import { isWithinBase } from '../../core/paths.js';
|
|
10
|
+
import { DocsFs } from '../../io/DocsFs.js';
|
|
11
|
+
import { pick } from '../locale.js';
|
|
12
|
+
// POSIX path semantics (inputs are normalised to `/` at the IO boundary).
|
|
13
|
+
const path = nodePath.posix;
|
|
14
|
+
const withAncestors = (files) => {
|
|
15
|
+
const set = new Set(files);
|
|
16
|
+
for (const file of files) {
|
|
17
|
+
let dir = path.dirname(file);
|
|
18
|
+
while (!set.has(dir)) {
|
|
19
|
+
set.add(dir);
|
|
20
|
+
const parent = path.dirname(dir);
|
|
21
|
+
if (parent === dir) {
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
dir = parent;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return set;
|
|
28
|
+
};
|
|
29
|
+
/** Occurrences of `token` in `text`. */
|
|
30
|
+
const countOccurrences = (text, token) => (token === '' ? 0 : text.split(token).length - 1);
|
|
31
|
+
/**
|
|
32
|
+
* Replace a broken link/definition target with its suggestion, but ONLY when the
|
|
33
|
+
* target does not also appear inside a code span — replacing it there would
|
|
34
|
+
* corrupt a code example. Returns the new content and whether a change was made.
|
|
35
|
+
*/
|
|
36
|
+
const applyFix = (content, target, suggestion) => {
|
|
37
|
+
const masked = stripCode(content);
|
|
38
|
+
let next = content;
|
|
39
|
+
let changed = false;
|
|
40
|
+
for (const [from, to] of [
|
|
41
|
+
[`](${target})`, `](${suggestion})`],
|
|
42
|
+
[`]: ${target}`, `]: ${suggestion}`],
|
|
43
|
+
]) {
|
|
44
|
+
if (countOccurrences(content, from) === 0) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// Every occurrence must be outside code (full count === masked count).
|
|
48
|
+
if (countOccurrences(masked, from) !== countOccurrences(content, from)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
next = next.split(from).join(to);
|
|
52
|
+
changed = true;
|
|
53
|
+
}
|
|
54
|
+
return { changed, content: next };
|
|
55
|
+
};
|
|
56
|
+
/** 0 when no broken links remain, 1 otherwise. */
|
|
57
|
+
export const linkExitCode = (result) => (result.broken.length > 0 ? 1 : 0);
|
|
58
|
+
/** Human-readable report lines (pure, so it can be unit-tested). */
|
|
59
|
+
export const formatLinkReport = (result, options = {}) => {
|
|
60
|
+
const locale = options.locale ?? 'en';
|
|
61
|
+
const lines = [];
|
|
62
|
+
if (result.fixed > 0) {
|
|
63
|
+
lines.push(pick(locale, { en: `🔧 Auto-repaired ${result.fixed} link(s).`, fr: `🔧 Auto-réparé ${result.fixed} lien(s).` }));
|
|
64
|
+
}
|
|
65
|
+
if (result.broken.length === 0) {
|
|
66
|
+
lines.push(pick(locale, {
|
|
67
|
+
en: `✅ Markdown links OK (${result.checked} file(s) checked).`,
|
|
68
|
+
fr: `✅ Liens Markdown OK (${result.checked} fichier(s) vérifié(s)).`,
|
|
69
|
+
}));
|
|
70
|
+
return lines;
|
|
71
|
+
}
|
|
72
|
+
const total = result.broken.reduce((n, f) => n + f.links.length, 0);
|
|
73
|
+
lines.push(pick(locale, { en: `❌ ${total} dead link(s):`, fr: `❌ ${total} lien(s) mort(s) :` }));
|
|
74
|
+
for (const { file, links } of result.broken) {
|
|
75
|
+
lines.push(` ${file}`);
|
|
76
|
+
for (const link of links) {
|
|
77
|
+
lines.push(` ✗ [${link.text}](${link.target})${linkHint(locale, link)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return lines;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Why a link is broken, for a human — distinct from `(no unique target)`
|
|
84
|
+
* (a *path* with no unambiguous fix) so an anchor/line failure never reads as
|
|
85
|
+
* a missing-file problem: the target resolves fine, only its `#fragment` doesn't.
|
|
86
|
+
*/
|
|
87
|
+
const linkHint = (locale, link) => {
|
|
88
|
+
if (link.suggestion !== undefined) {
|
|
89
|
+
return pick(locale, { en: ` → suggestion: ${link.suggestion}`, fr: ` → suggestion : ${link.suggestion}` });
|
|
90
|
+
}
|
|
91
|
+
const detail = link.detail !== undefined ? ` — ${link.detail}` : '';
|
|
92
|
+
if (link.reason === 'anchor') {
|
|
93
|
+
return pick(locale, { en: ` (heading/anchor not found${detail})`, fr: ` (ancre introuvable${detail})` });
|
|
94
|
+
}
|
|
95
|
+
if (link.reason === 'line') {
|
|
96
|
+
return pick(locale, { en: ` (line number out of range${detail})`, fr: ` (numéro de ligne hors limites${detail})` });
|
|
97
|
+
}
|
|
98
|
+
return pick(locale, { en: ' (no unique target)', fr: ' (aucune cible unique)' });
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Resolve one deferred `PendingCheck` with real IO, bounded by `base`
|
|
102
|
+
* (issue #39's security requirement — a target outside `base` is never
|
|
103
|
+
* stat'd, existence-oracle risk closed regardless of what's actually there).
|
|
104
|
+
* `existsCache`/`contentCache`/`anchorCache` are shared across every pending
|
|
105
|
+
* check in a run so a file referenced by many links is only ever stat'd/
|
|
106
|
+
* read/slugged once. Content is fetched only when an anchor actually needs
|
|
107
|
+
* validating — a plain out-of-root existence check (no `#fragment`) never
|
|
108
|
+
* reads the target's full body just to confirm it's there.
|
|
109
|
+
*/
|
|
110
|
+
const resolvePendingCheck = ({ base, existsCache, contentCache, anchorCache, dfs, index, item, known, }) => Effect.gen(function* () {
|
|
111
|
+
let exists = existsCache.get(item.targetAbs);
|
|
112
|
+
if (exists === undefined) {
|
|
113
|
+
if (known.has(item.targetAbs)) {
|
|
114
|
+
exists = true;
|
|
115
|
+
}
|
|
116
|
+
else if (isWithinBase(item.targetAbs, base)) {
|
|
117
|
+
exists = yield* dfs.exists(item.targetAbs);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Outside the checkout root entirely: never touched, unconditionally
|
|
121
|
+
// "cannot verify" — the observable signal stays constant regardless
|
|
122
|
+
// of what's actually on disk there.
|
|
123
|
+
exists = false;
|
|
124
|
+
}
|
|
125
|
+
existsCache.set(item.targetAbs, exists);
|
|
126
|
+
}
|
|
127
|
+
if (!exists) {
|
|
128
|
+
const suggestion = suggestFix({ fromDir: item.fromDir, index, target: item.target });
|
|
129
|
+
return suggestion
|
|
130
|
+
? { reason: 'path', suggestion, target: item.target, text: item.text }
|
|
131
|
+
: { reason: 'path', target: item.target, text: item.text };
|
|
132
|
+
}
|
|
133
|
+
if (item.anchor === null) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
let content = contentCache.get(item.targetAbs);
|
|
137
|
+
if (content === undefined) {
|
|
138
|
+
// `exists` only proves the path resolves to SOMETHING — a directory
|
|
139
|
+
// (from `known`'s ancestor-dir entries, or from a real out-of-root
|
|
140
|
+
// directory) also "exists" but isn't readable as text, and would
|
|
141
|
+
// otherwise die here (Effect.orDie) and take the whole run down over
|
|
142
|
+
// one malformed/unusual link. Existence already holds, so this is
|
|
143
|
+
// genuinely unverifiable, not broken and not a crash.
|
|
144
|
+
content = yield* dfs.readFile(item.targetAbs).pipe(Effect.catchDefect(() => Effect.succeed(null)));
|
|
145
|
+
contentCache.set(item.targetAbs, content);
|
|
146
|
+
}
|
|
147
|
+
if (content === null) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const normalized = normalizeAnchor(item.anchor);
|
|
151
|
+
const lineRange = parseLineAnchor(normalized);
|
|
152
|
+
if (lineRange) {
|
|
153
|
+
const lineCount = content.split('\n').length;
|
|
154
|
+
if (isValidLineAnchor(lineRange, lineCount)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const detail = `target has ${lineCount} line${lineCount === 1 ? '' : 's'}`;
|
|
158
|
+
return { detail, reason: 'line', target: item.target, text: item.text };
|
|
159
|
+
}
|
|
160
|
+
if (!item.targetAbs.toLowerCase().endsWith('.md')) {
|
|
161
|
+
// A non-line, non-md fragment is a symbol anchor (`x.ts#someExport`) —
|
|
162
|
+
// explicitly out of v1 scope (issue #39, scenario G): unverifiable, so
|
|
163
|
+
// never flagged broken rather than risk a false positive.
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
let anchors = anchorCache.get(item.targetAbs);
|
|
167
|
+
if (!anchors) {
|
|
168
|
+
anchors = extractAnchors(content);
|
|
169
|
+
anchorCache.set(item.targetAbs, anchors);
|
|
170
|
+
}
|
|
171
|
+
return anchors.has(normalized)
|
|
172
|
+
? null
|
|
173
|
+
: { detail: describeAnchors(anchors), reason: 'anchor', target: item.target, text: item.text };
|
|
174
|
+
});
|
|
175
|
+
export const checkLinks = ({ base, fix, ignore = [], roots, }) => Effect.gen(function* () {
|
|
176
|
+
const dfs = yield* DocsFs;
|
|
177
|
+
// The existence universe stays complete (so links to ignored files still
|
|
178
|
+
// resolve); `ignore` only removes files from the set we scan as sources.
|
|
179
|
+
const allFiles = yield* dfs.listFiles(roots);
|
|
180
|
+
const index = buildBasenameIndex(allFiles);
|
|
181
|
+
const known = withAncestors(allFiles);
|
|
182
|
+
const existsAbs = (p) => known.has(p);
|
|
183
|
+
const inRoots = (p) => roots.some((root) => p === root || p.startsWith(`${root}/`));
|
|
184
|
+
const mdFiles = allFiles.filter((file) => file.endsWith('.md') && !matchesAny(file, ignore));
|
|
185
|
+
const scans = [];
|
|
186
|
+
for (const file of mdFiles) {
|
|
187
|
+
const content = yield* dfs.readFile(file);
|
|
188
|
+
const { broken: syncBroken, pending } = checkContent({ content, existsAbs, fileAbs: file, inRoots, index });
|
|
189
|
+
scans.push({ content, file, pending, resolvedExtra: [], syncBroken });
|
|
190
|
+
}
|
|
191
|
+
// Resolve every deferred anchor/cross-hierarchy check once, sharing
|
|
192
|
+
// per-target caches across the whole run.
|
|
193
|
+
const existsCache = new Map();
|
|
194
|
+
const contentCache = new Map();
|
|
195
|
+
const anchorCache = new Map();
|
|
196
|
+
for (const scan of scans) {
|
|
197
|
+
for (const item of scan.pending) {
|
|
198
|
+
const result = yield* resolvePendingCheck({
|
|
199
|
+
anchorCache,
|
|
200
|
+
base,
|
|
201
|
+
contentCache,
|
|
202
|
+
dfs,
|
|
203
|
+
existsCache,
|
|
204
|
+
index,
|
|
205
|
+
item,
|
|
206
|
+
known,
|
|
207
|
+
});
|
|
208
|
+
if (result) {
|
|
209
|
+
scan.resolvedExtra.push(result);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const broken = [];
|
|
214
|
+
let fixed = 0;
|
|
215
|
+
for (const scan of scans) {
|
|
216
|
+
let content = scan.content;
|
|
217
|
+
const links = [...scan.syncBroken, ...scan.resolvedExtra];
|
|
218
|
+
if (links.length === 0) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const remaining = [];
|
|
222
|
+
let changed = false;
|
|
223
|
+
for (const link of links) {
|
|
224
|
+
const repair = fix && link.suggestion !== undefined ? applyFix(content, link.target, link.suggestion) : undefined;
|
|
225
|
+
if (repair?.changed) {
|
|
226
|
+
content = repair.content;
|
|
227
|
+
changed = true;
|
|
228
|
+
fixed += 1;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
remaining.push(link);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (changed) {
|
|
235
|
+
yield* dfs.writeFile(scan.file, content);
|
|
236
|
+
}
|
|
237
|
+
if (remaining.length > 0) {
|
|
238
|
+
broken.push({ file: scan.file, links: remaining });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return { broken, checked: mdFiles.length, fixed };
|
|
242
|
+
});
|
|
243
|
+
//# sourceMappingURL=CheckLinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CheckLinks.js","sourceRoot":"","sources":["../../../src/program/links/CheckLinks.ts"],"names":[],"mappings":"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"}
|