kibi-cli 2.22.0 → 2.23.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 (142) hide show
  1. package/dist/cli-register-foundation.d.ts.map +1 -1
  2. package/dist/cli-register-foundation.js +1 -0
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +51 -3
  5. package/dist/commands/doctor.d.ts.map +1 -1
  6. package/dist/commands/doctor.js +40 -0
  7. package/dist/commands/init-helpers.d.ts +2 -1
  8. package/dist/commands/init-helpers.d.ts.map +1 -1
  9. package/dist/commands/init-helpers.js +3 -0
  10. package/dist/commands/migrate.d.ts +47 -0
  11. package/dist/commands/migrate.d.ts.map +1 -1
  12. package/dist/commands/migrate.js +82 -15
  13. package/dist/commands/sync.d.ts.map +1 -1
  14. package/dist/commands/sync.js +15 -1
  15. package/dist/extractors/markdown.d.ts.map +1 -1
  16. package/dist/extractors/markdown.js +37 -35
  17. package/dist/intent-search.d.ts.map +1 -1
  18. package/dist/intent-search.js +32 -4
  19. package/dist/operations/bootstrap/activation.d.ts +7 -1
  20. package/dist/operations/bootstrap/activation.d.ts.map +1 -1
  21. package/dist/operations/bootstrap/activation.js +27 -2
  22. package/dist/operations/bootstrap/generate.d.ts.map +1 -1
  23. package/dist/operations/bootstrap/generate.js +56 -16
  24. package/dist/operations/bootstrap/guidance-actions.d.ts.map +1 -1
  25. package/dist/operations/bootstrap/guidance-actions.js +14 -2
  26. package/dist/operations/bootstrap/guidance.d.ts.map +1 -1
  27. package/dist/operations/bootstrap/guidance.js +6 -2
  28. package/dist/operations/bootstrap/presentation.d.ts.map +1 -1
  29. package/dist/operations/bootstrap/presentation.js +75 -9
  30. package/dist/operations/bootstrap/types.d.ts +6 -0
  31. package/dist/operations/bootstrap/types.d.ts.map +1 -1
  32. package/dist/operations/migration/kb-sources.d.ts +8 -5
  33. package/dist/operations/migration/kb-sources.d.ts.map +1 -1
  34. package/dist/operations/migration/kb-sources.js +50 -14
  35. package/dist/operations/migration/schema6.d.ts.map +1 -1
  36. package/dist/operations/migration/schema6.js +11 -2
  37. package/dist/operations/migration/source-paths.d.ts +18 -1
  38. package/dist/operations/migration/source-paths.d.ts.map +1 -1
  39. package/dist/operations/migration/source-paths.js +111 -16
  40. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  41. package/dist/operations/modeling/suggest-predicates.js +22 -7
  42. package/dist/operations/mutation/requirement-semantics.d.ts +16 -0
  43. package/dist/operations/mutation/requirement-semantics.d.ts.map +1 -1
  44. package/dist/operations/mutation/requirement-semantics.js +50 -0
  45. package/dist/operations/mutation/strict-fact.js +1 -1
  46. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  47. package/dist/operations/mutation/upsert.js +2 -1
  48. package/dist/operations/mutation/validation.d.ts.map +1 -1
  49. package/dist/operations/mutation/validation.js +43 -1
  50. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  51. package/dist/operations/planning/apply-plan.js +38 -9
  52. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  53. package/dist/operations/semantic-advisor/analyze-prose.js +49 -17
  54. package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -1
  55. package/dist/operations/semantic-advisor/ingestion-boundary.js +25 -1
  56. package/dist/operations/semantic-advisor/stored-ledger-drift.d.ts +19 -0
  57. package/dist/operations/semantic-advisor/stored-ledger-drift.d.ts.map +1 -0
  58. package/dist/operations/semantic-advisor/stored-ledger-drift.js +78 -0
  59. package/dist/public/impact/analyzer.d.ts.map +1 -1
  60. package/dist/public/impact/analyzer.js +7 -1
  61. package/dist/public/impact/diagnostics.d.ts.map +1 -1
  62. package/dist/public/impact/diagnostics.js +16 -3
  63. package/dist/public/impact/types.d.ts +2 -0
  64. package/dist/public/impact/types.d.ts.map +1 -1
  65. package/dist/public/operations/catalog.d.ts +7 -2
  66. package/dist/public/operations/catalog.d.ts.map +1 -1
  67. package/dist/public/operations/check-executor.d.ts.map +1 -1
  68. package/dist/public/operations/check-executor.js +10 -3
  69. package/dist/public/operations/check-helpers.d.ts +10 -0
  70. package/dist/public/operations/check-helpers.d.ts.map +1 -1
  71. package/dist/public/operations/check-helpers.js +54 -0
  72. package/dist/public/operations/contracts.d.ts.map +1 -1
  73. package/dist/public/operations/contracts.js +1 -0
  74. package/dist/public/operations/decision-firmness.d.ts +3 -1
  75. package/dist/public/operations/decision-firmness.d.ts.map +1 -1
  76. package/dist/public/operations/decision-firmness.js +13 -3
  77. package/dist/public/operations/discovery-executors.d.ts +18 -0
  78. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  79. package/dist/public/operations/discovery-executors.js +77 -3
  80. package/dist/public/operations/migration-plan.d.ts +13 -0
  81. package/dist/public/operations/migration-plan.d.ts.map +1 -1
  82. package/dist/public/operations/migration-plan.js +29 -1
  83. package/dist/public/operations/schema6-check-actions.d.ts +4 -0
  84. package/dist/public/operations/schema6-check-actions.d.ts.map +1 -1
  85. package/dist/public/operations/schema6-check-actions.js +39 -2
  86. package/dist/public/operations/source-path-dangling.d.ts +1 -1
  87. package/dist/public/operations/source-path-dangling.d.ts.map +1 -1
  88. package/dist/public/operations/source-path-dangling.js +11 -2
  89. package/dist/public/operations/specs/composite.d.ts +1 -1
  90. package/dist/public/operations/specs/discovery.d.ts +5 -0
  91. package/dist/public/operations/specs/discovery.d.ts.map +1 -1
  92. package/dist/public/operations/specs/discovery.js +5 -0
  93. package/dist/public/operations/specs/planning.d.ts +1 -1
  94. package/dist/public/operations/specs/planning.js +1 -1
  95. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  96. package/dist/public/operations/specs/reporting.js +6 -4
  97. package/dist/public/operations/specs/semantic.d.ts +1 -1
  98. package/dist/public/operations/specs/semantic.d.ts.map +1 -1
  99. package/dist/public/operations/specs/semantic.js +30 -3
  100. package/dist/public/skills/kibi-bootstrap/SKILL.md +3 -2
  101. package/dist/public/skills/kibi-usage/SKILL.md +13 -5
  102. package/dist/public/skills/kibi-usage/resources/workflows.md +3 -1
  103. package/dist/search-answer.d.ts +14 -0
  104. package/dist/search-answer.d.ts.map +1 -1
  105. package/dist/search-answer.js +42 -4
  106. package/dist/traceability/git-change-snapshot.d.ts +46 -0
  107. package/dist/traceability/git-change-snapshot.d.ts.map +1 -1
  108. package/dist/traceability/git-change-snapshot.js +187 -3
  109. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  110. package/dist/traceability/staged-diagnostics.js +5 -6
  111. package/dist/traceability/staged-inventory-drift.d.ts +24 -0
  112. package/dist/traceability/staged-inventory-drift.d.ts.map +1 -0
  113. package/dist/traceability/staged-inventory-drift.js +84 -0
  114. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  115. package/dist/traceability/staged-symbols-manifest.js +1 -2
  116. package/dist/utils/content-freshness.d.ts +9 -0
  117. package/dist/utils/content-freshness.d.ts.map +1 -1
  118. package/dist/utils/content-freshness.js +33 -0
  119. package/dist/utils/merge-driver-setup.d.ts +10 -0
  120. package/dist/utils/merge-driver-setup.d.ts.map +1 -0
  121. package/dist/utils/merge-driver-setup.js +69 -0
  122. package/dist/utils/rule-registry.generated.d.ts +1 -1
  123. package/dist/utils/rule-registry.generated.js +1 -1
  124. package/package.json +2 -2
  125. package/src/public/impact/analyzer.ts +13 -1
  126. package/src/public/impact/diagnostics.ts +20 -3
  127. package/src/public/impact/types.ts +2 -0
  128. package/src/public/operations/check-executor.ts +16 -4
  129. package/src/public/operations/check-helpers.ts +65 -0
  130. package/src/public/operations/contracts.ts +1 -0
  131. package/src/public/operations/decision-firmness.ts +18 -2
  132. package/src/public/operations/discovery-executors.ts +113 -3
  133. package/src/public/operations/migration-plan.ts +47 -1
  134. package/src/public/operations/schema6-check-actions.ts +43 -2
  135. package/src/public/operations/source-path-dangling.ts +12 -2
  136. package/src/public/operations/specs/discovery.ts +6 -0
  137. package/src/public/operations/specs/planning.ts +1 -1
  138. package/src/public/operations/specs/reporting.ts +6 -4
  139. package/src/public/operations/specs/semantic.ts +41 -4
  140. package/src/public/skills/kibi-bootstrap/SKILL.md +3 -2
  141. package/src/public/skills/kibi-usage/SKILL.md +13 -5
  142. package/src/public/skills/kibi-usage/resources/workflows.md +3 -1
