llmnav 0.5.2 → 0.6.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 CHANGED
@@ -6,6 +6,18 @@ The npm package follows Semantic Versioning. The `llmnav/N` source protocol is v
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.6.0] — 2026-08-10
10
+
11
+ ### Added
12
+
13
+ * Added semantic boundaries for configuration, source discovery and formatting, project scanning, declaration extraction, diagnostics, ID lifecycle, and the public protocol specification, with targeted coverage and retrieval regression queries.
14
+ * Added a deterministic, read-only `audit` command and public API that ranks likely missing module boundaries, explains every signal, suggests narrow coverage rules, and supports opt-in CI thresholds.
15
+ * Added generated agent guidance and an `llmnav:audit` package script so new repositories review coverage after initialization and structural changes.
16
+
17
+ ### Fixed
18
+
19
+ * Suppressed declaration files and reduced false positives from tests, fixtures, benchmarks, broad utilities, and re-export barrels.
20
+
9
21
  ## [0.5.2] — 2026-08-09
10
22
 
11
23
  ### Fixed
package/README.md CHANGED
@@ -6,7 +6,7 @@ It adds compact, stable metadata to a small number of architectural and behavior
6
6
 
7
7
  LLMNav is not a documentation generator, an embedding database, or a reason to annotate every function. It is a zero-runtime-dependency Node.js CLI and ESM library for reducing broad repository scans, irrelevant context, stale hand-written links, repeated card tokenization, and avoidable cache invalidation.
8
8
 
9
- ## What v0.5 provides
9
+ ## What v0.6 provides
10
10
 
11
11
  * The backward-compatible `llmnav/1` source comment specification
12
12
  * A parser and data-loss-resistant canonical formatter
@@ -31,6 +31,7 @@ LLMNav is not a documentation generator, an embedding database, or a reason to a
31
31
  * Multilingual alias routing and CJK n-gram retrieval
32
32
  * Search regression tests with Recall@1, Recall@5, and MRR
33
33
  * Managed instructions for AGENTS.md, Claude Code, GitHub Copilot, and Cursor
34
+ * A deterministic, read-only annotation coverage audit with explicit CI thresholds
34
35
  * Linux and Windows CI gates plus npm pack installation smoke tests
35
36
 
36
37
  The package supports Node.js 22 or newer, uses ESM, performs no network requests, and has no runtime dependencies.
@@ -44,6 +45,8 @@ npx llmnav init --agents all --package-scripts
44
45
 
45
46
  Initialization is explicit. LLMNav never edits a consumer repository from an npm `postinstall` script. Use `--agents none` when only the machine-readable control directory is desired.
46
47
 
48
+ Before writing cards, run `npx llmnav audit`. It ranks likely architectural boundaries and explains each score. The command never edits source and remains advisory unless `--fail-on high`, `medium`, or `low` is selected. Review the candidates: a high score is evidence to inspect a file, not permission to generate semantic meaning automatically.
49
+
47
50
  ## Add the first card
48
51
 
49
52
  ```ts
@@ -166,6 +169,7 @@ Line-comment cards require an explicit terminator and work with `//`, `#`, and `
166
169
  | Command | Purpose |
167
170
  | --- | --- |
168
171
  | `llmnav init` | Create configuration, registry, schemas, agent instructions, and the initial cache |
172
+ | `llmnav audit` | Rank unannotated architectural boundary candidates without modifying source |
169
173
  | `llmnav check` | Validate cards, relations, coverage rules, and registry state |
170
174
  | `llmnav format` | Rewrite safe cards into canonical order and spacing |
171
175
  | `llmnav generate` | Incrementally compile and transactionally commit generated artifacts |
@@ -246,7 +250,7 @@ Do not annotate trivial getters, generated files, obvious wrappers, every test f
246
250
 
247
251
  ## Current implementation boundary
248
252
 
249
- Version 0.5 adds stable provider-neutral agent tools, trusted-root operation dispatch, explicit prompt-prefix cache partitions, deterministic editor diagnostics, a VS Code task integration, and a runnable typed host example to the repository graph and incremental navigation layers.
253
+ Version 0.6 adds a deterministic coverage audit that prioritizes public entrypoints, structural boundaries, and high fan-in modules while suppressing declaration files, test support code, simple barrels, and broad utilities. It suggests narrow coverage rules but never writes cards or invents semantic roles.
250
254
 
251
255
  LLMNav does not discover sibling repositories automatically and does not ship an MCP server, embedding database, hosted service, SCIP generator, or complete language-aware call graph. External tools may export the documented compact graph-input schema. Generated structure never writes derived edges into source cards.
252
256
 
@@ -287,7 +291,7 @@ The project uses the Node.js standard library and built-in test runner. There is
287
291
 
288
292
  ## Status
289
293
 
290
- LLMNav is an experimental protocol and a usable v0.5 CLI. The source format remains `llmnav/1`; npm package changes and source-grammar changes are versioned independently.
294
+ LLMNav is an experimental protocol and a usable v0.6 CLI. The source format remains `llmnav/1`; npm package changes and source-grammar changes are versioned independently.
291
295
 
292
296
  ## License
293
297
 
package/ROADMAP.md CHANGED
@@ -62,6 +62,17 @@ Implemented:
62
62
  * editor integrations and diagnostics
63
63
  * provider-neutral integration examples
64
64
 
65
+ ## 0.6 — annotation coverage audit
66
+
67
+ Implemented:
68
+
69
+ * deterministic read-only discovery of unannotated architectural boundary candidates
70
+ * explainable priority signals for package entrypoints, public APIs, structural boundaries, fan-in, exports, and source size
71
+ * false-positive penalties for declaration files, test support code, broad utilities, and re-export barrels
72
+ * opt-in `--fail-on high|medium|low` CI enforcement
73
+ * path-specific coverage-rule suggestions without automatic source annotation
74
+ * audit guidance in generated agent instructions and package scripts
75
+
65
76
  ## 1.0 criteria
66
77
 
67
78
  The source grammar and generated formats will be declared stable only after use across multiple TypeScript, Go, Rust, Python, and mixed-language repositories. A 1.0 release requires migration tooling, documented compatibility guarantees, benchmark fixtures with published methodology, sustained Windows and Linux verification, and no unresolved high-severity parser or transaction ambiguity.
@@ -44,8 +44,10 @@ The generated instruction tells an agent to:
44
44
  5. Keep IDs stable across moves and renames.
