kibi-cli 2.36.0 → 2.39.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 (224) hide show
  1. package/dist/cli-json-command.d.ts.map +1 -1
  2. package/dist/cli-json-command.js +4 -0
  3. package/dist/cli-operation-metadata.d.ts +1 -1
  4. package/dist/cli-operation-metadata.js +1 -1
  5. package/dist/cli-register-foundation.d.ts.map +1 -1
  6. package/dist/cli-register-foundation.js +2 -1
  7. package/dist/cli-register-maintenance.d.ts.map +1 -1
  8. package/dist/cli-register-maintenance.js +5 -0
  9. package/dist/cli-register-reporting.d.ts.map +1 -1
  10. package/dist/cli-register-reporting.js +1 -0
  11. package/dist/cli-validate.d.ts.map +1 -1
  12. package/dist/cli-validate.js +21 -2
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +19 -1
  15. package/dist/commands/aggregated-checks.d.ts.map +1 -1
  16. package/dist/commands/aggregated-checks.js +89 -16
  17. package/dist/commands/coverage.d.ts +1 -0
  18. package/dist/commands/coverage.d.ts.map +1 -1
  19. package/dist/commands/coverage.js +40 -0
  20. package/dist/commands/doctor.d.ts +5 -0
  21. package/dist/commands/doctor.d.ts.map +1 -1
  22. package/dist/commands/doctor.js +70 -35
  23. package/dist/commands/github-init.d.ts.map +1 -1
  24. package/dist/commands/github-init.js +4 -2
  25. package/dist/commands/hook-managers.d.ts +43 -0
  26. package/dist/commands/hook-managers.d.ts.map +1 -0
  27. package/dist/commands/hook-managers.js +426 -0
  28. package/dist/commands/init.d.ts +1 -0
  29. package/dist/commands/init.d.ts.map +1 -1
  30. package/dist/commands/init.js +40 -5
  31. package/dist/commands/migrate.d.ts.map +1 -1
  32. package/dist/commands/migrate.js +9 -3
  33. package/dist/commands/sync/persistence.d.ts.map +1 -1
  34. package/dist/commands/sync/persistence.js +49 -1
  35. package/dist/commands/telemetry.d.ts +7 -0
  36. package/dist/commands/telemetry.d.ts.map +1 -0
  37. package/dist/commands/telemetry.js +23 -0
  38. package/dist/decision-firmness.d.ts.map +1 -1
  39. package/dist/decision-firmness.js +11 -14
  40. package/dist/engine-limits.d.ts.map +1 -1
  41. package/dist/engine-limits.js +8 -10
  42. package/dist/entity-body-context.d.ts.map +1 -1
  43. package/dist/entity-body-context.js +39 -36
  44. package/dist/extractors/markdown.d.ts +6 -0
  45. package/dist/extractors/markdown.d.ts.map +1 -1
  46. package/dist/extractors/markdown.js +21 -0
  47. package/dist/extractors/symbol-coordinates.d.ts.map +1 -1
  48. package/dist/extractors/symbol-coordinates.js +23 -4
  49. package/dist/firm-decision-edit.d.ts.map +1 -1
  50. package/dist/firm-decision-edit.js +1 -3
  51. package/dist/intent-search.d.ts +5 -1
  52. package/dist/intent-search.d.ts.map +1 -1
  53. package/dist/intent-search.js +118 -33
  54. package/dist/logic/ir.d.ts.map +1 -1
  55. package/dist/logic/ir.js +82 -108
  56. package/dist/operations/bootstrap/discovery-evidence.d.ts.map +1 -1
  57. package/dist/operations/bootstrap/discovery-evidence.js +44 -2
  58. package/dist/operations/bootstrap/discovery.d.ts.map +1 -1
  59. package/dist/operations/bootstrap/discovery.js +1 -0
  60. package/dist/operations/bootstrap/guidance-actions.d.ts.map +1 -1
  61. package/dist/operations/bootstrap/guidance-actions.js +2 -1
  62. package/dist/operations/bootstrap/plugin-offers.d.ts +11 -2
  63. package/dist/operations/bootstrap/plugin-offers.d.ts.map +1 -1
  64. package/dist/operations/bootstrap/plugin-offers.js +48 -9
  65. package/dist/operations/proof/claim-bindings.d.ts.map +1 -1
  66. package/dist/operations/proof/claim-bindings.js +1 -0
  67. package/dist/operations/proof/code-certificates.d.ts +2 -0
  68. package/dist/operations/proof/code-certificates.d.ts.map +1 -1
  69. package/dist/operations/proof/code-certificates.js +34 -14
  70. package/dist/operations/proof/code-models.d.ts.map +1 -1
  71. package/dist/operations/proof/code-models.js +19 -5
  72. package/dist/operations/proof/code-runner.d.ts +16 -0
  73. package/dist/operations/proof/code-runner.d.ts.map +1 -1
  74. package/dist/operations/proof/code-runner.js +42 -6
  75. package/dist/operations/proof/explain.d.ts +63 -0
  76. package/dist/operations/proof/explain.d.ts.map +1 -1
  77. package/dist/operations/proof/explain.js +162 -0
  78. package/dist/operations/semantic-advisor/analyze-prose.d.ts +9 -1
  79. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  80. package/dist/operations/semantic-advisor/analyze-prose.js +21 -0
  81. package/dist/operations/semantic-advisor/case-preview.d.ts +24 -0
  82. package/dist/operations/semantic-advisor/case-preview.d.ts.map +1 -0
  83. package/dist/operations/semantic-advisor/case-preview.js +216 -0
  84. package/dist/operations/semantic-advisor/shared.d.ts.map +1 -1
  85. package/dist/operations/semantic-advisor/shared.js +3 -0
  86. package/dist/operations/semantic-advisor/types.d.ts +23 -0
  87. package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
  88. package/dist/plugins/approved-source-analyzers.generated.d.ts +11 -8
  89. package/dist/plugins/approved-source-analyzers.generated.d.ts.map +1 -1
  90. package/dist/plugins/approved-source-analyzers.generated.js +11 -8
  91. package/dist/plugins/compose-search-hints.d.ts +29 -0
  92. package/dist/plugins/compose-search-hints.d.ts.map +1 -0
  93. package/dist/plugins/compose-search-hints.js +94 -0
  94. package/dist/plugins/external-allowlist.d.ts +7 -0
  95. package/dist/plugins/external-allowlist.d.ts.map +1 -1
  96. package/dist/plugins/external-allowlist.js +11 -0
  97. package/dist/plugins/registry.d.ts +8 -1
  98. package/dist/plugins/registry.d.ts.map +1 -1
  99. package/dist/plugins/registry.js +22 -3
  100. package/dist/plugins/source-classification.d.ts +2 -2
  101. package/dist/plugins/source-classification.d.ts.map +1 -1
  102. package/dist/plugins/source-classification.js +25 -1
  103. package/dist/prolog/codec.d.ts.map +1 -1
  104. package/dist/prolog/codec.js +10 -20
  105. package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -1
  106. package/dist/public/impact/coverage-depth-quality.js +32 -16
  107. package/dist/public/impact/full-kb-quality.d.ts +6 -0
  108. package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
  109. package/dist/public/impact/full-kb-quality.js +8 -3
  110. package/dist/public/impact/requirement-quality.d.ts.map +1 -1
  111. package/dist/public/impact/requirement-quality.js +56 -29
  112. package/dist/public/operations/catalog.d.ts +44 -4
  113. package/dist/public/operations/catalog.d.ts.map +1 -1
  114. package/dist/public/operations/check-executor.d.ts +6 -0
  115. package/dist/public/operations/check-executor.d.ts.map +1 -1
  116. package/dist/public/operations/check-executor.js +34 -2
  117. package/dist/public/operations/discovery-executors.d.ts +5 -1
  118. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  119. package/dist/public/operations/discovery-executors.js +65 -4
  120. package/dist/public/operations/evidence-floor.d.ts +8 -1
  121. package/dist/public/operations/evidence-floor.d.ts.map +1 -1
  122. package/dist/public/operations/evidence-floor.js +3 -4
  123. package/dist/public/operations/migration-plan.d.ts.map +1 -1
  124. package/dist/public/operations/migration-plan.js +41 -10
  125. package/dist/public/operations/node-ports.d.ts.map +1 -1
  126. package/dist/public/operations/node-ports.js +3 -1
  127. package/dist/public/operations/repair-plan.d.ts +46 -0
  128. package/dist/public/operations/repair-plan.d.ts.map +1 -1
  129. package/dist/public/operations/repair-plan.js +110 -1
  130. package/dist/public/operations/schema6-check-actions.js +1 -1
  131. package/dist/public/operations/source-analysis-cache.d.ts +22 -0
  132. package/dist/public/operations/source-analysis-cache.d.ts.map +1 -0
  133. package/dist/public/operations/source-analysis-cache.js +157 -0
  134. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  135. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  136. package/dist/public/operations/specs/check-rules.generated.js +2 -1
  137. package/dist/public/operations/specs/check.d.ts +1 -1
  138. package/dist/public/operations/specs/discovery.d.ts +7 -2
  139. package/dist/public/operations/specs/discovery.d.ts.map +1 -1
  140. package/dist/public/operations/specs/discovery.js +7 -2
  141. package/dist/public/operations/specs/mutation-schemas.d.ts +36 -1
  142. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  143. package/dist/public/operations/specs/mutation-schemas.js +30 -0
  144. package/dist/public/operations/specs/upsert.d.ts +36 -1
  145. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  146. package/dist/public/operations/symbol-granularity-check.d.ts +3 -1
  147. package/dist/public/operations/symbol-granularity-check.d.ts.map +1 -1
  148. package/dist/public/operations/symbol-granularity-check.js +21 -7
  149. package/dist/public/proof-protocol.d.ts +6 -1
  150. package/dist/public/proof-protocol.d.ts.map +1 -1
  151. package/dist/public/proof-protocol.js +12 -3
  152. package/dist/public/proof-sources.d.ts +2 -0
  153. package/dist/public/proof-sources.d.ts.map +1 -1
  154. package/dist/public/proof-sources.js +32 -6
  155. package/dist/public/schemas/entity.d.ts.map +1 -1
  156. package/dist/public/schemas/entity.js +59 -0
  157. package/dist/public/skills/kibi-bootstrap/SKILL.md +2 -2
  158. package/dist/public/skills/kibi-commit-review/SKILL.md +6 -2
  159. package/dist/public/skills/kibi-decisions/SKILL.md +7 -2
  160. package/dist/public/skills/kibi-decisions/resources/signoff-views.md +3 -1
  161. package/dist/public/skills/kibi-usage/SKILL.md +50 -3
  162. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +34 -0
  163. package/dist/public/skills/kibi-usage/resources/proof.md +13 -5
  164. package/dist/public/skills/kibi-usage/resources/workflows.md +10 -0
  165. package/dist/relationships/shards.d.ts +2 -0
  166. package/dist/relationships/shards.d.ts.map +1 -1
  167. package/dist/relationships/shards.js +25 -1
  168. package/dist/schemas/entity.schema.json +98 -1
  169. package/dist/search-answer.d.ts +3 -2
  170. package/dist/search-answer.d.ts.map +1 -1
  171. package/dist/search-answer.js +57 -4
  172. package/dist/search-hints.d.ts +58 -0
  173. package/dist/search-hints.d.ts.map +1 -0
  174. package/dist/search-hints.js +187 -0
  175. package/dist/search-ranking.d.ts +7 -0
  176. package/dist/search-ranking.d.ts.map +1 -1
  177. package/dist/search-ranking.js +54 -56
  178. package/dist/search-token-cache.d.ts +30 -0
  179. package/dist/search-token-cache.d.ts.map +1 -0
  180. package/dist/search-token-cache.js +86 -0
  181. package/dist/traceability/impact-knowledge.d.ts.map +1 -1
  182. package/dist/traceability/impact-knowledge.js +2 -0
  183. package/dist/traceability/temp-kb.d.ts.map +1 -1
  184. package/dist/traceability/temp-kb.js +11 -1
  185. package/dist/types/entities.d.ts +12 -0
  186. package/dist/types/entities.d.ts.map +1 -1
  187. package/dist/usage-telemetry.d.ts +20 -0
  188. package/dist/usage-telemetry.d.ts.map +1 -0
  189. package/dist/usage-telemetry.js +93 -0
  190. package/dist/utils/branch-resolver.d.ts.map +1 -1
  191. package/dist/utils/branch-resolver.js +1 -0
  192. package/dist/utils/rule-registry.generated.d.ts +5 -0
  193. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  194. package/dist/utils/rule-registry.generated.js +6 -0
  195. package/package.json +5 -4
  196. package/schema/entities.pl +12 -0
  197. package/src/public/impact/coverage-depth-quality.ts +59 -21
  198. package/src/public/impact/full-kb-quality.ts +15 -1
  199. package/src/public/impact/requirement-quality.ts +95 -38
  200. package/src/public/operations/check-executor.ts +41 -1
  201. package/src/public/operations/discovery-executors.ts +92 -4
  202. package/src/public/operations/evidence-floor.ts +15 -3
  203. package/src/public/operations/migration-plan.ts +41 -9
  204. package/src/public/operations/node-ports.ts +3 -1
  205. package/src/public/operations/repair-plan.ts +152 -1
  206. package/src/public/operations/schema6-check-actions.ts +1 -1
  207. package/src/public/operations/source-analysis-cache.ts +196 -0
  208. package/src/public/operations/specs/check-rules.generated.ts +2 -1
  209. package/src/public/operations/specs/discovery.ts +8 -2
  210. package/src/public/operations/specs/mutation-schemas.ts +34 -0
  211. package/src/public/operations/symbol-granularity-check.ts +25 -5
  212. package/src/public/proof-protocol-types.d.ts +1 -1
  213. package/src/public/proof-protocol.ts +18 -4
  214. package/src/public/proof-sources.ts +37 -6
  215. package/src/public/schemas/entity.ts +65 -0
  216. package/src/public/skills/kibi-bootstrap/SKILL.md +2 -2
  217. package/src/public/skills/kibi-commit-review/SKILL.md +6 -2
  218. package/src/public/skills/kibi-decisions/SKILL.md +7 -2
  219. package/src/public/skills/kibi-decisions/resources/signoff-views.md +3 -1
  220. package/src/public/skills/kibi-usage/SKILL.md +50 -3
  221. package/src/public/skills/kibi-usage/resources/fact-lanes.md +34 -0
  222. package/src/public/skills/kibi-usage/resources/proof.md +13 -5
  223. package/src/public/skills/kibi-usage/resources/workflows.md +10 -0
  224. package/src/schemas/entity.schema.json +98 -1
