@any-nav/scan 0.0.0-stage → 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.
package/dist/scan.js ADDED
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Scan: walk a Markdown folder into a NavTree under the routes traversal
3
+ * semantics (absorption — port, don't rewrite). Since the pathscope
4
+ * delegation (facade transition: the public API and
5
+ * behavior stand, the walk engine is swapped) the scan is two layers:
6
+ *
7
+ * - engine — @catheadowl/pathscope `walkScope` answers "which files are in
8
+ * scope": the gitignore cascade (nested, plus inheritance above the scan
9
+ * root), mounted exclusion presets
10
+ * (vcs/deps/build = the old nine-entry floor plus vendor/ and coverage/,
11
+ * an accepted increment; agentDocs = the built-in AGENTS.md/CLAUDE.md
12
+ * exclusion), the policy layer carrying the resolved excludeDirs (rear
13
+ * seat: gitignore negations cannot resurrect a floor entry, as before),
14
+ * the dot category, and boundary stones. respectGitignore and
15
+ * excludeDotEntries pass straight through.
16
+ * - consumer replay (this package) — the Markdown-only post-filter, the
17
+ * case-insensitive excludeFiles check, the sorted (localeCompare) DFS
18
+ * assembly with empty-directory pruning, the maxFiles budget (its unit
19
+ * is a TREE ENTRY — a Markdown file surviving the filters — so the
20
+ * engine query runs unbounded and the replay owns the fact-safe cut and
21
+ * its diagnostics), NavNode/markdown-locator assembly, and the
22
+ * description chains: folder descriptions from the directory's
23
+ * standardized carriers in order — its `package.json` `description`,
24
+ * then its `manifest.yml` `metadata.description` — else from
25
+ * the folder's own README.md; dir nodes also carry
26
+ * `markdown` — the representative README child's path, present iff the
27
+ * tree contains it, known from enumeration alone in both
28
+ * walks. Description extraction is the ported routes chain
29
+ * (description.ts); null falls to core's placeholder + diagnostic,
30
+ * which routes does not have and core keeps (stricter).
31
+ *
32
+ * Read-only by contract: scanning must never modify source files
33
+ * (map is not territory).
34
+ *
35
+ * Two entry points share one walk (2026-10-20): scanSource reads and
36
+ * extracts every Markdown file (snapshot semantics — the search/candidates
37
+ * anchor); scanStructure walks structure-only and hydrates descriptions on
38
+ * demand via describe(), so a depth-0 view pays reads for the rows it
39
+ * renders, not the whole tree. Reads overlap behind a bounded in-flight
40
+ * window — budget cut, tree shape, and diagnostic order stay byte-identical
41
+ * to the serial walk this replaced — and fetch a bounded head that provably
42
+ * covers the extraction chain's input (frontmatter + the body-head window),
43
+ * extended adaptively, never more bytes than the chain can consume.
44
+ *
45
+ * File split 2026-10-24: this module keeps the two entry points and their
46
+ * output faces; the four blocks they share live beside it — engine-mapping
47
+ * (options/preset mapping + the walkScope bridge), tree-replay (the sorted
48
+ * DFS replay), budget (the maxFiles state + cut diagnostics), hydration
49
+ * (the read pipeline + provider face).
50
+ */
51
+ import { stat } from 'node:fs/promises';
52
+ import { fileLimitDiagnostic } from './budget.js';
53
+ import { resolveScanOptions } from './engine-mapping.js';
54
+ import { builtinDescriptionProvider, settleDescription } from './hydration.js';
55
+ import { assignFolderDescriptions, performWalk, rootNode } from './tree-replay.js';
56
+ export async function scanSource(root, provider = builtinDescriptionProvider, options) {
57
+ let rootStat;
58
+ try {
59
+ rootStat = await stat(root);
60
+ }
61
+ catch {
62
+ return { tree: rootNode(root), itemCount: 0, diagnostics: [rootDiagnostic('scan-root-not-found', `Scan root does not exist or is unreadable.`)], countIncompletePaths: [] };
63
+ }
64
+ if (rootStat.isFile()) {
65
+ return { tree: rootNode(root), itemCount: 0, diagnostics: [rootDiagnostic('scan-root-is-file', `Scan root is a file. Navigation scans start from folders.`)], countIncompletePaths: [] };
66
+ }
67
+ if (!rootStat.isDirectory()) {
68
+ return { tree: rootNode(root), itemCount: 0, diagnostics: [rootDiagnostic('scan-root-unsupported', `Scan root is neither a directory nor a Markdown file.`)], countIncompletePaths: [] };
69
+ }
70
+ const walk = await performWalk(root, options, true);
71
+ const diagnostics = [];
72
+ // Drain: settle the started reads in walk order (they overlapped during
73
+ // the walk) — descriptions and per-file diagnostics land exactly where
74
+ // the serial walk would have put them.
75
+ for (const event of walk.events) {
76
+ if (event.kind === 'diag') {
77
+ diagnostics.push(event.diagnostic);
78
+ continue;
79
+ }
80
+ await settleDescription(event.read, provider, diagnostics);
81
+ }
82
+ if (walk.limitHit)
83
+ diagnostics.push(fileLimitDiagnostic(walk.ctx.opts.maxFiles));
84
+ assignFolderDescriptions(walk.tree, walk.ctx);
85
+ return { tree: walk.tree, itemCount: walk.itemCount, diagnostics, countIncompletePaths: walk.ctx.countIncompletePaths };
86
+ }
87
+ /**
88
+ * Structure-only walk: every exclusion, gitignore, budget, and pruning rule
89
+ * of scanSource with zero content reads — file nodes carry no item until
90
+ * describe() hydrates the ones a view actually renders. The tree shape and
91
+ * itemCount are exactly scanSource's (the budget counts entries, not reads).
92
+ */
93
+ export async function scanStructure(root, provider = builtinDescriptionProvider, options) {
94
+ let rootStat;
95
+ try {
96
+ rootStat = await stat(root);
97
+ }
98
+ catch {
99
+ return structureError(root, 'scan-root-not-found', `Scan root does not exist or is unreadable.`);
100
+ }
101
+ if (rootStat.isFile()) {
102
+ return structureError(root, 'scan-root-is-file', `Scan root is a file. Navigation scans start from folders.`);
103
+ }
104
+ if (!rootStat.isDirectory()) {
105
+ return structureError(root, 'scan-root-unsupported', `Scan root is neither a directory nor a Markdown file.`);
106
+ }
107
+ const walk = await performWalk(root, options, false);
108
+ const diagnostics = [];
109
+ for (const event of walk.events) {
110
+ if (event.kind === 'diag')
111
+ diagnostics.push(event.diagnostic);
112
+ }
113
+ if (walk.limitHit)
114
+ diagnostics.push(fileLimitDiagnostic(walk.ctx.opts.maxFiles));
115
+ return {
116
+ tree: walk.tree,
117
+ itemCount: walk.itemCount,
118
+ diagnostics,
119
+ countIncompletePaths: walk.ctx.countIncompletePaths,
120
+ describe: async (paths) => {
121
+ const targeted = paths instanceof Set ? paths : new Set(paths);
122
+ const records = [...walk.ctx.files.values()].filter((record) => targeted.has(record.rel) && record.node.item === undefined);
123
+ if (records.length === 0) {
124
+ assignFolderDescriptions(walk.tree, walk.ctx);
125
+ return [];
126
+ }
127
+ const reads = [];
128
+ for (const record of records) {
129
+ await walk.ctx.reads.gate();
130
+ const item = { name: record.node.name, description: '', locator: { kind: 'file', path: record.rel } };
131
+ record.node.item = item;
132
+ reads.push({ rel: record.rel, content: walk.ctx.reads.start(record.abs), item });
133
+ }
134
+ const sink = [];
135
+ for (const read of reads)
136
+ await settleDescription(read, provider, sink);
137
+ assignFolderDescriptions(walk.tree, walk.ctx);
138
+ return sink;
139
+ },
140
+ };
141
+ }
142
+ function structureError(root, code, message) {
143
+ return {
144
+ tree: rootNode(root),
145
+ itemCount: 0,
146
+ diagnostics: [rootDiagnostic(code, message)],
147
+ countIncompletePaths: [],
148
+ describe: async () => [],
149
+ };
150
+ }
151
+ function rootDiagnostic(code, message) {
152
+ return { code, severity: 'warning', message, path: '.', audience: 'consumer' };
153
+ }
154
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import { type ResolvedScanOptions } from './engine-mapping.ts';
2
+ import { BoundedReads, type DiagnosticEvent, type FileRecord } from './hydration.ts';
3
+ import type { NavNode } from './types.ts';
4
+ export declare function isExcludedFileName(name: string, excludeFiles: readonly string[]): boolean;
5
+ /**
6
+ * Directory description carriers: standardized files a folder
7
+ * may already carry, in descending order of standardization — the npm
8
+ * `package.json` description, then the manifest-authoring `manifest.yml`
9
+ * `metadata.description` gloss. Priority order, per stage: present with a
10
+ * usable value wins; present but empty or key-less falls through to the
11
+ * next stage (never fabricates); unreadable or invalid fires a loud
12
+ * maintainer diagnostic and falls through the same way. The README chain
13
+ * remains the tail after the last carrier. The files are never Markdown so
14
+ * they never enter the tree, and they never consume the maxFiles budget.
15
+ */
16
+ export declare const DIR_DESCRIPTION_CARRIERS: readonly string[];
17
+ export declare function isDirDescriptionCarrier(name: string): boolean;
18
+ /** Shared mutable walk state (the budget stays out: its arithmetic is
19
+ * synchronous exactly as in the serial walk, which is what keeps the
20
+ * maxFiles cut point identical). */
21
+ export interface WalkContext {
22
+ /** Scan root as given — replay paths join against it. */
23
+ root: string;
24
+ opts: ResolvedScanOptions;
25
+ events: DiagnosticEvent[];
26
+ /** Walked md files by source-relative path, in walk order — the structure
27
+ * walk's describe() index (the eager walk kicks reads inline instead). */
28
+ files: Map<string, FileRecord>;
29
+ /** Directory carrier descriptions captured during the walk; the folder
30
+ * pass folds them with README chains into node descriptions. */
31
+ carrierDescriptions: Map<NavNode, string | undefined>;
32
+ reads: BoundedReads;
33
+ /** Eager walks kick reads at walk positions; structure walks record files
34
+ * only, deferring every content read to describe(). */
35
+ eager: boolean;
36
+ /** Dir paths (walk order) whose descent the maxFiles budget cut short —
37
+ * the floor-count chain; the per-row diagnostics ride the
38
+ * walk events alongside. */
39
+ countIncompletePaths: string[];
40
+ }
41
+ /** The shared walk's result (either entry point assembles its output from
42
+ * this): assembled tree (carrier descriptions captured), budget, and
43
+ * walk-order events (diag events for directory findings; file events in
44
+ * eager mode). */
45
+ export interface WalkOutcome {
46
+ ctx: WalkContext;
47
+ tree: NavNode;
48
+ itemCount: number;
49
+ events: DiagnosticEvent[];
50
+ limitHit: boolean;
51
+ }
52
+ export declare function performWalk(root: string, options: ResolvedScanOptions | undefined, eager: boolean): Promise<WalkOutcome>;
53
+ /**
54
+ * Post-walk folder descriptions, bottom-up over the assembled tree — the
55
+ * same chain the serial walk computed inline: carrier description first
56
+ * (package.json, then manifest.yml), else the folder's own first
57
+ * README child (children are name-sorted) carrying a real description. Runs
58
+ * after the settle, so file descriptions are final.
59
+ */
60
+ export declare function assignFolderDescriptions(node: NavNode, ctx: WalkContext): void;
61
+ export declare function rootNode(root: string): NavNode;
@@ -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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"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Scan-domain types: the knowledge model and the scan policy inputs.
3
+ *
4
+ * This is the lean consumption face's type system: NavItem { name,
5
+ * description, locator } is the ONLY knowledge model in Any-Nav (the SDK's
6
+ * view DTOs build on it, never beside it), and ScanConfig is the scan
7
+ * boundary policy the TOML layer maps onto. Both live here — with the
8
+ * scanner — so embedders of the engine never pull the SDK's view types.
9
+ */
10
+ /** v1 locates markdown files only. Kinds beyond 'file' are out of v1 scope. */
11
+ export type NavLocatorKind = 'file';
12
+ export interface NavLocator {
13
+ kind: NavLocatorKind;
14
+ /** Path relative to the nav source root, POSIX separators. */
15
+ path: string;
16
+ }
17
+ export interface NavItem {
18
+ /** Stable name; defaults to the file stem. */
19
+ name: string;
20
+ /** One line, from the description source chain. */
21
+ description: string;
22
+ locator: NavLocator;
23
+ }
24
+ /** Tree used for drill-down. Directories are nodes, not items. */
25
+ export interface NavNode {
26
+ name: string;
27
+ /** Path relative to the source root, POSIX separators; '' for the root. */
28
+ path: string;
29
+ kind: 'dir' | 'file';
30
+ /** Present when kind === 'file'. */
31
+ item?: NavItem;
32
+ /** Present when kind === 'dir'. */
33
+ children?: NavNode[];
34
+ /**
35
+ * Dir nodes only (the root included): description from the directory's
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
+ */
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;
44
+ }
45
+ /**
46
+ * Global scan boundary policy (routes traversal semantics; exclude
47
+ * lists are additive). All keys default — the config
48
+ * states only exceptions, and stating one never drops the built-in floor.
49
+ */
50
+ export interface ScanConfig {
51
+ /** Directory names additionally skipped during traversal; appends to the always-on built-in list. */
52
+ excludeDirs?: string[];
53
+ /** Markdown file names (case-insensitive) additionally excluded from navigation, e.g. AGENTS.md; appends to the built-in list. */
54
+ excludeFiles?: string[];
55
+ /** Skip entries whose name starts with '.', e.g. .github. */
56
+ excludeDotEntries?: boolean;
57
+ /** Whether traversal respects .gitignore files. */
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[];
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). */
70
+ maxFiles?: number;
71
+ }
72
+ export type DiagnosticAudience = 'consumer' | 'maintainer';
73
+ export interface ScanDiagnostic {
74
+ code: string;
75
+ severity: 'info' | 'warning';
76
+ message: string;
77
+ path?: string;
78
+ audience: DiagnosticAudience;
79
+ /** Present only on write-artifact diagnostics (section-trimmed-to-budget):
80
+ * the rows answer is never touched by the budget guard, so rows packaging
81
+ * and agent faces stay silent — the report belongs where the artifact is
82
+ * produced (the CLI --write flow; the section's own tail note is the
83
+ * canonical text). */
84
+ face?: 'write';
85
+ }
package/dist/types.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Scan-domain types: the knowledge model and the scan policy inputs.
3
+ *
4
+ * This is the lean consumption face's type system: NavItem { name,
5
+ * description, locator } is the ONLY knowledge model in Any-Nav (the SDK's
6
+ * view DTOs build on it, never beside it), and ScanConfig is the scan
7
+ * boundary policy the TOML layer maps onto. Both live here — with the
8
+ * scanner — so embedders of the engine never pull the SDK's view types.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "@any-nav/scan",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/CatheadOwl/any-nav.git"
9
+ },
10
+ "type": "module",
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "files": [
14
+ "dist",
15
+ "CHANGELOG.md"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "provenance": false
20
+ },
21
+ "dependencies": {
22
+ "@catheadowl/pathscope": "^0.1.1"
23
+ },
24
+ "keywords": [
25
+ "agent",
26
+ "navigation",
27
+ "markdown",
28
+ "scan"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.build.json",
32
+ "typecheck": "tsc --noEmit",
33
+ "test": "node --test \"test/*.test.ts\""
34
+ }
6
35
  }