kibi-cli 2.21.0 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-operation-metadata.d.ts +1 -1
- package/dist/cli-operation-metadata.js +1 -1
- package/dist/cli-register-foundation.d.ts.map +1 -1
- package/dist/cli-register-foundation.js +1 -0
- package/dist/commands/check.d.ts +0 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +47 -111
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +107 -5
- package/dist/commands/init-helpers.d.ts +14 -1
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +49 -10
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +27 -4
- package/dist/commands/legacy-storage-migration.d.ts +46 -1
- package/dist/commands/legacy-storage-migration.d.ts.map +1 -1
- package/dist/commands/legacy-storage-migration.js +244 -9
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +150 -166
- package/dist/commands/sync/manifest.d.ts.map +1 -1
- package/dist/commands/sync/manifest.js +9 -0
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +4 -0
- package/dist/decision-firmness.d.ts +39 -0
- package/dist/decision-firmness.d.ts.map +1 -0
- package/dist/decision-firmness.js +115 -0
- package/dist/extractors/markdown.d.ts +2 -0
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +14 -0
- package/dist/extractors/symbols-coordinator.js +1 -0
- package/dist/intent-search.d.ts.map +1 -1
- package/dist/intent-search.js +28 -6
- package/dist/operations/migration/inventory-rederive.d.ts +40 -1
- package/dist/operations/migration/inventory-rederive.d.ts.map +1 -1
- package/dist/operations/migration/inventory-rederive.js +99 -8
- package/dist/operations/migration/requirement-status.d.ts +25 -0
- package/dist/operations/migration/requirement-status.d.ts.map +1 -0
- package/dist/operations/migration/requirement-status.js +105 -0
- package/dist/operations/migration/schema6.d.ts +27 -0
- package/dist/operations/migration/schema6.d.ts.map +1 -1
- package/dist/operations/migration/schema6.js +308 -7
- package/dist/operations/migration/supersedes-reason.d.ts +26 -0
- package/dist/operations/migration/supersedes-reason.d.ts.map +1 -0
- package/dist/operations/migration/supersedes-reason.js +69 -0
- package/dist/operations/migration/symbol-legacy-links.d.ts +51 -0
- package/dist/operations/migration/symbol-legacy-links.d.ts.map +1 -0
- package/dist/operations/migration/symbol-legacy-links.js +319 -0
- package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-applyplan.js +10 -2
- package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-loader.js +11 -0
- package/dist/operations/modeling/predicate-types.d.ts +32 -1
- package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
- package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
- package/dist/operations/modeling/suggest-predicates.js +175 -35
- package/dist/operations/mutation/context-warning.d.ts +12 -2
- package/dist/operations/mutation/context-warning.d.ts.map +1 -1
- package/dist/operations/mutation/context-warning.js +6 -6
- package/dist/operations/mutation/delete.d.ts +5 -1
- package/dist/operations/mutation/delete.d.ts.map +1 -1
- package/dist/operations/mutation/delete.js +223 -17
- package/dist/operations/mutation/serialization.d.ts.map +1 -1
- package/dist/operations/mutation/serialization.js +1 -0
- package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
- package/dist/operations/mutation/source-authoring.js +130 -35
- package/dist/operations/mutation/types.d.ts +39 -1
- package/dist/operations/mutation/types.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.d.ts +17 -2
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +82 -13
- package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
- package/dist/operations/mutation/validate-upsert.js +40 -8
- package/dist/operations/planning/apply-plan.d.ts +6 -1
- package/dist/operations/planning/apply-plan.d.ts.map +1 -1
- package/dist/operations/planning/apply-plan.js +26 -5
- package/dist/public/impact/diagnostics.d.ts.map +1 -1
- package/dist/public/impact/diagnostics.js +24 -14
- package/dist/public/operations/catalog.d.ts +14 -5
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/check-executor.d.ts.map +1 -1
- package/dist/public/operations/check-executor.js +6 -0
- package/dist/public/operations/contracts.d.ts.map +1 -1
- package/dist/public/operations/contracts.js +35 -2
- package/dist/public/operations/decision-firmness.d.ts +34 -0
- package/dist/public/operations/decision-firmness.d.ts.map +1 -0
- package/dist/public/operations/decision-firmness.js +216 -0
- package/dist/public/operations/discovery-executors.d.ts +2 -0
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +25 -4
- package/dist/public/operations/migration-plan.d.ts +2 -0
- package/dist/public/operations/migration-plan.d.ts.map +1 -1
- package/dist/public/operations/migration-plan.js +101 -16
- package/dist/public/operations/result-envelope.d.ts.map +1 -1
- package/dist/public/operations/result-envelope.js +27 -1
- package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
- package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
- package/dist/public/operations/specs/check-rules.generated.js +6 -1
- package/dist/public/operations/specs/check.d.ts +1 -1
- package/dist/public/operations/specs/mutation-schemas.d.ts +5 -0
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +5 -0
- package/dist/public/operations/specs/mutation.d.ts +6 -2
- package/dist/public/operations/specs/mutation.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation.js +6 -2
- package/dist/public/operations/specs/planning.d.ts +2 -2
- package/dist/public/operations/specs/planning.js +2 -2
- package/dist/public/operations/specs/upsert.d.ts +5 -0
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/operations/symbol-granularity-check.d.ts +18 -0
- package/dist/public/operations/symbol-granularity-check.d.ts.map +1 -0
- package/dist/public/operations/symbol-granularity-check.js +175 -0
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +11 -0
- package/dist/public/skills/kibi-traceability/SKILL.md +6 -4
- package/dist/public/skills/kibi-usage/SKILL.md +50 -10
- package/dist/public/skills/kibi-usage/resources/source-authoring.md +4 -2
- package/dist/public/symbol-granularity.d.ts +17 -0
- package/dist/public/symbol-granularity.d.ts.map +1 -1
- package/dist/public/symbol-granularity.js +21 -0
- package/dist/relationships/shards.d.ts +10 -0
- package/dist/relationships/shards.d.ts.map +1 -1
- package/dist/relationships/shards.js +27 -11
- package/dist/schemas/entity.schema.json +28 -0
- package/dist/search-answer.d.ts +31 -2
- package/dist/search-answer.d.ts.map +1 -1
- package/dist/search-answer.js +115 -17
- package/dist/traceability/evidence-model.d.ts +7 -3
- package/dist/traceability/evidence-model.d.ts.map +1 -1
- package/dist/traceability/staged-symbols-manifest.js +6 -5
- package/dist/traceability/validate.d.ts +14 -1
- package/dist/traceability/validate.d.ts.map +1 -1
- package/dist/traceability/validate.js +41 -0
- package/dist/types/entities.d.ts +4 -0
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/utils/kb-manifest.d.ts +6 -0
- package/dist/utils/kb-manifest.d.ts.map +1 -1
- package/dist/utils/kb-manifest.js +6 -0
- package/dist/utils/rule-registry.generated.d.ts +26 -0
- package/dist/utils/rule-registry.generated.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.js +31 -0
- package/dist/utils/schema-version.d.ts +1 -1
- package/dist/utils/schema-version.d.ts.map +1 -1
- package/dist/utils/schema-version.js +3 -1
- package/package.json +3 -3
- package/schema/entities.pl +6 -0
- package/src/public/impact/diagnostics.ts +29 -17
- package/src/public/operations/check-executor.ts +13 -0
- package/src/public/operations/contracts.ts +39 -2
- package/src/public/operations/decision-firmness.ts +280 -0
- package/src/public/operations/discovery-executors.ts +29 -4
- package/src/public/operations/migration-plan.ts +100 -19
- package/src/public/operations/result-envelope.ts +34 -1
- package/src/public/operations/specs/check-rules.generated.ts +6 -1
- package/src/public/operations/specs/mutation-schemas.ts +6 -0
- package/src/public/operations/specs/mutation.ts +7 -2
- package/src/public/operations/specs/planning.ts +2 -2
- package/src/public/operations/symbol-granularity-check.ts +233 -0
- package/src/public/schemas/entity.ts +12 -0
- package/src/public/skills/kibi-traceability/SKILL.md +6 -4
- package/src/public/skills/kibi-usage/SKILL.md +50 -10
- package/src/public/skills/kibi-usage/resources/source-authoring.md +4 -2
- package/src/public/symbol-granularity.ts +37 -0
- package/src/schemas/entity.schema.json +28 -0
|
@@ -9,7 +9,10 @@ import { type EntityLane } from "../utils/kb-paths.js";
|
|
|
9
9
|
* - legacy `.kb/config.json` is read (never trusted blindly);
|
|
10
10
|
* - configured or default legacy paths are discovered on disk;
|
|
11
11
|
* - files move into `.kb/<lane>/` without content changes;
|
|
12
|
-
* - destination
|
|
12
|
+
* - a legacy copy identical to its destination is removed instead of moved;
|
|
13
|
+
* - other destination conflicts abort before any file moves, each with the
|
|
14
|
+
* resolution Kibi can tell (a stale subset of the symbols manifest, a
|
|
15
|
+
* generated coordinates file, or a real conflict);
|
|
13
16
|
* - partial migrations resume safely (already-moved files are no-ops);
|
|
14
17
|
* - on success the legacy config is retired.
|
|
15
18
|
*/
|
|
@@ -22,9 +25,34 @@ export interface StorageMigrationPlan {
|
|
|
22
25
|
/** Lifecycle state preserved into the canonical manifest. */
|
|
23
26
|
schemaVersion: number | null;
|
|
24
27
|
semanticAdvisorBackfill: SemanticAdvisorBackfill | null;
|
|
28
|
+
/**
|
|
29
|
+
* Legacy copies byte-identical to their `.kb/` destination: the migration
|
|
30
|
+
* removes them instead of moving them.
|
|
31
|
+
*/
|
|
32
|
+
removals: StorageMigrationRemoval[];
|
|
33
|
+
/** Existing destinations that block a move, with how to resolve each. */
|
|
34
|
+
conflicts: StorageMigrationConflict[];
|
|
25
35
|
/** Why the migration cannot proceed automatically, if blocked. */
|
|
26
36
|
blockers: string[];
|
|
27
37
|
}
|
|
38
|
+
export interface StorageMigrationRemoval {
|
|
39
|
+
/** The legacy copy to remove. */
|
|
40
|
+
path: string;
|
|
41
|
+
/** The `.kb/` file it duplicates. */
|
|
42
|
+
duplicateOf: string;
|
|
43
|
+
}
|
|
44
|
+
export interface StorageMigrationConflict {
|
|
45
|
+
from: string;
|
|
46
|
+
to: string;
|
|
47
|
+
/**
|
|
48
|
+
* `stale_subset`: every symbol of the legacy manifest is already in the
|
|
49
|
+
* `.kb/` one. `generated`: a symbol-coordinates file, which sync
|
|
50
|
+
* regenerates. `ambiguous`: Kibi cannot tell which legacy file is meant.
|
|
51
|
+
* `conflict`: different content Kibi cannot classify.
|
|
52
|
+
*/
|
|
53
|
+
kind: "stale_subset" | "generated" | "ambiguous" | "conflict";
|
|
54
|
+
resolution: string;
|
|
55
|
+
}
|
|
28
56
|
export interface StorageMigrationMove {
|
|
29
57
|
from: string;
|
|
30
58
|
to: string;
|
|
@@ -34,6 +62,13 @@ export interface StorageMigrationMove {
|
|
|
34
62
|
export declare function planLegacyStorageMigration(cwd: string): StorageMigrationPlan;
|
|
35
63
|
export interface StorageMigrationResult {
|
|
36
64
|
movedFiles: string[];
|
|
65
|
+
/** Legacy copies removed because they were identical to their destination. */
|
|
66
|
+
removedFiles: string[];
|
|
67
|
+
/**
|
|
68
|
+
* Moved files Git does not track yet. Each got a pending-source receipt, so
|
|
69
|
+
* `kibi sync` compiles them before they are staged; empty outside Git.
|
|
70
|
+
*/
|
|
71
|
+
untrackedMovedFiles: string[];
|
|
37
72
|
retiredLegacyConfig: boolean;
|
|
38
73
|
manifestPath: string;
|
|
39
74
|
schemaVersion: number;
|
|
@@ -47,6 +82,16 @@ export interface StorageMigrationResult {
|
|
|
47
82
|
export declare function applyLegacyStorageMigration(cwd: string, plan: StorageMigrationPlan): StorageMigrationResult;
|
|
48
83
|
/** Rewrite recovery pending-source receipts after a storage cutover. */
|
|
49
84
|
export declare function rewritePendingSourceReceiptPaths(cwd: string, moves: readonly StorageMigrationMove[]): number;
|
|
85
|
+
/**
|
|
86
|
+
* Bind every moved file Git does not track to its bytes with a
|
|
87
|
+
* pending-source receipt, the record kb_upsert writes for the files it
|
|
88
|
+
* creates. `kibi sync` compiles tracked sources plus receipted ones, so the
|
|
89
|
+
* moved knowledge compiles at once; Kibi never stages anything itself, and
|
|
90
|
+
* each receipt retires when Git starts tracking its file. Later migration
|
|
91
|
+
* rewrites rebind the receipt they find. Covers files a previous,
|
|
92
|
+
* interrupted run moved too. Returns the untracked destinations.
|
|
93
|
+
*/
|
|
94
|
+
export declare function writeMovedSourceReceipts(cwd: string, moves: readonly StorageMigrationMove[]): string[];
|
|
50
95
|
/** True when the workspace still needs the legacy storage migration. */
|
|
51
96
|
export declare function needsLegacyStorageMigration(cwd: string): boolean;
|
|
52
97
|
/** Entity lanes that must exist for the canonical layout to be complete. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"legacy-storage-migration.d.ts","sourceRoot":"","sources":["../../src/commands/legacy-storage-migration.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"legacy-storage-migration.d.ts","sourceRoot":"","sources":["../../src/commands/legacy-storage-migration.ts"],"names":[],"mappings":"AAwCA,OAAO,EACL,KAAK,uBAAuB,EAI7B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAGL,KAAK,UAAU,EAGhB,MAAM,sBAAsB,CAAC;AAI9B;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,4CAA4C;IAC5C,YAAY,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,uBAAuB,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACxD;;;OAGG;IACH,QAAQ,EAAE,uBAAuB,EAAE,CAAC;IACpC,yEAAyE;IACzE,SAAS,EAAE,wBAAwB,EAAE,CAAC;IACtC,kEAAkE;IAClE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX;;;;;OAKG;IACH,IAAI,EAAE,cAAc,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,CAAC;IAC9D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,GAAG,SAAS,CAAC;CAC9B;AA2BD,6DAA6D;AAC7D,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,CAkJ5E;AAoID,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,8EAA8E;IAC9E,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB;;;OAGG;IACH,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,uBAAuB,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,oBAAoB,GACzB,sBAAsB,CAyExB;AAkCD,wEAAwE;AACxE,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,oBAAoB,EAAE,GACrC,MAAM,CAwCR;AAqBD;;;;;;;;GAQG;AAEH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,oBAAoB,EAAE,GACrC,MAAM,EAAE,CAqBV;AA8BD,wEAAwE;AACxE,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAShE;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,CAI/D"}
|
|
@@ -15,13 +15,16 @@
|
|
|
15
15
|
* You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
|
+
import { execFileSync } from "node:child_process";
|
|
18
19
|
import { createHash } from "node:crypto";
|
|
19
20
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
20
21
|
import * as path from "node:path";
|
|
21
22
|
import fg from "fast-glob";
|
|
23
|
+
import { load as parseYaml } from "js-yaml";
|
|
24
|
+
import { writePendingSourceReceipt } from "../operations/mutation/source-authoring.js";
|
|
22
25
|
import { readLegacyKbConfig, resolveLegacyEntityPaths, } from "../utils/config.js";
|
|
23
26
|
import { defaultKbManifest, readKbManifest, writeKbManifest, } from "../utils/kb-manifest.js";
|
|
24
|
-
import { CANONICAL_ENTITY_PATHS, ENTITY_LANES, KB_PATHS, } from "../utils/kb-paths.js";
|
|
27
|
+
import { CANONICAL_ENTITY_PATHS, ENTITY_LANES, KB_PATHS, LEGACY_DEFAULT_ENTITY_PATHS, } from "../utils/kb-paths.js";
|
|
25
28
|
import { LATEST_KB_SCHEMA_VERSION } from "../utils/schema-version.js";
|
|
26
29
|
import { updateGitIgnore } from "./init-helpers.js";
|
|
27
30
|
const LANE_BY_CONFIG_KEY = {
|
|
@@ -57,6 +60,8 @@ export function planLegacyStorageMigration(cwd) {
|
|
|
57
60
|
legacyConfigError: legacyConfigResult.error,
|
|
58
61
|
schemaVersion: null,
|
|
59
62
|
semanticAdvisorBackfill: null,
|
|
63
|
+
removals: [],
|
|
64
|
+
conflicts: [],
|
|
60
65
|
blockers: [
|
|
61
66
|
`Legacy .kb/config.json is malformed (${legacyConfigResult.error}). Repair the JSON or provide an explicit recovery/migration override; Kibi will not infer default legacy paths.`,
|
|
62
67
|
],
|
|
@@ -96,7 +101,11 @@ export function planLegacyStorageMigration(cwd) {
|
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
// Symbols manifest + coordinates.
|
|
99
|
-
const
|
|
104
|
+
const conflicts = [];
|
|
105
|
+
const symbolsSource = resolveLegacySymbolsSource(cwd, config, legacyPaths.symbols);
|
|
106
|
+
if (symbolsSource.conflict !== undefined)
|
|
107
|
+
conflicts.push(symbolsSource.conflict);
|
|
108
|
+
const legacySymbols = symbolsSource.path;
|
|
100
109
|
if (typeof legacySymbols === "string" && legacySymbols.trim().length > 0) {
|
|
101
110
|
const legacyManifest = path.resolve(cwd, legacySymbols);
|
|
102
111
|
if (existsSync(legacyManifest) &&
|
|
@@ -122,14 +131,33 @@ export function planLegacyStorageMigration(cwd) {
|
|
|
122
131
|
});
|
|
123
132
|
}
|
|
124
133
|
}
|
|
125
|
-
// Destination
|
|
126
|
-
//
|
|
134
|
+
// Destination classification: an identical legacy copy is removed, an
|
|
135
|
+
// empty seeded symbols manifest is replaced, and anything else is an
|
|
136
|
+
// operator decision with the resolution Kibi can tell.
|
|
137
|
+
const removals = [];
|
|
138
|
+
const pending = [];
|
|
127
139
|
for (const move of moves) {
|
|
128
140
|
const destinationAbsolute = path.resolve(cwd, move.to);
|
|
129
|
-
if (!existsSync(destinationAbsolute))
|
|
141
|
+
if (!existsSync(destinationAbsolute)) {
|
|
142
|
+
pending.push(move);
|
|
130
143
|
continue;
|
|
131
|
-
|
|
144
|
+
}
|
|
145
|
+
// 'kibi init' seeds an empty symbols manifest; the legacy one replaces it.
|
|
146
|
+
if (move.lane === "symbols" && isEmptySymbolsStub(destinationAbsolute)) {
|
|
147
|
+
pending.push(move);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const conflict = classifyExistingDestination(cwd, move);
|
|
151
|
+
if (conflict === null) {
|
|
152
|
+
removals.push({ path: move.from, duplicateOf: move.to });
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
pending.push(move);
|
|
156
|
+
conflicts.push(conflict);
|
|
132
157
|
}
|
|
158
|
+
moves.splice(0, moves.length, ...pending);
|
|
159
|
+
for (const conflict of conflicts)
|
|
160
|
+
blockers.push(conflict.resolution);
|
|
133
161
|
// Duplicate destinations (two legacy sources collapsing onto one target).
|
|
134
162
|
const destinations = new Map();
|
|
135
163
|
for (const move of moves) {
|
|
@@ -145,9 +173,126 @@ export function planLegacyStorageMigration(cwd) {
|
|
|
145
173
|
legacyConfig: legacyConfigResult.kind === "none" ? "absent" : "present",
|
|
146
174
|
schemaVersion: typeof config?.schemaVersion === "number" ? config.schemaVersion : null,
|
|
147
175
|
semanticAdvisorBackfill: config?.semanticAdvisorBackfill ?? null,
|
|
176
|
+
removals,
|
|
177
|
+
conflicts,
|
|
148
178
|
blockers,
|
|
149
179
|
};
|
|
150
180
|
}
|
|
181
|
+
function hashFile(filePath) {
|
|
182
|
+
try {
|
|
183
|
+
return createHash("sha256").update(readFileSync(filePath)).digest("hex");
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** Symbol ids a manifest declares, or null when it does not parse. */
|
|
190
|
+
function manifestSymbolIds(filePath) {
|
|
191
|
+
try {
|
|
192
|
+
const parsed = parseYaml(readFileSync(filePath, "utf8"));
|
|
193
|
+
if (!Array.isArray(parsed?.symbols))
|
|
194
|
+
return null;
|
|
195
|
+
return new Set(parsed.symbols.flatMap((symbol) => symbol !== null &&
|
|
196
|
+
typeof symbol === "object" &&
|
|
197
|
+
typeof symbol.id === "string"
|
|
198
|
+
? [symbol.id]
|
|
199
|
+
: []));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* How a move whose destination already exists is resolved: null when the
|
|
207
|
+
* legacy copy is identical (it is removed), otherwise the conflict and the
|
|
208
|
+
* resolution Kibi can tell.
|
|
209
|
+
*/
|
|
210
|
+
// implements REQ-cli-schema-migration
|
|
211
|
+
function classifyExistingDestination(cwd, move) {
|
|
212
|
+
const from = path.resolve(cwd, move.from);
|
|
213
|
+
const to = path.resolve(cwd, move.to);
|
|
214
|
+
const legacyHash = hashFile(from);
|
|
215
|
+
if (legacyHash !== null && legacyHash === hashFile(to))
|
|
216
|
+
return null;
|
|
217
|
+
if (move.to === CANONICAL_ENTITY_PATHS.symbols) {
|
|
218
|
+
const legacyIds = manifestSymbolIds(from);
|
|
219
|
+
const currentIds = manifestSymbolIds(to);
|
|
220
|
+
if (legacyIds !== null &&
|
|
221
|
+
currentIds !== null &&
|
|
222
|
+
[...legacyIds].every((id) => currentIds.has(id))) {
|
|
223
|
+
return {
|
|
224
|
+
from: move.from,
|
|
225
|
+
to: move.to,
|
|
226
|
+
kind: "stale_subset",
|
|
227
|
+
resolution: `The legacy copy ${move.from} is a stale subset of ${move.to}: every symbol it declares (${legacyIds.size}) is already there. Remove it (git rm ${move.from}); Kibi removes only a copy identical to its destination.`,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (move.to === KB_PATHS.symbolCoordinates) {
|
|
232
|
+
return {
|
|
233
|
+
from: move.from,
|
|
234
|
+
to: move.to,
|
|
235
|
+
kind: "generated",
|
|
236
|
+
resolution: `${move.from} is a generated coordinates file and ${move.to} already exists. Keep ${move.to} and remove the legacy copy (git rm ${move.from}); kibi sync --refresh-symbol-coordinates regenerates coordinates.`,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
from: move.from,
|
|
241
|
+
to: move.to,
|
|
242
|
+
kind: "conflict",
|
|
243
|
+
resolution: `Destination ${move.to} already exists with different content than ${move.from}. Compare them and keep the current one: replace ${move.to} when the legacy copy is newer, or remove (git rm) ${move.from} when it is a stale duplicate.`,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/** The parent folders of the lane roots a legacy config names explicitly. */
|
|
247
|
+
function configuredLaneParents(config) {
|
|
248
|
+
const parents = new Set();
|
|
249
|
+
for (const key of Object.keys(LANE_BY_CONFIG_KEY)) {
|
|
250
|
+
const value = config?.paths?.[key];
|
|
251
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
252
|
+
continue;
|
|
253
|
+
const { root } = normalizeLegacySource(value.trim());
|
|
254
|
+
const parent = path.posix.dirname(root.replace(/^\.\//, ""));
|
|
255
|
+
if (parent !== "." && parent !== "")
|
|
256
|
+
parents.add(parent);
|
|
257
|
+
}
|
|
258
|
+
return [...parents].sort();
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* The legacy symbols manifest to move. A configured path wins. Without one,
|
|
262
|
+
* the historical `documentation/symbols.yaml` default applies only when the
|
|
263
|
+
* config does not place its lanes under another folder: there the manifest
|
|
264
|
+
* sits beside the lanes, and a `documentation/` copy may be a stale leftover.
|
|
265
|
+
* When Kibi cannot tell which file is meant, it moves none and reports the
|
|
266
|
+
* ambiguity instead.
|
|
267
|
+
*/
|
|
268
|
+
// implements REQ-cli-schema-migration
|
|
269
|
+
function resolveLegacySymbolsSource(cwd, config, resolved) {
|
|
270
|
+
const configured = config?.paths?.symbols ?? config?.symbolsManifest;
|
|
271
|
+
if (typeof configured === "string" && configured.trim().length > 0)
|
|
272
|
+
return { path: resolved };
|
|
273
|
+
const parents = configuredLaneParents(config).filter((parent) => parent !== path.posix.dirname(LEGACY_DEFAULT_ENTITY_PATHS.symbols));
|
|
274
|
+
if (parents.length === 0)
|
|
275
|
+
return { path: resolved };
|
|
276
|
+
const candidates = parents
|
|
277
|
+
.map((parent) => `${parent}/symbols.yaml`)
|
|
278
|
+
.filter((candidate) => existsSync(path.resolve(cwd, candidate)));
|
|
279
|
+
const fallback = LEGACY_DEFAULT_ENTITY_PATHS.symbols;
|
|
280
|
+
const fallbackExists = existsSync(path.resolve(cwd, fallback));
|
|
281
|
+
if (candidates.length === 1 && !fallbackExists)
|
|
282
|
+
return { path: candidates[0] ?? null };
|
|
283
|
+
if (candidates.length === 0 && !fallbackExists)
|
|
284
|
+
return { path: null };
|
|
285
|
+
const all = [...candidates, ...(fallbackExists ? [fallback] : [])];
|
|
286
|
+
return {
|
|
287
|
+
path: null,
|
|
288
|
+
conflict: {
|
|
289
|
+
from: all.join(", "),
|
|
290
|
+
to: CANONICAL_ENTITY_PATHS.symbols,
|
|
291
|
+
kind: "ambiguous",
|
|
292
|
+
resolution: `The legacy .kb/config.json places its lanes under ${parents.join(", ")} but names no symbols manifest, and ${all.length === 1 ? `${all[0]} exists outside that folder` : `${all.join(" and ")} all exist`}; Kibi will not guess which one is current. Set paths.symbols in .kb/config.json to the current manifest, or remove (git rm) the stale one, then run kibi migrate --yes.`,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
151
296
|
/**
|
|
152
297
|
* Apply the legacy storage migration. Moves every planned file, writes the
|
|
153
298
|
* canonical lifecycle manifest, retires the legacy config, and prunes empty
|
|
@@ -157,6 +302,18 @@ export function applyLegacyStorageMigration(cwd, plan) {
|
|
|
157
302
|
if (plan.blockers.length > 0) {
|
|
158
303
|
throw new Error(`Legacy storage migration is blocked: ${plan.blockers.join(" ")}`);
|
|
159
304
|
}
|
|
305
|
+
// A legacy copy identical to its destination carries nothing the
|
|
306
|
+
// destination lacks; it is removed rather than left to block a later run.
|
|
307
|
+
const removedFiles = [];
|
|
308
|
+
for (const removal of plan.removals) {
|
|
309
|
+
const absolute = path.resolve(cwd, removal.path);
|
|
310
|
+
if (!existsSync(absolute))
|
|
311
|
+
continue;
|
|
312
|
+
if (hashFile(absolute) !== hashFile(path.resolve(cwd, removal.duplicateOf)))
|
|
313
|
+
throw new Error(`Legacy storage migration is blocked: ${removal.path} changed since planning and no longer matches ${removal.duplicateOf}.`);
|
|
314
|
+
rmSync(absolute);
|
|
315
|
+
removedFiles.push(removal.path);
|
|
316
|
+
}
|
|
160
317
|
const movedFiles = [];
|
|
161
318
|
for (const move of plan.moves) {
|
|
162
319
|
const sourceAbsolute = path.resolve(cwd, move.from);
|
|
@@ -174,10 +331,13 @@ export function applyLegacyStorageMigration(cwd, plan) {
|
|
|
174
331
|
const backfill = plan.semanticAdvisorBackfill ??
|
|
175
332
|
existing?.semanticAdvisorBackfill ??
|
|
176
333
|
"pending";
|
|
334
|
+
// Record the generation the knowledge was written for, not the latest
|
|
335
|
+
// one: 'kibi migrate' raises it after the schema steps, so a run
|
|
336
|
+
// interrupted between the move and those steps still replays them.
|
|
177
337
|
const manifest = {
|
|
178
338
|
...(existing ?? defaultKbManifest()),
|
|
179
339
|
manifestVersion: defaultKbManifest().manifestVersion,
|
|
180
|
-
schemaVersion:
|
|
340
|
+
schemaVersion: sourceSchemaVersion(plan.schemaVersion, existing),
|
|
181
341
|
semanticAdvisorBackfill: backfill,
|
|
182
342
|
};
|
|
183
343
|
const manifestPath = writeKbManifest(cwd, manifest);
|
|
@@ -190,9 +350,12 @@ export function applyLegacyStorageMigration(cwd, plan) {
|
|
|
190
350
|
}
|
|
191
351
|
pruneEmptyLegacyDirs(cwd, plan);
|
|
192
352
|
rewritePendingSourceReceiptPaths(cwd, plan.moves);
|
|
353
|
+
const untrackedMovedFiles = writeMovedSourceReceipts(cwd, plan.moves);
|
|
193
354
|
updateGitIgnore(cwd);
|
|
194
355
|
return {
|
|
195
356
|
movedFiles,
|
|
357
|
+
removedFiles,
|
|
358
|
+
untrackedMovedFiles,
|
|
196
359
|
retiredLegacyConfig,
|
|
197
360
|
manifestPath: path.relative(cwd, manifestPath).replaceAll(path.sep, "/"),
|
|
198
361
|
schemaVersion: manifest.schemaVersion,
|
|
@@ -201,8 +364,11 @@ export function applyLegacyStorageMigration(cwd, plan) {
|
|
|
201
364
|
}
|
|
202
365
|
function pruneEmptyLegacyDirs(cwd, plan) {
|
|
203
366
|
const roots = new Set();
|
|
204
|
-
for (const
|
|
205
|
-
|
|
367
|
+
for (const from of [
|
|
368
|
+
...plan.moves.map((move) => move.from),
|
|
369
|
+
...plan.removals.map((removal) => removal.path),
|
|
370
|
+
]) {
|
|
371
|
+
const absolute = path.resolve(cwd, from);
|
|
206
372
|
let dir = path.dirname(absolute);
|
|
207
373
|
while (dir.startsWith(path.resolve(cwd)) &&
|
|
208
374
|
dir !== path.resolve(cwd) &&
|
|
@@ -264,10 +430,79 @@ export function rewritePendingSourceReceiptPaths(cwd, moves) {
|
|
|
264
430
|
}
|
|
265
431
|
return rewritten;
|
|
266
432
|
}
|
|
433
|
+
/** Workspace-relative paths Git tracks, or null outside a repository. */
|
|
434
|
+
function gitTrackedFiles(cwd) {
|
|
435
|
+
try {
|
|
436
|
+
return new Set(execFileSync("git", ["-C", cwd, "ls-files", "--cached", "-z"], {
|
|
437
|
+
encoding: "utf8",
|
|
438
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
439
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
440
|
+
timeout: 30_000,
|
|
441
|
+
})
|
|
442
|
+
.split("\0")
|
|
443
|
+
.filter((file) => file.length > 0)
|
|
444
|
+
.map((file) => file.replaceAll("\\", "/")));
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Bind every moved file Git does not track to its bytes with a
|
|
452
|
+
* pending-source receipt, the record kb_upsert writes for the files it
|
|
453
|
+
* creates. `kibi sync` compiles tracked sources plus receipted ones, so the
|
|
454
|
+
* moved knowledge compiles at once; Kibi never stages anything itself, and
|
|
455
|
+
* each receipt retires when Git starts tracking its file. Later migration
|
|
456
|
+
* rewrites rebind the receipt they find. Covers files a previous,
|
|
457
|
+
* interrupted run moved too. Returns the untracked destinations.
|
|
458
|
+
*/
|
|
459
|
+
// implements REQ-cli-schema-migration, REQ-core-atomic-upsert-persistence
|
|
460
|
+
export function writeMovedSourceReceipts(cwd, moves) {
|
|
461
|
+
const tracked = gitTrackedFiles(cwd);
|
|
462
|
+
if (tracked === null)
|
|
463
|
+
return [];
|
|
464
|
+
const untracked = [];
|
|
465
|
+
for (const move of moves) {
|
|
466
|
+
if (tracked.has(move.to))
|
|
467
|
+
continue;
|
|
468
|
+
const absolute = path.resolve(cwd, move.to);
|
|
469
|
+
let bytes;
|
|
470
|
+
try {
|
|
471
|
+
bytes = readFileSync(absolute);
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
writePendingSourceReceipt(cwd, move.to, createHash("sha256").update(bytes).digest("hex"));
|
|
477
|
+
untracked.push(move.to);
|
|
478
|
+
}
|
|
479
|
+
return untracked;
|
|
480
|
+
}
|
|
481
|
+
// implements REQ-cli-schema-migration
|
|
482
|
+
function isEmptySymbolsStub(filePath) {
|
|
483
|
+
try {
|
|
484
|
+
const statements = readFileSync(filePath, "utf8")
|
|
485
|
+
.split(/\r?\n/)
|
|
486
|
+
.map((line) => line.trim())
|
|
487
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
488
|
+
return (statements.length === 1 &&
|
|
489
|
+
/^symbols:\s*\[\s*\]$/.test(statements[0] ?? ""));
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
// implements REQ-cli-schema-migration
|
|
496
|
+
function sourceSchemaVersion(legacyVersion, existing) {
|
|
497
|
+
const known = [legacyVersion, existing?.schemaVersion].filter((version) => typeof version === "number" && Number.isInteger(version) && version > 0);
|
|
498
|
+
return known.length > 0 ? Math.min(...known) : LATEST_KB_SCHEMA_VERSION;
|
|
499
|
+
}
|
|
267
500
|
/** True when the workspace still needs the legacy storage migration. */
|
|
268
501
|
export function needsLegacyStorageMigration(cwd) {
|
|
269
502
|
const plan = planLegacyStorageMigration(cwd);
|
|
270
503
|
return (plan.moves.length > 0 ||
|
|
504
|
+
plan.removals.length > 0 ||
|
|
505
|
+
plan.blockers.length > 0 ||
|
|
271
506
|
plan.legacyConfig === "present" ||
|
|
272
507
|
plan.legacyConfig === "malformed");
|
|
273
508
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAmEA,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,uFAAuF;IACvF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AA+JD,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,OAAO,EACzB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,CAUR;AAsOD,wBAAgB,+BAA+B,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAKtE;AAGD,wBAAsB,cAAc,CAClC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA2Y/B"}
|