@@ -14,7 +14,53 @@ export interface GitChangeSnapshot {
14
14
  /** Reject a changed HEAD or index tree after staged analysis. */
15
15
  assertUnchanged(): void;
16
16
  inventory: StagedPath[];
17
+ /**
18
+ * Staged merge commits only: paths whose staged content is taken unchanged
19
+ * from a merged parent. They are part of `baseTree`, so the inventory
20
+ * holds only what the merge itself wrote (its conflict resolutions and
21
+ * edits that differ from every parent).
22
+ */
23
+ mergeInherited?: readonly string[];
24
+ /**
25
+ * Staged snapshots only: knowledge files the commit moves into the
26
+ * canonical `.kb/` layout (for example after `kibi migrate`). `baseTree`
27
+ * holds each source's committed content at its target path, so the
28
+ * inventory shows only what the commit changed inside those files.
29
+ */
30
+ renameResolved?: ReadonlyArray<{
31
+ from: string;
32
+ to: string;
33
+ }>;
17
34
  }
35
+ /**
36
+ * For a staged merge commit, the tree the merge is judged against: HEAD
37
+ * with every path the merge takes unchanged from a merged parent replaced
38
+ * by that staged content. Diffing it against the staged tree leaves only
39
+ * what the merge itself wrote, so debt inherited from the merged branch is
40
+ * not charged to the merge. Null when no merge is in progress or nothing
41
+ * is inherited.
42
+ */
43
+ export declare function mergeResolutionBase(root: string, baseTree: string, headTree: string, parents: readonly string[]): {
44
+ baseTree: string;
45
+ inherited: string[];
46
+ } | null;
47
+ /**
48
+ * For a staged snapshot, the tree a commit that relocates knowledge files
49
+ * into `.kb/` is judged against: `baseTree` with each relocated file's
50
+ * committed content placed at its new path and the old path removed.
51
+ * Diffing it against the staged tree leaves only the edits the commit made
52
+ * inside those files, so findings that already existed at the old path are
53
+ * not charged to the move. Every other change, including an edit to a file
54
+ * that was already canonical, is diffed as before. Null when nothing is
55
+ * relocated.
56
+ */
57
+ export declare function renameResolutionBase(root: string, baseTree: string, headTree: string): {
58
+ baseTree: string;
59
+ renames: Array<{
60
+ from: string;
61
+ to: string;
62
+ }>;
63
+ } | null;
18
64
  /** Capture one immutable tree from the index and inventory only Git objects. */
