@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocsFs.js","sourceRoot":"","sources":["../../src/io/DocsFs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { Effect } from 'effect';
2
+ import type { BrokenLink } from '../core/MarkdownLinks.ts';
3
+ import { DocsFs } from '../io/DocsFs.ts';
4
+ import type { Locale } from './locale.ts';
5
+ export interface FileBroken {
6
+ readonly file: string;
7
+ readonly links: readonly BrokenLink[];
8
+ }
9
+ export interface LinkCheckResult {
10
+ readonly broken: readonly FileBroken[];
11
+ readonly checked: number;
12
+ readonly fixed: number;
13
+ }
14
+ export interface CheckLinksArgs {
15
+ readonly fix: boolean;
16
+ readonly ignore?: readonly string[];
17
+ readonly roots: readonly string[];
18
+ }
19
+ export interface LinkReportOptions {
20
+ readonly locale?: Locale;
21
+ }
22
+ /** 0 when no broken links remain, 1 otherwise. */
23
+ export declare const linkExitCode: (result: LinkCheckResult) => number;
24
+ /** Human-readable report lines (pure, so it can be unit-tested). */
25
+ export declare const formatLinkReport: (result: LinkCheckResult, options?: LinkReportOptions) => string[];
26
+ export declare const checkLinks: ({ fix, ignore, roots, }: CheckLinksArgs) => Effect.Effect<LinkCheckResult, never, DocsFs>;
27
+ //# sourceMappingURL=CheckLinks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CheckLinks.d.ts","sourceRoot":"","sources":["../../src/program/CheckLinks.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAG/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAMzC,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,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,GAAI,QAAQ,eAAe,KAAG,MAA4C,CAAA;AAEnG,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,GAAI,QAAQ,eAAe,EAAE,UAAS,iBAAsB,KAAG,MAAM,EA8BjG,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,yBAIxB,cAAc,KAAG,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,CA2C5D,CAAA"}
@@ -0,0 +1,122 @@
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/MarkdownLinks.ts; filesystem access goes through the DocsFs service.
4
+ import * as nodePath from 'node:path';
5
+ import { Effect } from 'effect';
6
+ import { matchesAny } from "../core/glob.js";
7
+ import { buildBasenameIndex, checkContent, stripCode } from "../core/MarkdownLinks.js";
8
+ import { DocsFs } from "../io/DocsFs.js";
9
+ import { pick } from "./locale.js";
10
+ // POSIX path semantics (inputs are normalised to `/` at the IO boundary).
11
+ const path = nodePath.posix;
12
+ const withAncestors = (files) => {
13
+ const set = new Set(files);
14
+ for (const file of files) {
15
+ let dir = path.dirname(file);
16
+ while (!set.has(dir)) {
17
+ set.add(dir);
18
+ const parent = path.dirname(dir);
19
+ if (parent === dir) {
20
+ break;
21
+ }
22
+ dir = parent;
23
+ }
24
+ }
25
+ return set;
26
+ };
27
+ /** Occurrences of `token` in `text`. */
28
+ const countOccurrences = (text, token) => (token === '' ? 0 : text.split(token).length - 1);
29
+ /**
30
+ * Replace a broken link/definition target with its suggestion, but ONLY when the
31
+ * target does not also appear inside a code span — replacing it there would
32
+ * corrupt a code example. Returns the new content and whether a change was made.
33
+ */
34
+ const applyFix = (content, target, suggestion) => {
35
+ const masked = stripCode(content);
36
+ let next = content;
37
+ let changed = false;
38
+ for (const [from, to] of [
39
+ [`](${target})`, `](${suggestion})`],
40
+ [`]: ${target}`, `]: ${suggestion}`],
41
+ ]) {
42
+ if (countOccurrences(content, from) === 0) {
43
+ continue;
44
+ }
45
+ // Every occurrence must be outside code (full count === masked count).
46
+ if (countOccurrences(masked, from) !== countOccurrences(content, from)) {
47
+ continue;
48
+ }
49
+ next = next.split(from).join(to);
50
+ changed = true;
51
+ }
52
+ return { changed, content: next };
53
+ };
54
+ /** 0 when no broken links remain, 1 otherwise. */
55
+ export const linkExitCode = (result) => (result.broken.length > 0 ? 1 : 0);
56
+ /** Human-readable report lines (pure, so it can be unit-tested). */
57
+ export const formatLinkReport = (result, options = {}) => {
58
+ const locale = options.locale ?? 'en';
59
+ const lines = [];
60
+ if (result.fixed > 0) {
61
+ lines.push(pick(locale, { en: `🔧 Auto-repaired ${result.fixed} link(s).`, fr: `🔧 Auto-réparé ${result.fixed} lien(s).` }));
62
+ }
63
+ if (result.broken.length === 0) {
64
+ lines.push(pick(locale, {
65
+ en: `✅ Markdown links OK (${result.checked} file(s) checked).`,
66
+ fr: `✅ Liens Markdown OK (${result.checked} fichier(s) vérifié(s)).`,
67
+ }));
68
+ return lines;
69
+ }
70
+ const total = result.broken.reduce((n, f) => n + f.links.length, 0);
71
+ lines.push(pick(locale, { en: `❌ ${total} dead link(s):`, fr: `❌ ${total} lien(s) mort(s) :` }));
72
+ for (const { file, links } of result.broken) {
73
+ lines.push(` ${file}`);
74
+ for (const link of links) {
75
+ const hint = link.suggestion !== undefined
76
+ ? pick(locale, { en: ` → suggestion: ${link.suggestion}`, fr: ` → suggestion : ${link.suggestion}` })
77
+ : pick(locale, { en: ' (no unique target)', fr: ' (aucune cible unique)' });
78
+ lines.push(` ✗ [${link.text}](${link.target})${hint}`);
79
+ }
80
+ }
81
+ return lines;
82
+ };
83
+ export const checkLinks = ({ fix, ignore = [], roots, }) => Effect.gen(function* () {
84
+ const dfs = yield* DocsFs;
85
+ // The existence universe stays complete (so links to ignored files still
86
+ // resolve); `ignore` only removes files from the set we scan as sources.
87
+ const allFiles = yield* dfs.listFiles(roots);
88
+ const index = buildBasenameIndex(allFiles);
89
+ const known = withAncestors(allFiles);
90
+ const existsAbs = (p) => known.has(p);
91
+ const mdFiles = allFiles.filter((file) => file.endsWith('.md') && !matchesAny(file, ignore));
92
+ const broken = [];
93
+ let fixed = 0;
94
+ for (const file of mdFiles) {
95
+ let content = yield* dfs.readFile(file);
96
+ const links = checkContent({ content, existsAbs, fileAbs: file, index });
97
+ if (links.length === 0) {
98
+ continue;
99
+ }
100
+ const remaining = [];
101
+ let changed = false;
102
+ for (const link of links) {
103
+ const repair = fix && link.suggestion !== undefined ? applyFix(content, link.target, link.suggestion) : undefined;
104
+ if (repair?.changed) {
105
+ content = repair.content;
106
+ changed = true;
107
+ fixed += 1;
108
+ }
109
+ else {
110
+ remaining.push(link);
111
+ }
112
+ }
113
+ if (changed) {
114
+ yield* dfs.writeFile(file, content);
115
+ }
116
+ if (remaining.length > 0) {
117
+ broken.push({ file, links: remaining });
118
+ }
119
+ }
120
+ return { broken, checked: mdFiles.length, fixed };
121
+ });
122
+ //# sourceMappingURL=CheckLinks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CheckLinks.js","sourceRoot":"","sources":["../../src/program/CheckLinks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { Effect } from 'effect';
2
+ import type { Naming } from '../core/DocSummaries.ts';
3
+ import type { PlanNode, SummaryPlan } from '../core/SummaryTree.ts';
4
+ import { DocsFs } from '../io/DocsFs.ts';
5
+ import type { Locale } from './locale.ts';
6
+ export { DEFAULT_STAMP_COMMAND } from '../core/Config.ts';
7
+ export interface CheckSummariesArgs {
8
+ readonly ignore?: readonly string[];
9
+ readonly naming?: Naming;
10
+ readonly requireDirSummaries?: boolean;
11
+ readonly roots: readonly string[];
12
+ readonly thresholdLines?: number;
13
+ }
14
+ export interface SummaryReportOptions {
15
+ readonly locale?: Locale;
16
+ readonly stampCommand?: string;
17
+ }
18
+ export interface StampResult {
19
+ readonly missing: readonly PlanNode[];
20
+ readonly stamped: number;
21
+ }
22
+ /** 0 when nothing is missing/stale and no orphans remain, 1 otherwise. */
23
+ export declare const summaryExitCode: (plan: SummaryPlan) => number;
24
+ /** Report lines: methodology + the bottom-up update order, for a one-pass fix. */
25
+ export declare const formatSummaryReport: (plan: SummaryPlan, options?: SummaryReportOptions) => string[];
26
+ export declare const checkSummaries: (args: CheckSummariesArgs) => Effect.Effect<SummaryPlan, never, DocsFs>;
27
+ /** `--explain`: why each todo node is not ok (see `explainPlan` for what this can and cannot show). */
28
+ export declare const explainSummaries: (args: CheckSummariesArgs, options?: SummaryReportOptions) => Effect.Effect<string[], never, DocsFs>;
29
+ /**
30
+ * Stamp every EXISTING summary with its current source/manifest hash, bottom-up.
31
+ * Summaries whose content has not been authored yet are returned as `missing`.
32
+ */
33
+ export declare const stampSummaries: (args: CheckSummariesArgs) => Effect.Effect<StampResult, never, DocsFs>;
34
+ /** Delete every orphan summary (source doc gone) and report how many were removed. */
35
+ export declare const pruneOrphans: (args: CheckSummariesArgs) => Effect.Effect<number, never, DocsFs>;
36
+ export { isSummaryFile } from '../core/DocSummaries.ts';
37
+ export { isDirSummary } from '../core/SummaryTree.ts';
38
+ //# sourceMappingURL=CheckSummaries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CheckSummaries.d.ts","sourceRoot":"","sources":["../../src/program/CheckSummaries.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAG/B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAErD,OAAO,KAAK,EAAY,QAAQ,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAE7E,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAEzD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAA;IACtC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAA;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAyBD,0EAA0E;AAC1E,eAAO,MAAM,eAAe,GAAI,MAAM,WAAW,KAAG,MAAmE,CAAA;AAEvH,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,GAAI,MAAM,WAAW,EAAE,UAAS,oBAAyB,KAAG,MAAM,EA4EjG,CAAA;AAoDD,eAAO,MAAM,cAAc,GAAI,MAAM,kBAAkB,KAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,CAI9F,CAAA;AAEJ,uGAAuG;AACvG,eAAO,MAAM,gBAAgB,GAC3B,MAAM,kBAAkB,EACxB,UAAS,oBAAyB,KACjC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,CAKpC,CAAA;AAEJ;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,kBAAkB,KAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,CA0B9F,CAAA;AAEJ,sFAAsF;AACtF,eAAO,MAAM,YAAY,GAAI,MAAM,kBAAkB,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CASvF,CAAA;AAGJ,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA"}
@@ -0,0 +1,198 @@
1
+ // Effect programs for the hierarchical summary system.
2
+ // - `checkSummaries` -> the plan (what is missing/stale, bottom-up order).
3
+ // - `stampSummaries` -> (re)writes the source-hash stamp of EXISTING summaries
4
+ // bottom-up, so a parent's manifest sees its freshly-stamped children.
5
+ // Pure planning lives in ../core/SummaryTree.ts; freshness primitives in
6
+ // ../core/DocSummaries.ts.
7
+ import { Effect } from 'effect';
8
+ import { DEFAULT_STAMP_COMMAND } from "../core/Config.js";
9
+ import { countLines, withSourceHash } from "../core/DocSummaries.js";
10
+ import { nodeExpectedHash, planSummaries } from "../core/SummaryTree.js";
11
+ import { DocsFs } from "../io/DocsFs.js";
12
+ import { enOnly, pick } from "./locale.js";
13
+ export { DEFAULT_STAMP_COMMAND } from "../core/Config.js";
14
+ /** Assemble the pure planner's arguments from the program args + file map. */
15
+ const toPlanArgs = (files, args) => ({
16
+ files,
17
+ ...(args.ignore === undefined ? {} : { ignore: args.ignore }),
18
+ ...(args.naming === undefined ? {} : { naming: args.naming }),
19
+ ...(args.requireDirSummaries === undefined ? {} : { requireDirSummaries: args.requireDirSummaries }),
20
+ roots: args.roots,
21
+ ...(args.thresholdLines === undefined ? {} : { thresholdLines: args.thresholdLines }),
22
+ });
23
+ const readMarkdown = (roots) => Effect.gen(function* () {
24
+ const dfs = yield* DocsFs;
25
+ const all = yield* dfs.listFiles(roots);
26
+ const files = new Map();
27
+ for (const file of all) {
28
+ if (file.endsWith('.md')) {
29
+ files.set(file, yield* dfs.readFile(file));
30
+ }
31
+ }
32
+ return files;
33
+ });
34
+ /** 0 when nothing is missing/stale and no orphans remain, 1 otherwise. */
35
+ export const summaryExitCode = (plan) => (plan.todo.length > 0 || plan.orphans.length > 0 ? 1 : 0);
36
+ /** Report lines: methodology + the bottom-up update order, for a one-pass fix. */
37
+ export const formatSummaryReport = (plan, options = {}) => {
38
+ const locale = options.locale ?? 'en';
39
+ const stampCommand = options.stampCommand ?? DEFAULT_STAMP_COMMAND;
40
+ if (plan.todo.length === 0 && plan.orphans.length === 0) {
41
+ return [
42
+ pick(locale, {
43
+ en: `✅ Hierarchical summaries OK (${plan.nodes.length} summary/ies checked).`,
44
+ fr: `✅ Résumés hiérarchiques OK (${plan.nodes.length} résumé(s) vérifié(s)).`,
45
+ }),
46
+ ];
47
+ }
48
+ const orphanLines = plan.orphans.map((p) => pick(locale, {
49
+ en: ` ✗ orphan summary (source gone): ${p}`,
50
+ fr: ` ✗ résumé orphelin (source disparue) : ${p}`,
51
+ }));
52
+ if (plan.todo.length === 0) {
53
+ return [
54
+ pick(locale, {
55
+ en: `❌ ${plan.orphans.length} orphan summary/ies (source doc deleted, renamed, or below threshold):`,
56
+ fr: `❌ ${plan.orphans.length} résumé(s) orphelin(s) (source supprimée, renommée, ou sous le seuil) :`,
57
+ }),
58
+ ...orphanLines,
59
+ ];
60
+ }
61
+ const lines = pick(locale, {
62
+ en: [
63
+ `❌ ${plan.todo.length} summary/ies to (re)generate.`,
64
+ '',
65
+ 'Methodology (a single, bottom-up pass):',
66
+ ' 1. File summaries: one per doc over the line threshold, a fast-to-read version of the source.',
67
+ ' 2. Directory summaries (_SUMMARY.md): aggregate the summaries of direct docs (or the doc itself when short) + the _SUMMARY.md of direct sub-directories.',
68
+ ` 3. Process in THIS order (files, then deepest directories), then run \`${stampCommand}\` to stamp the hashes.`,
69
+ '',
70
+ 'Update order:',
71
+ ],
72
+ fr: [
73
+ `❌ ${plan.todo.length} résumé(s) à (re)générer.`,
74
+ '',
75
+ 'Méthodologie (une seule passe, de bas en haut) :',
76
+ ' 1. Résumés de fichier : 1 par doc au-dessus du seuil de lignes, version rapide à lire de la source.',
77
+ " 2. Résumés de répertoire (_SUMMARY.md) : agrègent les résumés des docs directs (ou le doc s'il est court) + les _SUMMARY.md des sous-répertoires directs.",
78
+ ` 3. Traiter dans CET ordre (fichiers puis répertoires les plus profonds), puis lancer \`${stampCommand}\` pour tamponner les hash.`,
79
+ '',
80
+ 'Ordre de mise à jour :',
81
+ ],
82
+ });
83
+ for (const node of plan.todo) {
84
+ const tag = node.kind === 'dir'
85
+ ? pick(locale, { en: 'directory', fr: 'répertoire' })
86
+ : pick(locale, { en: 'file', fr: 'fichier' });
87
+ let reason = node.status === 'missing'
88
+ ? pick(locale, { en: 'missing', fr: 'manquant' })
89
+ : pick(locale, { en: 'stale (source changed)', fr: 'périmé (source modifiée)' });
90
+ if (node.missingLinks.length > 0) {
91
+ reason = pick(locale, {
92
+ en: `missing child links (${node.missingLinks.length})`,
93
+ fr: `liens enfants manquants (${node.missingLinks.length})`,
94
+ });
95
+ }
96
+ lines.push(` - [${tag}] ${node.path} : ${reason}`);
97
+ }
98
+ if (plan.orphans.length > 0) {
99
+ lines.push('', pick(locale, {
100
+ en: `${plan.orphans.length} orphan summary/ies (source doc deleted, renamed, or below threshold):`,
101
+ fr: `${plan.orphans.length} résumé(s) orphelin(s) (source supprimée, renommée, ou sous le seuil) :`,
102
+ }), ...orphanLines);
103
+ }
104
+ return lines;
105
+ };
106
+ const shortHash = (h) => (h === null ? 'none' : `${h.slice(0, 8)}…`);
107
+ /** Markdown headings in `content`, in order, for a quick outline of what changed. */
108
+ const headings = (content) => content
109
+ .split('\n')
110
+ .filter((line) => /^#{1,6}\s/.test(line))
111
+ .map((line) => line.trim());
112
+ /**
113
+ * Explain why each `todo` node is not ok. cairn stores only a content hash, not
114
+ * prior source text, so this cannot diff against the previously-summarized
115
+ * version — it surfaces what IS derivable: the expected/recorded hash pair, the
116
+ * changed source's current outline (file summaries), or which stale/missing
117
+ * child is driving a directory summary stale (dir summaries).
118
+ */
119
+ const explainPlan = (plan, files, options) => {
120
+ const locale = options.locale ?? 'en';
121
+ if (plan.todo.length === 0) {
122
+ return [pick(locale, enOnly('Nothing to explain — all summaries are fresh.'))];
123
+ }
124
+ const byPath = new Map(plan.nodes.map((n) => [n.path, n]));
125
+ const lines = [];
126
+ for (const node of plan.todo) {
127
+ lines.push(`${node.kind} ${node.path} (${node.status}):`, ` expected ${shortHash(node.expectedHash)} recorded ${shortHash(node.recordedHash)}`);
128
+ if (node.kind === 'file') {
129
+ const source = node.inputs[0];
130
+ const content = source === undefined ? '' : (files.get(source) ?? '');
131
+ lines.push(` source: ${source} (${countLines(content)} lines)`, ...headings(content).map((h) => ` ${h}`));
132
+ }
133
+ else {
134
+ const staleInputs = node.inputs.filter((input) => byPath.get(input)?.status !== 'ok');
135
+ if (staleInputs.length > 0) {
136
+ lines.push(` driven by stale/missing child: ${staleInputs.join(', ')}`);
137
+ }
138
+ if (node.missingLinks.length > 0) {
139
+ lines.push(` missing links to: ${node.missingLinks.join(', ')}`);
140
+ }
141
+ }
142
+ lines.push('');
143
+ }
144
+ return lines;
145
+ };
146
+ export const checkSummaries = (args) => Effect.gen(function* () {
147
+ const files = yield* readMarkdown(args.roots);
148
+ return planSummaries(toPlanArgs(files, args));
149
+ });
150
+ /** `--explain`: why each todo node is not ok (see `explainPlan` for what this can and cannot show). */
151
+ export const explainSummaries = (args, options = {}) => Effect.gen(function* () {
152
+ const files = yield* readMarkdown(args.roots);
153
+ const plan = planSummaries(toPlanArgs(files, args));
154
+ return explainPlan(plan, files, options);
155
+ });
156
+ /**
157
+ * Stamp every EXISTING summary with its current source/manifest hash, bottom-up.
158
+ * Summaries whose content has not been authored yet are returned as `missing`.
159
+ */
160
+ export const stampSummaries = (args) => Effect.gen(function* () {
161
+ const dfs = yield* DocsFs;
162
+ const files = yield* readMarkdown(args.roots);
163
+ const order = planSummaries(toPlanArgs(files, args)).nodes;
164
+ const missing = [];
165
+ let stamped = 0;
166
+ for (const node of order) {
167
+ if (!files.has(node.path)) {
168
+ missing.push(node);
169
+ continue;
170
+ }
171
+ // A node's `inputs` are structurally stable (only summary CONTENT changes
172
+ // during stamping, never which paths feed a node) — so its hash can be
173
+ // recomputed directly from `inputs` + their current content. Bottom-up
174
+ // order guarantees any child this node depends on was already stamped
175
+ // above, so `files` already reflects it. This avoids a full replan of the
176
+ // whole tree per node (previously O(nodes) work repeated once per node).
177
+ const expectedHash = nodeExpectedHash({ files, inputs: node.inputs, kind: node.kind, path: node.path });
178
+ const stampedContent = withSourceHash(files.get(node.path) ?? '', expectedHash);
179
+ files.set(node.path, stampedContent);
180
+ yield* dfs.writeFile(node.path, stampedContent);
181
+ stamped += 1;
182
+ }
183
+ return { missing, stamped };
184
+ });
185
+ /** Delete every orphan summary (source doc gone) and report how many were removed. */
186
+ export const pruneOrphans = (args) => Effect.gen(function* () {
187
+ const dfs = yield* DocsFs;
188
+ const files = yield* readMarkdown(args.roots);
189
+ const plan = planSummaries(toPlanArgs(files, args));
190
+ for (const orphan of plan.orphans) {
191
+ yield* dfs.deleteFile(orphan);
192
+ }
193
+ return plan.orphans.length;
194
+ });
195
+ // Re-exported so callers can recognise summary files without importing two modules.
196
+ export { isSummaryFile } from "../core/DocSummaries.js";
197
+ export { isDirSummary } from "../core/SummaryTree.js";
198
+ //# sourceMappingURL=CheckSummaries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CheckSummaries.js","sourceRoot":"","sources":["../../src/program/CheckSummaries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { SummaryPlan } from '../core/SummaryTree.ts';
2
+ import type { LinkCheckResult } from './CheckLinks.ts';
3
+ export interface JsonReport {
4
+ readonly exitCode: number;
5
+ readonly links: LinkCheckResult | null;
6
+ readonly summaries: SummaryPlan | null;
7
+ }
8
+ export interface JsonReportArgs {
9
+ readonly links: LinkCheckResult | null;
10
+ readonly summaries: SummaryPlan | null;
11
+ }
12
+ /** `summaries`/`links` are `null` when that check was skipped (`--links-only`/`--summaries-only`). */
13
+ export declare const buildJsonReport: ({ links, summaries }: JsonReportArgs) => JsonReport;
14
+ //# sourceMappingURL=JsonReport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JsonReport.d.ts","sourceRoot":"","sources":["../../src/program/JsonReport.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAItD,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,GAAI,sBAAsB,cAAc,KAAG,UAItE,CAAA"}
@@ -0,0 +1,12 @@
1
+ // `--json`: a single combined, machine-readable result for `cairn check`.
2
+ // Wraps the already-structured `SummaryPlan`/`LinkCheckResult` — no new
3
+ // computation, just packaging + the same exit-code rules as the text report.
4
+ import { linkExitCode } from "./CheckLinks.js";
5
+ import { summaryExitCode } from "./CheckSummaries.js";
6
+ /** `summaries`/`links` are `null` when that check was skipped (`--links-only`/`--summaries-only`). */
7
+ export const buildJsonReport = ({ links, summaries }) => {
8
+ const summaryCode = summaries === null ? 0 : summaryExitCode(summaries);
9
+ const linkCode = links === null ? 0 : linkExitCode(links);
10
+ return { exitCode: Math.max(summaryCode, linkCode), links, summaries };
11
+ };
12
+ //# sourceMappingURL=JsonReport.js.map
@@ -0,0 +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,iBAAiB,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAarD,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"}
@@ -0,0 +1,13 @@
1
+ import type { Locale } from '../core/Config.ts';
2
+ export type { Locale } from '../core/Config.ts';
3
+ /** Pick the value for `locale` from an `{ en, fr }` pair. */
4
+ export declare const pick: <T>(locale: Locale, values: {
5
+ readonly en: T;
6
+ readonly fr: T;
7
+ }) => T;
8
+ /** English-only text for a `pick()` call, e.g. new debug-oriented output not worth translating. */
9
+ export declare const enOnly: <T>(value: T) => {
10
+ readonly en: T;
11
+ readonly fr: T;
12
+ };
13
+ //# sourceMappingURL=locale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../src/program/locale.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C,6DAA6D;AAC7D,eAAO,MAAM,IAAI,GAAI,CAAC,EAAE,QAAQ,MAAM,EAAE,QAAQ;IAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAA;CAAE,KAAG,CAAmB,CAAA;AAExG,mGAAmG;AACnG,eAAO,MAAM,MAAM,GAAI,CAAC,EAAE,OAAO,CAAC,KAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAA;CAAgC,CAAA"}
@@ -0,0 +1,8 @@
1
+ // Report language. Only user-facing report strings are localised. `Locale` itself
2
+ // lives in `core/Config.ts` (it's a config field type) and is re-exported here so
3
+ // existing `program/`-relative imports keep working.
4
+ /** Pick the value for `locale` from an `{ en, fr }` pair. */
5
+ export const pick = (locale, values) => values[locale];
6
+ /** English-only text for a `pick()` call, e.g. new debug-oriented output not worth translating. */
7
+ export const enOnly = (value) => ({ en: value, fr: value });
8
+ //# sourceMappingURL=locale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.js","sourceRoot":"","sources":["../../src/program/locale.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,kFAAkF;AAClF,qDAAqD;AAMrD,6DAA6D;AAC7D,MAAM,CAAC,MAAM,IAAI,GAAG,CAAI,MAAc,EAAE,MAA0C,EAAK,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;AAExG,mGAAmG;AACnG,MAAM,CAAC,MAAM,MAAM,GAAG,CAAI,KAAQ,EAAsC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@sledorze/cairn",
3
+ "version": "0.1.0",
4
+ "description": "Hierarchical documentation summary consolidation with content-hash freshness (clone/CI-proof) and dead-link checking. Agent-agnostic CLI; scaffolds guidance for Claude Code and GitHub Copilot.",
5
+ "keywords": [
6
+ "documentation",
7
+ "summary",
8
+ "markdown",
9
+ "dead-links",
10
+ "effect",
11
+ "claude",
12
+ "copilot",
13
+ "agents"
14
+ ],
15
+ "license": "MIT",
16
+ "author": "Stéphane Le Dorze",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/sledorze/cairn.git"
20
+ },
21
+ "type": "module",
22
+ "sideEffects": false,
23
+ "engines": {
24
+ "node": ">=22"
25
+ },
26
+ "bin": {
27
+ "cairn": "./dist/cli.js"
28
+ },
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "default": "./dist/index.js"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "schema"
39
+ ],
40
+ "dependencies": {
41
+ "@effect/cli": "^0.76.0",
42
+ "@effect/platform": "^0.97.0",
43
+ "@effect/platform-node": "^0.108.0",
44
+ "effect": "^3.22.0"
45
+ },
46
+ "devDependencies": {
47
+ "@changesets/cli": "^2.27.0",
48
+ "@types/node": "^25.3.3",
49
+ "esbuild": "^0.28.1",
50
+ "lefthook": "^2.1.0",
51
+ "oxlint": "1.73.0",
52
+ "prettier": "^3.8.1",
53
+ "tsx": "^4.21.0",
54
+ "typescript": "^5.9.3",
55
+ "vitest": "^4.0.18"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public",
59
+ "provenance": true
60
+ },
61
+ "scripts": {
62
+ "bench": "vitest bench",
63
+ "build": "pnpm run generate-schema && tsc -p tsconfig.build.json && pnpm run bundle-cli",
64
+ "bundle-cli": "esbuild dist/cli.js --bundle --platform=node --format=esm --outfile=dist/cli.js --allow-overwrite --banner:js=\"import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);\"",
65
+ "check": "tsx src/cli.ts check",
66
+ "dev": "tsx src/cli.ts",
67
+ "format": "prettier --write .",
68
+ "format:check": "prettier --check .",
69
+ "generate-schema": "tsx scripts/generate-schema.ts",
70
+ "lint": "oxlint",
71
+ "release": "changeset publish",
72
+ "stamp": "tsx src/cli.ts check --summaries-only --stamp",
73
+ "test": "vitest run",
74
+ "test:watch": "vitest",
75
+ "typecheck": "tsc --noEmit",
76
+ "verify": "pnpm lint && pnpm typecheck && pnpm test && pnpm build && pnpm check"
77
+ }
78
+ }