45
45
  6. Update semantic fields only when meaning changes.
46
46
  7. Never hand-maintain call, import, reference, implementation, export, or override relations.
47
- 8. Run format, check, and generation after semantic changes.
48
- 9. Fall back to broad search when no credible card is returned.
47
+ 8. Run `llmnav audit` after initialization and structural boundary changes, review high and medium candidates, and never annotate automatically.
48
+ 9. Encode accepted boundaries in narrow coverage rules and retrieval queries.
49
+ 10. Run format, check, and generation after semantic changes.
50
+ 11. Fall back to broad search when no credible card is returned.
49
51
 
50
52
  When a host supports structured tool calls, `llmnav tools --json` returns four stable provider-neutral definitions in fixed order: `llmnav_query`, `llmnav_show`, `llmnav_context`, and `llmnav_check`. The schemas reject unknown fields and omit the repository root so the trusted host binds scope outside model-generated input.
51
53
 
@@ -112,3 +114,5 @@ An agent should not stop when a repository is partially annotated.
112
114
  When `query` returns no credible result, the correct fallback is normal symbol search, grep, or language-server navigation. After completing the task, the agent may propose a new card only when the missed boundary satisfies the repository's annotation policy.
113
115
 
114
116
  Search failure alone is not permission to annotate every function sharing a keyword.
117
+
118
+ Audit priority is also not permission to invent a card. It is a deterministic prompt to inspect likely boundaries; durable ownership and invariants still come from the current source and repository contracts.
package/docs/api.md CHANGED
@@ -62,6 +62,21 @@ Stat hints are an optimization, not deterministic output. The returned `fileStat
62
62
 
63
63
  Attached symbol declarations expose generated `language`, `exported`, `visibility`, optional Go `receiver`, `endOffset`, and declaration `bodyHash` fields. Indexed cards also expose sorted route, event, schema, migration, and command `boundaries` with confidence and evidence.
64
64
 
65
+ ## Audit annotation coverage
66
+
67
+ ```js
68
+ import { auditHasFindings, auditProject } from "llmnav";
69
+
70
+ const result = await auditProject(process.cwd());
71
+ for (const candidate of result.candidates) {
72
+ console.log(candidate.priority, candidate.path, candidate.reasons);
73
+ }
74
+
75
+ if (auditHasFindings(result, "high")) process.exitCode = 1;
76
+ ```
77
+
78
+ `auditProject` is read-only and returns schemaVersion 1 data with repository-relative paths and deterministic ordering. Candidate signals are structural heuristics, not generated semantic meaning. Consumers should review high and medium candidates before adding a card and should never turn the suggested coverage rule into automatic source annotation.
79
+
65
80
  ## Generate incrementally and transactionally
66
81
 
67
82
  ```js