19
65
  export declare function captureStagedSnapshot(workspaceRoot: string): GitChangeSnapshot;
20
66
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"git-change-snapshot.d.ts","sourceRoot":"","sources":["../../src/traceability/git-change-snapshot.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAIV,UAAU,EAEX,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,CAAC;AAGpE,MAAM,WAAW,iBAAiB;IAChC,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,gEAAgE;IAChE,OAAO,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC;IACzC,uEAAuE;IACvE,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChE,iEAAiE;IACjE,eAAe,IAAI,IAAI,CAAC;IACxB,SAAS,EAAE,UAAU,EAAE,CAAC;CACzB;AA4cD,gFAAgF;AAEhF,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,MAAM,GACpB,iBAAiB,CAuBnB;AAED;;;;;GAKG;AAEH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,iBAAiB,CAyBnB"}
1
+ {"version":3,"file":"git-change-snapshot.d.ts","sourceRoot":"","sources":["../../src/traceability/git-change-snapshot.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAIV,UAAU,EAEX,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,CAAC;AAGpE,MAAM,WAAW,iBAAiB;IAChC,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,gEAAgE;IAChE,OAAO,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC;IACzC,uEAAuE;IACvE,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChE,iEAAiE;IACjE,eAAe,IAAI,IAAI,CAAC;IACxB,SAAS,EAAE,UAAU,EAAE,CAAC;IACxB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9D;AAueD;;;;;;;GAOG;AAEH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CA6ClD;AAsED;;;;;;;;;GASG;AAEH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,IAAI,CAsB3E;AAED,gFAAgF;AAEhF,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,MAAM,GACpB,iBAAiB,CAuCnB;AAED;;;;;GAKG;AAEH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,iBAAiB,CAyBnB"}
@@ -1,5 +1,8 @@
1
1
  import { execFileSync, spawnSync } from "node:child_process";
2
- import { isEntityLanePath, isSymbolsManifestPath } from "../utils/kb-paths.js";
2
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { ENTITY_LANES, isEntityLanePath, isKbKnowledgePath, isSymbolsManifestPath, } from "../utils/kb-paths.js";
3
6
  const GIT_EXEC_MAX_BUFFER = 64 * 1024 * 1024;
4
7
  function runGit(root, args) {
5
8
  try {
@@ -368,12 +371,188 @@ function snapshotFromTrees(root, baseTree, headTree, headCommit, readGit, assert
368
371
  inventory: buildInventory(root, baseTree, headTree),
369
372
  };
370
373
  }
374
+ /** The commits named by MERGE_HEAD while a merge commit is being made. */
375
+ function pendingMergeParents(root) {
376
+ let mergeHeadPath;
377
+ try {
378
+ mergeHeadPath = gitText(root, ["rev-parse", "--git-path", "MERGE_HEAD"]);
379
+ }
380
+ catch {
381
+ return [];
382
+ }
383
+ let text;
384
+ try {
385
+ text = readFileSync(path.resolve(root, mergeHeadPath), "utf8");
386
+ }
387
+ catch {
388
+ return [];
389
+ }
390
+ return text
391
+ .split(/\r?\n/)
392
+ .map((line) => line.trim())
393
+ .filter((line) => /^[a-f0-9]{40,64}$/.test(line));
394
+ }
395
+ function changedPaths(root, from, to) {
396
+ return runGit(root, ["diff", "--name-only", "-z", "--no-renames", from, to])
397
+ .toString("utf8")
398
+ .split("\0")
399
+ .filter(Boolean);
400
+ }
401
+ /**
402
+ * For a staged merge commit, the tree the merge is judged against: HEAD
403
+ * with every path the merge takes unchanged from a merged parent replaced
404
+ * by that staged content. Diffing it against the staged tree leaves only
405
+ * what the merge itself wrote, so debt inherited from the merged branch is
406
+ * not charged to the merge. Null when no merge is in progress or nothing
407
+ * is inherited.
408
+ */
409
+ // implements REQ-014
410
+ export function mergeResolutionBase(root, baseTree, headTree, parents) {
411
+ if (parents.length === 0)
412
+ return null;
413
+ const changed = changedPaths(root, baseTree, headTree);
414
+ if (changed.length === 0)
415
+ return null;
416
+ const inherited = new Set();
417
+ for (const parent of parents) {
418
+ const parentTree = treeForCommit(root, parent);
419
+ const differs = new Set(changedPaths(root, parentTree, headTree));
420
+ for (const file of changed) {
421
+ if (!differs.has(file))
422
+ inherited.add(file);
423
+ }
424
+ }
425
+ if (inherited.size === 0)
426
+ return null;
427
+ const paths = [...inherited].sort();
428
+ const listed = runGit(root, [
429
+ "ls-tree",
430
+ "-r",
431
+ "-z",
432
+ "--full-tree",
433
+ headTree,
434
+ "--",
435
+ ...paths.map(literalPathspec),
436
+ ])
437
+ .toString("utf8")
438
+ .split("\0")
439
+ .filter(Boolean);
440
+ const present = new Set();
441
+ const entries = [];
442
+ for (const row of listed) {
443
+ const tab = row.indexOf("\t");
444
+ if (tab < 0)
445
+ continue;
446
+ const [mode, , oid] = row.slice(0, tab).split(" ");
447
+ const file = row.slice(tab + 1);
448
+ present.add(file);
449
+ entries.push(`${mode} ${oid}\t${file}`);
450
+ }
451
+ const zero = "0".repeat(baseTree.length);
452
+ for (const file of paths) {
453
+ // Deleted on the merged side and absent from the staged tree.
454
+ if (!present.has(file))
455
+ entries.push(`0 ${zero}\t${file}`);
456
+ }
457
+ return {
458
+ baseTree: writeTreeWithEntries(root, baseTree, entries, "kibi-merge-base-"),
459
+ inherited: paths,
460
+ };
461
+ }
462
+ /** Write `baseTree` with `--index-info` entries applied in a scratch index. */
463
+ function writeTreeWithEntries(root, baseTree, entries, prefix) {
464
+ const scratch = mkdtempSync(path.join(tmpdir(), prefix));
465
+ const env = { ...process.env, GIT_INDEX_FILE: path.join(scratch, "index") };
466
+ try {
467
+ const git = (args, input) => execFileSync("git", [...args], {
468
+ cwd: root,
469
+ env,
470
+ maxBuffer: GIT_EXEC_MAX_BUFFER,
471
+ ...(input !== undefined ? { input } : {}),
472
+ stdio: ["pipe", "pipe", "ignore"],
473
+ });
474
+ git(["read-tree", baseTree]);
475
+ git(["update-index", "-z", "--index-info"], `${entries.join("\0")}\0`);
476
+ return git(["write-tree"]).toString("utf8").trim();
477
+ }
478
+ finally {
479
+ rmSync(scratch, { recursive: true, force: true });
480
+ }
481
+ }
482
+ function renamedPaths(root, from, to) {
483
+ return parseNameStatus(runGit(root, [
484
+ "diff",
485
+ "--name-status",
486
+ "-z",
487
+ "-M",
488
+ "--diff-filter=R",
489
+ from,
490
+ to,
491
+ ])).flatMap((row) => row.status === "R" && row.oldPath !== undefined
492
+ ? [{ from: row.oldPath, to: row.path }]
493
+ : []);
494
+ }
495
+ /**
496
+ * True when `from -> to` moves one knowledge file from outside the canonical
497
+ * layout into the same kind of canonical `.kb/` location: an entity lane
498
+ * file into the same lane, or a symbols manifest / coordinates file onto the
499
+ * canonical file of the same name (`.yml` and `.yaml` alike).
500
+ */
501
+ function isKnowledgeRelocation(from, to) {
502
+ if (isKbKnowledgePath(from) || !isKbKnowledgePath(to))
503
+ return false;
504
+ if (isSymbolsManifestPath(to)) {
505
+ const stem = (file) => path.posix.basename(file).replace(/\.ya?ml$/, "");
506
+ return stem(from) === stem(to);
507
+ }
508
+ const lane = to.split("/")[1] ?? "";
509
+ return (ENTITY_LANES.includes(lane) &&
510
+ `/${from}`.includes(`/${lane}/`));
511
+ }
512
+ /**
513
+ * For a staged snapshot, the tree a commit that relocates knowledge files
514
+ * into `.kb/` is judged against: `baseTree` with each relocated file's
515
+ * committed content placed at its new path and the old path removed.
516
+ * Diffing it against the staged tree leaves only the edits the commit made
517
+ * inside those files, so findings that already existed at the old path are
518
+ * not charged to the move. Every other change, including an edit to a file
519
+ * that was already canonical, is diffed as before. Null when nothing is
520
+ * relocated.
521
+ */
522
+ // implements REQ-014
523
+ export function renameResolutionBase(root, baseTree, headTree) {
524
+ const zero = "0".repeat(baseTree.length);
525
+ const renames = [];
526
+ const entries = [];
527
+ for (const rename of renamedPaths(root, baseTree, headTree)) {
528
+ if (!isKnowledgeRelocation(rename.from, rename.to))
529
+ continue;
530
+ const source = readTreeEntry(root, baseTree, rename.from);
531
+ if (source?.type !== "blob")
532
+ continue;
533
+ renames.push(rename);
534
+ entries.push(`0 ${zero}\t${rename.from}`);
535
+ entries.push(`${source.mode} ${source.oid}\t${rename.to}`);
536
+ }
537
+ if (renames.length === 0)
538
+ return null;
539
+ return {
540
+ baseTree: writeTreeWithEntries(root, baseTree, entries, "kibi-rename-base-"),
541
+ renames: renames.sort((left, right) => left.to.localeCompare(right.to)),
542
+ };
543
+ }
371
544
  /** Capture one immutable tree from the index and inventory only Git objects. */
372
545
  // implements REQ-014
373
546
  export function captureStagedSnapshot(workspaceRoot) {
374
547
  const headCommit = resolveHeadCommit(workspaceRoot);
375
- const baseTree = treeForCommit(workspaceRoot, headCommit);
376
548
  const headTree = gitText(workspaceRoot, ["write-tree"]);
549
+ const commitTree = treeForCommit(workspaceRoot, headCommit);
550
+ const merge = headCommit
551
+ ? mergeResolutionBase(workspaceRoot, commitTree, headTree, pendingMergeParents(workspaceRoot))
552
+ : null;
553
+ const mergeTree = merge?.baseTree ?? commitTree;
554
+ const relocation = renameResolutionBase(workspaceRoot, mergeTree, headTree);
555
+ const baseTree = relocation?.baseTree ?? mergeTree;
377
556
  const readGit = (args) => translateReadGit(workspaceRoot, args, baseTree, headTree, headCommit);
378
557
  const assertUnchanged = () => {
379
558
  const currentHead = resolveHeadCommit(workspaceRoot);
@@ -382,7 +561,12 @@ export function captureStagedSnapshot(workspaceRoot) {
382
561
  throw new Error("Git index or HEAD changed during staged analysis; retry the operation");
383
562
  }
384
563
  };
385
- return snapshotFromTrees(workspaceRoot, baseTree, headTree, headCommit, readGit, assertUnchanged);
564
+ const snapshot = snapshotFromTrees(workspaceRoot, baseTree, headTree, headCommit, readGit, assertUnchanged);
565
+ return {
566
+ ...snapshot,
567
+ ...(merge ? { mergeInherited: merge.inherited } : {}),
568
+ ...(relocation ? { renameResolved: relocation.renames } : {}),
569
+ };
386
570
  }
387
571
  /**
388
572
  * Capture changes between explicit revisions. The caller chooses `base`; for
@@ -1 +1 @@
1
- {"version":3,"file":"staged-diagnostics.d.ts","sourceRoot":"","sources":["../../src/traceability/staged-diagnostics.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAGL,KAAK,kBAAkB,EAKxB,MAAM,qBAAqB,CAAC;AAQ7B,MAAM,MAAM,sBAAsB,GAC9B,8BAA8B,GAC9B,wBAAwB,GACxB,8BAA8B,GAC9B,+BAA+B,GAC/B,iCAAiC,GACjC,oCAAoC,GACpC,gCAAgC,GAChC,0BAA0B,GAC1B,2BAA2B,GAC3B,6BAA6B,GAC7B,yBAAyB,GACzB,wCAAwC,GACxC,wBAAwB,CAAC;AAE7B,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,uDAAuD;IACvD,EAAE,EAAE,sBAAsB,CAAC;IAC3B,QAAQ,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACxC,iEAAiE;IACjE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,gDAAgD;IAChD,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAwID,wBAAgB,wCAAwC,CACtD,QAAQ,EAAE,kBAAkB,GAC3B,oBAAoB,CAoBtB;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,kBAAkB,EAC5B,mBAAmB,GAAE,MAAmC,GACvD,oBAAoB,EAAE,CAiDxB"}
1
+ {"version":3,"file":"staged-diagnostics.d.ts","sourceRoot":"","sources":["../../src/traceability/staged-diagnostics.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAGL,KAAK,kBAAkB,EAKxB,MAAM,qBAAqB,CAAC;AAQ7B,MAAM,MAAM,sBAAsB,GAC9B,8BAA8B,GAC9B,wBAAwB,GACxB,8BAA8B,GAC9B,+BAA+B,GAC/B,iCAAiC,GACjC,oCAAoC,GACpC,gCAAgC,GAChC,0BAA0B,GAC1B,2BAA2B,GAC3B,6BAA6B,GAC7B,yBAAyB,GACzB,wCAAwC,GACxC,wBAAwB,CAAC;AAE7B,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,uDAAuD;IACvD,EAAE,EAAE,sBAAsB,CAAC;IAC3B,QAAQ,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACxC,iEAAiE;IACjE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,gDAAgD;IAChD,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAsID,wBAAgB,wCAAwC,CACtD,QAAQ,EAAE,kBAAkB,GAC3B,oBAAoB,CAoBtB;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,kBAAkB,EAC5B,mBAAmB,GAAE,MAAmC,GACvD,oBAAoB,EAAE,CAiDxB"}
@@ -8,13 +8,12 @@ function deriveCoordinatesPath(symbolsManifestPath) {
8
8
  function formatFileList(paths) {
9
9
  return paths.join(", ");
10
10
  }
11
- /** Max symbol names rendered per detail line before "+N more". */
12
- const MAX_DETAIL_NAMES = 6;
11
+ /**
12
+ * Every name, in order: the gate output is the list a person works through,
13
+ * so it is never cut short.
14
+ */
13
15
  function formatNameList(names) {
14
- const shown = names.slice(0, MAX_DETAIL_NAMES);
15
- const overflow = names.length - shown.length;
16
- const rendered = shown.join(", ");
17
- return overflow > 0 ? `${rendered} … and ${overflow} more` : rendered;
16
+ return names.join(", ");
18
17
  }
19
18
  function symbolsManifestFileDetails(evidence, paths) {
20
19
  const pathSet = new Set(paths);
@@ -0,0 +1,24 @@
1
+ import type { ExtractionResult } from "../extractors/markdown.js";
2
+ /** A staged requirement whose semantic inventory no longer matches its prose. */
3
+ export declare const STAGED_INVENTORY_DRIFT_RULE = "semantic-inventory-drift";
4
+ export type StagedInventoryDriftViolation = Readonly<{
5
+ rule: typeof STAGED_INVENTORY_DRIFT_RULE;
6
+ entityId: string;
7
+ source: string;
8
+ message: string;
9
+ errors: readonly string[];
10
+ }>;
11
+ /**
12
+ * The staged requirements that declare a semantic inventory contract (now
13
+ * or in the base tree) whose inventory no longer matches their prose. The
14
+ * next `kibi sync` refuses them under the proposition-complete boundary;
15
+ * the gate applies the same test, so drift is caught at the commit that
16
+ * introduces it instead of after a later merge. Requirements without a
17
+ * contract are left to sync, which decides from its own baseline.
18
+ */
19
+ export declare function stagedInventoryDriftViolations(input: {
20
+ stagedPaths: ReadonlySet<string>;
21
+ snapshotEntities: readonly ExtractionResult[];
22
+ readBase: (path: string) => string | null;
23
+ }): StagedInventoryDriftViolation[];
24
+ //# sourceMappingURL=staged-inventory-drift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staged-inventory-drift.d.ts","sourceRoot":"","sources":["../../src/traceability/staged-inventory-drift.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAKlE,iFAAiF;AAEjF,eAAO,MAAM,2BAA2B,6BAA6B,CAAC;AAGtE,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,IAAI,EAAE,OAAO,2BAA2B,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B,CAAC,CAAC;AAgCH;;;;;;;GAOG;AAEH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE;IACpD,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,gBAAgB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAC3C,GAAG,6BAA6B,EAAE,CA8BlC"}
@@ -0,0 +1,84 @@
1
+ /*
2
+ Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ Copyright (C) 2026 Piotr Franczyk
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+ import { extractFromMarkdownString } from "../extractors/markdown.js";
19
+ import { analyzeSemanticAdvisorInput } from "../operations/semantic-advisor/analyze-prose.js";
20
+ import { validateSemanticInventoryBoundary } from "../operations/semantic-advisor/ingestion-boundary.js";
21
+ /** A staged requirement whose semantic inventory no longer matches its prose. */
22
+ // implements REQ-kibi-proposition-complete-ingestion
23
+ export const STAGED_INVENTORY_DRIFT_RULE = "semantic-inventory-drift";
24
+ function advertisesContract(entity) {
25
+ return (entity.semantic_inventory_version !== undefined ||
26
+ entity.semantic_source_hash !== undefined ||
27
+ entity.semantic_inventory !== undefined);
28
+ }
29
+ function payloadOf(result) {
30
+ return {
31
+ type: result.entity.type,
32
+ id: result.entity.id,
33
+ properties: result.entity,
34
+ relationships: result.relationships,
35
+ };
36
+ }
37
+ function baseResult(path, readBase) {
38
+ const content = readBase(path);
39
+ if (content === null)
40
+ return null;
41
+ try {
42
+ return extractFromMarkdownString(content, path);
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ /**
49
+ * The staged requirements that declare a semantic inventory contract (now
50
+ * or in the base tree) whose inventory no longer matches their prose. The
51
+ * next `kibi sync` refuses them under the proposition-complete boundary;
52
+ * the gate applies the same test, so drift is caught at the commit that
53
+ * introduces it instead of after a later merge. Requirements without a
54
+ * contract are left to sync, which decides from its own baseline.
55
+ */
56
+ // implements REQ-kibi-proposition-complete-ingestion
57
+ export function stagedInventoryDriftViolations(input) {
58
+ const violations = [];
59
+ for (const result of input.snapshotEntities) {
60
+ if (result.entity.type !== "req")
61
+ continue;
62
+ const source = result.entity.source;
63
+ if (!input.stagedPaths.has(source))
64
+ continue;
65
+ const payload = payloadOf(result);
66
+ const semantic = analyzeSemanticAdvisorInput({ payload });
67
+ const boundary = validateSemanticInventoryBoundary(payload, result.relationships, semantic.receipt);
68
+ if (boundary.errors.length === 0)
69
+ continue;
70
+ const base = baseResult(source, input.readBase);
71
+ const enforce = advertisesContract(result.entity) ||
72
+ (base !== null && advertisesContract(base.entity));
73
+ if (!enforce)
74
+ continue;
75
+ violations.push({
76
+ rule: STAGED_INVENTORY_DRIFT_RULE,
77
+ entityId: result.entity.id,
78
+ source,
79
+ errors: boundary.errors,
80
+ message: `${result.entity.id} (${source}): the semantic inventory does not match the requirement's prose, so the next kibi sync rejects it: ${boundary.errors.join("; ")}. Run 'kibi migrate' to re-derive it, or kb_model mode analyze with the complete prose and keep its inventory contract.`,
81
+ });
82
+ }
83
+ return violations.sort((left, right) => left.entityId.localeCompare(right.entityId));
84
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"staged-symbols-manifest.d.ts","sourceRoot":"","sources":["../../src/traceability/staged-symbols-manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,oBAAoB,EAErB,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EACL,KAAK,wBAAwB,EAG9B,MAAM,qCAAqC,CAAC;AAI7C,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA0ClD,MAAM,WAAW,+BAA+B;IAC9C,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,cAAc,CAAC;IACtD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,WAAW,CAAC,EAAE,mCAAmC,EAAE,CAAC;CACrD;AAED,MAAM,WAAW,qCAAqC;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC5D,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AA6ED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,wBAAwB,GACpC,wBAAwB,GAAG,IAAI,CAOjC;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GAAG,MAAM,GAAG,IAAI,CAMhB;AAqTD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE;IACnD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACnD,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,oBAAoB,CAAC;CAC7E,GAAG,+BAA+B,CAoLlC;AAED,wBAAgB,4CAA4C,CAAC,OAAO,EAAE;IACpE,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACpD,GAAG,qCAAqC,CAqExC"}
1
+ {"version":3,"file":"staged-symbols-manifest.d.ts","sourceRoot":"","sources":["../../src/traceability/staged-symbols-manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,oBAAoB,EAErB,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EACL,KAAK,wBAAwB,EAG9B,MAAM,qCAAqC,CAAC;AAI7C,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA0ClD,MAAM,WAAW,+BAA+B;IAC9C,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,cAAc,CAAC;IACtD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,WAAW,CAAC,EAAE,mCAAmC,EAAE,CAAC;CACrD;AAED,MAAM,WAAW,qCAAqC;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC5D,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AA6ED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,wBAAwB,GACpC,wBAAwB,GAAG,IAAI,CAOjC;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GAAG,MAAM,GAAG,IAAI,CAMhB;AAoTD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE;IACnD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACnD,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,oBAAoB,CAAC;CAC7E,GAAG,+BAA+B,CAoLlC;AAED,wBAAgB,4CAA4C,CAAC,OAAO,EAAE;IACpE,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACpD,GAAG,qCAAqC,CAqExC"}
@@ -281,8 +281,7 @@ function diffSymbolEvidence(sourcePath, expectedSymbols, stagedSymbols) {
281
281
  if (missing.length === 0 && extra.length === 0) {
282
282
  return null;
283
283
  }
284
- // Keep the complete lists: human-facing renderers truncate (with an
285
- // accurate "… and N more"), while JSON evidence carries every name.
284
+ // Keep the complete lists: the gate output names every symbol.
286
285
  return {
287
286
  path: sourcePath,
288
287
  expectedCount: expectedSymbols.length,
@@ -9,5 +9,14 @@ export declare function confirmSourceNewerReasons(workspaceRoot: string, storePa
9
9
  kept: Reason[];
10
10
  dropped: Reason[];
11
11
  };
12
+ /**
13
+ * The `meta` freshness block reporting operations return (kb_graph,
14
+ * kb_find_gaps, kb_coverage) comes straight from Prolog, which compares file
15
+ * times only. Apply the same content confirmation kb_status does, so a
16
+ * checkout or `touch` that changed no byte is not reported as stale.
17
+ */
18
+ export declare function confirmMetaFreshness<T extends {
19
+ readonly meta?: Readonly<Record<string, unknown>>;
20
+ }>(payload: T, workspaceRoot: string): T;
12
21
  export {};
13
22
  //# sourceMappingURL=content-freshness.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"content-freshness.d.ts","sourceRoot":"","sources":["../../src/utils/content-freshness.ts"],"names":[],"mappings":"AAYA,uDAAuD;AACvD,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAIxD,CAAC;AAEH,KAAK,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAyJhD;;;GAGG;AAEH,wBAAgB,yBAAyB,CACvC,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAkBvC"}
1
+ {"version":3,"file":"content-freshness.d.ts","sourceRoot":"","sources":["../../src/utils/content-freshness.ts"],"names":[],"mappings":"AAYA,uDAAuD;AACvD,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAIxD,CAAC;AAEH,KAAK,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAyJhD;;;GAGG;AAEH,wBAAgB,yBAAyB,CACvC,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAkBvC;AAED;;;;;GAKG;AAEH,wBAAgB,oBAAoB,CAClC,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAAE,EAC/D,OAAO,EAAE,CAAC,EAAE,aAAa,EAAE,MAAM,GAAG,CAAC,CAoCtC"}
@@ -169,3 +169,36 @@ export function confirmSourceNewerReasons(workspaceRoot, storePath, reasons) {
169
169
  }
170
170
  return { kept, dropped };
171
171
  }
172
+ /**
173
+ * The `meta` freshness block reporting operations return (kb_graph,
174
+ * kb_find_gaps, kb_coverage) comes straight from Prolog, which compares file
175
+ * times only. Apply the same content confirmation kb_status does, so a
176
+ * checkout or `touch` that changed no byte is not reported as stale.
177
+ */
178
+ // implements REQ-cli-status-pre-first-sync
179
+ export function confirmMetaFreshness(payload, workspaceRoot) {
180
+ const meta = payload.meta;
181
+ if (meta === undefined ||
182
+ typeof meta.kbPath !== "string" ||
183
+ !Array.isArray(meta.staleReasons))
184
+ return payload;
185
+ const reasons = meta.staleReasons.filter((reason) => record(reason) !== null);
186
+ const { kept, dropped } = confirmSourceNewerReasons(workspaceRoot, meta.kbPath, reasons);
187
+ if (dropped.length === 0)
188
+ return payload;
189
+ const allConfirmed = kept.length === 0 &&
190
+ meta.staleReasonsTruncated !== true &&
191
+ meta.syncState === "stale";
192
+ return {
193
+ ...payload,
194
+ meta: {
195
+ ...meta,
196
+ staleReasons: kept,
197
+ // A truncated list counts more reasons than it carries.
198
+ staleReasonCount: Math.max(kept.length, (typeof meta.staleReasonCount === "number"
199
+ ? meta.staleReasonCount
200
+ : reasons.length) - dropped.length),
201
+ ...(allConfirmed ? { syncState: "fresh", dirty: false } : {}),
202
+ },
203
+ };
204
+ }
@@ -0,0 +1,10 @@
1
+ /** How to route the authored manifests to `kibi merge-driver`. */
2
+ export declare const MERGE_DRIVER_SETUP = "Add to .gitattributes: \".kb/symbols.yaml merge=kibi\" and \".kb/relationships/*.yaml merge=kibi\"; then run: git config merge.kibi.driver \"npm exec --no -- kibi merge-driver %O %A %B\" (see docs/cli-reference.md#kibi-merge-driver-base-current-other).";
3
+ /**
4
+ * What keeps Git from resolving `.kb/symbols.yaml` with `kibi merge-driver`:
5
+ * the attribute that routes the file to it, and the driver command. Empty
6
+ * when both are set, and null outside a Git work tree or while the
7
+ * manifest holds no symbol records (nothing to conflict on yet).
8
+ */
9
+ export declare function manifestMergeDriverGaps(cwd: string): string[] | null;
10
+ //# sourceMappingURL=merge-driver-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-driver-setup.d.ts","sourceRoot":"","sources":["../../src/utils/merge-driver-setup.ts"],"names":[],"mappings":"AAsBA,kEAAkE;AAElE,eAAO,MAAM,kBAAkB,iQAC2N,CAAC;AAwB3P;;;;;GAKG;AAEH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAiBpE"}
@@ -0,0 +1,69 @@
1
+ /*
2
+ Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ Copyright (C) 2026 Piotr Franczyk
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+ import { execFileSync } from "node:child_process";
19
+ import { readFileSync } from "node:fs";
20
+ import path from "node:path";
21
+ /** How to route the authored manifests to `kibi merge-driver`. */
22
+ // implements REQ-cli-doctor
23
+ export const MERGE_DRIVER_SETUP = 'Add to .gitattributes: ".kb/symbols.yaml merge=kibi" and ".kb/relationships/*.yaml merge=kibi"; then run: git config merge.kibi.driver "npm exec --no -- kibi merge-driver %O %A %B" (see docs/cli-reference.md#kibi-merge-driver-base-current-other).';
24
+ function gitOutput(cwd, args) {
25
+ try {
26
+ return execFileSync("git", [...args], {
27
+ cwd,
28
+ encoding: "utf8",
29
+ stdio: ["ignore", "pipe", "ignore"],
30
+ }).trim();
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ function hasSymbolRecords(cwd) {
37
+ try {
38
+ return /^\s*-\s+id\s*:/m.test(readFileSync(path.join(cwd, ".kb", "symbols.yaml"), "utf8"));
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }
44
+ /**
45
+ * What keeps Git from resolving `.kb/symbols.yaml` with `kibi merge-driver`:
46
+ * the attribute that routes the file to it, and the driver command. Empty
47
+ * when both are set, and null outside a Git work tree or while the
48
+ * manifest holds no symbol records (nothing to conflict on yet).
49
+ */
50
+ // implements REQ-cli-doctor
51
+ export function manifestMergeDriverGaps(cwd) {
52
+ if (gitOutput(cwd, ["rev-parse", "--is-inside-work-tree"]) !== "true") {
53
+ return null;
54
+ }
55
+ if (!hasSymbolRecords(cwd))
56
+ return null;
57
+ const attribute = gitOutput(cwd, [
58
+ "check-attr",
59
+ "merge",
60
+ "--",
61
+ ".kb/symbols.yaml",
62
+ ]);
63
+ const routed = attribute?.endsWith(": merge: kibi") === true;
64
+ const driver = gitOutput(cwd, ["config", "--get", "merge.kibi.driver"]);
65
+ return [
66
+ ...(routed ? [] : [".kb/symbols.yaml is not routed to merge=kibi"]),
67
+ ...(driver ? [] : ["merge.kibi.driver is not set"]),
68
+ ];
69
+ }
@@ -1,6 +1,6 @@
1
1
  export declare const GENERATED_RULES: readonly [{
2
2
  readonly name: "must-priority-coverage";
3
- readonly description: "Every must-priority requirement must have a scenario and a test";
3
+ readonly description: "Every current must-priority requirement (not superseded, retired, or closed without the implemented tag) must have a scenario and a test";
4
4
  readonly enforcementClass: "canonical";
5
5
  readonly category: "coverage";
6
6
  }, {
@@ -10,7 +10,7 @@
10
10
  export const GENERATED_RULES = [
11
11
  {
12
12
  name: "must-priority-coverage",
13
- description: "Every must-priority requirement must have a scenario and a test",
13
+ description: "Every current must-priority requirement (not superseded, retired, or closed without the implemented tag) must have a scenario and a test",
14
14
  enforcementClass: "canonical",
15
15
  category: "coverage",
16
16
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-cli",
3
- "version": "2.22.0",
3
+ "version": "2.23.0",
4
4
  "type": "module",
5
5
  "description": "Kibi CLI for knowledge base management",
6
6
  "engines": {
@@ -235,7 +235,7 @@
235
235
  "fast-glob": "^3.2.12",
236
236
  "gray-matter": "^4.0.3",
237
237
  "js-yaml": "^4.1.0",
238
- "kibi-core": "^0.22.0",
238
+ "kibi-core": "^0.22.1",
239
239
  "kibi-plugin-builtin": "^0.5.2",
240
240
  "kibi-plugin-sdk": "^0.5.0",
241
241
  "kibi-swipl": "^1.0.1",
@@ -11,6 +11,7 @@ import { captureStagedSnapshot } from "../../traceability/git-change-snapshot.js
11
11
  import { readSnapshotKnowledge } from "../../traceability/snapshot-knowledge.js";
12
12
  import type { ExtractedSymbol } from "../../traceability/symbol-extract.js";
13
13
  import { extractSymbolsFromStagedFileAsync } from "../../traceability/symbol-extract.js";
14
+ import { isTraceabilityRelationshipType } from "../symbol-granularity.js";
14
15
  import {
15
16
  createSemanticReviewDiagnostics,
16
17
  createSymbolGranularityDiagnostics,
@@ -153,7 +154,18 @@ export async function analyzeChangedFileImpact(
153
154
  manifestResults: activeManifestResults,
154
155
  symbolsByFile,
155
156
  }),
156
- ...createSemanticReviewDiagnostics({ symbolsByFile }),
157
+ ...createSemanticReviewDiagnostics({
158
+ symbolsByFile,
159
+ linkedSourceFiles: new Set(
160
+ activeManifestResults
161
+ .filter((result) =>
162
+ result.relationships.some((relationship) =>
163
+ isTraceabilityRelationshipType(relationship.type),
164
+ ),
165
+ )
166
+ .map((result) => result.sourceFile as string),
167
+ ),
168
+ }),
157
169
  ];
158
170
  const maxDiagnostics = options.maxDiagnostics;
159
171
  const cappedDiagnostics =