@any-nav/scan 0.1.0 → 0.2.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.
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Tree replay — the Markdown layer over the engine's in-scope set (file
3
+ * split 2026-10-24), in the pre-facade walk's exact order and shape: the
4
+ * md-only post-filter with the case-insensitive excludeFiles check and the
5
+ * regular-file gate, the name-sorted (localeCompare) DFS with
6
+ * empty-directory pruning, the budget's fact-safe cut and its floor-count
7
+ * chain, NavNode/markdown-locator assembly, and the folder description
8
+ * chains (carrier read at entry, README fold after the settle). The engine
9
+ * bridge lives in engine-mapping.ts, the budget state and diagnostics in
10
+ * budget.ts, the read pipeline in hydration.ts; the two entry points that
11
+ * assemble their outputs from this walk live in scan.ts.
12
+ */
13
+ import { lstat, readFile } from 'node:fs/promises';
14
+ import { basename, extname, join } from 'node:path';
15
+ import { extractManifestDescription, extractPackageJsonDescription } from './description.js';
16
+ import { countTruncatedDiagnostic } from './budget.js';
17
+ import { resolveScanOptions, scopeLevels } from './engine-mapping.js';
18
+ import { BoundedReads } from './hydration.js';
19
+ const MARKDOWN_EXT = '.md';
20
+ export function isExcludedFileName(name, excludeFiles) {
21
+ if (excludeFiles.length === 0)
22
+ return false;
23
+ const lower = name.toLowerCase();
24
+ return excludeFiles.some((excluded) => excluded.toLowerCase() === lower);
25
+ }
26
+ /**
27
+ * Directory description carriers: standardized files a folder
28
+ * may already carry, in descending order of standardization — the npm
29
+ * `package.json` description, then the manifest-authoring `manifest.yml`
30
+ * `metadata.description` gloss. Priority order, per stage: present with a
31
+ * usable value wins; present but empty or key-less falls through to the
32
+ * next stage (never fabricates); unreadable or invalid fires a loud
33
+ * maintainer diagnostic and falls through the same way. The README chain
34
+ * remains the tail after the last carrier. The files are never Markdown so
35
+ * they never enter the tree, and they never consume the maxFiles budget.
36
+ */
37
+ export const DIR_DESCRIPTION_CARRIERS = ['package.json', 'manifest.yml'];
38
+ export function isDirDescriptionCarrier(name) {
39
+ const lower = name.toLowerCase();
40
+ return DIR_DESCRIPTION_CARRIERS.some((carrier) => carrier === lower);
41
+ }
42
+ /**
43
+ * Read the directory's carrier description from the engine's in-scope set,
44
+ * carrier by carrier in DIR_DESCRIPTION_CARRIERS order (case-insensitive on
45
+ * the on-disk name) — scope membership IS the gitignore verdict the engine
46
+ * computed with its accumulated rules (a gitignored carrier falls through),
47
+ * and respectGitignore: false leaves the carrier in scope the same way.
48
+ * Returns undefined when no carrier yields a description — the README chain
49
+ * then fills in.
50
+ */
51
+ async function carrierDescription(relDir, level, ctx) {
52
+ for (const carrierName of DIR_DESCRIPTION_CARRIERS) {
53
+ const name = [...level.files].find((candidate) => candidate.toLowerCase() === carrierName);
54
+ if (name === undefined)
55
+ continue;
56
+ const rel = relDir === '' ? name : `${relDir}/${name}`;
57
+ let content;
58
+ try {
59
+ content = await readFile(join(ctx.root, rel), 'utf8');
60
+ }
61
+ catch {
62
+ ctx.events.push({
63
+ kind: 'diag',
64
+ diagnostic: {
65
+ code: 'description-carrier-unreadable',
66
+ severity: 'warning',
67
+ message: `Unable to read directory description carrier ${rel}.`,
68
+ path: rel,
69
+ audience: 'maintainer',
70
+ },
71
+ });
72
+ continue;
73
+ }
74
+ const read = carrierName === 'package.json'
75
+ ? extractPackageJsonDescription(content)
76
+ : extractManifestDescription(content);
77
+ if ('error' in read) {
78
+ ctx.events.push({
79
+ kind: 'diag',
80
+ diagnostic: {
81
+ code: 'description-carrier-unreadable',
82
+ severity: 'warning',
83
+ message: `Directory description carrier ${rel}: ${read.error}.`,
84
+ path: rel,
85
+ audience: 'maintainer',
86
+ },
87
+ });
88
+ continue;
89
+ }
90
+ if (read.description !== null && read.description.trim() !== '')
91
+ return read.description;
92
+ }
93
+ return undefined;
94
+ }
95
+ export async function performWalk(root, options, eager) {
96
+ const opts = options ?? resolveScanOptions();
97
+ const budget = { files: 0, limitHit: opts.maxFiles <= 0 };
98
+ const ctx = {
99
+ root,
100
+ opts,
101
+ events: [],
102
+ files: new Map(),
103
+ carrierDescriptions: new Map(),
104
+ reads: new BoundedReads(),
105
+ eager,
106
+ countIncompletePaths: [],
107
+ };
108
+ const level = await scopeLevels(root, opts);
109
+ const excludeDirNames = new Set(opts.excludeDirs);
110
+ const rootOutcome = await scanLevel('', level, ctx, budget, excludeDirNames);
111
+ const tree = rootNode(root);
112
+ if (rootOutcome.children.length > 0) {
113
+ tree.children = rootOutcome.children;
114
+ const markdown = readmeChildPath(tree);
115
+ if (markdown !== undefined)
116
+ tree.markdown = markdown;
117
+ }
118
+ ctx.carrierDescriptions.set(tree, rootOutcome.carrierDescription);
119
+ return { ctx, tree, itemCount: budget.files, events: ctx.events, limitHit: budget.limitHit };
120
+ }
121
+ /**
122
+ * Replay one directory level in the pre-facade walk's exact order and shape:
123
+ * entries name-sorted (localeCompare, directories and files together), the
124
+ * budget checked at every entry start (an entry past the cut is never
125
+ * examined), directories descending recursively and pruning when nothing
126
+ * Markdown survives beneath, the fact-safe-cut chain recorded on the way
127
+ * out. Markdown files additionally pass the case-insensitive excludeFiles
128
+ * check and a regular-file gate: symlinks never entered the old walk (the
129
+ * readdir dirent types said so) while the unresolved link IS an in-scope
130
+ * entry for the engine, so the lstat gate restores the old shape. Eager
131
+ * walks kick reads at walk positions; structure walks record files for
132
+ * describe().
133
+ */
134
+ async function scanLevel(relDir, level, ctx, budget, excludeDirNames) {
135
+ const { opts } = ctx;
136
+ const carrier = await carrierDescription(relDir, level, ctx);
137
+ const children = [];
138
+ const names = [...level.dirs.keys(), ...level.files].sort((a, b) => a.localeCompare(b));
139
+ for (const name of names) {
140
+ if (budget.files >= opts.maxFiles) {
141
+ budget.limitHit = true;
142
+ continue;
143
+ }
144
+ const rel = relDir === '' ? name : `${relDir}/${name}`;
145
+ const dirLevel = level.dirs.get(name);
146
+ if (dirLevel !== undefined) {
147
+ // Literal-name floor semantics: the old engine compared directory names
148
+ // by equality. Plain names already rode the engine's policy channel;
149
+ // this check also carries any non-plain entry verbatim.
150
+ if (excludeDirNames.has(name))
151
+ continue;
152
+ const outcome = await scanLevel(rel, dirLevel, ctx, budget, excludeDirNames);
153
+ if (outcome.children.length === 0)
154
+ continue; // empty-directory pruning (root exempt)
155
+ const node = { name, path: rel, kind: 'dir', children: outcome.children };
156
+ // Enumeration-known locator: set when the README child is in
157
+ // the tree — a budget cut past the README or an exclusion leaves it
158
+ // absent (the field mirrors the tree, never the filesystem).
159
+ const markdown = readmeChildPath(node);
160
+ if (markdown !== undefined)
161
+ node.markdown = markdown;
162
+ ctx.carrierDescriptions.set(node, outcome.carrierDescription);
163
+ // Fact-safe cut: the budget flipped inside this folder's
164
+ // span, so it survives with partial children — as does every surviving
165
+ // ancestor, which marks itself the same way at its own loop's end (the
166
+ // chain of folders whose counts are floors). A partial count must never
167
+ // present as the total: the path joins the floor-count set and the
168
+ // per-row diagnostic fires (consumer audience). Folders never entered
169
+ // after the flip vanish instead — the global file-limit-reached covers
170
+ // those, unchanged.
171
+ if (budget.limitHit) {
172
+ ctx.countIncompletePaths.push(rel);
173
+ ctx.events.push({ kind: 'diag', diagnostic: countTruncatedDiagnostic(rel) });
174
+ }
175
+ children.push(node);
176
+ continue;
177
+ }
178
+ if (extname(name).toLowerCase() !== MARKDOWN_EXT)
179
+ continue;
180
+ if (isExcludedFileName(name, opts.excludeFiles))
181
+ continue;
182
+ const abs = join(ctx.root, rel);
183
+ let regular = true;
184
+ try {
185
+ regular = (await lstat(abs)).isFile();
186
+ }
187
+ catch {
188
+ // Vanished between the engine walk and the replay: keep the node — the
189
+ // read settles through the unreadable-file path, as the old walk did.
190
+ }
191
+ if (!regular)
192
+ continue;
193
+ const node = { name: stem(name), path: rel, kind: 'file' };
194
+ // Eager start, deferred settle: the read overlaps the rest of the walk
195
+ // while the budget counts synchronously at kick-off — the cut point
196
+ // (first maxFiles in sorted DFS order) is the serial walk's.
197
+ if (ctx.eager) {
198
+ await ctx.reads.gate();
199
+ const item = { name: node.name, description: '', locator: { kind: 'file', path: rel } };
200
+ node.item = item;
201
+ ctx.events.push({ kind: 'file', read: { rel, content: ctx.reads.start(abs), item } });
202
+ }
203
+ else {
204
+ ctx.files.set(rel, { rel, abs, node });
205
+ }
206
+ budget.files += 1;
207
+ children.push(node);
208
+ }
209
+ return { children, carrierDescription: carrier };
210
+ }
211
+ /**
212
+ * The representative README child's path — the folder-description chain's
213
+ * own name rule (file node names are stems, so the stem 'readme',
214
+ * case-insensitive; children are name-sorted, the first match represents).
215
+ * Mirrors the tree, not the filesystem: the walk sets it at assembly, so
216
+ * an excluded README leaves it absent.
217
+ */
218
+ function readmeChildPath(node) {
219
+ for (const child of node.children ?? []) {
220
+ if (child.kind === 'file' && child.name.toLowerCase() === 'readme')
221
+ return child.path;
222
+ }
223
+ return undefined;
224
+ }
225
+ /**
226
+ * Post-walk folder descriptions, bottom-up over the assembled tree — the
227
+ * same chain the serial walk computed inline: carrier description first
228
+ * (package.json, then manifest.yml), else the folder's own first
229
+ * README child (children are name-sorted) carrying a real description. Runs
230
+ * after the settle, so file descriptions are final.
231
+ */
232
+ export function assignFolderDescriptions(node, ctx) {
233
+ let readmeDescription;
234
+ for (const child of node.children ?? []) {
235
+ if (child.kind === 'dir')
236
+ assignFolderDescriptions(child, ctx);
237
+ if (readmeDescription === undefined &&
238
+ child.kind === 'file' &&
239
+ child.name.toLowerCase() === 'readme' &&
240
+ child.item !== undefined &&
241
+ !child.item.description.startsWith('Undocumented')) {
242
+ readmeDescription = child.item.description;
243
+ }
244
+ }
245
+ const description = ctx.carrierDescriptions.get(node) ?? readmeDescription;
246
+ if (description !== undefined)
247
+ node.description = description;
248
+ }
249
+ export function rootNode(root) {
250
+ const name = basename(root);
251
+ return { name: name === '' || name === '.' || name === '/' ? 'root' : name, path: '', kind: 'dir' };
252
+ }
253
+ function stem(fileName) {
254
+ return fileName.slice(0, fileName.length - extname(fileName).length);
255
+ }
256
+ //# sourceMappingURL=tree-replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-replay.js","sourceRoot":"","sources":["../src/tree-replay.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -33,16 +33,18 @@ export interface NavNode {
33
33
  children?: NavNode[];