@@ -36,7 +36,7 @@ Generated paths, signatures, callers, references, timestamps, and hashes are nev
36
36
  {
37
37
  "schemaVersion": 1,
38
38
  "specVersion": "1",
39
- "generatedBy": "llmnav@0.5.2",
39
+ "generatedBy": "llmnav@0.6.0",
40
40
  "repositoryId": "example",
41
41
  "contractFingerprints": {
42
42
  "schemaVersion": 1,
@@ -68,6 +68,12 @@ Generated cards may contain a sorted `boundaries` array. LLMNav detects `route`,
68
68
 
69
69
  Generation compares the previous and current primary indexes to emit `affectedBoundaries`. The report preserves the card change and hash dimensions while adding module IDs, structural boundary records, outbound relation targets, and reverse semantic dependents. It is returned through the API and `generate --json`; it is not stored in source cards.
70
70
 
71
+ ## Annotation coverage audit
72
+
73
+ The read-only audit combines package entrypoints, public re-export reachability, generated structural boundaries, import fan-in, exported declaration counts, and source size. It lowers the priority of broad utilities, declaration files, non-production support paths, and pure re-export barrels. Every candidate retains its score inputs and deterministic repository-relative path.
74
+
75
+ This analyzer deliberately stops before semantic generation. Structure can identify a file worth inspecting, but it cannot safely invent a durable ID, role, ownership statement, invariant, risk, or relation. Accepted boundaries become explicit source cards, path-specific coverage rules, and retrieval regression queries through normal review.
76
+
71
77
  ## Deterministic inverted index
72
78
 
73
79
  `.llmnav/cache/search-index.json` schemaVersion 2 with `compact-v1` encoding stores a sorted card-ID table, normalized phrase documents, a sorted token dictionary, and posting lists.
package/docs/ci.md CHANGED
@@ -2,10 +2,11 @@
2
2
 
3
3
  ## Consumer repository gates
4
4
 
5
- A practical LLMNav gate has four stages.
5
+ A practical LLMNav gate has five stages after the repository has reviewed its initial audit baseline.
6
6
 
7
7
  ```sh
8
8
  npx llmnav format --check
9
+ npx llmnav audit --fail-on high
9
10
  npx llmnav check --format github
10
11
  npx llmnav generate --full --check
11
12
  npx llmnav eval
@@ -13,6 +14,8 @@ npx llmnav eval
13
14
 
14
15
  `format --check` rejects non-canonical source-card serialization.
15
16
 
17
+ `audit --fail-on high` rejects newly exposed high-priority unannotated boundaries. It does not modify source and should not be enabled until the initial candidate list has been reviewed. Medium and low candidates remain advisory by default to avoid turning heuristic scores into blanket annotation policy.
18
+
16
19
  `check` rejects invalid meaning, unresolved or retired semantic relations, malformed registry state, missing configured coverage, path escape, and declaration attachment failures.
17
20
 
18
21
  Use `npx llmnav check --format sarif > llmnav.sarif` when a code-scanning system accepts SARIF 2.1.0 rather than GitHub workflow annotations. SARIF output is deterministic for the same ordered diagnostics and uses repository-relative paths.
@@ -81,6 +84,7 @@ jobs:
81
84
  cache: npm
82
85
  - run: npm ci
83
86
  - run: npx llmnav format --check
87
+ - run: npx llmnav audit --fail-on high
84
88
  - run: npx llmnav check --format github
85
89
  - run: npx llmnav generate --full --check
86
90
  - run: npx llmnav eval
package/docs/cli.md CHANGED
@@ -25,10 +25,22 @@ Creates project configuration, schema, registry, lexicon, evaluation file, agent
25
25
 
26
26
  `--agents` accepts `agents`, `claude`, `copilot`, `cursor`, a comma-separated combination, `all`, or `none`. The default is `agents`.
27
27
 
28
- `--package-scripts` adds `llmnav:check`, `llmnav:format`, `llmnav:generate`, and `llmnav:eval` to an existing package.json.
28
+ `--package-scripts` adds `llmnav:audit`, `llmnav:check`, `llmnav:format`, `llmnav:generate`, and `llmnav:eval` to an existing package.json. The audit script gates only high-priority candidates.
29
29
 
30
30
  `--force` refreshes configuration templates, schema, managed agent blocks, and `.llmnav/.gitignore`. It never resets semantic ID history, catalog order, aliases, evaluation cases, or source cards.
31
31
 
32
+ ## `llmnav audit`
33
+
34
+ ```sh
35
+ llmnav audit [--fail-on none|high|medium|low] [--json]
36
+ ```
37
+
38
+ Reads the selected source set and ranks files that lack file or module cards. Signals include package entrypoints, public re-exports, generated command/route/schema/migration/event boundaries, import fan-in, exported declarations, and source size. Declaration files are omitted; test support code, broad utilities, and pure re-export barrels receive penalties. Candidates whose penalties reduce their score to zero are omitted.
39
+
40
+ The command never modifies source, configuration, registries, or generated caches. Its output is advisory and exits with status 0 by default. `--fail-on high` fails only for high candidates; `medium` fails for high or medium; `low` fails for any candidate. Invalid thresholds exit with status 2.
41
+
42
+ JSON output uses schemaVersion 1, repository-relative paths, deterministic ordering, explainable `reasons` and `signals`, and a path-specific `suggestedCoverageRule` for high and medium candidates. Suggestions require human or agent review: LLMNav cannot infer a durable role, ownership boundary, invariant, or semantic ID from structure alone.
43
+
32
44
  ## `llmnav check`
33
45
 
34
46
  ```sh
@@ -88,6 +88,8 @@ Coverage is opt-in and path-specific.
88
88
 
89
89
  A coverage rule checks files already selected by `sourceRoots` and extension filters. Its fields are closed and `requiredFields` must name valid LLMNav keys. It should target a real architectural boundary, never an entire source tree.
90
90
 
91
+ Run `llmnav audit` to obtain path-specific candidate rules. A suggestion is not applied automatically and should be accepted only after the file's durable responsibility is confirmed. This separation prevents structural heuristics from creating vague or stale semantic cards.
92
+
91
93
  ## Lint profile
92
94
 
93
95
  Byte and field-count limits prevent semantic cards from becoming mini-documents. `maxSemanticRatio` produces a warning after the scanned source exceeds `minimumSourceBytesForRatio`.
package/docs/faq.md CHANGED
@@ -8,9 +8,13 @@ No. That makes retrieval noisier and creates a second copy of the program in pro
8
8
 
9
9
  JSDoc and docstrings target generated human documentation and language tooling. They do not define durable semantic IDs, controlled effects and risks, append-only catalog order, relation validation, deterministic search artifacts, or search regression gates. They can coexist with LLMNav cards.
10
10
 
11
- ## Does v0.5 change the `llmnav/1` syntax?
11
+ ## Does v0.6 change the `llmnav/1` syntax?
12
12
 
13
- No. The source grammar remains `llmnav/1`. v0.5 adds independently versioned agent-operation, prompt-bundle, and editor-diagnostic contracts beside the existing schemaVersion 1 `index.json`.
13
+ No. The source grammar remains `llmnav/1`. v0.6 adds an independently versioned annotation-audit result beside the existing schemaVersion 1 `index.json` and v0.5 integration contracts.
14
+
15
+ ## Does `llmnav audit` write comments automatically?
16
+
17
+ No. It produces an explainable review queue from structural evidence and never edits source. A candidate can still be intentionally cardless; only a reviewer can define its durable ID, ownership, exclusions, and invariants.
14
18
 
15
19
  ## Does every query still tokenize every card?
16
20
 
@@ -40,7 +44,7 @@ The files are deterministic agent context. Committing them makes drift reviewabl
40
44
 
41
45
  ## Does LLMNav execute project code?
42
46
 
43
- No. Version 0.5 reads text and configured repository-local JSON files, then writes generated artifacts or explicitly requested card formatting. Its tool dispatcher remains local and has no plugin loader, install hook, or network request.
47
+ No. Version 0.6 reads text and configured repository-local JSON files, then writes generated artifacts or explicitly requested card formatting. The audit path is read-only. Its tool dispatcher remains local and has no plugin loader, install hook, or network request.
44
48
 
45
49
  ## Can cards be written in Korean?
46
50
 
package/docs/migration.md CHANGED
@@ -7,7 +7,7 @@ No source-card migration is required. Keep every `llmnav/1` comment and the exis
7
7
  Upgrade and regenerate:
8
8
 
9
9
  ```sh
10
- npm install --save-dev llmnav@^0.5.2
10
+ npm install --save-dev llmnav@^0.6.0
11
11
  npx llmnav init --agents all
12
12
  npx llmnav generate
13
13
  npx llmnav doctor
@@ -61,8 +61,8 @@ npm publish --provenance=false --access public
61
61
  Automated publication:
62
62
 
63
63
  ```sh
64
- git tag v0.5.2
65
- git push origin v0.5.2
64
+ git tag v0.6.0
65
+ git push origin v0.6.0
66
66
  ```
67
67
 
68
68
  The release workflow rejects a tag that does not match `package.json`. If the exact version is already present in npm, the workflow succeeds only when the registry tarball integrity matches the tagged package; a mismatched package fails closed.
@@ -13,6 +13,16 @@ Review `.llmnav/config.json` before annotating code. The default scans the repos
13
13
 
14
14
  Source roots and evaluation files must remain inside the repository. The generated cache directory must remain below `.llmnav/`; parent traversal and symbolic-link control directories are rejected.
15
15
 
16
+ ## Audit before annotating
17
+
18
+ ```sh
19
+ npx llmnav audit
20
+ ```
21
+
22
+ Start with high and medium candidates. The audit explains whether a file is a package entrypoint, public API, generated structural boundary, or high fan-in module. It does not write source or invent card contents. Declaration files and common non-production or utility shapes are suppressed so the result is a review queue, not a demand to annotate every file.
23
+
24
+ After accepting a candidate, write its durable meaning by inspecting the source, add a narrow `coverageRules` entry for that exact boundary, and add a real task-language query to `.llmnav/eval/queries.jsonl`. Use `npx llmnav audit --fail-on high` in CI only after the initial review.
25
+
16
26
  ## Annotate a module boundary
17
27
 
18
28
  Choose a boundary that an agent is likely to search for by behavior rather than by current symbol name.
@@ -1,3 +1,14 @@
1
+ /* llmnav/1 module
2
+ id=llmnav.agent.provider-neutral-host
3
+ role=Demonstrate a provider-neutral host that binds one trusted repository root to reusable LLMNav tools and prompt partitions.
4
+ owns=host-side root binding|project session lifecycle|prompt partition selection
5
+ excludes=model provider client|authentication|model-selected repository roots
6
+ search=provider neutral host|tool call adapter|project session example
7
+ invariant=The host supplies the repository root; model-generated tool input cannot replace it.
8
+ rel=workflow>llmnav.agent.protocol
9
+ stability=contract
10
+ */
11
+
1
12
  import {
2
13
  executeAgentOperation,
3
14
  createProjectSession,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llmnav",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "A deterministic semantic navigation layer for LLM coding agents.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/agents.js CHANGED
@@ -30,6 +30,8 @@ Keep an existing LLMNav ID when a symbol or file is renamed or moved. Change \`r
30
30
 
31
31
  Do not add hand-maintained \`calls\`, \`imports\`, \`references\`, \`implements\`, \`exports\`, or \`overrides\` relations. Do not put paths, line numbers, commit hashes, timestamps, callers, or current signatures in LLMNav source comments.
32
32
 
33
+ After initialization and whenever public entrypoints, commands, routes, schemas, migrations, or high fan-in modules change, run \`npm exec -- llmnav audit\`. Review high and medium candidates; never add cards automatically. Add a module card only after confirming a durable responsibility, then encode the accepted boundary in a path-specific \`coverageRules\` entry and add a representative retrieval query.
34
+
33
35
  After semantic changes, run \`npm exec -- llmnav format\`, \`npm exec -- llmnav check\`, and \`npm exec -- llmnav generate\`. Use broad text search only when LLMNav returns no credible candidate.
34
36
  ${END}`;
35
37
 
package/src/audit.js ADDED
@@ -0,0 +1,282 @@
1
+ /* llmnav/1 module
2
+ id=llmnav.audit.coverage
3
+ role=Identify high-value source modules that lack semantic navigation boundaries without modifying source.
4
+ owns=annotation coverage audit|candidate prioritization|coverage rule suggestions
5
+ excludes=automatic source annotation|semantic role generation
6
+ search=llmnav audit|missing module cards|coverage suggestions
7
+ invariant=Audit output is deterministic, repository-relative, and advisory unless an explicit fail threshold is selected.
8
+ rel=workflow>llmnav.project.scan
9
+ stability=contract
10
+ */
11
+
12
+ import path from "node:path";
13
+ import { detectBoundaries } from "./boundaries.js";
14
+ import { scanProject } from "./project.js";
15
+ import { compareText, readJsonSafe, toPosix } from "./util.js";
16
+
17
+ export const AUDIT_SCHEMA_VERSION = 1;
18
+ export const AUDIT_PRIORITIES = Object.freeze(["high", "medium", "low"]);
19
+
20
+ const SOURCE_EXTENSIONS = Object.freeze([
21
+ ".astro", ".c", ".cc", ".cjs", ".cpp", ".cs", ".cts", ".dart", ".go", ".h", ".hpp", ".java",
22
+ ".js", ".jsx", ".kt", ".kts", ".mjs", ".mts", ".php", ".py", ".rb", ".rs", ".svelte", ".swift",
23
+ ".ts", ".tsx", ".vue",
24
+ ]);
25
+ const UTILITY_NAME_PATTERN = /^(?:common|helpers?|shared|utils?)$/u;
26
+ const NON_PRODUCTION_PATH_PATTERN = /(?:^|\/)(?:__tests__|benchmarks?|fixtures?|tests?)(?:\/|$)|\.(?:spec|test)\.[^/]+$/u;
27
+ const LARGE_SOURCE_BYTES = 12_000;
28
+
29
+ export async function auditProject(root) {
30
+ const project = await scanProject(root);
31
+ const fileByPath = new Map(
32
+ project.fileRecords.map((record) => [toPosix(record.relativePath), record]),
33
+ );
34
+ const moduleCardPaths = new Set(
35
+ project.records
36
+ .filter((record) => record.card.scope === "file" || record.card.scope === "module")
37
+ .map((record) => toPosix(record.relativePath)),
38
+ );
39
+ const importsByPath = new Map();
40
+ const importedBy = new Map([...fileByPath.keys()].map((file) => [file, new Set()]));
41
+ for (const [file, record] of fileByPath) {
42
+ const resolved = record.imports
43
+ .map((specifier) => resolveLocalSpecifier(file, specifier, fileByPath))
44
+ .filter(Boolean);
45
+ const unique = [...new Set(resolved)].sort(compareText);
46
+ importsByPath.set(file, unique);
47
+ for (const target of unique) importedBy.get(target)?.add(file);
48
+ }
49
+
50
+ const packageJson = await readJsonSafe(path.join(root, "package.json"), {});
51
+ const entrypoints = collectPackageEntrypoints(packageJson, fileByPath);
52
+ const publicApiPaths = collectPublicApiPaths(entrypoints, fileByPath);
53
+ const candidates = [];
54
+
55
+ for (const [file, record] of [...fileByPath.entries()].sort(([left], [right]) => compareText(left, right))) {
56
+ if (moduleCardPaths.has(file)) continue;
57
+ if (/\.d\.[cm]?ts$/u.test(file)) continue;
58
+ const boundaries = detectBoundaries({
59
+ relativePath: file,
60
+ card: { effect: [], risk: [] },
61
+ }).map((boundary) => boundary.kind);
62
+ const exportedDeclarations = countExportedDeclarations(record.source ?? "", file);
63
+ const entrypoint = entrypoints.has(file);
64
+ const publicApi = publicApiPaths.has(file) && !entrypoint;
65
+ const importers = importedBy.get(file)?.size ?? 0;
66
+ const reexportBarrel = isReexportBarrel(record.source ?? "", file);
67
+ const basename = path.posix.basename(file, path.posix.extname(file)).toLowerCase();
68
+ const broadUtility = UTILITY_NAME_PATTERN.test(basename) ||
69
+ (exportedDeclarations >= 10 && !entrypoint && !publicApi && boundaries.length === 0);
70
+ const nonProduction = NON_PRODUCTION_PATH_PATTERN.test(file);
71
+ const largeSource = record.sourceBytes >= LARGE_SOURCE_BYTES;
72
+ const hasSignal = entrypoint || publicApi || boundaries.length > 0 || importers > 0 || exportedDeclarations > 0 || largeSource;
73
+ if (!hasSignal) continue;
74
+
75
+ let score = 0;
76
+ const reasons = ["missing-module-card"];
77
+ if (entrypoint) {
78
+ score += 50;
79
+ reasons.push("package-entrypoint");
80
+ }
81
+ if (publicApi) {
82
+ score += 45;
83
+ reasons.push("public-api-reexport");
84
+ }
85
+ for (const boundary of boundaries) {
86
+ score += boundary === "command" ? 40 : 30;
87
+ reasons.push(`${boundary}-boundary`);
88
+ }
89
+ if (importers > 0) {
90
+ score += Math.min(30, importers * 5);
91
+ reasons.push(`imported-by:${importers}`);
92
+ }
93
+ if (exportedDeclarations > 0) {
94
+ score += 10;
95
+ reasons.push(`exported-declarations:${exportedDeclarations}`);
96
+ }
97
+ if (largeSource) {
98
+ score += 10;
99
+ reasons.push("large-source");
100
+ }
101
+ if (broadUtility) {
102
+ score -= 30;
103
+ reasons.push("broad-utility-penalty");
104
+ }
105
+ if (nonProduction) {
106
+ score -= 40;
107
+ reasons.push("non-production-penalty");
108
+ }
109
+ if (reexportBarrel) {
110
+ score -= 60;
111
+ reasons.push("reexport-barrel-penalty");
112
+ }
113
+ score = Math.max(0, score);
114
+ if (score === 0) continue;
115
+ const priority = score >= 60 ? "high" : score >= 30 ? "medium" : "low";
116
+ candidates.push({
117
+ path: file,
118
+ priority,
119
+ score,
120
+ reasons,
121
+ signals: {
122
+ packageEntrypoint: entrypoint,
123
+ publicApi,
124
+ exportedDeclarations,
125
+ importedBy: importers,
126
+ boundaries,
127
+ largeSource,
128
+ broadUtility,
129
+ nonProduction,
130
+ reexportBarrel,
131
+ },
132
+ suggestedCoverageRule: priority === "low" ? null : buildCoverageSuggestion(file),
133
+ });
134
+ }
135
+
136
+ candidates.sort((left, right) =>
137
+ priorityRank(left.priority) - priorityRank(right.priority) ||
138
+ right.score - left.score ||
139
+ compareText(left.path, right.path));
140
+ const summary = {
141
+ analyzedFiles: fileByPath.size,
142
+ cardedFiles: moduleCardPaths.size,
143
+ filesWithoutModuleCards: fileByPath.size - moduleCardPaths.size,
144
+ candidates: candidates.length,
145
+ high: candidates.filter((candidate) => candidate.priority === "high").length,
146
+ medium: candidates.filter((candidate) => candidate.priority === "medium").length,
147
+ low: candidates.filter((candidate) => candidate.priority === "low").length,
148
+ coverageSuggestions: candidates.filter((candidate) => candidate.suggestedCoverageRule !== null).length,
149
+ };
150
+ return {
151
+ schemaVersion: AUDIT_SCHEMA_VERSION,
152
+ repositoryId: project.config.repositoryId,
153
+ summary,
154
+ candidates,
155
+ };
156
+ }
157
+
158
+ export function auditHasFindings(result, minimumPriority = "none") {
159
+ if (minimumPriority === "none") return false;
160
+ if (!AUDIT_PRIORITIES.includes(minimumPriority)) {
161
+ throw new TypeError(`Unknown audit priority: ${minimumPriority}.`);
162
+ }
163
+ const threshold = priorityRank(minimumPriority);
164
+ return result.candidates.some((candidate) => priorityRank(candidate.priority) <= threshold);
165
+ }
166
+
167
+ function buildCoverageSuggestion(file) {
168
+ const stem = path.posix.basename(file, path.posix.extname(file)).replace(/[^a-z0-9]+/giu, "-").toLowerCase();
169
+ return {
170
+ name: `${stem} module boundary`,
171
+ match: [file],
172
+ scope: "module",
173
+ requiredFields: ["owns", "search"],
174
+ };
175
+ }
176
+
177
+ function collectPackageEntrypoints(packageJson, fileByPath) {
178
+ const raw = [packageJson?.main, packageJson?.module, ...collectStringLeaves(packageJson?.bin), ...collectStringLeaves(packageJson?.exports)];
179
+ const entrypoints = new Set();
180
+ for (const value of raw) {
181
+ if (typeof value !== "string" || /\.d\.[cm]?ts$/u.test(value)) continue;
182
+ const normalized = normalizePackagePath(value);
183
+ if (!normalized) continue;
184
+ const resolved = resolveProjectPath(normalized, fileByPath);
185
+ if (resolved) entrypoints.add(resolved);
186
+ }
187
+ return entrypoints;
188
+ }
189
+
190
+ function collectStringLeaves(value, key = "") {
191
+ if (key === "types") return [];
192
+ if (typeof value === "string") return [value];
193
+ if (Array.isArray(value)) return value.flatMap((item) => collectStringLeaves(item));
194
+ if (!value || typeof value !== "object") return [];
195
+ return Object.entries(value).flatMap(([childKey, child]) => collectStringLeaves(child, childKey));
196
+ }
197
+
198
+ function normalizePackagePath(value) {
199
+ const normalized = toPosix(value).replace(/^\.\//u, "");
200
+ if (!normalized || normalized.startsWith("/") || normalized.split("/").includes("..")) return null;
201
+ return path.posix.normalize(normalized);
202
+ }
203
+
204
+ function collectPublicApiPaths(entrypoints, fileByPath) {
205
+ const publicPaths = new Set(entrypoints);
206
+ const queue = [...entrypoints].sort(compareText);
207
+ for (let index = 0; index < queue.length; index += 1) {
208
+ const file = queue[index];
209
+ const source = fileByPath.get(file)?.source ?? "";
210
+ for (const specifier of extractReexportSpecifiers(source)) {
211
+ const target = resolveLocalSpecifier(file, specifier, fileByPath);
212
+ if (!target || publicPaths.has(target)) continue;
213
+ publicPaths.add(target);
214
+ queue.push(target);
215
+ }
216
+ }
217
+ return publicPaths;
218
+ }
219
+
220
+ function extractReexportSpecifiers(source) {
221
+ const values = [];
222
+ const pattern = /^\s*export\s+(?:\*(?:\s+as\s+[A-Za-z_$][\w$]*)?|\{[^}]*\})\s+from\s+["']([^"']+)["']\s*;?/gmu;
223
+ for (const match of source.matchAll(pattern)) values.push(match[1]);
224
+ return [...new Set(values)].sort(compareText);
225
+ }
226
+
227
+ function isReexportBarrel(source, file) {
228
+ if (!/\.[cm]?[jt]sx?$/u.test(file)) return false;
229
+ const reexports = extractReexportSpecifiers(source);
230
+ if (reexports.length === 0) return false;
231
+ const withoutReexports = source
232
+ .replace(/^\s*export\s+(?:\*(?:\s+as\s+[A-Za-z_$][\w$]*)?|\{[^}]*\})\s+from\s+["'][^"']+["']\s*;?/gmu, "")
233
+ .replace(/^\s*\/\/.*$/gmu, "")
234
+ .replace(/\/\*[\s\S]*?\*\//gu, "")
235
+ .trim();
236
+ return withoutReexports === "";
237
+ }
238
+
239
+ function resolveLocalSpecifier(fromFile, specifier, fileByPath) {
240
+ if (!specifier.startsWith(".")) return null;
241
+ const base = path.posix.normalize(path.posix.join(path.posix.dirname(fromFile), toPosix(specifier)));
242
+ if (base.startsWith("../") || base === "..") return null;
243
+ return resolveProjectPath(base, fileByPath);
244
+ }
245
+
246
+ function resolveProjectPath(candidate, fileByPath) {
247
+ const values = [candidate];
248
+ const extension = path.posix.extname(candidate);
249
+ if (extension) {
250
+ const stem = candidate.slice(0, -extension.length);
251
+ for (const nextExtension of SOURCE_EXTENSIONS) values.push(`${stem}${nextExtension}`);
252
+ } else {
253
+ for (const nextExtension of SOURCE_EXTENSIONS) {
254
+ values.push(`${candidate}${nextExtension}`);
255
+ values.push(`${candidate}/index${nextExtension}`);
256
+ }
257
+ }
258
+ return values.find((value) => fileByPath.has(value)) ?? null;
259
+ }
260
+
261
+ function countExportedDeclarations(source, file) {
262
+ const extension = path.posix.extname(file).toLowerCase();
263
+ if ([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts", ".svelte", ".astro", ".vue"].includes(extension)) {
264
+ const declarations = source.match(/^\s*export\s+(?:default\s+)?(?:declare\s+)?(?:async\s+)?(?:function|class|const|let|var|type|interface|enum)\b/gmu) ?? [];
265
+ const localLists = source.match(/^\s*export\s*\{[^}]*\}\s*;?$/gmu) ?? [];
266
+ const reexports = extractReexportSpecifiers(source).length;
267
+ return declarations.length + localLists.length + reexports;
268
+ }
269
+ if (extension === ".go") {
270
+ return [...source.matchAll(/^\s*(?:func\s+(?:\([^)]*\)\s*)?|type\s+|var\s+|const\s+)([A-Z][A-Za-z0-9_]*)\b/gmu)].length;
271
+ }
272
+ if (extension === ".rs") return [...source.matchAll(/^\s*pub(?:\([^)]*\))?\s+(?:async\s+)?(?:fn|struct|enum|trait|type|const|static|mod)\b/gmu)].length;
273
+ if (extension === ".py") return [...source.matchAll(/^\s*(?:async\s+def|def|class)\s+([A-Za-z][A-Za-z0-9_]*)\b/gmu)].filter((match) => !match[1].startsWith("_")).length;
274
+ return 0;
275
+ }
276
+
277
+ function priorityRank(priority) {
278
+ if (priority === "high") return 0;
279
+ if (priority === "medium") return 1;
280
+ if (priority === "low") return 2;
281
+ return 3;
282
+ }
package/src/cli.js CHANGED
@@ -8,6 +8,7 @@ rel=workflow>llmnav.project.initialize
8
8
  rel=workflow>llmnav.rules.validate
