@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.
- package/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +77950 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +168 -0
- package/dist/config.js.map +1 -0
- package/dist/core/Config.d.ts +80 -0
- package/dist/core/Config.d.ts.map +1 -0
- package/dist/core/Config.js +135 -0
- package/dist/core/Config.js.map +1 -0
- package/dist/core/DocSummaries.d.ts +42 -0
- package/dist/core/DocSummaries.d.ts.map +1 -0
- package/dist/core/DocSummaries.js +74 -0
- package/dist/core/DocSummaries.js.map +1 -0
- package/dist/core/MarkdownLinks.bench.d.ts +2 -0
- package/dist/core/MarkdownLinks.bench.d.ts.map +1 -0
- package/dist/core/MarkdownLinks.bench.js +81 -0
- package/dist/core/MarkdownLinks.bench.js.map +1 -0
- package/dist/core/MarkdownLinks.d.ts +51 -0
- package/dist/core/MarkdownLinks.d.ts.map +1 -0
- package/dist/core/MarkdownLinks.js +115 -0
- package/dist/core/MarkdownLinks.js.map +1 -0
- package/dist/core/SummaryTree.bench.d.ts +2 -0
- package/dist/core/SummaryTree.bench.d.ts.map +1 -0
- package/dist/core/SummaryTree.bench.js +70 -0
- package/dist/core/SummaryTree.bench.js.map +1 -0
- package/dist/core/SummaryTree.d.ts +45 -0
- package/dist/core/SummaryTree.d.ts.map +1 -0
- package/dist/core/SummaryTree.js +196 -0
- package/dist/core/SummaryTree.js.map +1 -0
- package/dist/core/glob.bench.d.ts +2 -0
- package/dist/core/glob.bench.d.ts.map +1 -0
- package/dist/core/glob.bench.js +62 -0
- package/dist/core/glob.bench.js.map +1 -0
- package/dist/core/glob.d.ts +7 -0
- package/dist/core/glob.d.ts.map +1 -0
- package/dist/core/glob.js +52 -0
- package/dist/core/glob.js.map +1 -0
- package/dist/core/paths.d.ts +3 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +6 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/init/content.d.ts +3 -0
- package/dist/init/content.d.ts.map +1 -0
- package/dist/init/content.js +127 -0
- package/dist/init/content.js.map +1 -0
- package/dist/init/generate.d.ts +14 -0
- package/dist/init/generate.d.ts.map +1 -0
- package/dist/init/generate.js +121 -0
- package/dist/init/generate.js.map +1 -0
- package/dist/io/DocsFs.d.ts +30 -0
- package/dist/io/DocsFs.d.ts.map +1 -0
- package/dist/io/DocsFs.js +95 -0
- package/dist/io/DocsFs.js.map +1 -0
- package/dist/program/CheckLinks.d.ts +27 -0
- package/dist/program/CheckLinks.d.ts.map +1 -0
- package/dist/program/CheckLinks.js +122 -0
- package/dist/program/CheckLinks.js.map +1 -0
- package/dist/program/CheckSummaries.d.ts +38 -0
- package/dist/program/CheckSummaries.d.ts.map +1 -0
- package/dist/program/CheckSummaries.js +198 -0
- package/dist/program/CheckSummaries.js.map +1 -0
- package/dist/program/JsonReport.d.ts +14 -0
- package/dist/program/JsonReport.d.ts.map +1 -0
- package/dist/program/JsonReport.js +12 -0
- package/dist/program/JsonReport.js.map +1 -0
- package/dist/program/locale.d.ts +13 -0
- package/dist/program/locale.d.ts.map +1 -0
- package/dist/program/locale.js +8 -0
- package/dist/program/locale.js.map +1 -0
- package/package.json +78 -0
- package/schema/cairn.schema.json +77 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { bench, describe } from 'vitest';
|
|
2
|
+
import { buildBasenameIndex, checkContent, extractLinkDefinitions, extractLinks, stripCode } from "./MarkdownLinks.js";
|
|
3
|
+
const makeDoc = (linkCount) => {
|
|
4
|
+
const lines = [];
|
|
5
|
+
for (let i = 0; i < linkCount; i++) {
|
|
6
|
+
const kind = i % 5;
|
|
7
|
+
if (kind === 0) {
|
|
8
|
+
lines.push(`See [section ${i}](#heading-${i}) for details.`);
|
|
9
|
+
}
|
|
10
|
+
else if (kind === 1) {
|
|
11
|
+
lines.push(`External ref [site ${i}](https://example.com/page/${i}) for more.`);
|
|
12
|
+
}
|
|
13
|
+
else if (kind === 2) {
|
|
14
|
+
lines.push(`Broken link [doc ${i}](../missing/dir-${i}/file-${i}.md) here.`);
|
|
15
|
+
}
|
|
16
|
+
else if (kind === 3) {
|
|
17
|
+
lines.push(`Reference style [label-${i}][ref-${i}] appears here.`, `[ref-${i}]: ../ref-target/dir-${i}/file-${i}.md`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
lines.push('```md', `[in code](../should-be-ignored/${i}.md)`, '```', `Valid link [good ${i}](../real/dir-${i}/file-${i}.md).`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return lines.join('\n');
|
|
24
|
+
};
|
|
25
|
+
const makeAbsPaths = (count) => {
|
|
26
|
+
const paths = [];
|
|
27
|
+
for (let i = 0; i < count; i++) {
|
|
28
|
+
paths.push(`/repo/docs/dir-${i % 50}/file-${i}.md`);
|
|
29
|
+
}
|
|
30
|
+
return paths;
|
|
31
|
+
};
|
|
32
|
+
const smallDoc = makeDoc(20);
|
|
33
|
+
const largeDoc = makeDoc(500);
|
|
34
|
+
const fewPaths = makeAbsPaths(300);
|
|
35
|
+
const manyPaths = makeAbsPaths(3000);
|
|
36
|
+
const manyPathsIndex = buildBasenameIndex(manyPaths);
|
|
37
|
+
const existsAbs = (abs) => abs.includes('/real/');
|
|
38
|
+
describe('stripCode()', () => {
|
|
39
|
+
bench('20 links', () => {
|
|
40
|
+
stripCode(smallDoc);
|
|
41
|
+
});
|
|
42
|
+
bench('500 links', () => {
|
|
43
|
+
stripCode(largeDoc);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('extractLinks()', () => {
|
|
47
|
+
bench('20 links', () => {
|
|
48
|
+
extractLinks(smallDoc);
|
|
49
|
+
});
|
|
50
|
+
bench('500 links', () => {
|
|
51
|
+
extractLinks(largeDoc);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('extractLinkDefinitions()', () => {
|
|
55
|
+
bench('20 links', () => {
|
|
56
|
+
extractLinkDefinitions(smallDoc);
|
|
57
|
+
});
|
|
58
|
+
bench('500 links', () => {
|
|
59
|
+
extractLinkDefinitions(largeDoc);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('buildBasenameIndex()', () => {
|
|
63
|
+
bench('300 paths', () => {
|
|
64
|
+
buildBasenameIndex(fewPaths);
|
|
65
|
+
});
|
|
66
|
+
bench('3000 paths', () => {
|
|
67
|
+
buildBasenameIndex(manyPaths);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('checkContent()', () => {
|
|
71
|
+
bench('20 links, no index', () => {
|
|
72
|
+
checkContent({ content: smallDoc, existsAbs, fileAbs: '/repo/docs/dir-0/file.md' });
|
|
73
|
+
});
|
|
74
|
+
bench('500 links, no index', () => {
|
|
75
|
+
checkContent({ content: largeDoc, existsAbs, fileAbs: '/repo/docs/dir-0/file.md' });
|
|
76
|
+
});
|
|
77
|
+
bench('500 links, with 3000-path index', () => {
|
|
78
|
+
checkContent({ content: largeDoc, existsAbs, fileAbs: '/repo/docs/dir-0/file.md', index: manyPathsIndex });
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=MarkdownLinks.bench.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownLinks.bench.js","sourceRoot":"","sources":["../../src/core/MarkdownLinks.bench.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface MarkdownLink {
|
|
2
|
+
readonly target: string;
|
|
3
|
+
readonly text: string;
|
|
4
|
+
}
|
|
5
|
+
export interface MarkdownLinkDef {
|
|
6
|
+
readonly label: string;
|
|
7
|
+
readonly target: string;
|
|
8
|
+
}
|
|
9
|
+
export interface BrokenLink {
|
|
10
|
+
readonly suggestion?: string;
|
|
11
|
+
readonly target: string;
|
|
12
|
+
readonly text: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SuggestFixArgs {
|
|
15
|
+
readonly fromDir: string;
|
|
16
|
+
readonly index: ReadonlyMap<string, readonly string[]>;
|
|
17
|
+
readonly target: string;
|
|
18
|
+
}
|
|
19
|
+
export interface CheckContentArgs {
|
|
20
|
+
readonly content: string;
|
|
21
|
+
readonly existsAbs: (absPath: string) => boolean;
|
|
22
|
+
readonly fileAbs: string;
|
|
23
|
+
readonly index?: ReadonlyMap<string, readonly string[]>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Blank out fenced (``` / ~~~) and inline (`code`) spans so links that only
|
|
27
|
+
* appear inside code examples are NOT treated as real links. Newlines are kept
|
|
28
|
+
* so line-based reasoning is unaffected; other characters become spaces.
|
|
29
|
+
*/
|
|
30
|
+
export declare const stripCode: (content: string) => string;
|
|
31
|
+
/** Extract inline Markdown links/images as `{ target, text }`. */
|
|
32
|
+
export declare const extractLinks: (content: string) => MarkdownLink[];
|
|
33
|
+
/** Extract reference-style link definitions (`[label]: ./path "title"`). */
|
|
34
|
+
export declare const extractLinkDefinitions: (content: string) => MarkdownLinkDef[];
|
|
35
|
+
/** True only for relative paths we can resolve on disk. */
|
|
36
|
+
export declare const isCheckableTarget: (target: string) => boolean;
|
|
37
|
+
/** Drop `#anchor` and `?query` from a target. */
|
|
38
|
+
export declare const stripAnchor: (target: string) => string;
|
|
39
|
+
/** Map basename -> list of absolute paths, for ambiguity-aware fixing. */
|
|
40
|
+
export declare const buildBasenameIndex: (absPaths: readonly string[]) => Map<string, string[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Suggest a repaired relative path for a broken target. Returns a path only
|
|
43
|
+
* when exactly one indexed file matches the basename (no ambiguity); else null.
|
|
44
|
+
*/
|
|
45
|
+
export declare const suggestFix: ({ fromDir, index, target }: SuggestFixArgs) => string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Check one file's content for broken relative links. Returns the broken ones,
|
|
48
|
+
* each with an optional `suggestion` when an `index` is supplied.
|
|
49
|
+
*/
|
|
50
|
+
export declare const checkContent: ({ content, existsAbs, fileAbs, index }: CheckContentArgs) => BrokenLink[];
|
|
51
|
+
//# sourceMappingURL=MarkdownLinks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownLinks.d.ts","sourceRoot":"","sources":["../../src/core/MarkdownLinks.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAA;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAA;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAA;CACxD;AAMD;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,SAAS,MAAM,KAAG,MAGwB,CAAA;AAIpE,kEAAkE;AAClE,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,KAAG,YAAY,EAM1D,CAAA;AAED,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,GAAI,SAAS,MAAM,KAAG,eAAe,EAMvE,CAAA;AAED,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,KAAG,OAclD,CAAA;AAED,iDAAiD;AACjD,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,KAAG,MAAuC,CAAA;AAEpF,0EAA0E;AAC1E,eAAO,MAAM,kBAAkB,GAAI,UAAU,SAAS,MAAM,EAAE,KAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAYpF,CAAA;AAOD;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,4BAA4B,cAAc,KAAG,MAAM,GAAG,IAOhF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,wCAAwC,gBAAgB,KAAG,UAAU,EA2BjG,CAAA"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Pure, IO-free helpers to find and auto-repair relative Markdown links.
|
|
2
|
+
// All functions here are deterministic and unit-tested in MarkdownLinks.unit.test.ts.
|
|
3
|
+
// The Effect program that touches the filesystem lives in ../program/CheckLinks.ts.
|
|
4
|
+
import * as nodePath from 'node:path';
|
|
5
|
+
// Reason in POSIX so link resolution is identical on every OS (inputs are
|
|
6
|
+
// normalised to `/` at the IO boundary).
|
|
7
|
+
const path = nodePath.posix;
|
|
8
|
+
const LINK_RE = /!?\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
9
|
+
const FENCED_CODE_RE = /(^|\n)[ \t]*(```|~~~)[\s\S]*?\n[ \t]*\2[ \t]*(?=\n|$)/g;
|
|
10
|
+
const INLINE_CODE_RE = /`[^`\n]*`/g;
|
|
11
|
+
/**
|
|
12
|
+
* Blank out fenced (``` / ~~~) and inline (`code`) spans so links that only
|
|
13
|
+
* appear inside code examples are NOT treated as real links. Newlines are kept
|
|
14
|
+
* so line-based reasoning is unaffected; other characters become spaces.
|
|
15
|
+
*/
|
|
16
|
+
export const stripCode = (content) => content
|
|
17
|
+
.replaceAll(FENCED_CODE_RE, (block) => block.replaceAll(/[^\n]/g, ' '))
|
|
18
|
+
.replaceAll(INLINE_CODE_RE, (block) => ' '.repeat(block.length));
|
|
19
|
+
const LINK_DEF_RE = /^[ \t]*\[([^\]]+)\]:[ \t]*<?([^>\s]+)>?/gm;
|
|
20
|
+
/** Extract inline Markdown links/images as `{ target, text }`. */
|
|
21
|
+
export const extractLinks = (content) => {
|
|
22
|
+
const links = [];
|
|
23
|
+
for (const match of content.matchAll(LINK_RE)) {
|
|
24
|
+
links.push({ target: match[2] ?? '', text: match[1] ?? '' });
|
|
25
|
+
}
|
|
26
|
+
return links;
|
|
27
|
+
};
|
|
28
|
+
/** Extract reference-style link definitions (`[label]: ./path "title"`). */
|
|
29
|
+
export const extractLinkDefinitions = (content) => {
|
|
30
|
+
const defs = [];
|
|
31
|
+
for (const match of content.matchAll(LINK_DEF_RE)) {
|
|
32
|
+
defs.push({ label: match[1] ?? '', target: match[2] ?? '' });
|
|
33
|
+
}
|
|
34
|
+
return defs;
|
|
35
|
+
};
|
|
36
|
+
/** True only for relative paths we can resolve on disk. */
|
|
37
|
+
export const isCheckableTarget = (target) => {
|
|
38
|
+
if (!target) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (target.startsWith('#')) {
|
|
42
|
+
return false;
|
|
43
|
+
} // same-page anchor
|
|
44
|
+
if (target.startsWith('//')) {
|
|
45
|
+
return false;
|
|
46
|
+
} // protocol-relative URL
|
|
47
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(target)) {
|
|
48
|
+
return false;
|
|
49
|
+
} // http:, mailto:, etc.
|
|
50
|
+
return true;
|
|
51
|
+
};
|
|
52
|
+
/** Drop `#anchor` and `?query` from a target. */
|
|
53
|
+
export const stripAnchor = (target) => target.replace(/[#?].*$/, '');
|
|
54
|
+
/** Map basename -> list of absolute paths, for ambiguity-aware fixing. */
|
|
55
|
+
export const buildBasenameIndex = (absPaths) => {
|
|
56
|
+
const index = new Map();
|
|
57
|
+
for (const abs of absPaths) {
|
|
58
|
+
const base = path.basename(abs);
|
|
59
|
+
const bucket = index.get(base);
|
|
60
|
+
if (bucket) {
|
|
61
|
+
bucket.push(abs);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
index.set(base, [abs]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return index;
|
|
68
|
+
};
|
|
69
|
+
const toRelative = (fromDir, toAbs) => {
|
|
70
|
+
const rel = path.relative(fromDir, toAbs).split(path.sep).join('/');
|
|
71
|
+
return rel.startsWith('.') ? rel : `./${rel}`;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Suggest a repaired relative path for a broken target. Returns a path only
|
|
75
|
+
* when exactly one indexed file matches the basename (no ambiguity); else null.
|
|
76
|
+
*/
|
|
77
|
+
export const suggestFix = ({ fromDir, index, target }) => {
|
|
78
|
+
const base = path.basename(stripAnchor(target));
|
|
79
|
+
const candidates = index.get(base);
|
|
80
|
+
if (!candidates || candidates.length !== 1) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return toRelative(fromDir, candidates[0] ?? '');
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Check one file's content for broken relative links. Returns the broken ones,
|
|
87
|
+
* each with an optional `suggestion` when an `index` is supplied.
|
|
88
|
+
*/
|
|
89
|
+
export const checkContent = ({ content, existsAbs, fileAbs, index }) => {
|
|
90
|
+
const fromDir = path.dirname(fileAbs);
|
|
91
|
+
const broken = [];
|
|
92
|
+
const masked = stripCode(content);
|
|
93
|
+
const candidates = [
|
|
94
|
+
...extractLinks(masked),
|
|
95
|
+
// Reference-style definitions are checked by their target too.
|
|
96
|
+
...extractLinkDefinitions(masked).map((def) => ({ target: def.target, text: `[${def.label}]` })),
|
|
97
|
+
];
|
|
98
|
+
for (const link of candidates) {
|
|
99
|
+
if (!isCheckableTarget(link.target)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const rel = stripAnchor(link.target);
|
|
103
|
+
if (!rel) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const abs = path.resolve(fromDir, rel);
|
|
107
|
+
if (existsAbs(abs)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const suggestion = index ? suggestFix({ fromDir, index, target: link.target }) : null;
|
|
111
|
+
broken.push(suggestion ? { suggestion, target: link.target, text: link.text } : { target: link.target, text: link.text });
|
|
112
|
+
}
|
|
113
|
+
return broken;
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=MarkdownLinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownLinks.js","sourceRoot":"","sources":["../../src/core/MarkdownLinks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SummaryTree.bench.d.ts","sourceRoot":"","sources":["../../src/core/SummaryTree.bench.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as nodePath from 'node:path';
|
|
2
|
+
import { bench, describe } from 'vitest';
|
|
3
|
+
import { hashContent, isSummaryFile, sourceHashTag, summaryPathFor } from "./DocSummaries.js";
|
|
4
|
+
import { DIR_SUMMARY, isDirSummary, planSummaries } from "./SummaryTree.js";
|
|
5
|
+
const path = nodePath.posix;
|
|
6
|
+
const bigContent = (seed) => Array.from({ length: 40 }, (_, i) => `${seed} body line ${i} with some representative prose content`).join('\n');
|
|
7
|
+
const smallContent = (seed) => `${seed} short note`;
|
|
8
|
+
/** A fully stamped, link-complete tree: `planSummaries` on it reports every node `ok`. */
|
|
9
|
+
const buildFreshTree = ({ depth, dirsPerLevel, filesPerDir, root }) => {
|
|
10
|
+
const files = new Map();
|
|
11
|
+
const visit = (dir, level) => {
|
|
12
|
+
const docTargets = [];
|
|
13
|
+
const inputs = [];
|
|
14
|
+
for (let f = 0; f < filesPerDir; f++) {
|
|
15
|
+
const docPath = `${dir}/doc-${f}.md`;
|
|
16
|
+
const big = f % 3 !== 0;
|
|
17
|
+
const content = big ? bigContent(docPath) : smallContent(docPath);
|
|
18
|
+
files.set(docPath, content);
|
|
19
|
+
docTargets.push(docPath);
|
|
20
|
+
if (big) {
|
|
21
|
+
const sp = summaryPathFor(docPath);
|
|
22
|
+
files.set(sp, `${sourceHashTag(hashContent(content))}\n\n# summary\n\nSee [source](./${path.basename(docPath)}).`);
|
|
23
|
+
inputs.push(sp);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
inputs.push(docPath);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const dirTargets = [];
|
|
30
|
+
if (level < depth) {
|
|
31
|
+
for (let d = 0; d < dirsPerLevel; d++) {
|
|
32
|
+
const sub = `${dir}/sub-${d}`;
|
|
33
|
+
visit(sub, level + 1);
|
|
34
|
+
dirTargets.push(sub);
|
|
35
|
+
inputs.push(`${sub}/${DIR_SUMMARY}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const manifest = inputs
|
|
39
|
+
.map((input) => `${path.relative(dir, input)}:${hashContent(files.get(input) ?? '')}`)
|
|
40
|
+
.toSorted()
|
|
41
|
+
.join('\n');
|
|
42
|
+
const links = [...docTargets, ...dirTargets].map((t) => `- [link](${path.relative(dir, t)})`).join('\n');
|
|
43
|
+
files.set(`${dir}/${DIR_SUMMARY}`, `${sourceHashTag(hashContent(manifest))}\n\n${links}`);
|
|
44
|
+
};
|
|
45
|
+
visit(root, 0);
|
|
46
|
+
return files;
|
|
47
|
+
};
|
|
48
|
+
/** Same source docs, no summaries anywhere yet — the "first run" worst case. */
|
|
49
|
+
const sourceOnly = (files) => new Map([...files].filter(([p]) => !isSummaryFile(p) && !isDirSummary(p)));
|
|
50
|
+
const SMALL = { depth: 1, dirsPerLevel: 9, filesPerDir: 10, root: '/repo/docs' };
|
|
51
|
+
const LARGE = { depth: 4, dirsPerLevel: 3, filesPerDir: 16, root: '/repo/docs' };
|
|
52
|
+
const smallFresh = buildFreshTree(SMALL);
|
|
53
|
+
const smallRaw = sourceOnly(smallFresh);
|
|
54
|
+
const largeFresh = buildFreshTree(LARGE);
|
|
55
|
+
const largeRaw = sourceOnly(largeFresh);
|
|
56
|
+
describe('planSummaries()', () => {
|
|
57
|
+
bench('~100 files, flat/shallow, first run (no summaries)', () => {
|
|
58
|
+
planSummaries({ files: smallRaw, roots: [SMALL.root] });
|
|
59
|
+
});
|
|
60
|
+
bench('~100 files, flat/shallow, steady state (fully stamped)', () => {
|
|
61
|
+
planSummaries({ files: smallFresh, roots: [SMALL.root] });
|
|
62
|
+
});
|
|
63
|
+
bench('~2000 files, deep/nested, first run (no summaries)', () => {
|
|
64
|
+
planSummaries({ files: largeRaw, roots: [LARGE.root] });
|
|
65
|
+
});
|
|
66
|
+
bench('~2000 files, deep/nested, steady state (fully stamped)', () => {
|
|
67
|
+
planSummaries({ files: largeFresh, roots: [LARGE.root] });
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=SummaryTree.bench.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SummaryTree.bench.js","sourceRoot":"","sources":["../../src/core/SummaryTree.bench.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,WAAW,CAAA;AAErC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAExC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAC7F,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAE3E,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAA;AAS3B,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE,CAC1C,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,cAAc,CAAC,yCAAyC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAElH,MAAM,YAAY,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,GAAG,IAAI,aAAa,CAAA;AAEnE,0FAA0F;AAC1F,MAAM,cAAc,GAAG,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,IAAI,EAAa,EAAuB,EAAE;IACpG,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAA;IAEvC,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,KAAa,EAAQ,EAAE;QACjD,MAAM,UAAU,GAAa,EAAE,CAAA;QAC/B,MAAM,MAAM,GAAa,EAAE,CAAA;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,OAAO,GAAG,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAA;YACpC,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YACvB,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;YACjE,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAC3B,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACxB,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;gBAClC,KAAK,CAAC,GAAG,CACP,EAAE,EACF,GAAG,aAAa,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,mCAAmC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CACpG,CAAA;gBACD,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YACjB,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACtB,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAa,EAAE,CAAA;QAC/B,IAAI,KAAK,GAAG,KAAK,EAAE,CAAC;YAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,MAAM,GAAG,GAAG,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAA;gBAC7B,KAAK,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA;gBACrB,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;gBACpB,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC,CAAA;YACtC,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM;aACpB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;aACrF,QAAQ,EAAE;aACV,IAAI,CAAC,IAAI,CAAC,CAAA;QACb,MAAM,KAAK,GAAG,CAAC,GAAG,UAAU,EAAE,GAAG,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACxG,KAAK,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,WAAW,EAAE,EAAE,GAAG,aAAa,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAA;IAC3F,CAAC,CAAA;IAED,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IACd,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAED,gFAAgF;AAChF,MAAM,UAAU,GAAG,CAAC,KAAkC,EAAuB,EAAE,CAC7E,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAE5E,MAAM,KAAK,GAAc,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAA;AAC3F,MAAM,KAAK,GAAc,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAA;AAE3F,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;AACxC,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,CAAA;AACvC,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;AACxC,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,CAAA;AAEvC,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,KAAK,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC/D,aAAa,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;IAEF,KAAK,CAAC,wDAAwD,EAAE,GAAG,EAAE;QACnE,aAAa,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,KAAK,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC/D,aAAa,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;IAEF,KAAK,CAAC,wDAAwD,EAAE,GAAG,EAAE;QACnE,aAAa,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Naming, SummaryStatus } from './DocSummaries.ts';
|
|
2
|
+
/** Default per-directory digest filename. */
|
|
3
|
+
export declare const DIR_SUMMARY: string;
|
|
4
|
+
export interface PlanNode {
|
|
5
|
+
readonly expectedHash: string;
|
|
6
|
+
readonly inputs: readonly string[];
|
|
7
|
+
readonly kind: 'dir' | 'file';
|
|
8
|
+
readonly missingLinks: readonly string[];
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly recordedHash: string | null;
|
|
11
|
+
readonly status: SummaryStatus;
|
|
12
|
+
}
|
|
13
|
+
export interface PlanArgs {
|
|
14
|
+
readonly files: ReadonlyMap<string, string>;
|
|
15
|
+
readonly ignore?: readonly string[];
|
|
16
|
+
readonly naming?: Naming;
|
|
17
|
+
readonly requireDirSummaries?: boolean;
|
|
18
|
+
readonly roots: readonly string[];
|
|
19
|
+
readonly thresholdLines?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface SummaryPlan {
|
|
22
|
+
readonly nodes: readonly PlanNode[];
|
|
23
|
+
readonly orphans: readonly string[];
|
|
24
|
+
readonly todo: readonly PlanNode[];
|
|
25
|
+
}
|
|
26
|
+
/** True when `p` is a directory summary under the configured naming. */
|
|
27
|
+
export declare const isDirSummary: (p: string, naming?: Naming) => boolean;
|
|
28
|
+
export interface NodeHashArgs {
|
|
29
|
+
readonly files: ReadonlyMap<string, string>;
|
|
30
|
+
readonly inputs: readonly string[];
|
|
31
|
+
readonly kind: 'dir' | 'file';
|
|
32
|
+
readonly path: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A single node's expected hash, computed directly from its (structurally
|
|
36
|
+
* stable — doesn't change while stamping) `inputs` and their CURRENT content —
|
|
37
|
+
* without re-deriving the whole directory graph. `planSummaries` uses this to
|
|
38
|
+
* build each node; `stampSummaries` reuses it to recompute just-stamped nodes'
|
|
39
|
+
* parents against freshly-written children, without a full replan per node
|
|
40
|
+
* (previously O(nodes) work repeated once per node, i.e. O(nodes^2) overall).
|
|
41
|
+
*/
|
|
42
|
+
export declare const nodeExpectedHash: ({ files, inputs, kind, path: nodeAtPath }: NodeHashArgs) => string;
|
|
43
|
+
/** Compute the full hierarchical summary plan from the current file contents. */
|
|
44
|
+
export declare const planSummaries: ({ files, ignore, naming, requireDirSummaries, roots, thresholdLines, }: PlanArgs) => SummaryPlan;
|
|
45
|
+
//# sourceMappingURL=SummaryTree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SummaryTree.d.ts","sourceRoot":"","sources":["../../src/core/SummaryTree.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAgB9D,6CAA6C;AAC7C,eAAO,MAAM,WAAW,QAA4B,CAAA;AAEpD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,CAAA;IAC7B,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAC/B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC3C,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,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAA;CACnC;AAID,wEAAwE;AACxE,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,EAAE,SAAQ,MAAuB,KAAG,OACV,CAAA;AA2BhE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC3C,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,CAAA;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,2CAA2C,YAAY,KAAG,MAU1F,CAAA;AAED,iFAAiF;AACjF,eAAO,MAAM,aAAa,GAAI,wEAO3B,QAAQ,KAAG,WAiHb,CAAA"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// Pure planner for the HIERARCHICAL summary system.
|
|
2
|
+
//
|
|
3
|
+
// Two kinds of summaries:
|
|
4
|
+
// - file summary `X.summary.md` for every source doc `X.md` over the threshold;
|
|
5
|
+
// - directory summary `DIR/_SUMMARY.md` for every directory, aggregating its
|
|
6
|
+
// direct docs (or their summary when the doc is big) AND the `_SUMMARY.md`
|
|
7
|
+
// of its direct sub-directories.
|
|
8
|
+
//
|
|
9
|
+
// Freshness is content-hash based (clone/CI-proof, unlike mtime). Each summary
|
|
10
|
+
// embeds `<!-- source-sha256: H -->`:
|
|
11
|
+
// - file summary -> H = hash(source doc content)
|
|
12
|
+
// - dir summary -> H = hash(manifest of its inputs' relative-path:content-hash)
|
|
13
|
+
//
|
|
14
|
+
// `planSummaries` returns every expected summary with its status and, crucially,
|
|
15
|
+
// the bottom-up order in which to (re)generate them so a single pass converges:
|
|
16
|
+
// file summaries first, then directories deepest-first (a parent's manifest then
|
|
17
|
+
// sees its already-fresh children).
|
|
18
|
+
//
|
|
19
|
+
// Filenames (`naming`), the line threshold, ignored globs and whether directory
|
|
20
|
+
// summaries are required are all configurable; defaults reproduce the original.
|
|
21
|
+
import * as nodePath from 'node:path';
|
|
22
|
+
import { countLines, DEFAULT_NAMING, extractSourceHash, hashContent, isSummaryFile, summaryPathFor, } from "./DocSummaries.js";
|
|
23
|
+
import { matchesAny } from "./glob.js";
|
|
24
|
+
import { extractLinks, isCheckableTarget, stripAnchor, stripCode } from "./MarkdownLinks.js";
|
|
25
|
+
// POSIX path semantics so the plan is identical on every OS (inputs normalised
|
|
26
|
+
// to `/` at the IO boundary).
|
|
27
|
+
const path = nodePath.posix;
|
|
28
|
+
/** Default per-directory digest filename. */
|
|
29
|
+
export const DIR_SUMMARY = DEFAULT_NAMING.dirSummary;
|
|
30
|
+
const DEFAULT_THRESHOLD_LINES = 30;
|
|
31
|
+
/** True when `p` is a directory summary under the configured naming. */
|
|
32
|
+
export const isDirSummary = (p, naming = DEFAULT_NAMING) => p === naming.dirSummary || p.endsWith(`/${naming.dirSummary}`);
|
|
33
|
+
/** Absolute targets of the relative links found in `content`, resolved from `dir`. */
|
|
34
|
+
const resolveLinks = (content, dir) => {
|
|
35
|
+
const set = new Set();
|
|
36
|
+
for (const { target } of extractLinks(stripCode(content))) {
|
|
37
|
+
if (!isCheckableTarget(target)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const rel = stripAnchor(target);
|
|
41
|
+
if (rel) {
|
|
42
|
+
set.add(path.resolve(dir, rel));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return set;
|
|
46
|
+
};
|
|
47
|
+
const statusOf = (exists, recorded, expected) => {
|
|
48
|
+
if (!exists) {
|
|
49
|
+
return 'missing';
|
|
50
|
+
}
|
|
51
|
+
if (recorded !== expected) {
|
|
52
|
+
return 'stale';
|
|
53
|
+
}
|
|
54
|
+
return 'ok';
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* A single node's expected hash, computed directly from its (structurally
|
|
58
|
+
* stable — doesn't change while stamping) `inputs` and their CURRENT content —
|
|
59
|
+
* without re-deriving the whole directory graph. `planSummaries` uses this to
|
|
60
|
+
* build each node; `stampSummaries` reuses it to recompute just-stamped nodes'
|
|
61
|
+
* parents against freshly-written children, without a full replan per node
|
|
62
|
+
* (previously O(nodes) work repeated once per node, i.e. O(nodes^2) overall).
|
|
63
|
+
*/
|
|
64
|
+
export const nodeExpectedHash = ({ files, inputs, kind, path: nodeAtPath }) => {
|
|
65
|
+
if (kind === 'file') {
|
|
66
|
+
return hashContent(files.get(inputs[0] ?? '') ?? '');
|
|
67
|
+
}
|
|
68
|
+
const dir = path.dirname(nodeAtPath);
|
|
69
|
+
const manifest = inputs
|
|
70
|
+
.map((input) => `${path.relative(dir, input)}:${hashContent(files.get(input) ?? '')}`)
|
|
71
|
+
.toSorted()
|
|
72
|
+
.join('\n');
|
|
73
|
+
return hashContent(manifest);
|
|
74
|
+
};
|
|
75
|
+
/** Compute the full hierarchical summary plan from the current file contents. */
|
|
76
|
+
export const planSummaries = ({ files, ignore = [], naming = DEFAULT_NAMING, requireDirSummaries = true, roots, thresholdLines = DEFAULT_THRESHOLD_LINES, }) => {
|
|
77
|
+
const allPaths = [...files.keys()];
|
|
78
|
+
const recorded = (p) => (files.has(p) ? extractSourceHash(files.get(p) ?? '') : null);
|
|
79
|
+
const sourceDocs = allPaths.filter((p) => p.endsWith('.md') && !isSummaryFile(p, naming) && !isDirSummary(p, naming) && !matchesAny(p, ignore));
|
|
80
|
+
// Computed once per doc up front (a Set lookup below) rather than via a
|
|
81
|
+
// countLines() call at each of the two sites that ask "is this doc big" — the
|
|
82
|
+
// file-node filter and the dir-manifest input mapping both need the answer.
|
|
83
|
+
const bigDocs = new Set(sourceDocs.filter((doc) => countLines(files.get(doc) ?? '') > thresholdLines));
|
|
84
|
+
const isBig = (doc) => bigDocs.has(doc);
|
|
85
|
+
// --- file summaries ---
|
|
86
|
+
const fileNodes = [];
|
|
87
|
+
for (const doc of sourceDocs.toSorted()) {
|
|
88
|
+
if (!isBig(doc)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const sp = summaryPathFor(doc, naming);
|
|
92
|
+
const expectedHash = nodeExpectedHash({ files, inputs: [doc], kind: 'file', path: sp });
|
|
93
|
+
const recordedHash = recorded(sp);
|
|
94
|
+
fileNodes.push({
|
|
95
|
+
expectedHash,
|
|
96
|
+
inputs: [doc],
|
|
97
|
+
kind: 'file',
|
|
98
|
+
missingLinks: [],
|
|
99
|
+
path: sp,
|
|
100
|
+
recordedHash,
|
|
101
|
+
status: statusOf(files.has(sp), recordedHash, expectedHash),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (!requireDirSummaries) {
|
|
105
|
+
const orphans = findOrphans({ files, ignore, naming, nodes: fileNodes, requireDirSummaries });
|
|
106
|
+
return { nodes: fileNodes, orphans, todo: fileNodes.filter((n) => n.status !== 'ok') };
|
|
107
|
+
}
|
|
108
|
+
// --- directories in scope ---
|
|
109
|
+
const inScope = (d) => roots.some((r) => d === r || d.startsWith(`${r}/`));
|
|
110
|
+
const dirs = new Set();
|
|
111
|
+
for (const doc of sourceDocs) {
|
|
112
|
+
let d = path.dirname(doc);
|
|
113
|
+
while (inScope(d)) {
|
|
114
|
+
dirs.add(d);
|
|
115
|
+
const parent = path.dirname(d);
|
|
116
|
+
if (parent === d) {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
d = parent;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// --- bucket each doc/dir under its parent once, instead of re-filtering
|
|
123
|
+
// `sourceDocs`/`dirs` from scratch inside the loop below (which was
|
|
124
|
+
// O(dirs x docs) + O(dirs^2) on a large tree). ---
|
|
125
|
+
const docsByParent = new Map();
|
|
126
|
+
for (const doc of sourceDocs) {
|
|
127
|
+
const parent = path.dirname(doc);
|
|
128
|
+
const bucket = docsByParent.get(parent);
|
|
129
|
+
if (bucket) {
|
|
130
|
+
bucket.push(doc);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
docsByParent.set(parent, [doc]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const dirsByParent = new Map();
|
|
137
|
+
for (const d of dirs) {
|
|
138
|
+
const parent = path.dirname(d);
|
|
139
|
+
const bucket = dirsByParent.get(parent);
|
|
140
|
+
if (bucket) {
|
|
141
|
+
bucket.push(d);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
dirsByParent.set(parent, [d]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// --- directory summaries ---
|
|
148
|
+
const dirNodes = [];
|
|
149
|
+
for (const dir of dirs) {
|
|
150
|
+
const childDocs = docsByParent.get(dir) ?? [];
|
|
151
|
+
const childDirs = dirsByParent.get(dir) ?? [];
|
|
152
|
+
const inputs = [
|
|
153
|
+
...childDocs.map((doc) => (isBig(doc) ? summaryPathFor(doc, naming) : doc)),
|
|
154
|
+
...childDirs.map((sub) => path.join(sub, naming.dirSummary)),
|
|
155
|
+
];
|
|
156
|
+
const dsp = path.join(dir, naming.dirSummary);
|
|
157
|
+
const expectedHash = nodeExpectedHash({ files, inputs, kind: 'dir', path: dsp });
|
|
158
|
+
const recordedHash = recorded(dsp);
|
|
159
|
+
const exists = files.has(dsp);
|
|
160
|
+
// A directory summary must link every direct sub-file AND sub-folder.
|
|
161
|
+
const requiredLinks = [...childDocs, ...childDirs];
|
|
162
|
+
const linked = resolveLinks(files.get(dsp) ?? '', dir);
|
|
163
|
+
const missingLinks = requiredLinks.filter((target) => !linked.has(target)).toSorted();
|
|
164
|
+
const fresh = exists && recordedHash === expectedHash && missingLinks.length === 0;
|
|
165
|
+
dirNodes.push({
|
|
166
|
+
expectedHash,
|
|
167
|
+
inputs: inputs.toSorted(),
|
|
168
|
+
kind: 'dir',
|
|
169
|
+
missingLinks,
|
|
170
|
+
path: dsp,
|
|
171
|
+
recordedHash,
|
|
172
|
+
status: exists ? (fresh ? 'ok' : 'stale') : 'missing',
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// Bottom-up order: file summaries first, then dirs deepest-first.
|
|
176
|
+
const depth = (p) => p.split('/').length;
|
|
177
|
+
dirNodes.sort((a, b) => depth(b.path) - depth(a.path) || a.path.localeCompare(b.path));
|
|
178
|
+
const nodes = [...fileNodes, ...dirNodes];
|
|
179
|
+
const orphans = findOrphans({ files, ignore, naming, nodes, requireDirSummaries });
|
|
180
|
+
return { nodes, orphans, todo: nodes.filter((n) => n.status !== 'ok') };
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* A `.summary.md`/`_SUMMARY.md` on disk that no longer corresponds to any
|
|
184
|
+
* expected node — its source doc was deleted, renamed, or (for file summaries)
|
|
185
|
+
* dropped below the threshold. Excludes paths matching `ignore`. When
|
|
186
|
+
* `requireDirSummaries` is false, directory summaries are never expected, so
|
|
187
|
+
* they are never flagged as orphans.
|
|
188
|
+
*/
|
|
189
|
+
const findOrphans = ({ files, ignore, naming, nodes, requireDirSummaries }) => {
|
|
190
|
+
const expected = new Set(nodes.map((n) => n.path));
|
|
191
|
+
const actualSummaries = [...files.keys()].filter((p) => isManagedSummaryPath(p, naming, requireDirSummaries) && !matchesAny(p, ignore));
|
|
192
|
+
return actualSummaries.filter((p) => !expected.has(p)).toSorted();
|
|
193
|
+
};
|
|
194
|
+
/** A path cairn manages as a summary artifact under `naming` — a file summary always, a directory summary only when required. */
|
|
195
|
+
const isManagedSummaryPath = (p, naming, requireDirSummaries) => isSummaryFile(p, naming) || (requireDirSummaries && isDirSummary(p, naming));
|
|
196
|
+
//# sourceMappingURL=SummaryTree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SummaryTree.js","sourceRoot":"","sources":["../../src/core/SummaryTree.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,0BAA0B;AAC1B,iFAAiF;AACjF,8EAA8E;AAC9E,8EAA8E;AAC9E,oCAAoC;AACpC,EAAE;AACF,+EAA+E;AAC/E,sCAAsC;AACtC,mDAAmD;AACnD,mFAAmF;AACnF,EAAE;AACF,iFAAiF;AACjF,gFAAgF;AAChF,iFAAiF;AACjF,oCAAoC;AACpC,EAAE;AACF,gFAAgF;AAChF,gFAAgF;AAEhF,OAAO,KAAK,QAAQ,MAAM,WAAW,CAAA;AAGrC,OAAO,EACL,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,cAAc,GACf,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE5F,+EAA+E;AAC/E,8BAA8B;AAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAA;AAE3B,6CAA6C;AAC7C,MAAM,CAAC,MAAM,WAAW,GAAG,cAAc,CAAC,UAAU,CAAA;AA2BpD,MAAM,uBAAuB,GAAG,EAAE,CAAA;AAElC,wEAAwE;AACxE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAS,EAAE,SAAiB,cAAc,EAAW,EAAE,CAClF,CAAC,KAAK,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC,CAAA;AAEhE,sFAAsF;AACtF,MAAM,YAAY,GAAG,CAAC,OAAe,EAAE,GAAW,EAAe,EAAE;IACjE,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QAC1D,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/B,SAAQ;QACV,CAAC;QACD,MAAM,GAAG,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;QAC/B,IAAI,GAAG,EAAE,CAAC;YACR,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;QACjC,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAED,MAAM,QAAQ,GAAG,CAAC,MAAe,EAAE,QAAuB,EAAE,QAAgB,EAAiB,EAAE;IAC7F,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAA;IAChB,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AASD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAgB,EAAU,EAAE;IAClG,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAA;IACtD,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IACpC,MAAM,QAAQ,GAAG,MAAM;SACpB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;SACrF,QAAQ,EAAE;SACV,IAAI,CAAC,IAAI,CAAC,CAAA;IACb,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAA;AAC9B,CAAC,CAAA;AAED,iFAAiF;AACjF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAC5B,KAAK,EACL,MAAM,GAAG,EAAE,EACX,MAAM,GAAG,cAAc,EACvB,mBAAmB,GAAG,IAAI,EAC1B,KAAK,EACL,cAAc,GAAG,uBAAuB,GAC/B,EAAe,EAAE;IAC1B,MAAM,QAAQ,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;IAClC,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAiB,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IAE5G,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAChC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,CAC5G,CAAA;IACD,wEAAwE;IACxE,8EAA8E;IAC9E,4EAA4E;IAC5E,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAA;IACtG,MAAM,KAAK,GAAG,CAAC,GAAW,EAAW,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAExD,yBAAyB;IACzB,MAAM,SAAS,GAAe,EAAE,CAAA;IAChC,KAAK,MAAM,GAAG,IAAI,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAChB,SAAQ;QACV,CAAC;QACD,MAAM,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QACtC,MAAM,YAAY,GAAG,gBAAgB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;QACvF,MAAM,YAAY,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;QACjC,SAAS,CAAC,IAAI,CAAC;YACb,YAAY;YACZ,MAAM,EAAE,CAAC,GAAG,CAAC;YACb,IAAI,EAAE,MAAM;YACZ,YAAY,EAAE,EAAE;YAChB,IAAI,EAAE,EAAE;YACR,YAAY;YACZ,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,YAAY,EAAE,YAAY,CAAC;SAC5D,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,CAAC,CAAA;QAC7F,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,EAAE,CAAA;IACxF,CAAC;IAED,+BAA+B;IAC/B,MAAM,OAAO,GAAG,CAAC,CAAS,EAAW,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IAC3F,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACzB,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YACX,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;YAC9B,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;gBACjB,MAAK;YACP,CAAC;YACD,CAAC,GAAG,MAAM,CAAA;QACZ,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,oEAAoE;IACpE,mDAAmD;IACnD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAoB,CAAA;IAChD,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACvC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAClB,CAAC;aAAM,CAAC;YACN,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;QACjC,CAAC;IACH,CAAC;IACD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAoB,CAAA;IAChD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;QAC9B,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACvC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAChB,CAAC;aAAM,CAAC;YACN,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,MAAM,QAAQ,GAAe,EAAE,CAAA;IAC/B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAA;QAC7C,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAA;QAC7C,MAAM,MAAM,GAAG;YACb,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC3E,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;SAC7D,CAAA;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;QAC7C,MAAM,YAAY,GAAG,gBAAgB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAA;QAChF,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;QAClC,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC7B,sEAAsE;QACtE,MAAM,aAAa,GAAG,CAAC,GAAG,SAAS,EAAE,GAAG,SAAS,CAAC,CAAA;QAClD,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,CAAA;QACtD,MAAM,YAAY,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QACrF,MAAM,KAAK,GAAG,MAAM,IAAI,YAAY,KAAK,YAAY,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,CAAA;QAClF,QAAQ,CAAC,IAAI,CAAC;YACZ,YAAY;YACZ,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE;YACzB,IAAI,EAAE,KAAK;YACX,YAAY;YACZ,IAAI,EAAE,GAAG;YACT,YAAY;YACZ,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;SACtD,CAAC,CAAA;IACJ,CAAC;IAED,kEAAkE;IAClE,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;IACxD,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAEtF,MAAM,KAAK,GAAG,CAAC,GAAG,SAAS,EAAE,GAAG,QAAQ,CAAC,CAAA;IACzC,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAA;IAClF,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,EAAE,CAAA;AACzE,CAAC,CAAA;AAUD;;;;;;GAMG;AACH,MAAM,WAAW,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAmB,EAAY,EAAE;IACvG,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAClD,MAAM,eAAe,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAC9C,CAAC,CAAC,EAAE,EAAE,CAAC,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,CACtF,CAAA;IACD,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;AACnE,CAAC,CAAA;AAED,iIAAiI;AACjI,MAAM,oBAAoB,GAAG,CAAC,CAAS,EAAE,MAAc,EAAE,mBAA4B,EAAW,EAAE,CAChG,aAAa,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,mBAAmB,IAAI,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob.bench.d.ts","sourceRoot":"","sources":["../../src/core/glob.bench.ts"],"names":[],"mappings":""}
|