34
34
  /**
35
35
  * Dir nodes only (the root included): description from the directory's
36
- * `_category_.yml/.yaml/.json` sidecar when it carries a `description` key
37
- * (the exception channel for release-face READMEs), else the extraction
38
- * from the directory's own README.md (routes folder-representative
39
- * semantics). Absent when neither carries one.
36
+ * standardized carriers in order — its `package.json` `description`, then
37
+ * its `manifest.yml` `metadata.description` — else the
38
+ * extraction from the directory's own README.md (routes
39
+ * folder-representative semantics). Absent when none carries one.
40
40
  */
41
41
  description?: string;
42
+ /** Dir nodes only (root included): the representative README child's POSIX-relative path; present iff it is in the tree. */
43
+ markdown?: string;
42
44
  }
43
45
  /**
44
- * Global scan boundary policy (ADR-0010: routes traversal semantics; update
45
- * 2026-10-07: exclude lists are additive). All keys default — the config
46
+ * Global scan boundary policy (routes traversal semantics; exclude
47
+ * lists are additive). All keys default — the config
46
48
  * states only exceptions, and stating one never drops the built-in floor.
47
49
  */
48
50
  export interface ScanConfig {
@@ -54,6 +56,16 @@ export interface ScanConfig {
54
56
  excludeDotEntries?: boolean;
55
57
  /** Whether traversal respects .gitignore files. */
56
58
  respectGitignore?: boolean;
59
+ /**
60
+ * Subtrees whose own nested `.git` does not cut them from this view — the
61
+ * engine's named-subtree core-stone exemption (git's
62
+ * submodule-registration face: an unregistered embedded repo is foreign
63
+ * territory, a registered one participates). Entries are gitignore-style
64
+ * patterns anchored at the view's scan root (e.g. 'deepseek-harness/');
65
+ * registration is not rescue — the subtree still passes every exclusion
66
+ * floor, and deeper embedded repos inside it still cut.
67
+ */
68
+ boundaryExempt?: string[];
57
69
  /** Maximum Markdown files examined per source walk; past the cap entries go unexamined — diagnostics fire and folders whose descent was cut disclose floor (≥) counts (never a silent cut). */
58
70
  maxFiles?: number;
59
71
  }
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@any-nav/scan",
3
- "version": "0.1.0",
4
- "description": "Any-Nav scan core: the zero-dependency engine that walks a Markdown folder into a nav tree under the routes traversal semantics (exclusions, gitignore inheritance, a loud maxFiles budget, folder descriptions). Embedders consume this directly; @any-nav/core composes it with curation, render, and search.",
3
+ "version": "0.2.0",
4
+ "description": "Any-Nav scan core: the engine facade that walks a Markdown folder into a nav tree under the routes traversal semantics (exclusions, gitignore inheritance, a loud maxFiles budget, folder descriptions) with the walk delegated to @catheadowl/pathscope. Embedders consume this directly; @any-nav/core composes it with curation, render, and search.",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/CatheadOwl/any-nav.git"
9
+ },
6
10
  "type": "module",
