@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/CHANGELOG.md +93 -0
- package/LICENSE +21 -0
- package/README.md +126 -2
- package/dist/budget.d.ts +26 -0
- package/dist/budget.js +25 -0
- package/dist/budget.js.map +1 -0
- package/dist/description.d.ts +44 -0
- package/dist/description.js +292 -0
- package/dist/description.js.map +1 -0
- package/dist/engine-mapping.d.ts +48 -0
- package/dist/engine-mapping.js +127 -0
- package/dist/engine-mapping.js.map +1 -0
- package/dist/hydration.d.ts +49 -0
- package/dist/hydration.js +181 -0
- package/dist/hydration.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/scan.d.ts +46 -0
- package/dist/scan.js +154 -0
- package/dist/scan.js.map +1 -0
- package/dist/tree-replay.d.ts +61 -0
- package/dist/tree-replay.js +256 -0
- package/dist/tree-replay.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +32 -3
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
|
package/dist/scan.js.map
ADDED
|
@@ -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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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
}
|