kibi-cli 0.8.0 → 0.10.1

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 (70) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +11 -0
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +229 -4
  5. package/dist/commands/init-helpers.d.ts.map +1 -1
  6. package/dist/commands/init-helpers.js +11 -14
  7. package/dist/commands/migrate.d.ts +9 -0
  8. package/dist/commands/migrate.d.ts.map +1 -0
  9. package/dist/commands/migrate.js +183 -0
  10. package/dist/commands/sync/manifest.d.ts +8 -2
  11. package/dist/commands/sync/manifest.d.ts.map +1 -1
  12. package/dist/commands/sync/manifest.js +56 -11
  13. package/dist/commands/sync.d.ts +1 -0
  14. package/dist/commands/sync.d.ts.map +1 -1
  15. package/dist/commands/sync.js +9 -7
  16. package/dist/extractors/manifest.d.ts +30 -0
  17. package/dist/extractors/manifest.d.ts.map +1 -1
  18. package/dist/extractors/manifest.js +60 -7
  19. package/dist/extractors/symbol-coordinates.d.ts +15 -0
  20. package/dist/extractors/symbol-coordinates.d.ts.map +1 -0
  21. package/dist/extractors/symbol-coordinates.js +83 -0
  22. package/dist/public/check-types.d.ts +3 -1
  23. package/dist/public/check-types.d.ts.map +1 -1
  24. package/dist/public/check-types.js +1 -0
  25. package/dist/public/extractors/manifest.d.ts +1 -1
  26. package/dist/public/extractors/manifest.d.ts.map +1 -1
  27. package/dist/public/extractors/manifest.js +1 -1
  28. package/dist/public/ignore-policy.d.ts +10 -0
  29. package/dist/public/ignore-policy.d.ts.map +1 -0
  30. package/dist/public/ignore-policy.js +219 -0
  31. package/dist/public/schema-version.d.ts +3 -0
  32. package/dist/public/schema-version.d.ts.map +1 -0
  33. package/dist/public/schema-version.js +1 -0
  34. package/dist/traceability/evidence-model.d.ts +142 -0
  35. package/dist/traceability/evidence-model.d.ts.map +1 -0
  36. package/dist/traceability/evidence-model.js +70 -0
  37. package/dist/traceability/git-staged.d.ts +1 -0
  38. package/dist/traceability/git-staged.d.ts.map +1 -1
  39. package/dist/traceability/git-staged.js +28 -3
  40. package/dist/traceability/staged-diagnostics.d.ts +25 -0
  41. package/dist/traceability/staged-diagnostics.d.ts.map +1 -0
  42. package/dist/traceability/staged-diagnostics.js +67 -0
  43. package/dist/traceability/staged-impact-contract.d.ts +57 -0
  44. package/dist/traceability/staged-impact-contract.d.ts.map +1 -0
  45. package/dist/traceability/staged-impact-contract.js +202 -0
  46. package/dist/traceability/staged-symbols-manifest.d.ts +23 -0
  47. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -0
  48. package/dist/traceability/staged-symbols-manifest.js +269 -0
  49. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  50. package/dist/traceability/symbol-extract.js +33 -9
  51. package/dist/utils/config.d.ts +1 -0
  52. package/dist/utils/config.d.ts.map +1 -1
  53. package/dist/utils/config.js +35 -22
  54. package/dist/utils/manifest-paths.d.ts +8 -0
  55. package/dist/utils/manifest-paths.d.ts.map +1 -0
  56. package/dist/utils/manifest-paths.js +62 -0
  57. package/dist/utils/rule-registry.d.ts.map +1 -1
  58. package/dist/utils/rule-registry.js +6 -0
  59. package/dist/utils/schema-version.d.ts +14 -0
  60. package/dist/utils/schema-version.d.ts.map +1 -0
  61. package/dist/utils/schema-version.js +59 -0
  62. package/dist/utils/strict-modeling.d.ts +64 -0
  63. package/dist/utils/strict-modeling.d.ts.map +1 -0
  64. package/dist/utils/strict-modeling.js +371 -0
  65. package/package.json +13 -3
  66. package/schema/config.json +8 -1
  67. package/src/public/check-types.ts +15 -1
  68. package/src/public/extractors/manifest.ts +2 -0
  69. package/src/public/ignore-policy.ts +229 -0
  70. package/src/public/schema-version.ts +6 -0
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAsCA,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAuCA,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
package/dist/cli.js CHANGED
@@ -25,6 +25,7 @@ import { gapsCommand } from "./commands/gaps.js";
25
25
  import { gcCommand } from "./commands/gc.js";
26
26
  import { graphCommand } from "./commands/graph.js";
27
27
  import { initCommand } from "./commands/init.js";
28
+ import { migrateCommand } from "./commands/migrate.js";
28
29
  import { queryCommand } from "./commands/query.js";
29
30
  import { searchCommand } from "./commands/search.js";