@@ -0,0 +1,43 @@
1
+ import { type ManagedHookName } from "./init-helpers.js";
2
+ /** Git hook managers whose configuration Kibi can print and read. */
3
+ export declare const HOOK_MANAGER_NAMES: readonly ["lefthook", "husky", "pre-commit"];
4
+ export type HookManagerName = (typeof HOOK_MANAGER_NAMES)[number];
5
+ /** Formats `kibi init --print-hook` can emit. */
6
+ export declare const HOOK_SNIPPET_FORMATS: readonly ["shell", "lefthook", "husky", "pre-commit"];
7
+ export type HookSnippetFormat = (typeof HOOK_SNIPPET_FORMATS)[number];
8
+ export interface DetectedHookManager {
9
+ name: HookManagerName;
10
+ /** Repository-relative file or directory that holds the hook definitions. */
11
+ configPath: string;
12
+ }
13
+ /**
14
+ * The hook manager that owns this repository's Git hooks, if any. Installed
15
+ * hook files are the strongest evidence (they show what Git runs today), then
16
+ * a core.hooksPath pointing into `.husky`, then a manager configuration file
17
+ * that has not been installed yet.
18
+ */
19
+ export declare function detectHookManager(worktreeRoot: string, effectiveHooksDir: string, hooksPathConfig?: string | null): DetectedHookManager | null;
20
+ /**
21
+ * The commands a hook manager runs for one Git hook, read from its
22
+ * configuration, or null when the manager does not define that hook (or its
23
+ * configuration cannot be read).
24
+ */
25
+ export declare function hookManagerCommands(manager: HookManagerName, worktreeRoot: string, hook: ManagedHookName): string[] | null;
26
+ /** Labels of the Kibi commands a hook definition never visibly runs. */
27
+ export declare function missingKibiHookCommands(hook: ManagedHookName, scripts: readonly string[]): string[];
28
+ /**
29
+ * Configuration that wires the given hooks to the commands Kibi's managed
30
+ * templates run, in a hook manager's own format. `shell` and `husky` print
31
+ * the hook-script snippets of `kibi init --print-hook`.
32
+ */
33
+ export declare function hookManagerSnippet(format: HookSnippetFormat, hooks: readonly ManagedHookName[]): string;
34
+ export interface HookManagerCoverage {
35
+ hook: ManagedHookName;
36
+ /** Kibi commands the manager's definition of this hook does not visibly run. */
37
+ missing: string[];
38
+ /** Whether the manager defines the hook at all. */
39
+ configured: boolean;
40
+ }
41
+ /** What each Kibi-managed hook looks like in the manager's configuration. */
42
+ export declare function hookManagerCoverage(manager: HookManagerName, worktreeRoot: string): HookManagerCoverage[];
43
+ //# sourceMappingURL=hook-managers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-managers.d.ts","sourceRoot":"","sources":["../../src/commands/hook-managers.ts"],"names":[],"mappings":"AAqBA,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,mBAAmB,CAAC;AAE3B,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,8CAA+C,CAAC;AAE/E,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,iDAAiD;AACjD,eAAO,MAAM,oBAAoB,uDAA4C,CAAC;AAE9E,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;CACpB;AAqGD;;;;;GAKG;AAEH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,EACzB,eAAe,GAAE,MAAM,GAAG,IAAW,GACpC,mBAAmB,GAAG,IAAI,CAwB5B;AAmID;;;;GAIG;AAEH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,eAAe,EACxB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,eAAe,GACpB,MAAM,EAAE,GAAG,IAAI,CAWjB;AAkED,wEAAwE;AAExE,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,MAAM,EAAE,CAKV;AAsFD;;;;GAIG;AAEH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,SAAS,eAAe,EAAE,GAChC,MAAM,CAiBR;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,gFAAgF;IAChF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mDAAmD;IACnD,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,6EAA6E;AAE7E,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,eAAe,EACxB,YAAY,EAAE,MAAM,GACnB,mBAAmB,EAAE,CASvB"}
@@ -0,0 +1,426 @@
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 { readFileSync, statSync } from "node:fs";
19
+ import * as path from "node:path";
20
+ import { parse as parseYaml } from "yaml";
21
+ import { MANAGED_HOOK_NAMES, hookDelegationSnippet, } from "./init-helpers.js";
22
+ /** Git hook managers whose configuration Kibi can print and read. */
23
+ export const HOOK_MANAGER_NAMES = ["lefthook", "husky", "pre-commit"];
24
+ /** Formats `kibi init --print-hook` can emit. */
25
+ export const HOOK_SNIPPET_FORMATS = ["shell", ...HOOK_MANAGER_NAMES];
26
+ // lefthook reads the first of these it finds; the -local variants are merged
27
+ // on top of the main file.
28
+ const LEFTHOOK_CONFIG_FILES = [
29
+ "lefthook.yml",
30
+ ".lefthook.yml",
31
+ "lefthook.yaml",
32
+ ".lefthook.yaml",
33
+ "lefthook.json",
34
+ ".lefthook.json",
35
+ "lefthook.jsonc",
36
+ ".lefthook.jsonc",
37
+ "lefthook.toml",
38
+ ".lefthook.toml",
39
+ ".config/lefthook.yml",
40
+ ".config/lefthook.yaml",
41
+ ];
42
+ const LEFTHOOK_LOCAL_CONFIG_FILES = [
43
+ "lefthook-local.yml",
44
+ ".lefthook-local.yml",
45
+ "lefthook-local.yaml",
46
+ ".lefthook-local.yaml",
47
+ "lefthook-local.json",
48
+ ".lefthook-local.json",
49
+ ];
50
+ const PRE_COMMIT_CONFIG_FILE = ".pre-commit-config.yaml";
51
+ const HUSKY_DIR = ".husky";
52
+ /** Hooks in the order a reader expects them: the commit gate first. */
53
+ const PRINT_ORDER = [
54
+ "pre-commit",
55
+ "post-checkout",
56
+ "post-merge",
57
+ "post-rewrite",
58
+ ];
59
+ // The local runner every Node.js install ships with. `--no-install` never
60
+ // downloads a kibi outside the project lockfile.
61
+ const KIBI_COMMAND = "npx --no-install kibi";
62
+ function isFile(filePath) {
63
+ try {
64
+ return statSync(filePath).isFile();
65
+ }
66
+ catch {
67
+ return false;
68
+ }
69
+ }
70
+ function isDirectory(filePath) {
71
+ try {
72
+ return statSync(filePath).isDirectory();
73
+ }
74
+ catch {
75
+ return false;
76
+ }
77
+ }
78
+ function readText(filePath) {
79
+ try {
80
+ return readFileSync(filePath, "utf8");
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ function firstExisting(root, candidates) {
87
+ return candidates.find((file) => isFile(path.join(root, file))) ?? null;
88
+ }
89
+ /**
90
+ * Which manager generated a hook file, from the marker text each one emits.
91
+ * husky is recognized by core.hooksPath instead: its hook files are the
92
+ * project's own scripts, which may mention husky without being generated.
93
+ */
94
+ function hookFileManager(content) {
95
+ if (content.includes("File generated by pre-commit"))
96
+ return "pre-commit";
97
+ if (/\blefthook\b/.test(content))
98
+ return "lefthook";
99
+ return null;
100
+ }
101
+ function managerConfigPath(worktreeRoot, name) {
102
+ switch (name) {
103
+ case "lefthook":
104
+ return (firstExisting(worktreeRoot, LEFTHOOK_CONFIG_FILES) ?? "lefthook.yml");
105
+ case "husky":
106
+ return HUSKY_DIR;
107
+ case "pre-commit":
108
+ return PRE_COMMIT_CONFIG_FILE;
109
+ }
110
+ }
111
+ /**
112
+ * The hook manager that owns this repository's Git hooks, if any. Installed
113
+ * hook files are the strongest evidence (they show what Git runs today), then
114
+ * a core.hooksPath pointing into `.husky`, then a manager configuration file
115
+ * that has not been installed yet.
116
+ */
117
+ // implements REQ-git-hook-manager-integration
118
+ export function detectHookManager(worktreeRoot, effectiveHooksDir, hooksPathConfig = null) {
119
+ const found = (name) => ({
120
+ name,
121
+ configPath: managerConfigPath(worktreeRoot, name),
122
+ });
123
+ for (const hook of MANAGED_HOOK_NAMES) {
124
+ const content = readText(path.join(effectiveHooksDir, hook));
125
+ const manager = content === null ? null : hookFileManager(content);
126
+ if (manager)
127
+ return found(manager);
128
+ }
129
+ if (hooksPathConfig !== null &&
130
+ /(?:^|[\\/])\.husky(?:[\\/]_)?[\\/]?$/.test(hooksPathConfig.trim())) {
131
+ return found("husky");
132
+ }
133
+ if (firstExisting(worktreeRoot, LEFTHOOK_CONFIG_FILES)) {
134
+ return found("lefthook");
135
+ }
136
+ if (isDirectory(path.join(worktreeRoot, HUSKY_DIR)))
137
+ return found("husky");
138
+ if (isFile(path.join(worktreeRoot, PRE_COMMIT_CONFIG_FILE))) {
139
+ return found("pre-commit");
140
+ }
141
+ return null;
142
+ }
143
+ function parseConfig(filePath) {
144
+ const text = readText(filePath);
145
+ if (text === null)
146
+ return undefined;
147
+ try {
148
+ return parseYaml(text);
149
+ }
150
+ catch {
151
+ return undefined;
152
+ }
153
+ }
154
+ function asRecord(value) {
155
+ return value !== null && typeof value === "object" && !Array.isArray(value)
156
+ ? value
157
+ : null;
158
+ }
159
+ /**
160
+ * Every shell command lefthook runs for one hook: `run` strings of commands
161
+ * and jobs (including nested groups), plus the contents of the scripts it
162
+ * executes from `.lefthook/<hook>/`.
163
+ */
164
+ function lefthookRuns(node, scriptDir, commands) {
165
+ if (Array.isArray(node)) {
166
+ for (const item of node)
167
+ lefthookRuns(item, scriptDir, commands);
168
+ return;
169
+ }
170
+ const record = asRecord(node);
171
+ if (!record)
172
+ return;
173
+ for (const [key, value] of Object.entries(record)) {
174
+ if (key === "run" && typeof value === "string") {
175
+ commands.push(value);
176
+ }
177
+ else if (key === "script" && typeof value === "string") {
178
+ const script = readText(path.join(scriptDir, value));
179
+ if (script !== null)
180
+ commands.push(script);
181
+ }
182
+ else if (key === "scripts" && asRecord(value)) {
183
+ for (const name of Object.keys(value)) {
184
+ const script = readText(path.join(scriptDir, name));
185
+ if (script !== null)
186
+ commands.push(script);
187
+ }
188
+ }
189
+ else {
190
+ lefthookRuns(value, scriptDir, commands);
191
+ }
192
+ }
193
+ }
194
+ function lefthookCommands(worktreeRoot, hook) {
195
+ const main = firstExisting(worktreeRoot, LEFTHOOK_CONFIG_FILES);
196
+ const files = [
197
+ ...(main ? [main] : []),
198
+ ...LEFTHOOK_LOCAL_CONFIG_FILES.filter((file) => isFile(path.join(worktreeRoot, file))),
199
+ ];
200
+ let configured = false;
201
+ const commands = [];
202
+ for (const file of files) {
203
+ // TOML configs are not read; their hooks count as not visible.
204
+ const hookNode = asRecord(parseConfig(path.join(worktreeRoot, file)))?.[hook];
205
+ if (hookNode === undefined || hookNode === null)
206
+ continue;
207
+ configured = true;
208
+ lefthookRuns(hookNode, path.join(worktreeRoot, ".lefthook", hook), commands);
209
+ }
210
+ return configured ? commands : null;
211
+ }
212
+ // pre-commit stage names before 3.2 dropped the `pre-` prefix.
213
+ const LEGACY_PRE_COMMIT_STAGES = {
214
+ commit: "pre-commit",
215
+ push: "pre-push",
216
+ "merge-commit": "pre-merge-commit",
217
+ };
218
+ function preCommitFrameworkCommands(worktreeRoot, hook) {
219
+ const config = asRecord(parseConfig(path.join(worktreeRoot, PRE_COMMIT_CONFIG_FILE)));
220
+ if (!config || !Array.isArray(config.repos))
221
+ return null;
222
+ const defaultStages = Array.isArray(config.default_stages)
223
+ ? config.default_stages
224
+ : null;
225
+ let configured = false;
226
+ const commands = [];
227
+ for (const repo of config.repos) {
228
+ const hooks = asRecord(repo)?.hooks;
229
+ if (!Array.isArray(hooks))
230
+ continue;
231
+ for (const entry of hooks) {
232
+ const definition = asRecord(entry);
233
+ if (!definition)
234
+ continue;
235
+ const stages = Array.isArray(definition.stages)
236
+ ? definition.stages
237
+ : defaultStages;
238
+ const runsHere = stages === null
239
+ ? hook === "pre-commit"
240
+ : stages.some((stage) => typeof stage === "string" &&
241
+ (LEGACY_PRE_COMMIT_STAGES[stage] ?? stage) === hook);
242
+ if (!runsHere)
243
+ continue;
244
+ configured = true;
245
+ // Only local hooks carry a shell entry; remote hooks are opaque here.
246
+ if (typeof definition.entry === "string") {
247
+ const args = Array.isArray(definition.args)
248
+ ? definition.args.filter((arg) => typeof arg === "string")
249
+ : [];
250
+ commands.push([definition.entry, ...args].join(" "));
251
+ }
252
+ }
253
+ }
254
+ return configured ? commands : null;
255
+ }
256
+ /**
257
+ * The commands a hook manager runs for one Git hook, read from its
258
+ * configuration, or null when the manager does not define that hook (or its
259
+ * configuration cannot be read).
260
+ */
261
+ // implements REQ-git-hook-manager-integration
262
+ export function hookManagerCommands(manager, worktreeRoot, hook) {
263
+ switch (manager) {
264
+ case "lefthook":
265
+ return lefthookCommands(worktreeRoot, hook);
266
+ case "husky": {
267
+ const script = readText(path.join(worktreeRoot, HUSKY_DIR, hook));
268
+ return script === null ? null : [script];
269
+ }
270
+ case "pre-commit":
271
+ return preCommitFrameworkCommands(worktreeRoot, hook);
272
+ }
273
+ }
274
+ /**
275
+ * Individual shell commands of a hook script, ignoring comments. Lines are
276
+ * split on command separators so `a && b` counts as two commands.
277
+ */
278
+ function splitShellCommands(content) {
279
+ return content
280
+ .split("\n")
281
+ .filter((line) => !line.trimStart().startsWith("#"))
282
+ .flatMap((line) => line.split(/&&|\|\||;|\|/))
283
+ .map((command) => command.trim())
284
+ .filter((command) => command.length > 0);
285
+ }
286
+ // Matches `kibi <sub>`, `npx kibi <sub>`, `node_modules/.bin/kibi <sub>` and
287
+ // the template's `"$KIBI_BIN" <sub>`.
288
+ function invokesKibi(command, subcommand) {
289
+ return new RegExp(`(?:\\bkibi|\\bKIBI_BIN)["'}]*\\s+${subcommand}(?=[\\s"']|$)`).test(command);
290
+ }
291
+ // A flag ends at whitespace, a closing quote (`sh -c '... --staged'`) or the
292
+ // end of the command.
293
+ function hasFlag(command, flag) {
294
+ return new RegExp(`\\s${flag}(?=[\\s"']|$)`).test(command);
295
+ }
296
+ /** The commands each Kibi-managed hook template runs. */
297
+ const KIBI_HOOK_DELEGATIONS = (() => {
298
+ const sync = [
299
+ {
300
+ label: "kibi sync",
301
+ matches: (command) => invokesKibi(command, "sync"),
302
+ },
303
+ ];
304
+ return {
305
+ "pre-commit": [
306
+ {
307
+ label: "kibi check-generated --staged --changed-only",
308
+ matches: (command) => invokesKibi(command, "check-generated") &&
309
+ hasFlag(command, "--staged") &&
310
+ hasFlag(command, "--changed-only"),
311
+ },
312
+ {
313
+ label: "kibi check --staged",
314
+ matches: (command) => invokesKibi(command, "check") && hasFlag(command, "--staged"),
315
+ },
316
+ ],
317
+ "post-checkout": sync,
318
+ "post-merge": sync,
319
+ "post-rewrite": sync,
320
+ };
321
+ })();
322
+ /** Labels of the Kibi commands a hook definition never visibly runs. */
323
+ // implements REQ-git-hook-manager-integration
324
+ export function missingKibiHookCommands(hook, scripts) {
325
+ const commands = scripts.flatMap(splitShellCommands);
326
+ return KIBI_HOOK_DELEGATIONS[hook]
327
+ .filter((delegation) => !commands.some(delegation.matches))
328
+ .map((delegation) => delegation.label);
329
+ }
330
+ function ordered(hooks) {
331
+ return PRINT_ORDER.filter((hook) => hooks.includes(hook));
332
+ }
333
+ // Runs in lefthook's shell, where {N} is the Nth Git hook argument.
334
+ const LEFTHOOK_RUNS = {
335
+ "pre-commit": `${KIBI_COMMAND} check-generated --staged --changed-only && ${KIBI_COMMAND} check --staged`,
336
+ "post-checkout": `if [ "{3}" = "1" ]; then ${KIBI_COMMAND} sync; fi`,
337
+ "post-merge": `${KIBI_COMMAND} sync`,
338
+ "post-rewrite": `if [ "{1}" = "rebase" ]; then ${KIBI_COMMAND} sync; fi`,
339
+ };
340
+ function lefthookSnippet(hooks) {
341
+ const lines = [
342
+ "# Kibi git hook commands for lefthook. Merge these entries into your",
343
+ "# lefthook.yml (keep your existing commands), then run 'lefthook install'.",
344
+ ];
345
+ for (const hook of hooks) {
346
+ lines.push(`${hook}:`, " commands:");
347
+ if (hook === "post-checkout") {
348
+ lines.push(" # {3} is 1 for a branch checkout, 0 for a file checkout.");
349
+ }
350
+ lines.push(` ${hook === "pre-commit" ? "kibi-check" : "kibi-sync"}:`, ` run: ${LEFTHOOK_RUNS[hook]}`);
351
+ }
352
+ return `${lines.join("\n")}\n`;
353
+ }
354
+ // pre-commit exposes post-checkout and post-rewrite arguments as variables.
355
+ const PRE_COMMIT_FRAMEWORK_HOOKS = {
356
+ "pre-commit": {
357
+ id: "kibi-check",
358
+ name: "kibi check --staged",
359
+ entry: `sh -c '${KIBI_COMMAND} check-generated --staged --changed-only && ${KIBI_COMMAND} check --staged'`,
360
+ },
361
+ "post-checkout": {
362
+ id: "kibi-sync-checkout",
363
+ name: "kibi sync (branch checkout)",
364
+ entry: `sh -c 'if [ "$PRE_COMMIT_CHECKOUT_TYPE" = "1" ]; then ${KIBI_COMMAND} sync; fi'`,
365
+ },
366
+ "post-merge": {
367
+ id: "kibi-sync-merge",
368
+ name: "kibi sync (merge)",
369
+ entry: `${KIBI_COMMAND} sync`,
370
+ },
371
+ "post-rewrite": {
372
+ id: "kibi-sync-rebase",
373
+ name: "kibi sync (rebase)",
374
+ entry: `sh -c 'if [ "$PRE_COMMIT_REWRITE_COMMAND" = "rebase" ]; then ${KIBI_COMMAND} sync; fi'`,
375
+ },
376
+ };
377
+ function preCommitFrameworkSnippet(hooks) {
378
+ const lines = [
379
+ "# Kibi git hooks for the pre-commit framework. Merge into your",
380
+ "# .pre-commit-config.yaml (add the hook types to any existing",
381
+ "# default_install_hook_types and the hooks to a local repo), then run",
382
+ `# 'pre-commit install'.`,
383
+ `default_install_hook_types: [${hooks.join(", ")}]`,
384
+ "repos:",
385
+ " - repo: local",
386
+ " hooks:",
387
+ ];
388
+ for (const hook of hooks) {
389
+ const definition = PRE_COMMIT_FRAMEWORK_HOOKS[hook];
390
+ lines.push(` - id: ${definition.id}`, ` name: ${definition.name}`, ` entry: ${definition.entry}`, " language: system", " pass_filenames: false", " always_run: true", ` stages: [${hook}]`);
391
+ }
392
+ return `${lines.join("\n")}\n`;
393
+ }
394
+ /**
395
+ * Configuration that wires the given hooks to the commands Kibi's managed
396
+ * templates run, in a hook manager's own format. `shell` and `husky` print
397
+ * the hook-script snippets of `kibi init --print-hook`.
398
+ */
399
+ // implements REQ-git-hook-manager-integration
400
+ export function hookManagerSnippet(format, hooks) {
401
+ const selected = ordered(hooks);
402
+ switch (format) {
403
+ case "shell":
404
+ return selected.map(hookDelegationSnippet).join("\n");
405
+ case "husky":
406
+ return selected
407
+ .map((hook) => `# --- ${HUSKY_DIR}/${hook} ---\n${hookDelegationSnippet(hook)}`)
408
+ .join("\n");
409
+ case "lefthook":
410
+ return lefthookSnippet(selected);
411
+ case "pre-commit":
412
+ return preCommitFrameworkSnippet(selected);
413
+ }
414
+ }
415
+ /** What each Kibi-managed hook looks like in the manager's configuration. */
416
+ // implements REQ-git-hook-manager-integration
417
+ export function hookManagerCoverage(manager, worktreeRoot) {
418
+ return ordered(MANAGED_HOOK_NAMES).map((hook) => {
419
+ const commands = hookManagerCommands(manager, worktreeRoot, hook);
420
+ return {
421
+ hook,
422
+ configured: commands !== null,
423
+ missing: missingKibiHookCommands(hook, commands ?? []),
424
+ };
425
+ });
426
+ }
@@ -3,6 +3,7 @@ interface InitOptions {
3
3
  github?: boolean;
4
4
  badgeOnly?: boolean;
5
5
  printHook?: string;
6
+ format?: string;
6
7
  }
7
8
  export declare function initCommand(options: InitOptions): Promise<{
8
9
  exitCode: number;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAuGA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAoDD,wBAAsB,WAAW,CAC/B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgK/B"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA8GA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAkFD,wBAAsB,WAAW,CAC/B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA0L/B"}
@@ -24,7 +24,8 @@ import { resolveBranchAttachment } from "../utils/branch-resolver.js";
24
24
  import { readLegacyKbConfig } from "../utils/config.js";
25
25
  import { resolveGitRepository, } from "../utils/git-repository-context.js";
26
26
  import { scaffoldGitHubIntegration } from "./github-init.js";
27
- import { MANAGED_HOOK_NAMES, copySchemaFiles, createKbDirectoryStructure, createManifestFile, ensureSymbolsManifestFile, hookDelegationSnippet, installGitHooks, isManagedHookName, updateGitIgnore, } from "./init-helpers.js";
27
+ import { HOOK_SNIPPET_FORMATS, detectHookManager, hookManagerCoverage, hookManagerSnippet, } from "./hook-managers.js";
28
+ import { MANAGED_HOOK_NAMES, copySchemaFiles, createKbDirectoryStructure, createManifestFile, ensureSymbolsManifestFile, installGitHooks, isManagedHookName, updateGitIgnore, } from "./init-helpers.js";
28
29
  const __filename = fileURLToPath(import.meta.url);
29
30
  const __dirname = path.dirname(__filename);
30
31
  /**
@@ -83,6 +84,24 @@ function isRepositoryManagedHooksDir(context) {
83
84
  // directory, which can sit outside the current worktree root.
84
85
  return isWithin(hooksDir, commonGitDir) || isWithin(hooksDir, worktreeRoot);
85
86
  }
87
+ /**
88
+ * Hooks owned by a hook manager are left to it: Kibi installs nothing into
89
+ * the hooks directory and prints the manager configuration that runs Kibi's
90
+ * commands for every hook the manager does not visibly cover yet.
91
+ */
92
+ // implements REQ-git-hook-manager-integration
93
+ function reportHookManager(manager, worktreeRoot) {
94
+ const gaps = hookManagerCoverage(manager.name, worktreeRoot).filter((entry) => entry.missing.length > 0);
95
+ const owner = `Git hooks are managed by ${manager.name} (${manager.configPath})`;
96
+ if (gaps.length === 0) {
97
+ console.log(`✓ ${owner}, which already runs Kibi's hook commands; Kibi installed no hooks of its own.`);
98
+ return;
99
+ }
100
+ const hooks = gaps.map((entry) => entry.hook);
101
+ console.log(`! ${owner}; Kibi installed no hooks of its own. ${manager.configPath} does not visibly run Kibi's commands for: ${hooks.join(", ")}. Add this (running the same commands through a task runner or script works too), or Kibi enforcement stays OFF for those hooks:\n`);
102
+ console.log(hookManagerSnippet(manager.name, hooks));
103
+ console.log(` Print it again with: kibi init --print-hook ${hooks.length === 1 ? hooks[0] : "all"} --format ${manager.name}`);
104
+ }
86
105
  async function initNextAction(projectRoot) {
87
106
  const sourceFiles = await nodeFilesystem.glob([
88
107
  ".kb/requirements/**/*.md",
@@ -121,15 +140,25 @@ async function initNextAction(projectRoot) {
121
140
  // implements REQ-cli-init
122
141
  // implements REQ-kibi-github-report-integration
123
142
  export async function initCommand(options) {
143
+ if (options.format !== undefined && options.printHook === undefined) {
144
+ console.error("Error: --format applies only to --print-hook.");
145
+ return { exitCode: 1 };
146
+ }
124
147
  if (options.printHook !== undefined) {
125
148
  // Print-only mode: emit a delegation snippet for a hook Kibi does not
126
149
  // own and touch nothing, not even the repository context.
127
- // implements REQ-git-hook-effective-install
128
- if (!isManagedHookName(options.printHook)) {
129
- console.error(`Error: unknown hook '${options.printHook}' for --print-hook. Expected one of: ${MANAGED_HOOK_NAMES.join(", ")}.`);
150
+ // implements REQ-git-hook-effective-install, REQ-git-hook-manager-integration
151
+ const printHook = options.printHook;
152
+ if (printHook !== "all" && !isManagedHookName(printHook)) {
153
+ console.error(`Error: unknown hook '${printHook}' for --print-hook. Expected one of: ${MANAGED_HOOK_NAMES.join(", ")}, all.`);
154
+ return { exitCode: 1 };
155
+ }
156
+ const format = options.format ?? "shell";
157
+ if (!HOOK_SNIPPET_FORMATS.includes(format)) {
158
+ console.error(`Error: unknown --format '${format}' for --print-hook. Expected one of: ${HOOK_SNIPPET_FORMATS.join(", ")}.`);
130
159
  return { exitCode: 1 };
131
160
  }
132
- process.stdout.write(hookDelegationSnippet(options.printHook));
161
+ process.stdout.write(hookManagerSnippet(format, printHook === "all" ? MANAGED_HOOK_NAMES : [printHook]));
133
162
  return { exitCode: 0 };
134
163
  }
135
164
  if (options.badgeOnly === true && options.github !== true) {
@@ -202,9 +231,15 @@ export async function initCommand(options) {
202
231
  ensureSymbolsManifestFile(projectRoot);
203
232
  }
204
233
  if (options.hooks) {
234
+ const manager = repoContext
235
+ ? detectHookManager(repoContext.worktreeRoot, repoContext.effectiveHooksDir, repoContext.hooksPathConfig)
236
+ : null;
205
237
  if (!repoContext) {
206
238
  console.error("Warning: No git repository found, skipping hooks");
207
239
  }
240
+ else if (manager) {
241
+ reportHookManager(manager, repoContext.worktreeRoot);
242
+ }
208
243
  else if (!isRepositoryManagedHooksDir(repoContext)) {
209
244
  // core.hooksPath is only the culprit when it is configured; the
210
245
  // default hooks directory can also escape the repository through a
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAmEA,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,wCAAwC,CAAC;AAwBhD,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,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB,EAAE,SAAS,GAAG,WAAW,GAAG,gBAAgB,GAAG,IAAI,CAAC;IAC3E,MAAM,EAAE,SAAS,CAAC;IAClB,4BAA4B,EAAE,MAAM,CAAC;IACrC,8EAA8E;IAC9E,oBAAoB,EAAE,MAAM,CAAC;IAC7B,8DAA8D;IAC9D,4BAA4B,EAAE,MAAM,CAAC;IACrC,sEAAsE;IACtE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,4FAA4F;IAC5F,kBAAkB,EAAE,MAAM,CAAC;IAC3B,wHAAwH;IACxH,oBAAoB,EAAE,MAAM,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,iFAAiF;AACjF,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,oBAAoB,CAAC;IAC5B,0EAA0E;IAC1E,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAqLD,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,OAAO,EACzB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,CAUR;AAwXD,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,CAAC;IAAC,OAAO,CAAC,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAgkB9D;AAED;;;;GAIG;AAEH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAGzE;AA8YD;;;;;GAKG;AAEH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,GACpC,OAAO,CAOT"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAmEA,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,wCAAwC,CAAC;AAyBhD,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,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB,EAAE,SAAS,GAAG,WAAW,GAAG,gBAAgB,GAAG,IAAI,CAAC;IAC3E,MAAM,EAAE,SAAS,CAAC;IAClB,4BAA4B,EAAE,MAAM,CAAC;IACrC,8EAA8E;IAC9E,oBAAoB,EAAE,MAAM,CAAC;IAC7B,8DAA8D;IAC9D,4BAA4B,EAAE,MAAM,CAAC;IACrC,sEAAsE;IACtE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,4FAA4F;IAC5F,kBAAkB,EAAE,MAAM,CAAC;IAC3B,wHAAwH;IACxH,oBAAoB,EAAE,MAAM,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,iFAAiF;AACjF,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,oBAAoB,CAAC;IAC5B,0EAA0E;IAC1E,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAqLD,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,OAAO,EACzB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,CAUR;AAwXD,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,CAAC;IAAC,OAAO,CAAC,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAgkB9D;AAED;;;;GAIG;AAEH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAGzE;AAoZD;;;;;GAKG;AAEH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,GACpC,OAAO,CAOT"}
@@ -27,6 +27,7 @@ import { applySourcePathRepairs, findSourceRepairs, } from "../operations/migrat
27
27
  import { applySupersededClosures, planSupersededClosures, } from "../operations/migration/superseded-closure.js";
28
28
  import { coordinateRefreshFailureMessage, readGeneratedManifestBytes, refreshCoordinatesAfterMigration, } from "../operations/migration/symbol-coordinates-refresh.js";
29
29
  import { applySymbolLegacyLinks, planSymbolLegacyLinks, } from "../operations/migration/symbol-legacy-links.js";
30
+ import { noteCliErrorCategory } from "../usage-telemetry.js";
30
31
  import { resolveBranchAttachment } from "../utils/branch-resolver.js";
31
32
  import { defaultKbManifest, readKbManifest, readKbManifestStatus, writeKbManifest, } from "../utils/kb-manifest.js";
32
33
  import { CANONICAL_ENTITY_PATHS, KB_PATHS } from "../utils/kb-paths.js";
@@ -1067,11 +1068,13 @@ async function migratePlanCommand(options) {
1067
1068
  ? plan.planHash
1068
1069
  : options.approvedPlanHash;
1069
1070
  if (!approvedPlanHash) {
1070
- console.error("--apply-safe requires --approved-plan-hash <sha256>.");
1071
+ noteCliErrorCategory("migrate:approved_plan_hash_missing");
1072
+ console.error(`--apply-safe requires --approved-plan-hash <sha256>. Review the plan with 'kibi migrate', then approve its current hash: kibi migrate --apply-safe --approved-plan-hash ${plan.planHash}`);
1071
1073
  return { exitCode: 2 };
1072
1074
  }
1073
1075
  if (approvedPlanHash !== plan.planHash) {
1074
- console.error("Migration plan changed; regenerate the plan and approve its current hash.");
1076
+ noteCliErrorCategory("migrate:plan_hash_changed");
1077
+ console.error(`Migration plan changed; regenerate the plan and approve its current hash: kibi migrate --apply-safe --approved-plan-hash ${plan.planHash}`);
1075
1078
  return { exitCode: 2 };
1076
1079
  }
1077
1080
  const explicitActionIds = approveWholePlan
@@ -1109,8 +1112,11 @@ async function migratePlanCommand(options) {
1109
1112
  console.log(result.content[0]?.text ?? "Migration applied.");
1110
1113
  printApplySummary(result.structuredContent);
1111
1114
  }
1115
+ const outcome = result.structuredContent?.outcome;
1116
+ if (outcome !== "applied")
1117
+ noteCliErrorCategory(`migrate:${outcome}`);
1112
1118
  return {
1113
- exitCode: result.structuredContent?.outcome === "applied" ? 0 : 1,
1119
+ exitCode: outcome === "applied" ? 0 : 1,
1114
1120
  };
1115
1121
  }
1116
1122
  if (options.format === "json") {
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/persistence.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA4HrD,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;CACrB;AAuFD,iFAAiF;AACjF,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,MAAM,CAAC,CA+BjB;AAED,0EAA0E;AAC1E,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,MAAM,CAAC,CAmBjB;AAED,8EAA8E;AAC9E,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,aAAa,EAAE,SAAS,qBAAqB,EAAE,GAC9C,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAmCD,6EAA6E;AAE7E,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAAC;AAEH;;;GAGG;AAEH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,GACnC,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAmBlC;AAED,wBAAsB,eAAe,CAEnC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,OAAO,GAAE;IAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAO,GAChD,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAyRvD;AA4BD,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,MAAM,CAER;AAGD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,kBAAkB,EAAE,qBAAqB,EAAE,EAC3C,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,OAAO,CAAC;IAAE,iBAAiB,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CA0O7D"}
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/persistence.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAgIrD,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;CACrB;AA0HD,iFAAiF;AACjF,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,MAAM,CAAC,CA+BjB;AAED,0EAA0E;AAC1E,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,MAAM,CAAC,CAmBjB;AAED,8EAA8E;AAC9E,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,aAAa,EAAE,SAAS,qBAAqB,EAAE,GAC9C,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAmCD,6EAA6E;AAE7E,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAAC;AAEH;;;GAGG;AAEH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,GACnC,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAmBlC;AAED,wBAAsB,eAAe,CAEnC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,OAAO,GAAE;IAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAO,GAChD,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CA2RvD;AA4BD,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,MAAM,CAER;AAGD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,kBAAkB,EAAE,qBAAqB,EAAE,EAC3C,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,OAAO,CAAC;IAAE,iBAAiB,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CA4P7D"}