llmnav 0.6.4 → 0.7.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 +17 -0
- package/README.md +2 -1
- package/ROADMAP.md +17 -0
- package/docs/api.md +3 -0
- package/docs/benchmarking.md +20 -0
- package/docs/cli.md +13 -0
- package/docs/migration.md +7 -6
- package/package.json +1 -1
- package/src/cli.js +25 -0
- package/src/incremental.js +1 -1
- package/src/index.d.ts +23 -0
- package/src/index.js +1 -0
- package/src/migration.js +194 -0
- package/src/spec.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,23 @@ The npm package follows Semantic Versioning. The `llmnav/N` source protocol is v
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.7.0] — 2026-08-13
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
* Added `llmnav migrate --check/--write` and `migrateProject` to report generated-format compatibility and apply full transactional upgrades from canonical source.
|
|
14
|
+
* Added migration regression coverage for read-only planning, idempotent writes, validation blocking, and transaction rollback.
|
|
15
|
+
|
|
16
|
+
## [0.6.6] — 2026-08-12
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
* Added a read-only cross-repository conformance matrix with repository-isolated validation, retrieval, audit, repeatability, cache-freshness, and held-language evidence.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
* Invalidated parsed file-state caches created by older source indexers so upgraded installations rebuild semantic and declaration hashes from source instead of silently reusing incompatible state.
|
|
25
|
+
|
|
9
26
|
## [0.6.4] — 2026-08-11
|
|
10
27
|
|
|
11
28
|
### Fixed
|
package/README.md
CHANGED
|
@@ -179,6 +179,7 @@ Line-comment cards require an explicit terminator and work with `//`, `#`, and `
|
|
|
179
179
|
| `llmnav context` | Build a bounded context bundle around one ID |
|
|
180
180
|
| `llmnav eval` | Run repository-specific search regression queries |
|
|
181
181
|
| `llmnav doctor` | Verify installation, cache integrity, transaction recovery, and drift |
|
|
182
|
+
| `llmnav migrate` | Check or transactionally apply generated-format upgrades from canonical source |
|
|
182
183
|
| `llmnav spec` | Print source-spec vocabularies and key order |
|
|
183
184
|
| `llmnav tools` | Print stable provider-neutral agent tool schemas |
|
|
184
185
|
| `llmnav bundle` | Inspect the generated prompt-prefix cache partitions |
|
|
@@ -291,7 +292,7 @@ The project uses the Node.js standard library and built-in test runner. There is
|
|
|
291
292
|
|
|
292
293
|
## Status
|
|
293
294
|
|
|
294
|
-
LLMNav is an experimental protocol and a usable v0.
|
|
295
|
+
LLMNav is an experimental protocol and a usable v0.7 CLI. The source format remains `llmnav/1`; npm package changes and source-grammar changes are versioned independently.
|
|
295
296
|
|
|
296
297
|
## License
|
|
297
298
|
|
package/ROADMAP.md
CHANGED
|
@@ -73,10 +73,27 @@ Implemented:
|
|
|
73
73
|
* path-specific coverage-rule suggestions without automatic source annotation
|
|
74
74
|
* audit guidance in generated agent instructions and package scripts
|
|
75
75
|
|
|
76
|
+
## 0.7 — generated-format migration
|
|
77
|
+
|
|
78
|
+
Implemented:
|
|
79
|
+
|
|
80
|
+
* read-only compatibility reports for the primary index and generated accelerators
|
|
81
|
+
* full migration plans reconstructed from canonical repository source
|
|
82
|
+
* explicit transactional writes with staging verification, rollback, and interrupted-process recovery
|
|
83
|
+
* validation blocking before mutation and idempotent repeated writes
|
|
84
|
+
* public CLI, ESM API, TypeScript declarations, and migration documentation
|
|
85
|
+
|
|
76
86
|
## 1.0 criteria
|
|
77
87
|
|
|
78
88
|
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.
|
|
79
89
|
|
|
90
|
+
In progress:
|
|
91
|
+
|
|
92
|
+
* Generated-format migration tooling is implemented for the current public formats; compatibility guarantees still need a documented support and deprecation policy.
|
|
93
|
+
* A read-only cross-repository conformance matrix measures validation, retrieval, audit, repeatability, and cache freshness without averaging weak repositories away.
|
|
94
|
+
* LLMNav, Workduck, Sairon, and AI BOM Generator currently pass repository-isolated conformance checks across JavaScript, TypeScript, Rust, Go, and Python.
|
|
95
|
+
* The current evidence covers 4 repositories and 5 required languages with no held or failed repository, while 1.0 still requires sustained Windows and Linux verification and published benchmark methodology.
|
|
96
|
+
|
|
80
97
|
## Non-goals
|
|
81
98
|
|
|
82
99
|
LLMNav will not become a general documentation generator, a source-of-truth call graph maintained by comments, an autonomous code modification service, a hosted source-ingestion platform, or a mandatory embedding database.
|
package/docs/api.md
CHANGED
|
@@ -229,6 +229,7 @@ The package exports versioned generated-format constants:
|
|
|
229
229
|
import {
|
|
230
230
|
CONTRACT_FINGERPRINT_SCHEMA_VERSION,
|
|
231
231
|
FILE_STATE_SCHEMA_VERSION,
|
|
232
|
+
MIGRATION_REPORT_SCHEMA_VERSION,
|
|
232
233
|
SEARCH_INDEX_ENCODING,
|
|
233
234
|
SEARCH_INDEX_SCHEMA_VERSION,
|
|
234
235
|
SEARCH_SHARD_ENCODING,
|
|
@@ -240,6 +241,8 @@ import {
|
|
|
240
241
|
} from "llmnav";
|
|
241
242
|
```
|
|
242
243
|
|
|
244
|
+
`migrateProject(root, { write: false })` returns a schemaVersion 1 compatibility report and a full-regeneration plan without committing generated artifacts. With `{ write: true }`, it applies a required upgrade through the same generation lock and recoverable transaction used by `generateProject`. Source validation errors stop the operation before mutation, and successful repeated writes are no-ops.
|
|
245
|
+
|
|
243
246
|
`diagnosticsToSarif(diagnostics)` maps existing diagnostics to a deterministic SARIF 2.1.0 object without discovering or mutating diagnostics.
|
|
244
247
|
|
|
245
248
|
`diagnosticsToEditor(diagnostics)` groups repository-relative diagnostics into schemaVersion 1 documents with zero-based ranges and stable severity mappings. `renderEditorDiagnostics` serializes the report deterministically. `getEditorIntegration("vscode")` returns a VS Code task and custom problem matcher without modifying editor files.
|
package/docs/benchmarking.md
CHANGED
|
@@ -94,6 +94,26 @@ npx llmnav eval --json
|
|
|
94
94
|
|
|
95
95
|
Track at least Recall@1, Recall@5, and mean reciprocal rank. Record every acceptable target ID when a task has multiple valid entry points.
|
|
96
96
|
|
|
97
|
+
## Cross-repository conformance matrix
|
|
98
|
+
|
|
99
|
+
Run the checked-in read-only matrix from a workspace that contains the configured sibling repositories:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
node benchmarks/run-conformance.js
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Use `--matrix <path>` to select another checked-in matrix and `--output <path>` to retain a JSON report. The report schema is `benchmarks/conformance-report.schema.json`.
|
|
106
|
+
|
|
107
|
+
Each repository keeps its own validation counts, retrieval thresholds, Recall@1, Recall@5, MRR, audit summary, repeatability result, and generated-cache freshness result. The harness deliberately does not average retrieval scores across repositories. A large or easy dataset must not hide a failing repository.
|
|
108
|
+
|
|
109
|
+
Verdicts have asymmetric meaning:
|
|
110
|
+
|
|
111
|
+
* `pass` requires valid cards, sufficient reviewed queries, repository thresholds, repeatable retrieval, and a source-current generated cache.
|
|
112
|
+
* `fail` requires a reproducible validation, retrieval, repeatability, cache-freshness, or measurement failure.
|
|
113
|
+
* `held` records missing adoption or insufficient reviewed cases without pretending that unmeasured language coverage passed or failed.
|
|
114
|
+
|
|
115
|
+
The default matrix currently measures LLMNav and Workduck. Go and Python remain held until their candidate repositories receive reviewed cards and task-language query datasets. Matrix output is navigation evidence, not an end-to-end claim about model tokens or coding-task success.
|
|
116
|
+
|
|
97
117
|
## End-to-end agent metrics
|
|
98
118
|
|
|
99
119
|
A successful deployment should reduce exploration cost without reducing task correctness.
|
package/docs/cli.md
CHANGED
|
@@ -212,6 +212,19 @@ Checks:
|
|
|
212
212
|
|
|
213
213
|
`doctor` may perform transaction recovery, but it does not regenerate stale cache content.
|
|
214
214
|
|
|
215
|
+
## `llmnav migrate`
|
|
216
|
+
|
|
217
|
+
```sh
|
|
218
|
+
llmnav migrate [--check] [--json]
|
|
219
|
+
llmnav migrate --write [--json]
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The default and `--check` modes inspect the primary index, search accelerator, file state, graph, graph state, prompt-prefix bundle, and manifest against the formats supported by the installed LLMNav version. They also perform a full source reconstruction in check mode. No generated artifacts are committed. Exit status 1 means migration is required or source validation blocks a safe migration.
|
|
223
|
+
|
|
224
|
+
`--write` first builds the same plan from canonical source. If source validation succeeds and migration is required, it performs a full regeneration through the normal generation lock, staging verification, directory transaction, rollback, and interrupted-process recovery boundaries. It never rewrites source cards or reassigns semantic IDs. Repeating `--write` after a successful migration makes no changes and exits successfully.
|
|
225
|
+
|
|
226
|
+
`--check` and `--write` are mutually exclusive. Review JSON `formats`, `changedFiles`, and `diagnostics` before applying an upgrade in automation.
|
|
227
|
+
|
|
215
228
|
## `llmnav spec`
|
|
216
229
|
|
|
217
230
|
```sh
|
package/docs/migration.md
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
# Gradual migration
|
|
2
2
|
|
|
3
|
-
## Upgrade to 0.
|
|
3
|
+
## Upgrade to 0.7
|
|
4
4
|
|
|
5
5
|
No source-card migration is required. Keep every `llmnav/1` comment and the existing `.llmnav/cache/index.json` consumer contract.
|
|
6
6
|
|
|
7
7
|
Upgrade and regenerate:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npm install --save-dev llmnav@^0.
|
|
10
|
+
npm install --save-dev llmnav@^0.7.0
|
|
11
11
|
npx llmnav init --agents all
|
|
12
|
-
npx llmnav
|
|
12
|
+
npx llmnav migrate --check
|
|
13
|
+
npx llmnav migrate --write
|
|
13
14
|
npx llmnav doctor
|
|
14
15
|
```
|
|
15
16
|
|
|
16
|
-
The
|
|
17
|
+
The migration planner preserves the `llmnav/1` source comments and schemaVersion 1 primary-index contract. It reports incompatible or missing generated formats before applying a full transactional regeneration from canonical source. Initialization refreshes the managed agent protocol so structured-tool hosts learn the trusted-root boundary. Volatile `state/`, `.transactions/`, and `generation-transaction.json` remain ignored.
|
|
17
18
|
|
|
18
|
-
Review `
|
|
19
|
+
Review `migrate --check --json` during the first upgrade. Exit status 1 means generated formats need migration or source validation prevents a safe write. `migrate --write` stages and verifies a complete replacement before swapping the cache, and a repeated write is a no-op. Existing cards are reported as added only when no previous compatible primary index exists. `LNV009` remains a non-failing contract-fingerprint review signal.
|
|
19
20
|
|
|
20
|
-
Do not delete `index.json`, rewrite semantic IDs, or copy generated paths or graph edges into comments.
|
|
21
|
+
Do not delete `index.json`, rewrite semantic IDs, or copy generated paths or graph edges into comments. The migration command never edits source cards or the semantic ID registry; it upgrades disposable generated formats from their canonical inputs.
|
|
21
22
|
|
|
22
23
|
## Do not annotate the whole repository
|
|
23
24
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -39,6 +39,7 @@ import { getAgentToolDefinitions } from "./agent-protocol.js";
|
|
|
39
39
|
import { loadPromptPrefixBundle } from "./prompt-bundle.js";
|
|
40
40
|
import { diagnosticsToEditor, getEditorIntegration } from "./editor.js";
|
|
41
41
|
import { AUDIT_PRIORITIES, auditHasFindings, auditProject } from "./audit.js";
|
|
42
|
+
import { migrateProject } from "./migration.js";
|
|
42
43
|
|
|
43
44
|
const VALUE_OPTIONS = new Set(["--root", "--format", "--top", "--depth", "--budget", "--max-edges", "--agents", "--file", "--fail-on", "--output"]);
|
|
44
45
|
|
|
@@ -53,6 +54,7 @@ const COMMAND_OPTIONS = Object.freeze({
|
|
|
53
54
|
context: new Set(["--depth", "--budget", "--max-edges", "--root", "--json"]),
|
|
54
55
|
eval: new Set(["--file", "--top", "--root", "--json"]),
|
|
55
56
|
doctor: new Set(["--root", "--json"]),
|
|
57
|
+
migrate: new Set(["--check", "--write", "--root", "--json"]),
|
|
56
58
|
audit: new Set(["--root", "--json", "--summary", "--fail-on", "--output"]),
|
|
57
59
|
spec: new Set(["--root", "--json"]),
|
|
58
60
|
tools: new Set(["--json"]),
|
|
@@ -100,6 +102,8 @@ export async function runCli(argv) {
|
|
|
100
102
|
return runEval(root, args, json);
|
|
101
103
|
case "doctor":
|
|
102
104
|
return runDoctor(root, json);
|
|
105
|
+
case "migrate":
|
|
106
|
+
return runMigrate(root, args, json);
|
|
103
107
|
case "audit":
|
|
104
108
|
return runAudit(root, args, json);
|
|
105
109
|
case "spec":
|
|
@@ -295,6 +299,26 @@ async function runDoctor(root, json) {
|
|
|
295
299
|
return result.ok ? 0 : 1;
|
|
296
300
|
}
|
|
297
301
|
|
|
302
|
+
async function runMigrate(root, args, json) {
|
|
303
|
+
if (hasFlag(args, "--check") && hasFlag(args, "--write")) {
|
|
304
|
+
throw usageError("migrate accepts either --check or --write, not both.");
|
|
305
|
+
}
|
|
306
|
+
const result = await migrateProject(root, { write: hasFlag(args, "--write") });
|
|
307
|
+
if (json) {
|
|
308
|
+
console.log(JSON.stringify(result, null, 2));
|
|
309
|
+
} else {
|
|
310
|
+
for (const format of result.formats) {
|
|
311
|
+
console.log(`${format.status} ${format.id}: ${format.path}${format.detail ? ` (${format.detail})` : ""}`);
|
|
312
|
+
}
|
|
313
|
+
if (result.diagnostics.length > 0) printDiagnostics(result.diagnostics, "text");
|
|
314
|
+
if (result.ok && result.applied) console.log(`Migrated ${result.changedFiles.length} generated file(s) transactionally.`);
|
|
315
|
+
else if (result.ok) console.log("Generated LLMNav formats are current.");
|
|
316
|
+
else if (result.required && result.mode === "check") console.log("Migration is required. Run llmnav migrate --write after reviewing this plan.");
|
|
317
|
+
else if (result.required) console.error("Migration was not applied because source validation failed.");
|
|
318
|
+
}
|
|
319
|
+
return result.ok ? 0 : 1;
|
|
320
|
+
}
|
|
321
|
+
|
|
298
322
|
async function runAudit(root, args, json) {
|
|
299
323
|
const failOn = getOption(args, "--fail-on") ?? "none";
|
|
300
324
|
if (failOn !== "none" && !AUDIT_PRIORITIES.includes(failOn)) {
|
|
@@ -492,6 +516,7 @@ Usage
|
|
|
492
516
|
llmnav context <semantic-id> [--depth 1] [--budget 2500] [--max-edges 24]
|
|
493
517
|
llmnav eval [--file path] [--top 5]
|
|
494
518
|
llmnav doctor
|
|
519
|
+
llmnav migrate [--check|--write]
|
|
495
520
|
llmnav audit [--summary] [--output path] [--fail-on none|high|medium|low]
|
|
496
521
|
llmnav spec
|
|
497
522
|
llmnav tools [--json]
|
package/src/incremental.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from "./util.js";
|
|
27
27
|
|
|
28
28
|
export const FILE_STATE_SCHEMA_VERSION = 1;
|
|
29
|
-
export const SOURCE_INDEXER_VERSION =
|
|
29
|
+
export const SOURCE_INDEXER_VERSION = 6;
|
|
30
30
|
const STAT_HINTS_SCHEMA_VERSION = 1;
|
|
31
31
|
|
|
32
32
|
export async function scanProjectIncremental(root, options = {}) {
|
package/src/index.d.ts
CHANGED
|
@@ -561,6 +561,27 @@ export interface GenerationResult {
|
|
|
561
561
|
transaction: TransactionResult;
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
+
export interface MigrationFormatRecord {
|
|
565
|
+
id: string;
|
|
566
|
+
path: string;
|
|
567
|
+
status: "current" | "missing" | "incompatible";
|
|
568
|
+
expected: string;
|
|
569
|
+
detail: string | null;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export interface MigrationResult {
|
|
573
|
+
schemaVersion: 1;
|
|
574
|
+
ok: boolean;
|
|
575
|
+
mode: "check" | "write";
|
|
576
|
+
required: boolean;
|
|
577
|
+
applied: boolean;
|
|
578
|
+
changedFiles: string[];
|
|
579
|
+
formats: MigrationFormatRecord[];
|
|
580
|
+
previousFormats?: MigrationFormatRecord[];
|
|
581
|
+
diagnostics: Diagnostic[];
|
|
582
|
+
transaction: TransactionResult;
|
|
583
|
+
}
|
|
584
|
+
|
|
564
585
|
export interface EvaluationResult {
|
|
565
586
|
ok: boolean;
|
|
566
587
|
errors: string[];
|
|
@@ -588,6 +609,7 @@ export const AGENT_OPERATION_SCHEMA_VERSION: 1;
|
|
|
588
609
|
export const AGENT_TOOL_SCHEMA_VERSION: 1;
|
|
589
610
|
export const BOUNDARY_KINDS: readonly DetectedBoundary["kind"][];
|
|
590
611
|
export const GRAPH_INPUT_SCHEMA_VERSION: 1;
|
|
612
|
+
export const MIGRATION_REPORT_SCHEMA_VERSION: 1;
|
|
591
613
|
export const EDITOR_DIAGNOSTIC_SCHEMA_VERSION: 1;
|
|
592
614
|
export const EDITOR_INTEGRATION_SCHEMA_VERSION: 1;
|
|
593
615
|
export const GRAPH_SCHEMA_VERSION: 1;
|
|
@@ -663,6 +685,7 @@ export function auditHasFindings(result: AuditResult, minimumPriority?: AuditPri
|
|
|
663
685
|
export function findAttachedDeclaration(source: string, block: LlmnavBlock, filePath: string): Declaration | null;
|
|
664
686
|
export function extractImports(source: string, filePath: string): string[];
|
|
665
687
|
export function doctorProject(root: string): Promise<{ ok: boolean; checks: Array<{ name: string; ok: boolean; message: string }> }>;
|
|
688
|
+
export function migrateProject(root: string, options?: { write?: boolean; failpoint?: string; renameOptions?: Record<string, unknown>; lockOptions?: Record<string, unknown>; onTransactionPhase?: (phase: string) => void | Promise<void> }): Promise<MigrationResult>;
|
|
666
689
|
export function evaluateProject(root: string, options?: { top?: number; file?: string }): Promise<EvaluationResult>;
|
|
667
690
|
export function collectSourceFiles(root: string, config: LlmnavConfig, requestedPaths?: string[]): Promise<string[]>;
|
|
668
691
|
export function findProjectRoot(start?: string): Promise<string>;
|
package/src/index.js
CHANGED
|
@@ -49,6 +49,7 @@ export {
|
|
|
49
49
|
} from "./contracts.js";
|
|
50
50
|
export { findAttachedDeclaration, extractImports } from "./declaration.js";
|
|
51
51
|
export { doctorProject } from "./doctor.js";
|
|
52
|
+
export { MIGRATION_REPORT_SCHEMA_VERSION, migrateProject } from "./migration.js";
|
|
52
53
|
export { evaluateProject } from "./evaluation.js";
|
|
53
54
|
export { collectSourceFiles, findProjectRoot } from "./files.js";
|
|
54
55
|
export { formatProject } from "./formatter.js";
|
package/src/migration.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/* llmnav/1 module
|
|
2
|
+
id=llmnav.project.migrate
|
|
3
|
+
role=Plan and apply safe generated-format upgrades from canonical repository source.
|
|
4
|
+
owns=migration planning|format compatibility reporting|full transactional regeneration
|
|
5
|
+
excludes=source-card rewriting|semantic ID reassignment
|
|
6
|
+
search=llmnav migrate|cache format upgrade|migration dry run
|
|
7
|
+
invariant=Check mode never commits generated artifacts.
|
|
8
|
+
invariant=Write mode stops before mutation when canonical source validation fails.
|
|
9
|
+
rel=workflow>llmnav.index.generate
|
|
10
|
+
rel=workflow>llmnav.index.transaction
|
|
11
|
+
stability=contract
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { loadConfig } from "./config.js";
|
|
16
|
+
import { generateProject } from "./generator.js";
|
|
17
|
+
import { compatibleGraphState, GRAPH_SCHEMA_VERSION, GRAPH_STATE_SCHEMA_VERSION, isCompatibleRepositoryGraph } from "./graph.js";
|
|
18
|
+
import { FILE_STATE_SCHEMA_VERSION, SOURCE_INDEXER_VERSION, usableFileState } from "./incremental.js";
|
|
19
|
+
import { isCompatibleSearchIndex, SEARCH_INDEX_ENCODING, SEARCH_INDEX_SCHEMA_VERSION } from "./inverted-index.js";
|
|
20
|
+
import { isCompatiblePromptPrefixBundle, PROMPT_BUNDLE_SCHEMA_VERSION } from "./prompt-bundle.js";
|
|
21
|
+
import { SEARCH_SHARD_SCHEMA_VERSION } from "./search-shards.js";
|
|
22
|
+
import { assertNoSymlinkTraversal, readText, toPosix } from "./util.js";
|
|
23
|
+
|
|
24
|
+
export const MIGRATION_REPORT_SCHEMA_VERSION = 1;
|
|
25
|
+
|
|
26
|
+
export async function migrateProject(root, options = {}) {
|
|
27
|
+
const write = options.write === true;
|
|
28
|
+
const { config } = await loadConfig(root);
|
|
29
|
+
const before = await inspectGeneratedFormats(root, config);
|
|
30
|
+
const plan = await generateProject(root, {
|
|
31
|
+
check: true,
|
|
32
|
+
incremental: false,
|
|
33
|
+
lockOptions: options.lockOptions,
|
|
34
|
+
renameOptions: options.renameOptions,
|
|
35
|
+
});
|
|
36
|
+
const required = plan.changedFiles.length > 0 || before.some((format) => format.status !== "current");
|
|
37
|
+
const blocked = plan.counts.error > 0;
|
|
38
|
+
|
|
39
|
+
if (!write || blocked || !required) {
|
|
40
|
+
return {
|
|
41
|
+
schemaVersion: MIGRATION_REPORT_SCHEMA_VERSION,
|
|
42
|
+
ok: !blocked && !required,
|
|
43
|
+
mode: write ? "write" : "check",
|
|
44
|
+
required,
|
|
45
|
+
applied: false,
|
|
46
|
+
changedFiles: plan.changedFiles,
|
|
47
|
+
formats: before,
|
|
48
|
+
diagnostics: plan.diagnostics,
|
|
49
|
+
transaction: plan.transaction,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const applied = await generateProject(root, {
|
|
54
|
+
check: false,
|
|
55
|
+
incremental: false,
|
|
56
|
+
failpoint: options.failpoint,
|
|
57
|
+
lockOptions: options.lockOptions,
|
|
58
|
+
renameOptions: options.renameOptions,
|
|
59
|
+
onTransactionPhase: options.onTransactionPhase,
|
|
60
|
+
});
|
|
61
|
+
const after = applied.ok ? await inspectGeneratedFormats(root, config) : before;
|
|
62
|
+
return {
|
|
63
|
+
schemaVersion: MIGRATION_REPORT_SCHEMA_VERSION,
|
|
64
|
+
ok: applied.ok && after.every((format) => format.status === "current"),
|
|
65
|
+
mode: "write",
|
|
66
|
+
required,
|
|
67
|
+
applied: applied.ok,
|
|
68
|
+
changedFiles: plan.changedFiles,
|
|
69
|
+
formats: after,
|
|
70
|
+
previousFormats: before,
|
|
71
|
+
diagnostics: applied.diagnostics,
|
|
72
|
+
transaction: applied.transaction,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function inspectGeneratedFormats(root, config) {
|
|
77
|
+
const cacheDirectory = toPosix(config.generation.cacheDirectory).replace(/\/+$/u, "");
|
|
78
|
+
const cacheRoot = path.join(root, cacheDirectory);
|
|
79
|
+
await assertNoSymlinkTraversal(root, cacheRoot, cacheDirectory);
|
|
80
|
+
const repositoryId = config.repositoryId;
|
|
81
|
+
const records = [];
|
|
82
|
+
|
|
83
|
+
const index = await inspectJson(root, path.join(cacheRoot, "index.json"), `${cacheDirectory}/index.json`);
|
|
84
|
+
records.push(formatRecord(
|
|
85
|
+
"primary-index",
|
|
86
|
+
`${cacheDirectory}/index.json`,
|
|
87
|
+
index,
|
|
88
|
+
(value) => value?.schemaVersion === 1 && value.repositoryId === repositoryId && Array.isArray(value.cards),
|
|
89
|
+
"schemaVersion 1",
|
|
90
|
+
));
|
|
91
|
+
|
|
92
|
+
const search = await inspectJson(root, path.join(cacheRoot, "search-index.json"), `${cacheDirectory}/search-index.json`);
|
|
93
|
+
records.push(formatRecord(
|
|
94
|
+
"search-index",
|
|
95
|
+
`${cacheDirectory}/search-index.json`,
|
|
96
|
+
search,
|
|
97
|
+
(value) => isCompatibleSearchIndex(value, repositoryId),
|
|
98
|
+
`schemaVersion ${SEARCH_INDEX_SCHEMA_VERSION} ${SEARCH_INDEX_ENCODING}`,
|
|
99
|
+
));
|
|
100
|
+
|
|
101
|
+
const fileState = await inspectJson(root, path.join(cacheRoot, "file-state.json"), `${cacheDirectory}/file-state.json`);
|
|
102
|
+
records.push(formatRecord(
|
|
103
|
+
"file-state",
|
|
104
|
+
`${cacheDirectory}/file-state.json`,
|
|
105
|
+
fileState,
|
|
106
|
+
usableFileState,
|
|
107
|
+
`schemaVersion ${FILE_STATE_SCHEMA_VERSION} indexerVersion ${SOURCE_INDEXER_VERSION}`,
|
|
108
|
+
));
|
|
109
|
+
|
|
110
|
+
const graph = await inspectJson(root, path.join(cacheRoot, "graph.json"), `${cacheDirectory}/graph.json`);
|
|
111
|
+
records.push(formatRecord(
|
|
112
|
+
"repository-graph",
|
|
113
|
+
`${cacheDirectory}/graph.json`,
|
|
114
|
+
graph,
|
|
115
|
+
(value) => isCompatibleRepositoryGraph(value, repositoryId),
|
|
116
|
+
`schemaVersion ${GRAPH_SCHEMA_VERSION}`,
|
|
117
|
+
));
|
|
118
|
+
|
|
119
|
+
const graphState = await inspectJson(root, path.join(cacheRoot, "graph-state.json"), `${cacheDirectory}/graph-state.json`);
|
|
120
|
+
records.push(formatRecord(
|
|
121
|
+
"graph-state",
|
|
122
|
+
`${cacheDirectory}/graph-state.json`,
|
|
123
|
+
graphState,
|
|
124
|
+
(value) => compatibleGraphState(value, repositoryId),
|
|
125
|
+
`schemaVersion ${GRAPH_STATE_SCHEMA_VERSION}`,
|
|
126
|
+
));
|
|
127
|
+
|
|
128
|
+
const prompt = await inspectJson(root, path.join(cacheRoot, "prompt-prefix.json"), `${cacheDirectory}/prompt-prefix.json`);
|
|
129
|
+
records.push(formatRecord(
|
|
130
|
+
"prompt-prefix",
|
|
131
|
+
`${cacheDirectory}/prompt-prefix.json`,
|
|
132
|
+
prompt,
|
|
133
|
+
(value) => isCompatiblePromptPrefixBundle(value, repositoryId),
|
|
134
|
+
`schemaVersion ${PROMPT_BUNDLE_SCHEMA_VERSION}`,
|
|
135
|
+
));
|
|
136
|
+
|
|
137
|
+
const manifest = await inspectJson(root, path.join(cacheRoot, "manifest.json"), `${cacheDirectory}/manifest.json`);
|
|
138
|
+
records.push(formatRecord(
|
|
139
|
+
"manifest",
|
|
140
|
+
`${cacheDirectory}/manifest.json`,
|
|
141
|
+
manifest,
|
|
142
|
+
(value) => compatibleManifest(value, repositoryId),
|
|
143
|
+
"schemaVersion 1 with current generated format versions",
|
|
144
|
+
));
|
|
145
|
+
|
|
146
|
+
return records;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function compatibleManifest(value, repositoryId) {
|
|
150
|
+
return Boolean(
|
|
151
|
+
value &&
|
|
152
|
+
value.schemaVersion === 1 &&
|
|
153
|
+
value.repositoryId === repositoryId &&
|
|
154
|
+
value.fileStateSchemaVersion === FILE_STATE_SCHEMA_VERSION &&
|
|
155
|
+
value.searchIndexSchemaVersion === SEARCH_INDEX_SCHEMA_VERSION &&
|
|
156
|
+
value.graphSchemaVersion === GRAPH_SCHEMA_VERSION &&
|
|
157
|
+
value.graphStateSchemaVersion === GRAPH_STATE_SCHEMA_VERSION &&
|
|
158
|
+
value.promptBundleSchemaVersion === PROMPT_BUNDLE_SCHEMA_VERSION &&
|
|
159
|
+
(value.searchShardSchemaVersion === null || value.searchShardSchemaVersion === SEARCH_SHARD_SCHEMA_VERSION) &&
|
|
160
|
+
value.files && typeof value.files === "object",
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function inspectJson(root, filePath, label) {
|
|
165
|
+
await assertNoSymlinkTraversal(root, filePath, label);
|
|
166
|
+
const text = await readText(filePath, null);
|
|
167
|
+
if (text === null) return { state: "missing", value: null, detail: "file is missing" };
|
|
168
|
+
try {
|
|
169
|
+
return { state: "present", value: JSON.parse(text), detail: null };
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return { state: "malformed", value: null, detail: error instanceof Error ? error.message : String(error) };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function formatRecord(id, relativePath, inspected, compatible, expected) {
|
|
176
|
+
if (inspected.state === "missing") return { id, path: relativePath, status: "missing", expected, detail: inspected.detail };
|
|
177
|
+
if (inspected.state === "malformed") return { id, path: relativePath, status: "incompatible", expected, detail: inspected.detail };
|
|
178
|
+
const current = compatible(inspected.value);
|
|
179
|
+
return {
|
|
180
|
+
id,
|
|
181
|
+
path: relativePath,
|
|
182
|
+
status: current ? "current" : "incompatible",
|
|
183
|
+
expected,
|
|
184
|
+
detail: current ? null : describeVersion(inspected.value),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function describeVersion(value) {
|
|
189
|
+
if (!value || typeof value !== "object") return "expected a JSON object";
|
|
190
|
+
const fields = ["schemaVersion", "indexerVersion", "encoding", "tokenizerVersion"]
|
|
191
|
+
.filter((name) => value[name] !== undefined)
|
|
192
|
+
.map((name) => `${name}=${JSON.stringify(value[name])}`);
|
|
193
|
+
return fields.length > 0 ? `found ${fields.join(" ")}` : "version fields are missing";
|
|
194
|
+
}
|
package/src/spec.js
CHANGED
|
@@ -10,7 +10,7 @@ rel=workflow>llmnav.rules.validate
|
|
|
10
10
|
stability=contract
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
export const PACKAGE_VERSION = "0.
|
|
13
|
+
export const PACKAGE_VERSION = "0.7.0";
|
|
14
14
|
export const SPEC_VERSION = "1";
|
|
15
15
|
|
|
16
16
|
export const SCOPES = Object.freeze(["file", "module", "symbol"]);
|