30
31
  import { statusCommand } from "./commands/status.js";
@@ -53,14 +54,24 @@ program
53
54
  .action(withExitCode(async (options) => {
54
55
  return initCommand(options);
55
56
  }));
57
+ program
58
+ .command("migrate")
59
+ .description("Migrate .kb/config.json to the latest schema version")
60
+ .option("--dry-run", "Preview migration changes without writing files")
61
+ .option("--yes", "Apply migration changes without prompting")
62
+ .action(withExitCode(async (options) => {
63
+ return migrateCommand(options);
64
+ }));
56
65
  program
57
66
  .command("sync")
58
67
  .description("Sync entities from documents")
59
68
  .option("--validate-only", "Perform validation without mutations")
69
+ .option("--refresh-symbol-coordinates", "Refresh generated symbol coordinates")
60
70
  .option("--rebuild", "Rebuild branch snapshot from scratch (discards current KB)")
61
71
  .action(withExitCode(async (options) => {
62
72
  return syncCommand(options);
63
73
  }));
74
+ program;
64
75
  program
65
76
  .command("query [type]")
66
77
  .description("Query knowledge base")
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAuDA,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,SAAS,EAAE,CAAC;AAI1B,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA+GD,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAkR/B"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AA8EA,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,SAAS,EAAE,CAAC;AAI1B,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA+UD,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAmU/B"}
@@ -22,8 +22,12 @@ import { extractFromManifest, extractFromManifestString, } from "../extractors/m
22
22
  import { extractFromMarkdownString, } from "../extractors/markdown.js";
23
23
  import { PrologProcess } from "../prolog.js";
24
24
  import { escapeAtom, parseTriples, parseViolationRows, } from "../prolog/codec.js";
25
- import { getStagedFiles } from "../traceability/git-staged.js";
25
+ import { getStagedFiles, } from "../traceability/git-staged.js";
26
+ import { KIBI_NO_IMPACT_DECLARATION, KIBI_SYMBOLS_MANIFEST_PATH, } from "../traceability/evidence-model.js";
26
27
  import { validateStagedMarkdown } from "../traceability/markdown-validate.js";
28
+ import { collectStagedKibiDiagnostics, } from "../traceability/staged-diagnostics.js";
29
+ import { classifyKibiImpactEvidence, isBehaviorSourceEdit, parseKibiImpactOverride, } from "../traceability/staged-impact-contract.js";
30
+ import { assessStagedSymbolsManifest, collectStagedAuthoredSymbolsManifestEvidence, } from "../traceability/staged-symbols-manifest.js";
27
31
  import { createManifestLookupSentinelKey, extractSymbolsFromStagedFile, } from "../traceability/symbol-extract.js";
28
32
  import { cleanupTempKb, consultOverlay, createOverlayFacts, createTempKb, projectStagedEntities, } from "../traceability/temp-kb.js";
29
33
  import { formatViolations as formatStagedViolations, validateStagedSymbols, } from "../traceability/validate.js";
@@ -112,6 +116,173 @@ function buildManifestLookup(stagedFiles) {
112
116
  }
113
117
  return { manifestLookup, manifestResults };
114
118
  }
