@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
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ScanConfig } from './types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Agent-instruction files harnesses load on their own; they carry no
|
|
4
|
+
* navigation value (the agent already has them) so they never appear in
|
|
5
|
+
* navigation (routes DEFAULT_EXCLUDE_FILES, ported).
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_EXCLUDE_FILES: readonly string[];
|
|
8
|
+
/** routes DEFAULT_EXCLUDE_DIRS, ported verbatim. Always on (the safety
|
|
9
|
+
* floor); user excludeDirs entries append to it, never replace it — a
|
|
10
|
+
* generate-era 'any-nav' entry was retired with `list --write` (the
|
|
11
|
+
* materialization target is now any file the caller names). */
|
|
12
|
+
export declare const DEFAULT_EXCLUDE_DIRS: readonly string[];
|
|
13
|
+
export interface ResolvedScanOptions {
|
|
14
|
+
excludeDirs: readonly string[];
|
|
15
|
+
excludeFiles: readonly string[];
|
|
16
|
+
excludeDotEntries: boolean;
|
|
17
|
+
respectGitignore: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Named-subtree core-stone exemptions, passed verbatim to the
|
|
20
|
+
* engine's `boundaryStones.exempt`: gitignore-style patterns anchored at
|
|
21
|
+
* the scan root. Optional — absent means no exemptions (every embedded
|
|
22
|
+
* repo stays foreign territory, exactly as before the key existed); the
|
|
23
|
+
* field stays optional so pre-existing terminal callers keep compiling.
|
|
24
|
+
*/
|
|
25
|
+
boundaryExempt?: readonly string[];
|
|
26
|
+
maxFiles: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function resolveScanOptions(config?: ScanConfig): ResolvedScanOptions;
|
|
29
|
+
/** One trie level over the engine's in-scope set: files sit at their direct
|
|
30
|
+
* parent, directory segments carry the levels beneath them. Only levels with
|
|
31
|
+
* in-scope content exist — an empty directory (or one whose content is all
|
|
32
|
+
* out of scope) never enters the trie, which is the empty-directory pruning
|
|
33
|
+
* the old walk computed by hand. */
|
|
34
|
+
export interface ScopeLevel {
|
|
35
|
+
files: Set<string>;
|
|
36
|
+
dirs: Map<string, ScopeLevel>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The one engine query — "which files are in scope". Unbounded on purpose: the
|
|
40
|
+
* budget's unit is a TREE ENTRY (a Markdown file surviving the consumer
|
|
41
|
+
* filters), so the engine's raw-file budget never fires here and the
|
|
42
|
+
* replay below owns the fact-safe cut, its ordering, and its diagnostics.
|
|
43
|
+
* (Engine diagnostics beyond the budget do not exist in 0.1.0.)
|
|
44
|
+
* boundaryExempt rides boundaryStones.exempt verbatim: naming a
|
|
45
|
+
* subtree registers its participation — its own .git no longer cuts it,
|
|
46
|
+
* while deeper embedded repos inside it and un-named siblings keep the cut.
|
|
47
|
+
*/
|
|
48
|
+
export declare function scopeLevels(root: string, opts: ResolvedScanOptions): Promise<ScopeLevel>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine mapping face — everything crossing the boundary into
|
|
3
|
+
* @catheadowl/pathscope (the facade face): the
|
|
4
|
+
* resolved-options face (the additive default floor), the preset mounts, the
|
|
5
|
+
* policy-channel patterns, the walkScope query itself, and the scope trie the
|
|
6
|
+
* replay descends. "Which files are in scope" is the engine's question; the
|
|
7
|
+
* Markdown layer that turns the scope into a tree lives in tree-replay.ts.
|
|
8
|
+
*/
|
|
9
|
+
import { walkScope } from '@catheadowl/pathscope';
|
|
10
|
+
/**
|
|
11
|
+
* Agent-instruction files harnesses load on their own; they carry no
|
|
12
|
+
* navigation value (the agent already has them) so they never appear in
|
|
13
|
+
* navigation (routes DEFAULT_EXCLUDE_FILES, ported).
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_EXCLUDE_FILES = ['AGENTS.md', 'CLAUDE.md'];
|
|
16
|
+
/** routes DEFAULT_EXCLUDE_DIRS, ported verbatim. Always on (the safety
|
|
17
|
+
* floor); user excludeDirs entries append to it, never replace it — a
|
|
18
|
+
* generate-era 'any-nav' entry was retired with `list --write` (the
|
|
19
|
+
* materialization target is now any file the caller names). */
|
|
20
|
+
export const DEFAULT_EXCLUDE_DIRS = [
|
|
21
|
+
'.git',
|
|
22
|
+
'.hg',
|
|
23
|
+
'.svn',
|
|
24
|
+
'node_modules',
|
|
25
|
+
'.pnpm',
|
|
26
|
+
'dist',
|
|
27
|
+
'build',
|
|
28
|
+
'lib',
|
|
29
|
+
'out',
|
|
30
|
+
];
|
|
31
|
+
export function resolveScanOptions(config) {
|
|
32
|
+
// Additive ruling: the built-in tables are the
|
|
33
|
+
// always-on floor, user entries append — "the file states only exceptions"
|
|
34
|
+
// must not mean restating the defaults to add one. Duplicates are harmless
|
|
35
|
+
// (membership checks only), so a plain concat stays honest. boundaryExempt
|
|
36
|
+
// has no floor to append to — an absent key means no exemptions.
|
|
37
|
+
return {
|
|
38
|
+
excludeDirs: [...DEFAULT_EXCLUDE_DIRS, ...(config?.excludeDirs ?? [])],
|
|
39
|
+
excludeFiles: [...DEFAULT_EXCLUDE_FILES, ...(config?.excludeFiles ?? [])],
|
|
40
|
+
excludeDotEntries: config?.excludeDotEntries ?? true,
|
|
41
|
+
respectGitignore: config?.respectGitignore ?? true,
|
|
42
|
+
boundaryExempt: config?.boundaryExempt ?? [],
|
|
43
|
+
maxFiles: config?.maxFiles ?? 2000,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The engine-mounted exclusion floor: when the caller resolved
|
|
48
|
+
* the built-in floor (resolveScanOptions output, or an equivalent terminal
|
|
49
|
+
* list), its dir table rides the engine's preset form — vcs/deps/build carry
|
|
50
|
+
* the old nine-entry DEFAULT_EXCLUDE_DIRS table plus vendor/ and coverage/
|
|
51
|
+
* (the accepted preset increment) — and agentDocs carries the built-in
|
|
52
|
+
* AGENTS.md/CLAUDE.md file exclusion. A terminal caller that narrowed the
|
|
53
|
+
* floor (BASE_SCAN-style excludeDirs: []) keeps exact name semantics via
|
|
54
|
+
* the policy channel and the replay's literal check: nothing is
|
|
55
|
+
* force-mounted. (The preset increment is negatable by a repo's .gitignore
|
|
56
|
+
* — engine front-seat semantics; the floor names on the policy channel are
|
|
57
|
+
* not, exactly as before.)
|
|
58
|
+
*/
|
|
59
|
+
function engineMounts(opts) {
|
|
60
|
+
const mounts = [];
|
|
61
|
+
if (DEFAULT_EXCLUDE_DIRS.every((name) => opts.excludeDirs.includes(name))) {
|
|
62
|
+
mounts.push('vcs', 'deps', 'build');
|
|
63
|
+
}
|
|
64
|
+
if (DEFAULT_EXCLUDE_FILES.every((name) => opts.excludeFiles.includes(name))) {
|
|
65
|
+
mounts.push('agentDocs');
|
|
66
|
+
}
|
|
67
|
+
return mounts;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The resolved excludeDirs ride the engine's policy channel as dir-only
|
|
71
|
+
* patterns at the rear seat of the rule table — exactly the old floor's
|
|
72
|
+
* independence from gitignore (a repo's `!node_modules/` cannot resurrect a
|
|
73
|
+
* floor entry). Entries that are not plain names (separators, globs, a
|
|
74
|
+
* leading '!') stay out of the channel: the old engine compared directory
|
|
75
|
+
* names by literal equality, and those entries keep that semantics in the
|
|
76
|
+
* replay instead of acquiring pattern meaning.
|
|
77
|
+
*/
|
|
78
|
+
function excludeChannelPatterns(excludeDirs) {
|
|
79
|
+
return excludeDirs
|
|
80
|
+
.filter((name) => /^[^/\\!*?[\]!#]+$/u.test(name))
|
|
81
|
+
.map((name) => `${name}/`);
|
|
82
|
+
}
|
|
83
|
+
function buildScopeLevel(paths) {
|
|
84
|
+
const root = { files: new Set(), dirs: new Map() };
|
|
85
|
+
for (const path of paths) {
|
|
86
|
+
const segments = path.split('/');
|
|
87
|
+
let level = root;
|
|
88
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
89
|
+
const segment = segments[index];
|
|
90
|
+
if (segment === undefined)
|
|
91
|
+
continue;
|
|
92
|
+
let child = level.dirs.get(segment);
|
|
93
|
+
if (child === undefined) {
|
|
94
|
+
child = { files: new Set(), dirs: new Map() };
|
|
95
|
+
level.dirs.set(segment, child);
|
|
96
|
+
}
|
|
97
|
+
level = child;
|
|
98
|
+
}
|
|
99
|
+
const file = segments[segments.length - 1];
|
|
100
|
+
if (file !== undefined && file !== '')
|
|
101
|
+
level.files.add(file);
|
|
102
|
+
}
|
|
103
|
+
return root;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The one engine query — "which files are in scope". Unbounded on purpose: the
|
|
107
|
+
* budget's unit is a TREE ENTRY (a Markdown file surviving the consumer
|
|
108
|
+
* filters), so the engine's raw-file budget never fires here and the
|
|
109
|
+
* replay below owns the fact-safe cut, its ordering, and its diagnostics.
|
|
110
|
+
* (Engine diagnostics beyond the budget do not exist in 0.1.0.)
|
|
111
|
+
* boundaryExempt rides boundaryStones.exempt verbatim: naming a
|
|
112
|
+
* subtree registers its participation — its own .git no longer cuts it,
|
|
113
|
+
* while deeper embedded repos inside it and un-named siblings keep the cut.
|
|
114
|
+
*/
|
|
115
|
+
export async function scopeLevels(root, opts) {
|
|
116
|
+
const boundaryExempt = opts.boundaryExempt ?? [];
|
|
117
|
+
const scope = await walkScope(root, {
|
|
118
|
+
mountPresets: engineMounts(opts),
|
|
119
|
+
exclude: excludeChannelPatterns(opts.excludeDirs),
|
|
120
|
+
excludeDotEntries: opts.excludeDotEntries,
|
|
121
|
+
respectGitignore: opts.respectGitignore,
|
|
122
|
+
...(boundaryExempt.length > 0 ? { boundaryStones: { exempt: [...boundaryExempt] } } : {}),
|
|
123
|
+
maxFiles: Number.MAX_SAFE_INTEGER,
|
|
124
|
+
});
|
|
125
|
+
return buildScopeLevel(scope.files);
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=engine-mapping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-mapping.js","sourceRoot":"","sources":["../src/engine-mapping.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { NavItem, NavNode, ScanDiagnostic } from './types.ts';
|
|
2
|
+
export interface ScannedFile {
|
|
3
|
+
path: string;
|
|
4
|
+
content: string;
|
|
5
|
+
}
|
|
6
|
+
/** Port: built-in chain is the ported routes extraction; LLM providers arrive in v1.x. */
|
|
7
|
+
export interface DescriptionProvider {
|
|
8
|
+
extract(file: ScannedFile): Promise<string | null>;
|
|
9
|
+
}
|
|
10
|
+
export declare const builtinDescriptionProvider: DescriptionProvider;
|
|
11
|
+
/** One started read awaiting its settle: item attaches blank at kick-off,
|
|
12
|
+
* the description lands at the settle. */
|
|
13
|
+
export interface PendingRead {
|
|
14
|
+
rel: string;
|
|
15
|
+
content: Promise<string | null>;
|
|
16
|
+
item: NavItem;
|
|
17
|
+
}
|
|
18
|
+
/** Walk-order diagnostic events: directory diagnostics record where they
|
|
19
|
+
* fired; file events settle at the drain — the flattened order equals the
|
|
20
|
+
* serial walk's emission order. Structure walks emit diag events only. */
|
|
21
|
+
export type DiagnosticEvent = {
|
|
22
|
+
kind: 'diag';
|
|
23
|
+
diagnostic: ScanDiagnostic;
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'file';
|
|
26
|
+
read: PendingRead;
|
|
27
|
+
};
|
|
28
|
+
/** Structure-walk file record: the node exists without an item; describe()
|
|
29
|
+
* attaches the item after extraction. */
|
|
30
|
+
export interface FileRecord {
|
|
31
|
+
rel: string;
|
|
32
|
+
abs: string;
|
|
33
|
+
node: NavNode;
|
|
34
|
+
}
|
|
35
|
+
/** Bounded in-flight read window: kicks never reject (read errors resolve
|
|
36
|
+
* null — the unreadable-file path), and the gate waits for the next settle
|
|
37
|
+
* before kicking past the ceiling. gate() then start() keeps the started
|
|
38
|
+
* promise unwrapped for fire-and-continue callers. */
|
|
39
|
+
export declare class BoundedReads {
|
|
40
|
+
private readonly pending;
|
|
41
|
+
/** Wait for a window slot (no-op while under the ceiling). */
|
|
42
|
+
gate(): Promise<void>;
|
|
43
|
+
/** Start one read and return its still-running promise. */
|
|
44
|
+
start(abs: string): Promise<string | null>;
|
|
45
|
+
}
|
|
46
|
+
/** Settle one started read: the description lands on the item and any
|
|
47
|
+
* per-file diagnostic (unreadable-file / no-description placeholder) goes
|
|
48
|
+
* to the sink at the caller's position. */
|
|
49
|
+
export declare function settleDescription(read: PendingRead, provider: DescriptionProvider, sink: ScanDiagnostic[]): Promise<void>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read pipeline for descriptions — the machinery both entry points share
|
|
3
|
+
* (file split 2026-10-24): the bounded in-flight read window, the bounded
|
|
4
|
+
* head ladder that provably covers the extraction chain's input (frontmatter
|
|
5
|
+
* plus the body-head window, extended adaptively, never more bytes than the
|
|
6
|
+
* chain can consume), the walk-order read events, and the settle that lands
|
|
7
|
+
* a description — or the unreadable-file / no-description paths — on an
|
|
8
|
+
* item. The extraction chain itself lives in description.ts.
|
|
9
|
+
*/
|
|
10
|
+
import { open } from 'node:fs/promises';
|
|
11
|
+
import { extractDescription, BODY_HEAD_LINE_LIMIT } from './description.js';
|
|
12
|
+
export const builtinDescriptionProvider = {
|
|
13
|
+
extract: (file) => Promise.resolve(extractDescription(file.content)),
|
|
14
|
+
};
|
|
15
|
+
/** Read fan-out ceiling: the 2026-10-20 probe saturated at 8 lanes (~4.3x,
|
|
16
|
+
* 39→9ms over 177 files) with nothing at 16 — the window keeps stragglers
|
|
17
|
+
* from stalling the pipeline and bounds transient memory to the window's
|
|
18
|
+
* in-flight file heads. */
|
|
19
|
+
const MAX_INFLIGHT_READS = 16;
|
|
20
|
+
/** Bounded in-flight read window: kicks never reject (read errors resolve
|
|
21
|
+
* null — the unreadable-file path), and the gate waits for the next settle
|
|
22
|
+
* before kicking past the ceiling. gate() then start() keeps the started
|
|
23
|
+
* promise unwrapped for fire-and-continue callers. */
|
|
24
|
+
export class BoundedReads {
|
|
25
|
+
pending = [];
|
|
26
|
+
/** Wait for a window slot (no-op while under the ceiling). */
|
|
27
|
+
async gate() {
|
|
28
|
+
if (this.pending.length >= MAX_INFLIGHT_READS) {
|
|
29
|
+
await Promise.race(this.pending);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Start one read and return its still-running promise. */
|
|
33
|
+
start(abs) {
|
|
34
|
+
const content = readDescriptionInput(abs);
|
|
35
|
+
this.pending.push(content);
|
|
36
|
+
void content.then(() => {
|
|
37
|
+
const index = this.pending.indexOf(content);
|
|
38
|
+
if (index !== -1)
|
|
39
|
+
this.pending.splice(index, 1);
|
|
40
|
+
});
|
|
41
|
+
return content;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** First head rung and the extension factor: extraction reads frontmatter
|
|
45
|
+
* plus a 15-line body-head window (fences/HTML comments stripped inside
|
|
46
|
+
* it), so a head closing the frontmatter and showing 16+ open body lines
|
|
47
|
+
* with nothing dangling at the cut is provably equivalent to the full file
|
|
48
|
+
* for the chain — the ladder only climbs for long frontmatter or
|
|
49
|
+
* fence/comment-heavy heads. */
|
|
50
|
+
const DESC_HEAD_BYTES = 16 * 1024;
|
|
51
|
+
const DESC_HEAD_LADDER = 4;
|
|
52
|
+
/**
|
|
53
|
+
* Read the file head the description chain consumes — never more bytes than
|
|
54
|
+
* the chain can read, provably equivalent input or the ladder climbs (the
|
|
55
|
+
* whole file is the terminal rung). Any I/O error resolves null (the
|
|
56
|
+
* unreadable-file path); the full readFile this replaced had the same
|
|
57
|
+
* contract with far more bytes.
|
|
58
|
+
*/
|
|
59
|
+
async function readDescriptionInput(abs) {
|
|
60
|
+
let handle;
|
|
61
|
+
try {
|
|
62
|
+
handle = await open(abs);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
let capacity = DESC_HEAD_BYTES;
|
|
69
|
+
for (;;) {
|
|
70
|
+
const bytes = await readUpTo(handle, capacity);
|
|
71
|
+
const text = bytes.toString('utf8');
|
|
72
|
+
if (bytes.length < capacity || headCoversExtraction(text))
|
|
73
|
+
return text;
|
|
74
|
+
capacity *= DESC_HEAD_LADDER;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
await handle.close().catch(() => { });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function readUpTo(handle, capacity) {
|
|
85
|
+
const buffer = Buffer.alloc(capacity);
|
|
86
|
+
let filled = 0;
|
|
87
|
+
while (filled < capacity) {
|
|
88
|
+
const { bytesRead } = await handle.read(buffer, filled, capacity - filled, filled);
|
|
89
|
+
if (bytesRead === 0)
|
|
90
|
+
break;
|
|
91
|
+
filled += bytesRead;
|
|
92
|
+
}
|
|
93
|
+
return filled === capacity ? buffer : buffer.subarray(0, filled);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether a head provably covers every byte extractDescription reads:
|
|
97
|
+
* frontmatter closed when opened; past it, at least BODY_HEAD_LINE_LIMIT + 1
|
|
98
|
+
* open lines (the chain slices 15 — the +1 guarantees the 15th ended with a
|
|
99
|
+
* newline and is complete) with no code fence or HTML comment left dangling
|
|
100
|
+
* at the cut (mirrors stripCodeFences / stripHtmlComments state shapes; a
|
|
101
|
+
* dangling marker means later content could still change the strip).
|
|
102
|
+
*/
|
|
103
|
+
function headCoversExtraction(text) {
|
|
104
|
+
const lines = text.split(/\r?\n/u);
|
|
105
|
+
let start = 0;
|
|
106
|
+
if (lines[0]?.trim() === '---') {
|
|
107
|
+
const end = lines.findIndex((line, index) => index > 0 && line.trim() === '---');
|
|
108
|
+
if (end === -1)
|
|
109
|
+
return false;
|
|
110
|
+
start = end + 1;
|
|
111
|
+
}
|
|
112
|
+
let inFence = false;
|
|
113
|
+
let inComment = false;
|
|
114
|
+
let openLines = 0;
|
|
115
|
+
for (let index = start; index < lines.length; index += 1) {
|
|
116
|
+
const line = lines[index];
|
|
117
|
+
if (/^\s*(`{3,}|~{3,})/u.test(line)) {
|
|
118
|
+
inFence = !inFence;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (inFence)
|
|
122
|
+
continue;
|
|
123
|
+
let rest = line;
|
|
124
|
+
while (rest.length > 0) {
|
|
125
|
+
if (!inComment) {
|
|
126
|
+
const opens = rest.indexOf('<!--');
|
|
127
|
+
if (opens === -1)
|
|
128
|
+
break;
|
|
129
|
+
rest = rest.slice(opens + 4);
|
|
130
|
+
inComment = true;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const closes = rest.indexOf('-->');
|
|
134
|
+
if (closes === -1) {
|
|
135
|
+
rest = '';
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
rest = rest.slice(closes + 3);
|
|
139
|
+
inComment = false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
openLines += 1;
|
|
143
|
+
}
|
|
144
|
+
return !inFence && !inComment && openLines >= BODY_HEAD_LINE_LIMIT + 1;
|
|
145
|
+
}
|
|
146
|
+
/** Settle one started read: the description lands on the item and any
|
|
147
|
+
* per-file diagnostic (unreadable-file / no-description placeholder) goes
|
|
148
|
+
* to the sink at the caller's position. */
|
|
149
|
+
export async function settleDescription(read, provider, sink) {
|
|
150
|
+
const content = await read.content;
|
|
151
|
+
if (content === null) {
|
|
152
|
+
// routes port: an unreadable Markdown file still becomes a bare-path
|
|
153
|
+
// route (diagnostic fires, the entry survives) — it must not silently
|
|
154
|
+
// vanish from navigation, and it still consumes the maxFiles budget.
|
|
155
|
+
sink.push({
|
|
156
|
+
code: 'unreadable-file',
|
|
157
|
+
severity: 'warning',
|
|
158
|
+
message: `Unable to read Markdown file ${read.rel}.`,
|
|
159
|
+
path: read.rel,
|
|
160
|
+
audience: 'maintainer',
|
|
161
|
+
});
|
|
162
|
+
read.item.description = `Undocumented — unreadable file ${read.rel}`;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const extracted = await provider.extract({ path: read.rel, content });
|
|
166
|
+
read.item.description =
|
|
167
|
+
extracted !== null && extracted.trim() !== '' ? extracted : placeholder(read.rel, sink);
|
|
168
|
+
}
|
|
169
|
+
function placeholder(relPath, diagnostics) {
|
|
170
|
+
diagnostics.push({
|
|
171
|
+
code: 'no-description',
|
|
172
|
+
severity: 'warning',
|
|
173
|
+
// The code states the condition; the message carries only the datum
|
|
174
|
+
// (the WARN | code | source=... convention — no restating sentence).
|
|
175
|
+
message: relPath,
|
|
176
|
+
path: relPath,
|
|
177
|
+
audience: 'maintainer',
|
|
178
|
+
});
|
|
179
|
+
return `Undocumented — add a description to ${relPath}`;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=hydration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hydration.js","sourceRoot":"","sources":["../src/hydration.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @any-nav/scan public API — the lean engine face.
|
|
3
|
+
*
|
|
4
|
+
* Boundary rules this surface must preserve:
|
|
5
|
+
* 1. one runtime dependency by ruling — @catheadowl/pathscope, the shared
|
|
6
|
+
* file-scope engine the walk delegates to. That delegation replaced the
|
|
7
|
+
* zero-dependency premise: embedders carry pathscope
|
|
8
|
+
* and its three deps (ignore/fdir/find-up) in the install closure, and
|
|
9
|
+
* no other dependency may join them;
|
|
10
|
+
* 2. read-only over the filesystem — scanning never modifies sources
|
|
11
|
+
* (map is not territory);
|
|
12
|
+
* 3. terminal options: ResolvedScanOptions is what it says — callers
|
|
13
|
+
* hand the scanner final values; the additive default floor is a
|
|
14
|
+
* convenience (resolveScanOptions), not an obligation;
|
|
15
|
+
* 4. API shell policy: export what real consumption forms
|
|
16
|
+
* need — a consumption form is someone importing today, a local
|
|
17
|
+
* fork being the unwired equivalent — never speculative breadth;
|
|
18
|
+
* each future export passes a light ruling first.
|
|
19
|
+
*/
|
|
20
|
+
export type { DiagnosticAudience, NavItem, NavLocator, NavLocatorKind, NavNode, ScanConfig, ScanDiagnostic, } from './types.ts';
|
|
21
|
+
export type { ResolvedScanOptions } from './engine-mapping.ts';
|
|
22
|
+
export type { DescriptionProvider, ScannedFile, } from './hydration.ts';
|
|
23
|
+
export type { ScanOutput, StructureScanOutput, } from './scan.ts';
|
|
24
|
+
export { builtinDescriptionProvider, } from './hydration.ts';
|
|
25
|
+
export { DEFAULT_EXCLUDE_DIRS, DEFAULT_EXCLUDE_FILES, resolveScanOptions, } from './engine-mapping.ts';
|
|
26
|
+
export { DIR_DESCRIPTION_CARRIERS, isDirDescriptionCarrier, isExcludedFileName, } from './tree-replay.ts';
|
|
27
|
+
export { scanSource, scanStructure, } from './scan.ts';
|
|
28
|
+
export { extractDescription } from './description.ts';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { builtinDescriptionProvider, } from './hydration.js';
|
|
2
|
+
export { DEFAULT_EXCLUDE_DIRS, DEFAULT_EXCLUDE_FILES, resolveScanOptions, } from './engine-mapping.js';
|
|
3
|
+
export { DIR_DESCRIPTION_CARRIERS, isDirDescriptionCarrier, isExcludedFileName, } from './tree-replay.js';
|
|
4
|
+
export { scanSource, scanStructure, } from './scan.js';
|
|
5
|
+
export { extractDescription } from './description.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqCA,OAAO,EACL,0BAA0B,GAC3B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,UAAU,EACV,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type ResolvedScanOptions } from './engine-mapping.ts';
|
|
2
|
+
import { type DescriptionProvider } from './hydration.ts';
|
|
3
|
+
import type { NavNode, ScanDiagnostic } from './types.ts';
|
|
4
|
+
export interface ScanOutput {
|
|
5
|
+
tree: NavNode;
|
|
6
|
+
itemCount: number;
|
|
7
|
+
diagnostics: ScanDiagnostic[];
|
|
8
|
+
/**
|
|
9
|
+
* Dir paths (walk order) whose descent the maxFiles budget cut short —
|
|
10
|
+
* the cut chain (fact-safe cut): the folder where the
|
|
11
|
+
* budget flipped plus every surviving ancestor. Their recursive counts
|
|
12
|
+
* are floors, never totals; views render ≥N and the walk events carry a
|
|
13
|
+
* per-row diagnostic. Identical for the structure walk.
|
|
14
|
+
*/
|
|
15
|
+
countIncompletePaths: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function scanSource(root: string, provider?: DescriptionProvider, options?: ResolvedScanOptions): Promise<ScanOutput>;
|
|
18
|
+
export interface StructureScanOutput {
|
|
19
|
+
tree: NavNode;
|
|
20
|
+
itemCount: number;
|
|
21
|
+
/** Walk diagnostics (directory findings + file-limit); per-file
|
|
22
|
+
* diagnostics (no-description / unreadable-file) ride describe(). */
|
|
23
|
+
diagnostics: ScanDiagnostic[];
|
|
24
|
+
/**
|
|
25
|
+
* Dir paths whose descent the maxFiles budget cut short — counts are
|
|
26
|
+
* floors (≥ rows, per-row diagnostics already in `diagnostics`);
|
|
27
|
+
* identical to scanSource's list (same cut point).
|
|
28
|
+
*/
|
|
29
|
+
countIncompletePaths: string[];
|
|
30
|
+
/**
|
|
31
|
+
* Hydrate descriptions for the given source-relative Markdown paths, in
|
|
32
|
+
* place: file nodes gain their NavItem, and folders fold descriptions
|
|
33
|
+
* (carrier ?? own README chain, identical to a full scan) once their
|
|
34
|
+
* README hydrates. Returns the per-file diagnostics in walk order.
|
|
35
|
+
* Idempotent — already-hydrated paths are skipped; unknown paths are
|
|
36
|
+
* ignored. Reads fetch bounded heads under the same in-flight window.
|
|
37
|
+
*/
|
|
38
|
+
describe(paths: Iterable<string>): Promise<ScanDiagnostic[]>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Structure-only walk: every exclusion, gitignore, budget, and pruning rule
|
|
42
|
+
* of scanSource with zero content reads — file nodes carry no item until
|
|
43
|
+
* describe() hydrates the ones a view actually renders. The tree shape and
|
|
44
|
+
* itemCount are exactly scanSource's (the budget counts entries, not reads).
|
|
45
|
+
*/
|
|
46
|
+
export declare function scanStructure(root: string, provider?: DescriptionProvider, options?: ResolvedScanOptions): Promise<StructureScanOutput>;
|