7
11
  "main": "./dist/index.js",
8
12
  "types": "./dist/index.d.ts",
@@ -11,7 +15,11 @@
11
15
  "CHANGELOG.md"
12
16
  ],
13
17
  "publishConfig": {
14
- "access": "public"
18
+ "access": "public",
19
+ "provenance": false
20
+ },
21
+ "dependencies": {
22
+ "@catheadowl/pathscope": "^0.1.1"
15
23
  },
16
24
  "keywords": [
17
25
  "agent",
@@ -1,9 +0,0 @@
1
- export interface GitignoreRule {
2
- baseDir: string;
3
- pattern: string;
4
- negated: boolean;
5
- directoryOnly: boolean;
6
- }
7
- export declare function readInheritedGitignoreRules(root: string, scanRoot: string): Promise<GitignoreRule[]>;
8
- export declare function readGitignoreRules(dir: string): Promise<GitignoreRule[]>;
9
- export declare function isGitignored(filePath: string, isDirectory: boolean, rules: readonly GitignoreRule[]): boolean;
package/dist/gitignore.js DELETED
@@ -1,128 +0,0 @@
1
- /**
2
- * gitignore-aware traversal support, ported verbatim from the routes module
3
- * (dsh-plugin-dev/extras/modules/routes/src/gitignore.ts, ADR-0010: port,
4
- * don't rewrite). Known subset boundary (routes-documented): `!` re-includes
5
- * inside one file are honored; cross-file re-include of an ignored path is
6
- * not — revisit via a port if real corpora hit it (ADR-0010 reversal).
7
- */
8
- import { readFile } from 'node:fs/promises';
9
- import { join, relative, resolve } from 'node:path';
10
- export async function readInheritedGitignoreRules(root, scanRoot) {
11
- const rootPath = resolve(root);
12
- const scanRootPath = resolve(scanRoot);
13
- const relativeScanRoot = normalizeSlash(relative(rootPath, scanRootPath));
14
- const segments = relativeScanRoot && !relativeScanRoot.startsWith('..')
15
- ? relativeScanRoot.split('/').filter(Boolean)
16
- : [];
17
- const dirs = [rootPath];
18
- for (let index = 0; index < segments.length; index += 1) {
19
- dirs.push(join(rootPath, ...segments.slice(0, index + 1)));
20
- }
21
- const rules = await Promise.all(dirs.map((dir) => readGitignoreRules(dir)));
22
- return rules.flat();
23
- }
24
- export async function readGitignoreRules(dir) {
25
- let content;
26
- try {
27
- content = await readFile(join(dir, '.gitignore'), 'utf8');
28
- }
29
- catch {
30
- return [];
31
- }
32
- return content
33
- .split(/\r?\n/u)
34
- .map((line) => parseGitignoreLine(dir, line))
35
- .filter((rule) => rule !== null);
36
- }
37
- export function isGitignored(filePath, isDirectory, rules) {
38
- let ignored = false;
39
- // Perf (2026-10-20, semantics identical): one relative() per distinct
40
- // baseDir per call — a typical tree carries many rules from the same
41
- // ancestor .gitignore, and the path result is rule-invariant.
42
- const relativeByBaseDir = new Map();
43
- for (const rule of rules) {
44
- if (rule.directoryOnly && !isDirectory)
45
- continue;
46
- if (!matchesRule(filePath, rule, relativeByBaseDir))
47
- continue;
48
- ignored = !rule.negated;
49
- }
50
- return ignored;
51
- }
52
- function parseGitignoreLine(baseDir, rawLine) {
53
- let line = rawLine.trim();
54
- if (!line || line.startsWith('#'))
55
- return null;
56
- let negated = false;
57
- if (line.startsWith('!')) {
58
- negated = true;
59
- line = line.slice(1).trim();
60
- }
61
- if (!line)
62
- return null;
63
- const directoryOnly = line.endsWith('/');
64
- line = line.replace(/^\/+/u, '').replace(/\/+$/u, '');
65
- if (!line)
66
- return null;
67
- return {
68
- baseDir,
69
- pattern: normalizeSlash(line),
70
- negated,
71
- directoryOnly,
72
- };
73
- }
74
- function matchesRule(filePath, rule, relativeByBaseDir) {
75
- let relativePath = relativeByBaseDir.get(rule.baseDir);
76
- if (relativePath === undefined) {
77
- relativePath = normalizeSlash(relative(rule.baseDir, filePath));
78
- relativeByBaseDir.set(rule.baseDir, relativePath);
79
- }
80
- if (!relativePath || relativePath.startsWith('..'))
81
- return false;
82
- if (!rule.pattern.includes('/')) {
83
- return relativePath.split('/').some((part) => matchesGlob(part, rule.pattern));
84
- }
85
- return matchesGlob(relativePath, rule.pattern);
86
- }
87
- /** Compiled-glob memo (perf 2026-10-20, semantics identical): the regex
88
- * depends only on the pattern, and a scan checks the same handful of
89
- * patterns once per entry — compiling per check was ~70% of large-tree
90
- * scan CPU. Patterns are few and bounded by the corpus's .gitignore lines. */
91
- const globRegexCache = new Map();
92
- function matchesGlob(value, pattern) {
93
- let regex = globRegexCache.get(pattern);
94
- if (regex === undefined) {
95
- regex = new RegExp(`^${globToRegex(pattern)}$`, 'u');
96
- globRegexCache.set(pattern, regex);
97
- }
98
- return regex.test(value);
99
- }
100
- function globToRegex(pattern) {
101
- let regex = '';
102
- for (let index = 0; index < pattern.length; index += 1) {
103
- const char = pattern[index];
104
- const next = pattern[index + 1];
105
- if (char === '*' && next === '*') {
106
- regex += '.*';
107
- index += 1;
108
- continue;
109
- }
110
- if (char === '*') {
111
- regex += '[^/]*';
112
- continue;
113
- }
114
- if (char === '?') {
115
- regex += '[^/]';
116
- continue;
117
- }
118
- regex += escapeRegex(char);
119
- }
120
- return regex;
121
- }
122
- function escapeRegex(value) {
123
- return value.replace(/[|\\{}()[\]^$+*?.]/gu, '\\$&');
124
- }
125
- function normalizeSlash(value) {
126
- return value.replace(/\\/gu, '/');
127
- }
128
- //# sourceMappingURL=gitignore.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../src/gitignore.ts"],"names":[],"mappings":"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"}