119
+ const KIBI_ENTITY_TYPES = new Set([
120
+ "req",
121
+ "scenario",
122
+ "test",
123
+ "adr",
124
+ "flag",
125
+ "event",
126
+ "symbol",
127
+ "fact",
128
+ ]);
129
+ function isKibiEntityType(value) {
130
+ return KIBI_ENTITY_TYPES.has(value);
131
+ }
132
+ function isStagedManifestPath(filePath) {
133
+ if (filePath.endsWith("/symbols.yaml") ||
134
+ filePath.endsWith("/symbols.yml") ||
135
+ filePath.endsWith("/symbol-coordinates.yaml") ||
136
+ filePath === "symbols.yaml" ||
137
+ filePath === "symbols.yml" ||
138
+ filePath === "symbol-coordinates.yaml") {
139
+ return true;
140
+ }
141
+ try {
142
+ const config = loadConfig(process.cwd());
143
+ if (config.paths.symbols) {
144
+ const relSymbols = config.paths.symbols;
145
+ const configuredBase = relSymbols.split(/[\\/]/).pop();
146
+ if (filePath === relSymbols ||
147
+ (configuredBase && filePath.endsWith(`/${configuredBase}`))) {
148
+ return true;
149
+ }
150
+ }
151
+ }
152
+ catch {
153
+ // ignore config read errors
154
+ }
155
+ return false;
156
+ }
157
+ function isTestOnlySourcePath(filePath) {
158
+ return (filePath.startsWith("tests/") ||
159
+ filePath.includes("/tests/") ||
160
+ filePath.endsWith(".test.ts") ||
161
+ filePath.endsWith(".test.tsx") ||
162
+ filePath.endsWith(".test.js") ||
163
+ filePath.endsWith(".test.jsx") ||
164
+ filePath.endsWith(".spec.ts") ||
165
+ filePath.endsWith(".spec.tsx") ||
166
+ filePath.endsWith(".spec.js") ||
167
+ filePath.endsWith(".spec.jsx"));
168
+ }
169
+ function getStagedDiffText(stagedFile) {
170
+ return stagedFile.diffText ?? "";
171
+ }
172
+ function formatStagedKibiDiagnostics(diagnostics) {
173
+ return diagnostics
174
+ .map((diagnostic) => {
175
+ const lines = [`[${diagnostic.id}] ${diagnostic.message}`];
176
+ if (diagnostic.files.length > 0) {
177
+ lines.push(` Files: ${diagnostic.files.join(", ")}`);
178
+ }
179
+ if (diagnostic.docs.length > 0) {
180
+ lines.push(` Docs: ${diagnostic.docs.join(", ")}`);
181
+ }
182
+ lines.push(` Suggestion: ${diagnostic.suggestion}`);
183
+ return lines.join("\n");
184
+ })
185
+ .join("\n\n");
186
+ }
187
+ function uniqueSorted(values) {
188
+ return Array.from(new Set(values)).sort();
189
+ }
190
+ function buildStagedKibiImpactEvidence(options) {
191
+ const { stagedFiles, sourceFiles, markdownFiles, markdownResults, symbolsByFile, symbolsManifestPath, } = options;
192
+ const sourceChanges = sourceFiles.map((file) => {
193
+ const symbolsForFile = symbolsByFile.get(file.path) ?? [];
194
+ const behaviorCandidate = !isTestOnlySourcePath(file.path) &&
195
+ isBehaviorSourceEdit({
196
+ path: file.path,
197
+ diffText: getStagedDiffText(file),
198
+ intersectsBehaviorBearingSymbol: symbolsForFile.length > 0,
199
+ knownUserFacingSurface: false,
200
+ });
201
+ return {
202
+ path: file.path,
203
+ kind: behaviorCandidate
204
+ ? "behavior_source_edit"
205
+ : "non_behavior_source_edit",
206
+ };
207
+ });
208
+ const behaviorSourcePaths = sourceChanges
209
+ .filter((change) => change.kind === "behavior_source_edit")
210
+ .map((change) => change.path);
211
+ const allSourcePaths = sourceChanges.map((change) => change.path);
212
+ const behaviorSourceFiles = sourceFiles.filter((file) => behaviorSourcePaths.includes(file.path));
213
+ const stagedSymbolsManifest = assessStagedSymbolsManifest({
214
+ symbolsManifestPath,
215
+ stagedFiles,
216
+ sourceFiles: behaviorSourceFiles,
217
+ });
218
+ const stagedAuthoredSymbolsEvidence = collectStagedAuthoredSymbolsManifestEvidence({
219
+ stagedFiles,
220
+ sourceFiles: behaviorSourceFiles,
221
+ });
222
+ const markdownResultsByPath = new Map();
223
+ for (const [index, file] of markdownFiles.entries()) {
224
+ const result = markdownResults[index];
225
+ if (result) {
226
+ markdownResultsByPath.set(file.path, result);
227
+ }
228
+ }
229
+ const resolvedKbArtifacts = [];
230
+ let override = null;
231
+ for (const file of markdownFiles) {
232
+ const parsedOverride = parseKibiImpactOverride(file.content ?? "");
233
+ const evidenceKind = classifyKibiImpactEvidence({
234
+ filePath: file.path,
235
+ extractionOutputChanged: false,
236
+ overrideDeclared: parsedOverride.declared,
237
+ overrideRationale: parsedOverride.rationale,
238
+ });
239
+ if (evidenceKind === "entity_markdown") {
240
+ const result = markdownResultsByPath.get(file.path);
241
+ if (result && isKibiEntityType(result.entity.type)) {
242
+ resolvedKbArtifacts.push({
243
+ kind: "entity_markdown",
244
+ path: file.path,
245
+ entityTypes: [result.entity.type],
246
+ entityIds: [result.entity.id],
247
+ sourcePaths: [...behaviorSourcePaths],
248
+ });
249
+ }
250
+ continue;
251
+ }
252
+ if (!parsedOverride.declared || override !== null) {
253
+ continue;
254
+ }
255
+ override = {
256
+ declaration: KIBI_NO_IMPACT_DECLARATION,
257
+ path: file.path,
258
+ sourcePaths: [...allSourcePaths],
259
+ reason: "non_behavioral_source_edit",
260
+ rationale: parsedOverride.rationale ?? "",
261
+ };
262
+ }
263
+ if (stagedAuthoredSymbolsEvidence.entries.length > 0) {
264
+ resolvedKbArtifacts.push({
265
+ kind: "symbols_manifest",
266
+ path: stagedAuthoredSymbolsEvidence.path,
267
+ entityTypes: ["symbol"],
268
+ entityIds: uniqueSorted(stagedAuthoredSymbolsEvidence.entries.flatMap((entry) => entry.entityIds)),
269
+ sourcePaths: uniqueSorted(stagedAuthoredSymbolsEvidence.entries.map((entry) => entry.sourcePath)),
270
+ });
271
+ }
272
+ return {
273
+ sourceChanges,
274
+ symbolsManifest: {
275
+ path: stagedSymbolsManifest.path,
276
+ state: stagedSymbolsManifest.state,
277
+ sourcePaths: stagedSymbolsManifest.sourcePaths,
278
+ },
279
+ mode: resolvedKbArtifacts.length > 0
280
+ ? { kind: "kb_changes", kbArtifacts: resolvedKbArtifacts }
281
+ : override
282
+ ? { kind: "no_impact_override", override }
283
+ : { kind: "missing" },
284
+ };
285
+ }
115
286
  // implements REQ-006