9
9
  rel=workflow>llmnav.search.query
10
10
  rel=workflow>llmnav.eval.measure
11
+ rel=workflow>llmnav.audit.coverage
11
12
  stability=architecture
12
13
  */
13
14
 
@@ -37,8 +38,9 @@ import { renderGraphNode } from "./graph.js";
37
38
  import { getAgentToolDefinitions } from "./agent-protocol.js";
38
39
  import { loadPromptPrefixBundle } from "./prompt-bundle.js";
39
40
  import { diagnosticsToEditor, getEditorIntegration } from "./editor.js";
41
+ import { AUDIT_PRIORITIES, auditHasFindings, auditProject } from "./audit.js";
40
42
 
41
- const VALUE_OPTIONS = new Set(["--root", "--format", "--top", "--depth", "--budget", "--max-edges", "--agents", "--file"]);
43
+ const VALUE_OPTIONS = new Set(["--root", "--format", "--top", "--depth", "--budget", "--max-edges", "--agents", "--file", "--fail-on"]);
42
44
 
43
45
  const COMMAND_OPTIONS = Object.freeze({
44
46
  init: new Set(["--agents", "--package-scripts", "--force", "--root", "--json"]),
@@ -51,6 +53,7 @@ const COMMAND_OPTIONS = Object.freeze({
51
53
  context: new Set(["--depth", "--budget", "--max-edges", "--root", "--json"]),
52
54
  eval: new Set(["--file", "--top", "--root", "--json"]),
53
55
  doctor: new Set(["--root", "--json"]),
56
+ audit: new Set(["--root", "--json", "--fail-on"]),
54
57
  spec: new Set(["--root", "--json"]),
55
58
  tools: new Set(["--json"]),
56
59
  bundle: new Set(["--root", "--json"]),
@@ -97,6 +100,8 @@ export async function runCli(argv) {
97
100
  return runEval(root, args, json);
98
101
  case "doctor":
99
102
  return runDoctor(root, json);
103
+ case "audit":
104
+ return runAudit(root, args, json);
100
105
  case "spec":
101
106
  return runSpec(json);
102
107
  case "bundle":
@@ -290,6 +295,29 @@ async function runDoctor(root, json) {
290
295
  return result.ok ? 0 : 1;
291
296
  }
292
297
 
298
+ async function runAudit(root, args, json) {
299
+ const failOn = getOption(args, "--fail-on") ?? "none";
300
+ if (failOn !== "none" && !AUDIT_PRIORITIES.includes(failOn)) {
301
+ throw usageError(`audit --fail-on must be one of none, ${AUDIT_PRIORITIES.join(", ")}.`);
302
+ }
303
+ const result = await auditProject(root);
304
+ const report = { ...result, failOn };
305
+ if (json) {
306
+ console.log(JSON.stringify(report, null, 2));
307
+ } else {
308
+ const { summary } = result;
309
+ console.log(
310
+ `audit files=${summary.analyzedFiles} carded=${summary.cardedFiles} candidates=${summary.candidates} ` +
311
+ `high=${summary.high} medium=${summary.medium} low=${summary.low}`,
312
+ );
313
+ for (const candidate of result.candidates.filter((item) => item.priority !== "low")) {
314
+ console.log(`${candidate.priority} ${candidate.path} score=${candidate.score} ${candidate.reasons.join(",")}`);
315
+ }
316
+ if (summary.low > 0) console.log(`${summary.low} low-priority candidate(s) are available in --json output.`);
317
+ }
318
+ return auditHasFindings(result, failOn) ? 1 : 0;
319
+ }
320
+
293
321
  function runSpec(json) {
294
322
  const spec = {
295
323
  specVersion: SPEC_VERSION,
@@ -446,6 +474,7 @@ Usage
446
474
  llmnav context <semantic-id> [--depth 1] [--budget 2500] [--max-edges 24]
447
475
  llmnav eval [--file path] [--top 5]
448
476
  llmnav doctor
477
+ llmnav audit [--fail-on none|high|medium|low]
449
478
  llmnav spec
450
479
  llmnav tools [--json]
451
480
  llmnav bundle [--json]
package/src/config.js CHANGED
@@ -1,3 +1,14 @@
1
+ /* llmnav/1 module
2
+ id=llmnav.config.load
3
+ role=Load, merge, and validate repository configuration before project operations observe it.
4
+ owns=configuration loading|default merging|configuration schema validation
5
+ excludes=source-card validation|schema file generation
6
+ search=load llmnav config|configuration validation|coverage rules
7
+ invariant=Unknown configuration keys and escaping project paths are rejected before use.
8
+ rel=workflow>llmnav.rules.validate
9
+ stability=contract
10
+ */
11
+
1
12
  import path from "node:path";
2
13
  import {
3
14
  ALLOWED_KEYS,
@@ -1,3 +1,14 @@
1
+ /* llmnav/1 module
2
+ id=llmnav.declaration.extract
3
+ role=Attach symbol cards to language declarations and derive structural metadata without executing source.
4
+ owns=declaration attachment|signature extraction|import extraction
5
+ excludes=semantic card parsing|type-system resolution
6
+ search=declaration extraction|symbol attachment|body hash
7
+ invariant=String literals and comments do not create false declarations or imports.
8
+ rel=workflow>llmnav.project.scan
9
+ stability=architecture
10
+ */
11
+
1
12
  import path from "node:path";
2
13
  import { lineAtOffset, normalizeNewlines, sha256 } from "./util.js";
3
14
 
package/src/doctor.js CHANGED
@@ -1,3 +1,14 @@
1
+ /* llmnav/1 module
2
+ id=llmnav.diagnostics.doctor
3
+ role=Diagnose project configuration, generated cache integrity, and recoverable generation state.
4
+ owns=project health checks|cache integrity checks|release readiness diagnostics
5
+ excludes=source formatting|package publication
6
+ search=llmnav doctor|project health|cache integrity
7
+ invariant=Only interrupted generation recovery may mutate state; all other health checks are read-only.
8
+ rel=workflow>llmnav.index.generate
9
+ stability=contract
10
+ */
11
+
1
12
  import path from "node:path";
2
13
  import { loadConfig } from "./config.js";
3
14
  import { generateProject } from "./generator.js";
package/src/files.js CHANGED
@@ -1,3 +1,14 @@
1
+ /* llmnav/1 module
2
+ id=llmnav.source.discover
3
+ role=Locate ancestor LLMNav roots and collect project-contained source files without following symlinks.
4
+ owns=project root discovery|source traversal|include and exclude filtering
5
+ excludes=source parsing|generated artifact selection
6
+ search=source discovery|project root|file traversal
7
+ invariant=Selected source paths remain inside the repository and symbolic links are never traversed.
8
+ rel=workflow>llmnav.project.scan
9
+ stability=contract
10
+ */
11
+
1
12
  import { lstat, readdir, stat } from "node:fs/promises";
2
13
  import path from "node:path";
3
14
  import { compareText, matchesAnyGlob, relativePosix, toPosix } from "./util.js";
package/src/formatter.js CHANGED
@@ -1,3 +1,14 @@
1
+ /* llmnav/1 module
2
+ id=llmnav.source.format
3
+ role=Canonicalize LLMNav source blocks across selected files with atomic writes and check-only reporting.
4
+ owns=source card formatting|format check mode|atomic source rewrite
5
+ excludes=semantic validation|generated cache formatting
6
+ search=llmnav format|canonical comments|source rewrite
7
+ invariant=Check mode reports non-canonical files without writing source files.
8
+ rel=workflow>llmnav.syntax.parse
9
+ stability=contract
10
+ */
11
+
1
12
  import { readFile } from "node:fs/promises";
2
13
  import { loadConfig } from "./config.js";
3
14
  import { collectSourceFiles } from "./files.js";
package/src/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export type LlmnavScope = "file" | "module" | "symbol";
2
2
  export type LlmnavStability = "architecture" | "contract" | "implementation";
3
3
  export type DiagnosticSeverity = "error" | "warning" | "info";
4
+ export type AuditPriority = "high" | "medium" | "low";
4
5
 
5
6
  export interface LlmnavCard {
6
7
  scope: LlmnavScope;
@@ -71,6 +72,46 @@ export interface Diagnostic {
71
72
  column: number;
72
73
  }
73
74
 
75
+ export interface AuditCandidate {
76
+ path: string;
77
+ priority: AuditPriority;
78
+ score: number;
79
+ reasons: string[];
80
+ signals: {
81
+ packageEntrypoint: boolean;
82
+ publicApi: boolean;
83
+ exportedDeclarations: number;
84
+ importedBy: number;
85
+ boundaries: DetectedBoundary["kind"][];
86
+ largeSource: boolean;
87
+ broadUtility: boolean;
88
+ nonProduction: boolean;
89
+ reexportBarrel: boolean;
90
+ };
91
+ suggestedCoverageRule: null | {
92
+ name: string;
93
+ match: [string];
94
+ scope: "module";
95
+ requiredFields: ["owns", "search"];
96
+ };
97
+ }
98
+
99
+ export interface AuditResult {
100
+ schemaVersion: 1;
101
+ repositoryId: string;
102
+ summary: {
103
+ analyzedFiles: number;
104
+ cardedFiles: number;
105
+ filesWithoutModuleCards: number;
106
+ candidates: number;
107
+ high: number;
108
+ medium: number;
109
+ low: number;
110
+ coverageSuggestions: number;
111
+ };
112
+ candidates: AuditCandidate[];
113
+ }
114
+
74
115
  export interface IndexedLocation {
75
116
  path: string;
76
117
  startLine: number;
@@ -541,6 +582,8 @@ export interface EvaluationResult {
541
582
  }
542
583
 
543
584
  export const AGENT_PROTOCOL: string;
585
+ export const AUDIT_PRIORITIES: readonly AuditPriority[];
586
+ export const AUDIT_SCHEMA_VERSION: 1;
544
587
  export const AGENT_OPERATION_SCHEMA_VERSION: 1;
545
588
  export const AGENT_TOOL_SCHEMA_VERSION: 1;
546
589
  export const BOUNDARY_KINDS: readonly DetectedBoundary["kind"][];
@@ -615,6 +658,8 @@ export function describeAffectedBoundaries(changedCards: ChangedCardRecord[], co
615
658
  export function describeAffectedCatalogs(changedFiles: string[], cacheDirectory: string, config: LlmnavConfig, previousIndex: LlmnavIndex | null, currentIndex: LlmnavIndex): AffectedCatalogRecord[];
616
659
  export function loadConfig(root: string): Promise<{ config: LlmnavConfig; configPath: string }>;
617
660
  export function validateConfig(config: LlmnavConfig, configPath?: string): void;
661
+ export function auditProject(root: string): Promise<AuditResult>;
662
+ export function auditHasFindings(result: AuditResult, minimumPriority?: AuditPriority | "none"): boolean;
618
663
  export function findAttachedDeclaration(source: string, block: LlmnavBlock, filePath: string): Declaration | null;
619
664
  export function extractImports(source: string, filePath: string): string[];
620
665
  export function doctorProject(root: string): Promise<{ ok: boolean; checks: Array<{ name: string; ok: boolean; message: string }> }>;
package/src/index.js CHANGED
@@ -14,6 +14,7 @@ export {
14
14
  } from "./prompt-bundle.js";
15
15
  export { compareCardIndexes, describeAffectedBoundaries, describeAffectedCatalogs } from "./changes.js";
16
16
  export { loadConfig, validateConfig } from "./config.js";
17
+ export { AUDIT_PRIORITIES, AUDIT_SCHEMA_VERSION, auditHasFindings, auditProject } from "./audit.js";
17
18
  export { BOUNDARY_KINDS, detectBoundaries } from "./boundaries.js";
18
19
  export {
19
20
  diagnosticsToEditor,
@@ -121,6 +121,7 @@ async function addPackageScripts(root) {
121
121
  const parsed = JSON.parse(text);
122
122
  parsed.scripts ??= {};
123
123
  const desired = {
124
+ "llmnav:audit": "llmnav audit --fail-on high",
124
125
  "llmnav:check": "llmnav check",
125
126
  "llmnav:format": "llmnav format",
126
127
  "llmnav:generate": "llmnav generate",
package/src/project.js CHANGED
@@ -1,3 +1,15 @@
1
+ /* llmnav/1 module
2
+ id=llmnav.project.scan
3
+ role=Scan project sources into semantic card and file records with declarations, imports, and content hashes.
4
+ owns=project scan|card record assembly|source metrics
5
+ excludes=semantic validation|cache generation
6
+ search=project scan|card records|source metadata
7
+ invariant=Generated paths and structural metadata remain derived data and are never written into source cards.
8
+ rel=workflow>llmnav.declaration.extract
9
+ rel=workflow>llmnav.syntax.parse
10
+ stability=architecture
11
+ */
12
+
1
13
  import { readFile } from "node:fs/promises";
2
14
  import { loadConfig } from "./config.js";
3
15
  import { findAttachedDeclaration, extractImports } from "./declaration.js";
package/src/registry.js CHANGED
@@ -1,3 +1,14 @@
1
+ /* llmnav/1 module
2
+ id=llmnav.registry.ids
3
+ role=Validate and evolve semantic ID lifecycle records across active, redirect, replaced, and retired states.
4
+ owns=semantic ID registry|redirect resolution|replacement lifecycle
5
+ excludes=source card parsing|search ranking
6
+ search=semantic id registry|redirected id|retired id
7
+ invariant=Registry resolution never silently chooses among multiple replacement targets.
8
+ rel=workflow>llmnav.index.generate
9
+ stability=contract
10
+ */
11
+
1
12
  import path from "node:path";
2
13
  import { ID_PATTERN } from "./spec.js";
3
14
  import { atomicWrite, parseJsonLines, readText } from "./util.js";
package/src/spec.js CHANGED
@@ -1,4 +1,16 @@
1
- export const PACKAGE_VERSION = "0.5.2";
1
+ /* llmnav/1 module
2
+ id=llmnav.spec.contract
3
+ role=Define the public LLMNav/1 vocabulary, limits, relation kinds, and default configuration.
4
+ owns=protocol vocabulary|controlled field values|default configuration
5
+ excludes=configuration overrides|source parsing
6
+ search=llmnav specification|protocol constants|default config
7
+ invariant=Public field, effect, risk, and relation vocabularies remain deterministic and versioned.
8
+ rel=workflow>llmnav.config.load
9
+ rel=workflow>llmnav.rules.validate
10
+ stability=contract
11
+ */
12
+
13
+ export const PACKAGE_VERSION = "0.6.0";
2
14
  export const SPEC_VERSION = "1";
3
15
 
4
16
  export const SCOPES = Object.freeze(["file", "module", "symbol"]);