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.
Files changed (164) hide show
  1. package/dist/cli-operation-metadata.d.ts +1 -1
  2. package/dist/cli-operation-metadata.js +1 -1
  3. package/dist/cli-register-foundation.d.ts.map +1 -1
  4. package/dist/cli-register-foundation.js +1 -0
  5. package/dist/commands/check.d.ts +0 -1
  6. package/dist/commands/check.d.ts.map +1 -1
  7. package/dist/commands/check.js +47 -111
  8. package/dist/commands/doctor.d.ts.map +1 -1
  9. package/dist/commands/doctor.js +107 -5
  10. package/dist/commands/init-helpers.d.ts +14 -1
  11. package/dist/commands/init-helpers.d.ts.map +1 -1
  12. package/dist/commands/init-helpers.js +49 -10
  13. package/dist/commands/init.d.ts +1 -0
  14. package/dist/commands/init.d.ts.map +1 -1
  15. package/dist/commands/init.js +27 -4
  16. package/dist/commands/legacy-storage-migration.d.ts +46 -1
  17. package/dist/commands/legacy-storage-migration.d.ts.map +1 -1
  18. package/dist/commands/legacy-storage-migration.js +244 -9
  19. package/dist/commands/migrate.d.ts.map +1 -1
  20. package/dist/commands/migrate.js +150 -166
  21. package/dist/commands/sync/manifest.d.ts.map +1 -1
  22. package/dist/commands/sync/manifest.js +9 -0
  23. package/dist/commands/sync/persistence.d.ts.map +1 -1
  24. package/dist/commands/sync/persistence.js +4 -0
  25. package/dist/decision-firmness.d.ts +39 -0
  26. package/dist/decision-firmness.d.ts.map +1 -0
  27. package/dist/decision-firmness.js +115 -0
  28. package/dist/extractors/markdown.d.ts +2 -0
  29. package/dist/extractors/markdown.d.ts.map +1 -1
  30. package/dist/extractors/markdown.js +14 -0
  31. package/dist/extractors/symbols-coordinator.js +1 -0
  32. package/dist/intent-search.d.ts.map +1 -1
  33. package/dist/intent-search.js +28 -6
  34. package/dist/operations/migration/inventory-rederive.d.ts +40 -1
  35. package/dist/operations/migration/inventory-rederive.d.ts.map +1 -1
  36. package/dist/operations/migration/inventory-rederive.js +99 -8
  37. package/dist/operations/migration/requirement-status.d.ts +25 -0
  38. package/dist/operations/migration/requirement-status.d.ts.map +1 -0
  39. package/dist/operations/migration/requirement-status.js +105 -0
  40. package/dist/operations/migration/schema6.d.ts +27 -0
  41. package/dist/operations/migration/schema6.d.ts.map +1 -1
  42. package/dist/operations/migration/schema6.js +308 -7
  43. package/dist/operations/migration/supersedes-reason.d.ts +26 -0
  44. package/dist/operations/migration/supersedes-reason.d.ts.map +1 -0
  45. package/dist/operations/migration/supersedes-reason.js +69 -0
  46. package/dist/operations/migration/symbol-legacy-links.d.ts +51 -0
  47. package/dist/operations/migration/symbol-legacy-links.d.ts.map +1 -0
  48. package/dist/operations/migration/symbol-legacy-links.js +319 -0
  49. package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
  50. package/dist/operations/modeling/predicate-applyplan.js +10 -2
  51. package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
  52. package/dist/operations/modeling/predicate-loader.js +11 -0
  53. package/dist/operations/modeling/predicate-types.d.ts +32 -1
  54. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  55. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  56. package/dist/operations/modeling/suggest-predicates.js +175 -35
  57. package/dist/operations/mutation/context-warning.d.ts +12 -2
  58. package/dist/operations/mutation/context-warning.d.ts.map +1 -1
  59. package/dist/operations/mutation/context-warning.js +6 -6
  60. package/dist/operations/mutation/delete.d.ts +5 -1
  61. package/dist/operations/mutation/delete.d.ts.map +1 -1
  62. package/dist/operations/mutation/delete.js +223 -17
  63. package/dist/operations/mutation/serialization.d.ts.map +1 -1
  64. package/dist/operations/mutation/serialization.js +1 -0
  65. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  66. package/dist/operations/mutation/source-authoring.js +130 -35
  67. package/dist/operations/mutation/types.d.ts +39 -1
  68. package/dist/operations/mutation/types.d.ts.map +1 -1
  69. package/dist/operations/mutation/upsert.d.ts +17 -2
  70. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  71. package/dist/operations/mutation/upsert.js +82 -13
  72. package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
  73. package/dist/operations/mutation/validate-upsert.js +40 -8
  74. package/dist/operations/planning/apply-plan.d.ts +6 -1
  75. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  76. package/dist/operations/planning/apply-plan.js +26 -5
  77. package/dist/public/impact/diagnostics.d.ts.map +1 -1
  78. package/dist/public/impact/diagnostics.js +24 -14
  79. package/dist/public/operations/catalog.d.ts +14 -5
  80. package/dist/public/operations/catalog.d.ts.map +1 -1
  81. package/dist/public/operations/check-executor.d.ts.map +1 -1
  82. package/dist/public/operations/check-executor.js +6 -0
  83. package/dist/public/operations/contracts.d.ts.map +1 -1
  84. package/dist/public/operations/contracts.js +35 -2
  85. package/dist/public/operations/decision-firmness.d.ts +34 -0
  86. package/dist/public/operations/decision-firmness.d.ts.map +1 -0
  87. package/dist/public/operations/decision-firmness.js +216 -0
  88. package/dist/public/operations/discovery-executors.d.ts +2 -0
  89. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  90. package/dist/public/operations/discovery-executors.js +25 -4
  91. package/dist/public/operations/migration-plan.d.ts +2 -0
  92. package/dist/public/operations/migration-plan.d.ts.map +1 -1
  93. package/dist/public/operations/migration-plan.js +101 -16
  94. package/dist/public/operations/result-envelope.d.ts.map +1 -1
  95. package/dist/public/operations/result-envelope.js +27 -1
  96. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  97. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  98. package/dist/public/operations/specs/check-rules.generated.js +6 -1
  99. package/dist/public/operations/specs/check.d.ts +1 -1
  100. package/dist/public/operations/specs/mutation-schemas.d.ts +5 -0
  101. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  102. package/dist/public/operations/specs/mutation-schemas.js +5 -0
  103. package/dist/public/operations/specs/mutation.d.ts +6 -2
  104. package/dist/public/operations/specs/mutation.d.ts.map +1 -1
  105. package/dist/public/operations/specs/mutation.js +6 -2
  106. package/dist/public/operations/specs/planning.d.ts +2 -2
  107. package/dist/public/operations/specs/planning.js +2 -2
  108. package/dist/public/operations/specs/upsert.d.ts +5 -0
  109. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  110. package/dist/public/operations/symbol-granularity-check.d.ts +18 -0
  111. package/dist/public/operations/symbol-granularity-check.d.ts.map +1 -0
  112. package/dist/public/operations/symbol-granularity-check.js +175 -0
  113. package/dist/public/schemas/entity.d.ts.map +1 -1
  114. package/dist/public/schemas/entity.js +11 -0
  115. package/dist/public/skills/kibi-traceability/SKILL.md +6 -4
  116. package/dist/public/skills/kibi-usage/SKILL.md +50 -10
  117. package/dist/public/skills/kibi-usage/resources/source-authoring.md +4 -2
  118. package/dist/public/symbol-granularity.d.ts +17 -0
  119. package/dist/public/symbol-granularity.d.ts.map +1 -1
  120. package/dist/public/symbol-granularity.js +21 -0
  121. package/dist/relationships/shards.d.ts +10 -0
  122. package/dist/relationships/shards.d.ts.map +1 -1
  123. package/dist/relationships/shards.js +27 -11
  124. package/dist/schemas/entity.schema.json +28 -0
  125. package/dist/search-answer.d.ts +31 -2
  126. package/dist/search-answer.d.ts.map +1 -1
  127. package/dist/search-answer.js +115 -17
  128. package/dist/traceability/evidence-model.d.ts +7 -3
  129. package/dist/traceability/evidence-model.d.ts.map +1 -1
  130. package/dist/traceability/staged-symbols-manifest.js +6 -5
  131. package/dist/traceability/validate.d.ts +14 -1
  132. package/dist/traceability/validate.d.ts.map +1 -1
  133. package/dist/traceability/validate.js +41 -0
  134. package/dist/types/entities.d.ts +4 -0
  135. package/dist/types/entities.d.ts.map +1 -1
  136. package/dist/utils/kb-manifest.d.ts +6 -0
  137. package/dist/utils/kb-manifest.d.ts.map +1 -1
  138. package/dist/utils/kb-manifest.js +6 -0
  139. package/dist/utils/rule-registry.generated.d.ts +26 -0
  140. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  141. package/dist/utils/rule-registry.generated.js +31 -0
  142. package/dist/utils/schema-version.d.ts +1 -1
  143. package/dist/utils/schema-version.d.ts.map +1 -1
  144. package/dist/utils/schema-version.js +3 -1
  145. package/package.json +3 -3
  146. package/schema/entities.pl +6 -0
  147. package/src/public/impact/diagnostics.ts +29 -17
  148. package/src/public/operations/check-executor.ts +13 -0
  149. package/src/public/operations/contracts.ts +39 -2
  150. package/src/public/operations/decision-firmness.ts +280 -0
  151. package/src/public/operations/discovery-executors.ts +29 -4
  152. package/src/public/operations/migration-plan.ts +100 -19
  153. package/src/public/operations/result-envelope.ts +34 -1
  154. package/src/public/operations/specs/check-rules.generated.ts +6 -1
  155. package/src/public/operations/specs/mutation-schemas.ts +6 -0
  156. package/src/public/operations/specs/mutation.ts +7 -2
  157. package/src/public/operations/specs/planning.ts +2 -2
  158. package/src/public/operations/symbol-granularity-check.ts +233 -0
  159. package/src/public/schemas/entity.ts +12 -0
  160. package/src/public/skills/kibi-traceability/SKILL.md +6 -4
  161. package/src/public/skills/kibi-usage/SKILL.md +50 -10
  162. package/src/public/skills/kibi-usage/resources/source-authoring.md +4 -2
  163. package/src/public/symbol-granularity.ts +37 -0
  164. 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 conflicts abort before any file moves;
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":"AAoCA,OAAO,EACL,KAAK,uBAAuB,EAI7B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAGL,KAAK,UAAU,EAEhB,MAAM,sBAAsB,CAAC;AAI9B;;;;;;;;;;;;GAYG;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,kEAAkE;IAClE,QAAQ,EAAE,MAAM,EAAE,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,CAqH5E;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,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,CAqDxB;AA+BD,wEAAwE;AACxE,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,oBAAoB,EAAE,GACrC,MAAM,CAwCR;AAED,wEAAwE;AACxE,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOhE;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,CAI/D"}
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 legacySymbols = legacyPaths.symbols;
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-conflict detection: a move whose destination already exists
126
- // with different content requires an operator decision.
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
- blockers.push(`Destination ${move.to} already exists; resolve the conflict manually before migrating ${move.from}.`);
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: LATEST_KB_SCHEMA_VERSION,
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 move of plan.moves) {
205
- const absolute = path.resolve(cwd, move.from);
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":"AAiEA,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;AAuKD,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,OAAO,EACzB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,CAUR;AAsXD,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,CAiW/B"}
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"}