116
287
  export async function checkCommand(options) {
117
288
  let prolog = null;
@@ -147,7 +318,8 @@ export async function checkCommand(options) {
147
318
  return { exitCode: 0 };
148
319
  }
149
320
  const { manifestLookup, manifestResults } = buildManifestLookup(stagedFiles);
150
- const codeFiles = stagedFiles.filter((f) => !f.path.endsWith(".md"));
321
+ const symbolsManifestPath = loadConfig(process.cwd()).paths.symbols ?? KIBI_SYMBOLS_MANIFEST_PATH;
322
+ const sourceFiles = stagedFiles.filter((file) => !file.path.endsWith(".md") && !isStagedManifestPath(file.path));
151
323
  const markdownFiles = stagedFiles.filter((f) => f.path.endsWith(".md"));
152
324
  const markdownErrors = [];
153
325
  for (const f of markdownFiles) {
@@ -168,9 +340,11 @@ export async function checkCommand(options) {
168
340
  return { exitCode: 1 };
169
341
  }
170
342
  const allSymbols = [];
171
- for (const f of codeFiles) {
343
+ const symbolsByFile = new Map();
344
+ for (const f of sourceFiles) {
172
345
  try {
173
346
  const symbols = extractSymbolsFromStagedFile(f, manifestLookup);
347
+ symbolsByFile.set(f.path, symbols);
174
348
  if (symbols?.length) {
175
349
  allSymbols.push(...symbols);
176
350
  }
@@ -184,11 +358,27 @@ export async function checkCommand(options) {
184
358
  ...manifestResults,
185
359
  ...markdownResults,
186
360
  ];
361
+ const stagedKibiEvidence = buildStagedKibiImpactEvidence({
362
+ stagedFiles,
363
+ sourceFiles,
364
+ markdownFiles,
365
+ markdownResults,
366
+ symbolsByFile,
367
+ symbolsManifestPath,
368
+ });
369
+ const stagedKibiDiagnostics = collectStagedKibiDiagnostics(stagedKibiEvidence);
187
370
  if (allSymbols.length === 0 && stagedEntityResults.length === 0) {
188
371
  console.log("No exported symbols or staged entities found in staged files.");
189
372
  return { exitCode: 0 };
190
373
  }
191
374
  if (allSymbols.length === 0) {
375
+ if (stagedKibiDiagnostics.length > 0) {
376
+ console.log(formatStagedKibiDiagnostics(stagedKibiDiagnostics));
377
+ if (options.dryRun) {
378
+ return { exitCode: 0 };
379
+ }
380
+ return { exitCode: 1 };
381
+ }
192
382
  console.log("✓ No violations found in staged files.");
193
383
  return { exitCode: 0 };
194
384
  }
@@ -207,6 +397,10 @@ export async function checkCommand(options) {
207
397
  prolog: tempCtx.prolog,
208
398
  });
209
399
  const violationsFormatted = formatStagedViolations(violationsRaw);
400
+ if (stagedKibiDiagnostics.length > 0) {
401
+ console.log(formatStagedKibiDiagnostics(stagedKibiDiagnostics));
402
+ console.log();
403
+ }
210
404
  if (violationsRaw && violationsRaw.length > 0) {
211
405
  console.log(violationsFormatted);
212
406
  await cleanupTempKb(tempCtx.tempDir);
@@ -215,6 +409,13 @@ export async function checkCommand(options) {
215
409
  }
216
410
  return { exitCode: 1 };
217
411
  }
412
+ if (stagedKibiDiagnostics.length > 0) {
413
+ await cleanupTempKb(tempCtx.tempDir);
414
+ if (options.dryRun) {
415
+ return { exitCode: 0 };
416
+ }
417
+ return { exitCode: 1 };
418
+ }
218
419
  console.log("✓ No violations found in staged symbols.");
219
420
  await cleanupTempKb(tempCtx.tempDir);
220
421
  return { exitCode: 0 };
@@ -278,6 +479,7 @@ export async function checkCommand(options) {
278
479
  "domain-contradictions",
279
480
  "strict-fact-shape",
280
481
  "strict-req-fact-pairing",
482
+ "strict-readiness",
281
483
  ];
282
484
  const canUseAggregated = Array.from(effectiveRules).every((r) => supportedRules.includes(r));
283
485
  if (canUseAggregated) {
@@ -302,6 +504,7 @@ export async function checkCommand(options) {
302
504
  await runCheck("domain-contradictions", checkDomainContradictions);
303
505
  await runCheck("strict-fact-shape", checkStrictFactShape);
304
506
  await runCheck("strict-req-fact-pairing", checkStrictReqFactPairing);
507
+ await runCheck("strict-readiness", checkStrictReadiness);
305
508
  }
306
509
  if (violations.length === 0) {
307
510
  console.log("✓ No violations found. KB is valid.");
@@ -312,6 +515,12 @@ export async function checkCommand(options) {
312
515
  for (const v of violations) {
313
516
  const filename = v.source ? path.basename(v.source, ".md") : v.entityId;
314
517
  console.log(`[${v.rule}] ${filename}`);
518
+ if (filename !== v.entityId) {
519
+ console.log(` Entity: ${v.entityId}`);
520
+ }
521
+ if (v.source) {
522
+ console.log(` Source: ${v.source}`);
523
+ }
315
524
  console.log(` ${v.description}`);
316
525
  if (options.fix && v.suggestion) {
317
526
  console.log(` Suggestion: ${v.suggestion}`);
@@ -620,7 +829,7 @@ async function checkDomainContradictions(prolog) {
620
829
  violations.push({
621
830
  rule: "domain-contradictions",
622
831
  entityId: `${reqA}/${reqB}`,
623
- description: reason,
832
+ description: `${reason} [strict-readiness: contradiction-ready]`,
624
833
  suggestion: "Supersede one requirement or align both to the same required property",
625
834
  });
626
835
  }
@@ -658,6 +867,22 @@ async function checkStrictReqFactPairing(prolog) {
658
867
  }
659
868
  return violations;
660
869
  }
870
+ async function checkStrictReadiness(prolog) {
871
+ const violations = [];
872
+ const result = await prolog.query(`findall(violation(Rule, EntityId, Desc, Sugg, Src),
873
+ checks:strict_readiness_violation(violation(Rule, EntityId, Desc, Sugg, Src)),
874
+ Violations)`);
875
+ if (!result.success || !result.bindings.Violations) {
876
+ return violations;
877
+ }
878
+ const violationsStr = result.bindings.Violations;
879
+ if (violationsStr && violationsStr !== "[]") {
880
+ for (const v of parseViolationRows(violationsStr)) {
881
+ violations.push(v);
882
+ }
883
+ }
884
+ return violations;
885
+ }
661
886
  async function checkSymbolCoverage(prolog) {
662
887
  const violations = [];
663
888
  const uncoveredResult = await prolog.query("setof(Symbol, symbol_no_req_coverage(Symbol, _), Symbols)");
@@ -1 +1 @@
1
- {"version":3,"file":"init-helpers.d.ts","sourceRoot":"","sources":["../../src/commands/init-helpers.ts"],"names":[],"mappings":"AAuGA,wBAAsB,gBAAgB,CACpC,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,IAAI,CAQN;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAMpD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAsBjD;AAGD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAU3D;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAYf;AASD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAkCnE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAiBpD"}
1
+ {"version":3,"file":"init-helpers.d.ts","sourceRoot":"","sources":["../../src/commands/init-helpers.ts"],"names":[],"mappings":"AAoGA,wBAAsB,gBAAgB,CACpC,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,IAAI,CAQN;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAMpD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAsBjD;AAGD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAU3D;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAYf;AASD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAkCnE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAiBpD"}
@@ -27,6 +27,8 @@ const POST_CHECKOUT_HOOK = `#!/bin/sh
27
27
  # branch_flag is 1 for branch checkout, 0 for file checkout
28
28
  # Refresh branch/worktree assumptions after checkout so advisory plugin state
29
29
  # starts from synced KB data instead of stale in-memory cache assumptions.
30
+ # Uses default non-coordinate-writing sync to avoid writing
31
+ # committed symbol artifacts during automatic hook execution.
30
32
 
31
33
  old_ref=$1
32
34
  new_ref=$2
@@ -48,6 +50,8 @@ const POST_MERGE_HOOK = `#!/bin/sh
48
50
  # post-merge hook for kibi
49
51
  # Parameter: squash_flag (not used)
50
52
  # Refresh KB state after merge so branch-level assumptions remain current.
53
+ # Uses default non-coordinate-writing sync to avoid writing
54
+ # committed symbol artifacts during automatic hook execution.
51
55
 
52
56
  kibi sync
53
57
  `;
@@ -55,6 +59,8 @@ const POST_REWRITE_HOOK = `#!/bin/sh
55
59
  # post-rewrite hook for kibi
56
60
  # Triggered after git rebase, git commit --amend, etc.
57
61
  # Parameter: rewrite_type (rebase or amend)
62
+ # Uses default non-coordinate-writing sync to avoid writing
63
+ # committed symbol artifacts during automatic hook execution.
58
64
 
59
65
  rewrite_type=$1
60
66
 
@@ -66,23 +72,14 @@ const PRE_COMMIT_HOOK = `#!/bin/sh
66
72
  # pre-commit hook for kibi
67
73
  # Hard enforcement boundary: commits are blocked only here via kibi check.
68
74
  # The OpenCode plugin remains advisory and must not replace this gate.
75
+ # Behavior-changing source edits require staged Kibi impact evidence
76
+ # (KB entity docs, authored symbols metadata, or refreshed symbol
77
+ # coordinates). Test-only and docs-only edits are exempt.
78
+ # Refresh with:
79
+ # kibi sync --refresh-symbol-coordinates && git add documentation/symbol-coordinates.yaml documentation/symbols.yaml
69
80
 
70
81
  set -e
71
82
 
72
- symbols_manifest="documentation/symbols.yaml"
73
-
74
- if [ ! -f "$symbols_manifest" ]; then
75
- echo "Kibi symbols manifest is missing: $symbols_manifest" >&2
76
- echo "Run 'kibi init' to create it, then stage and commit it." >&2
77
- exit 1
78
- fi
79
-
80
- if ! git diff --quiet -- "$symbols_manifest"; then
81
- echo "Kibi symbols manifest has unstaged changes: $symbols_manifest" >&2
82
- echo "Stage and commit documentation/symbols.yaml with the code changes that refreshed it." >&2
83
- exit 1
84
- fi
85
-
86
83
  kibi check --staged
87
84
  `;
88
85
  export async function getCurrentBranch(cwd = process.cwd()) {
@@ -0,0 +1,9 @@
1
+ interface MigrateOptions {
2
+ dryRun?: boolean;
3
+ yes?: boolean;
4
+ }
5
+ export declare function migrateCommand(options?: MigrateOptions): Promise<{
6
+ exitCode: number;
7
+ }>;
8
+ export {};
9
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAkCA,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AA6ID,wBAAsB,cAAc,CAClC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAwG/B"}
@@ -0,0 +1,183 @@
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 { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
19
+ import * as path from "node:path";
20
+ import { resolveActiveBranch } from "../utils/branch-resolver.js";
21
+ import { LATEST_KB_SCHEMA_VERSION, getSchemaVersionStatus, normalizeSchemaVersion, } from "../utils/schema-version.js";
22
+ const MIGRATION_AUDIT_VERSION = 1;
23
+ function printWarning(message) {
24
+ console.log(`Warning: ${message}`);
25
+ }
26
+ function toRelativePath(cwd, filePath) {
27
+ const relativePath = path.relative(cwd, filePath);
28
+ return relativePath.length > 0 ? relativePath : path.basename(filePath);
29
+ }
30
+ function resolveMigrationBranch(cwd) {
31
+ const result = resolveActiveBranch(cwd);
32
+ if ("error" in result) {
33
+ const isNonGitContext = result.code === "NOT_A_GIT_REPO" || result.code === "GIT_NOT_AVAILABLE";
34
+ if (isNonGitContext) {
35
+ return {
36
+ branch: "main",
37
+ warnings: [
38
+ "Not in a git repository; using 'main' for migration audit metadata.",
39
+ ],
40
+ };
41
+ }
42
+ return {
43
+ error: `Failed to resolve active branch: ${result.error}`,
44
+ };
45
+ }
46
+ return {
47
+ branch: result.branch,
48
+ warnings: [],
49
+ };
50
+ }
51
+ function loadRawConfigDocument(cwd) {
52
+ const kbDir = path.join(cwd, ".kb");
53
+ const configPath = path.join(kbDir, "config.json");
54
+ if (!existsSync(kbDir)) {
55
+ return {
56
+ error: "Missing .kb/ directory. Run 'kibi init' before 'kibi migrate'.",
57
+ };
58
+ }
59
+ if (!existsSync(configPath)) {
60
+ return {
61
+ error: "Missing .kb/config.json. Run 'kibi init' to create a baseline config before migrating.",
62
+ };
63
+ }
64
+ try {
65
+ const parsed = JSON.parse(readFileSync(configPath, "utf8"));
66
+ if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
67
+ return {
68
+ error: ".kb/config.json must contain a JSON object. Fix the file and retry 'kibi migrate'.",
69
+ };
70
+ }
71
+ return {
72
+ config: parsed,
73
+ configPath,
74
+ };
75
+ }
76
+ catch (error) {
77
+ const message = error instanceof Error ? error.message : String(error);
78
+ return {
79
+ error: `Invalid .kb/config.json: ${message}. Fix the JSON or re-run 'kibi init'.`,
80
+ };
81
+ }
82
+ }
83
+ function writeJsonAtomically(filePath, value) {
84
+ mkdirSync(path.dirname(filePath), { recursive: true });
85
+ const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
86
+ writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
87
+ renameSync(tempPath, filePath);
88
+ }
89
+ function formatSchemaVersion(rawSchemaVersion, normalized) {
90
+ if (normalized === null) {
91
+ if (rawSchemaVersion === undefined) {
92
+ return "missing";
93
+ }
94
+ return `invalid (${JSON.stringify(rawSchemaVersion)})`;
95
+ }
96
+ return String(normalized);
97
+ }
98
+ function buildMigrationAuditRecord(args) {
99
+ return {
100
+ auditVersion: MIGRATION_AUDIT_VERSION,
101
+ branch: args.branch,
102
+ configPath: args.configPath,
103
+ fromVersion: args.fromVersion,
104
+ migratedAt: args.migratedAt,
105
+ status: "applied",
106
+ toVersion: LATEST_KB_SCHEMA_VERSION,
107
+ warning: args.warning,
108
+ };
109
+ }
110
+ // implements REQ-003
111
+ export async function migrateCommand(options = {}) {
112
+ const cwd = process.cwd();
113
+ const branchResult = resolveMigrationBranch(cwd);
114
+ if ("error" in branchResult) {
115
+ console.error(branchResult.error);
116
+ return { exitCode: 1 };
117
+ }
118
+ const configResult = loadRawConfigDocument(cwd);
119
+ if ("error" in configResult) {
120
+ console.error(configResult.error);
121
+ return { exitCode: 1 };
122
+ }
123
+ const { branch, warnings: branchWarnings } = branchResult;
124
+ const { config, configPath } = configResult;
125
+ const configStatus = getSchemaVersionStatus(config);
126
+ const normalizedVersion = normalizeSchemaVersion(config.schemaVersion);
127
+ const rawSchemaVersion = config.schemaVersion;
128
+ const needsCanonicalSchemaWrite = normalizedVersion === LATEST_KB_SCHEMA_VERSION &&
129
+ rawSchemaVersion !== undefined &&
130
+ rawSchemaVersion !== LATEST_KB_SCHEMA_VERSION;
131
+ const migrationWarning = needsCanonicalSchemaWrite
132
+ ? "KB config schemaVersion should be normalized to the latest numeric version."
133
+ : configStatus.warning;
134
+ const warnings = [...branchWarnings, ...(migrationWarning ? [migrationWarning] : [])];
135
+ const auditPath = path.join(cwd, ".kb", "migrations", `${branch}.json`);
136
+ const configPathRelative = toRelativePath(cwd, configPath);
137
+ const auditPathRelative = toRelativePath(cwd, auditPath);
138
+ for (const warning of warnings) {
139
+ printWarning(warning);
140
+ }
141
+ if (configStatus.currentVersion !== null &&
142
+ configStatus.currentVersion > configStatus.latestVersion) {
143
+ console.error(`Unsupported schemaVersion ${configStatus.currentVersion}. Upgrade kibi-cli before migrating this KB.`);
144
+ return { exitCode: 1 };
145
+ }
146
+ if (!configStatus.needsMigration && !needsCanonicalSchemaWrite) {
147
+ console.log(`No migration needed: ${configPathRelative} is already at schemaVersion ${LATEST_KB_SCHEMA_VERSION}.`);
148
+ if (existsSync(auditPath)) {
149
+ console.log(`Existing migration audit metadata: ${auditPathRelative}`);
150
+ }
151
+ return { exitCode: 0 };
152
+ }
153
+ const fromVersionLabel = formatSchemaVersion(rawSchemaVersion, normalizedVersion);
154
+ if (options.dryRun) {
155
+ console.log(`dry run: would migrate ${configPathRelative} schemaVersion from ${fromVersionLabel} to ${LATEST_KB_SCHEMA_VERSION}.`);
156
+ console.log(`dry run: would write migration audit metadata to ${auditPathRelative}.`);
157
+ console.log("Re-run with --yes to apply these changes.");
158
+ return { exitCode: 0 };
159
+ }
160
+ if (!options.yes) {
161
+ printWarning(`Migration required for ${configPathRelative}.`);
162
+ console.log("No changes applied.");
163
+ console.log("Use --dry-run to preview or --yes to apply the migration.");
164
+ return { exitCode: 0 };
165
+ }
166
+ const nextConfig = {
167
+ ...config,
168
+ schemaVersion: LATEST_KB_SCHEMA_VERSION,
169
+ };
170
+ const migratedAt = new Date().toISOString();
171
+ writeJsonAtomically(configPath, nextConfig);
172
+ writeJsonAtomically(auditPath, buildMigrationAuditRecord({
173
+ branch,
174
+ configPath: configPathRelative,
175
+ fromVersion: configStatus.currentVersion,
176
+ migratedAt,
177
+ warning: migrationWarning,
178
+ }));
179
+ console.log(`Migrated ${configPathRelative} schemaVersion from ${fromVersionLabel} to ${LATEST_KB_SCHEMA_VERSION}.`);
180
+ console.log(`Wrote migration audit metadata to ${auditPathRelative}.`);
181
+ console.log("Migration complete. Future 'kibi migrate' runs will be a no-op.");
182
+ return { exitCode: 0 };
183
+ }
@@ -1,6 +1,8 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dump as dumpYAML, load as parseYAML } from "js-yaml";
3
3
  import { type ManifestSymbolEntry, enrichSymbolCoordinates } from "../../extractors/symbols-coordinator.js";
4
+ import { writeCoordinateArtifact } from "../../extractors/symbol-coordinates.js";
5
+ import { resolveSymbolsManifestPaths } from "../../utils/manifest-paths.js";
4
6
  interface ManifestDeps {
5
7
  dumpYAML: typeof dumpYAML;
6
8
  enrichSymbolCoordinates: typeof enrichSymbolCoordinates;
@@ -8,9 +10,13 @@ interface ManifestDeps {
8
10
  parseYAML: typeof parseYAML;
9
11
  readFileSync: typeof readFileSync;
10
12
  writeFileSync: typeof writeFileSync;
13
+ writeCoordinateArtifact: typeof writeCoordinateArtifact;
14
+ resolveSymbolsManifestPaths: typeof resolveSymbolsManifestPaths;
11
15
  }
12
- export declare const SYMBOLS_MANIFEST_COMMENT_BLOCK = "# symbols.yaml\n# AUTHORED fields (edit freely):\n# id, title, sourceFile, links, status, tags, owner, priority\n# GENERATED fields (never edit manually \u2014 overwritten by kibi sync and kb.symbols.refresh):\n# sourceLine, sourceColumn, sourceEndLine, sourceEndColumn, coordinatesGeneratedAt\n# Run `kibi sync` or call the `kb.symbols.refresh` MCP tool to refresh coordinates.\n";
13
- export declare function refreshManifestCoordinates(manifestPath: string, workspaceRoot: string, deps?: Partial<ManifestDeps>): Promise<void>;
16
+ export declare const SYMBOLS_MANIFEST_COMMENT_BLOCK = "# symbols.yaml\n# AUTHORED fields (edit freely):\n# id, title, sourceFile, links, status, tags, owner, priority\n# GENERATED fields (never edit manually \u2014 overwritten by kibi sync and kb.symbols.refresh):\n# sourceLine, sourceColumn, sourceEndLine, sourceEndColumn\n# Run `kibi sync` or call the `kb.symbols.refresh` MCP tool to refresh coordinates.\n";
17
+ export declare function refreshManifestCoordinates(manifestPath: string, workspaceRoot: string, deps?: Partial<ManifestDeps> & {
18
+ refreshSymbolCoordinates?: boolean;
19
+ }): Promise<void>;
14
20
  export declare function hasAllGeneratedCoordinates(entry: ManifestSymbolEntry): boolean;
15
21
  export declare function isEligibleForCoordinateRefresh(sourceFile: string | undefined, workspaceRoot: string, deps?: Partial<ManifestDeps>): boolean;
16
22
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/manifest.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAElE,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EACL,KAAK,mBAAmB,EACxB,uBAAuB,EACxB,MAAM,yCAAyC,CAAC;AAEjD,UAAU,YAAY;IACpB,QAAQ,EAAE,OAAO,QAAQ,CAAC;IAC1B,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,YAAY,EAAE,OAAO,YAAY,CAAC;IAClC,aAAa,EAAE,OAAO,aAAa,CAAC;CACrC;AAcD,eAAO,MAAM,8BAA8B,qYAM1C,CAAC;AAqBF,wBAAsB,0BAA0B,CAE9C,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CA8Ef;AAED,wBAAgB,0BAA0B,CAExC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAST;AAED,wBAAgB,8BAA8B,CAE5C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC3B,OAAO,CAUT"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/manifest.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAElE,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EACL,KAAK,mBAAmB,EACxB,uBAAuB,EACxB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACjF,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAE5E,UAAU,YAAY;IACpB,QAAQ,EAAE,OAAO,QAAQ,CAAC;IAC1B,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,YAAY,EAAE,OAAO,YAAY,CAAC;IAClC,aAAa,EAAE,OAAO,aAAa,CAAC;IACpC,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,2BAA2B,EAAE,OAAO,2BAA2B,CAAC;CACjE;AAgBD,eAAO,MAAM,8BAA8B,6WAM1C,CAAC;AAoBF,wBAAsB,0BAA0B,CAE9C,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG;IAAE,wBAAwB,CAAC,EAAE,OAAO,CAAA;CAAE,GACpE,OAAO,CAAC,IAAI,CAAC,CAmIf;AAED,wBAAgB,0BAA0B,CAExC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAOT;AAED,wBAAgB,8BAA8B,CAE5C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC3B,OAAO,CAUT"}