kibi-cli 0.10.0 → 0.11.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 (46) 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 +204 -3
  5. package/dist/commands/init-helpers.d.ts.map +1 -1
  6. package/dist/commands/init-helpers.js +11 -14
  7. package/dist/commands/sync/manifest.d.ts +8 -2
  8. package/dist/commands/sync/manifest.d.ts.map +1 -1
  9. package/dist/commands/sync/manifest.js +56 -11
  10. package/dist/commands/sync.d.ts +1 -0
  11. package/dist/commands/sync.d.ts.map +1 -1
  12. package/dist/commands/sync.js +9 -7
  13. package/dist/commands/usage-metrics.d.ts +8 -0
  14. package/dist/commands/usage-metrics.d.ts.map +1 -0
  15. package/dist/commands/usage-metrics.js +323 -0
  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/extractors/manifest.d.ts +1 -1
  23. package/dist/public/extractors/manifest.d.ts.map +1 -1
  24. package/dist/public/extractors/manifest.js +1 -1
  25. package/dist/traceability/evidence-model.d.ts +142 -0
  26. package/dist/traceability/evidence-model.d.ts.map +1 -0
  27. package/dist/traceability/evidence-model.js +70 -0
  28. package/dist/traceability/git-staged.d.ts +1 -0
  29. package/dist/traceability/git-staged.d.ts.map +1 -1
  30. package/dist/traceability/git-staged.js +28 -3
  31. package/dist/traceability/staged-diagnostics.d.ts +25 -0
  32. package/dist/traceability/staged-diagnostics.d.ts.map +1 -0
  33. package/dist/traceability/staged-diagnostics.js +67 -0
  34. package/dist/traceability/staged-impact-contract.d.ts +57 -0
  35. package/dist/traceability/staged-impact-contract.d.ts.map +1 -0
  36. package/dist/traceability/staged-impact-contract.js +202 -0
  37. package/dist/traceability/staged-symbols-manifest.d.ts +23 -0
  38. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -0
  39. package/dist/traceability/staged-symbols-manifest.js +269 -0
  40. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  41. package/dist/traceability/symbol-extract.js +33 -9
  42. package/dist/utils/manifest-paths.d.ts +8 -0
  43. package/dist/utils/manifest-paths.d.ts.map +1 -0
  44. package/dist/utils/manifest-paths.js +62 -0
  45. package/package.json +1 -1
  46. package/src/public/extractors/manifest.ts +2 -0
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAwCA,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
package/dist/cli.js CHANGED
@@ -30,6 +30,7 @@ import { queryCommand } from "./commands/query.js";
30
30
  import { searchCommand } from "./commands/search.js";
31
31
  import { statusCommand } from "./commands/status.js";
32
32
  import { syncCommand } from "./commands/sync.js";
33
+ import { usageMetricsCommand } from "./commands/usage-metrics.js";
33
34
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
34
35
  const VERSION = packageJson.version ?? "0.1.0";
35
36
  // implements REQ-003
@@ -66,10 +67,12 @@ program
66
67
  .command("sync")
67
68
  .description("Sync entities from documents")
68
69
  .option("--validate-only", "Perform validation without mutations")
70
+ .option("--refresh-symbol-coordinates", "Refresh generated symbol coordinates")
69
71
  .option("--rebuild", "Rebuild branch snapshot from scratch (discards current KB)")
70
72
  .action(withExitCode(async (options) => {
71
73
  return syncCommand(options);
72
74
  }));
75
+ program;
73
76
  program
74
77
  .command("query [type]")
75
78
  .description("Query knowledge base")
@@ -165,6 +168,14 @@ program
165
168
  .command("doctor")
166
169
  .description("Diagnose KB setup and configuration")
167
170
  .action(withExitCode(async () => doctorCommand()));
171
+ program
172
+ .command("usage-metrics")
173
+ .description("Report usage and quality metrics from .kb/usage.log")
174
+ .option("--format <format>", "Output format: json|table", "table")
175
+ .option("--limit <n>", "Limit top zero-result source files", "10")
176
+ .action(withExitCode(async (options) => {
177
+ return usageMetricsCommand(options);
178
+ }));
168
179
  program
169
180
  .command("branch")
170
181
  .description("Manage branch KBs")
@@ -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,CA0R/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 };
@@ -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()) {
@@ -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"}
@@ -14,11 +14,13 @@
14
14
 
15
15
  You should have received a copy of the GNU Affero General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
- */
17
+ */
18
18
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
19
19
  import * as path from "node:path";
20
20
  import { dump as dumpYAML, load as parseYAML } from "js-yaml";
21
21
  import { enrichSymbolCoordinates, } from "../../extractors/symbols-coordinator.js";
22
+ import { writeCoordinateArtifact } from "../../extractors/symbol-coordinates.js";
23
+ import { resolveSymbolsManifestPaths } from "../../utils/manifest-paths.js";
22
24
  function resolveDeps(overrides) {
23
25
  return {
24
26
  dumpYAML,
@@ -27,6 +29,8 @@ function resolveDeps(overrides) {
27
29
  parseYAML,
28
30
  readFileSync,
29
31
  writeFileSync,
32
+ writeCoordinateArtifact,
33
+ resolveSymbolsManifestPaths,
30
34
  ...overrides,
31
35
  };
32
36
  }
@@ -34,7 +38,7 @@ export const SYMBOLS_MANIFEST_COMMENT_BLOCK = `# symbols.yaml
34
38
  # AUTHORED fields (edit freely):
35
39
  # id, title, sourceFile, links, status, tags, owner, priority
36
40
  # GENERATED fields (never edit manually — overwritten by kibi sync and kb.symbols.refresh):
37
- # sourceLine, sourceColumn, sourceEndLine, sourceEndColumn, coordinatesGeneratedAt
41
+ # sourceLine, sourceColumn, sourceEndLine, sourceEndColumn
38
42
  # Run \`kibi sync\` or call the \`kb.symbols.refresh\` MCP tool to refresh coordinates.
39
43
  `;
40
44
  const SYMBOL_COORD_EXTENSIONS = new Set([
@@ -52,12 +56,12 @@ const GENERATED_COORD_FIELDS = [
52
56
  "sourceColumn",
53
57
  "sourceEndLine",
54
58
  "sourceEndColumn",
55
- "coordinatesGeneratedAt",
56
59
  ];
57
60
  export async function refreshManifestCoordinates(
58
61
  // implements REQ-003
59
62
  manifestPath, workspaceRoot, deps) {
60
63
  const resolved = resolveDeps(deps);
64
+ const shouldRefreshCoordinates = deps?.refreshSymbolCoordinates ?? false;
61
65
  const rawContent = resolved.readFileSync(manifestPath, "utf8");
62
66
  const parsed = resolved.parseYAML(rawContent);
63
67
  if (!isRecord(parsed)) {
@@ -69,11 +73,54 @@ manifestPath, workspaceRoot, deps) {
69
73
  console.warn(`Warning: symbols manifest ${manifestPath} has no symbols array; skipping coordinate refresh`);
70
74
  return;
71
75
  }
72
- const before = rawSymbols.map((entry) => isRecord(entry)
73
- ? { ...entry }
74
- : {});
76
+ const before = rawSymbols.map((entry) => isRecord(entry) ? { ...entry } : {});
75
77
  const enriched = await resolved.enrichSymbolCoordinates(before, workspaceRoot);
76
- parsed.symbols = enriched;
78
+ // Build coordinates map keyed by symbol id
79
+ const coordinatesMap = {};
80
+ for (const entry of enriched) {
81
+ const id = typeof entry?.id === "string" ? entry.id : undefined;
82
+ if (!id)
83
+ continue;
84
+ if (typeof entry.sourceFile === "string" &&
85
+ typeof entry.sourceLine === "number" &&
86
+ typeof entry.sourceColumn === "number" &&
87
+ typeof entry.sourceEndLine === "number" &&
88
+ typeof entry.sourceEndColumn === "number") {
89
+ coordinatesMap[id] = {
90
+ sourceFile: entry.sourceFile,
91
+ sourceLine: entry.sourceLine,
92
+ sourceColumn: entry.sourceColumn,
93
+ sourceEndLine: entry.sourceEndLine,
94
+ sourceEndColumn: entry.sourceEndColumn,
95
+ };
96
+ }
97
+ }
98
+ // Optionally write the coordinate artifact to the coordinates path when explicitly requested
99
+ if (shouldRefreshCoordinates) {
100
+ try {
101
+ const coordinatesPath = resolved.resolveSymbolsManifestPaths(workspaceRoot).coordinatesPath;
102
+ const artifactContent = resolved.writeCoordinateArtifact(coordinatesMap);
103
+ resolved.writeFileSync(coordinatesPath, artifactContent, "utf8");
104
+ }
105
+ catch (err) {
106
+ console.warn(`Warning: Failed to write symbol-coordinates artifact: ${String(err)}`);
107
+ }
108
+ }
109
+ // Strip generated fields from symbols.yaml entries only if original had them
110
+ const strippedEnriched = enriched.map((current, idx) => {
111
+ const prev = before[idx] ?? {};
112
+ const out = { ...current };
113
+ const originalHadGenerated = GENERATED_COORD_FIELDS.some((f) => prev[f] !== undefined);
114
+ if (originalHadGenerated) {
115
+ for (const field of GENERATED_COORD_FIELDS) {
116
+ delete out[field];
117
+ }
118
+ }
119
+ // Ensure we never write coordinatesGeneratedAt
120
+ delete out["coordinatesGeneratedAt"];
121
+ return out;
122
+ });
123
+ parsed.symbols = strippedEnriched;
77
124
  let refreshed = 0;
78
125
  let failed = 0;
79
126
  let unchanged = 0;
@@ -106,7 +153,7 @@ manifestPath, workspaceRoot, deps) {
106
153
  if (rawContent !== nextContent) {
107
154
  resolved.writeFileSync(manifestPath, nextContent, "utf8");
108
155
  }
109
- console.log(`✓ Refreshed symbol coordinates in ${path.relative(workspaceRoot, manifestPath)} (refreshed=${refreshed}, unchanged=${unchanged}, failed=${failed})`);
156
+ console.log(`\u2713 Refreshed symbol coordinates in ${path.relative(workspaceRoot, manifestPath)} (refreshed=${refreshed}, unchanged=${unchanged}, failed=${failed})`);
110
157
  }
111
158
  export function hasAllGeneratedCoordinates(
112
159
  // implements REQ-003
@@ -114,9 +161,7 @@ entry) {
114
161
  return (typeof entry.sourceLine === "number" &&
115
162
  typeof entry.sourceColumn === "number" &&
116
163
  typeof entry.sourceEndLine === "number" &&
117
- typeof entry.sourceEndColumn === "number" &&
118
- typeof entry.coordinatesGeneratedAt === "string" &&
119
- entry.coordinatesGeneratedAt.length > 0);
164
+ typeof entry.sourceEndColumn === "number");
120
165
  }
121
166
  export function isEligibleForCoordinateRefresh(
122
167
  // implements REQ-003
@@ -25,6 +25,7 @@ interface SyncCommandRuntime {
25
25
  export declare function syncCommand(options?: {
26
26
  validateOnly?: boolean;
27
27
  rebuild?: boolean;
28
+ refreshSymbolCoordinates?: boolean;
28
29
  }, runtime?: SyncCommandRuntime): Promise<SyncResult>;
29
30
  export { normalizeMarkdownPath } from "./sync/discovery.js";
30
31
  //# sourceMappingURL=sync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAejE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA4B7C,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI5B;AAGD,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,yBAAyB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,UAAU,kBAAkB;IAC1B,WAAW,CAAC,EAAE,CACZ,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,CACX,OAAO,EAAE,yBAAyB,GAAG;QAAE,UAAU,EAAE,OAAO,CAAA;KAAE,KACzD,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,aAAa,CAAC;CAChE;AAGD,wBAAsB,WAAW,CAC/B,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,EAC3D,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,UAAU,CAAC,CAmbrB;AAED,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAejE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA4B7C,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI5B;AAGD,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,yBAAyB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,UAAU,kBAAkB;IAC1B,WAAW,CAAC,EAAE,CACZ,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,CACX,OAAO,EAAE,yBAAyB,GAAG;QAAE,UAAU,EAAE,OAAO,CAAA;KAAE,KACzD,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,aAAa,CAAC;CAChE;AAGD,wBAAsB,WAAW,CAC/B,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,wBAAwB,CAAC,EAAE,OAAO,CAAA;CAAO,EAC/F,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,UAAU,CAAC,CAkbrB;AAED,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -140,13 +140,15 @@ export async function syncCommand(options = {}, runtime = {}) {
140
140
  }
141
141
  }
142
142
  if (!validateOnly) {
143
- for (const file of manifestFiles) {
144
- try {
145
- await refreshManifestCoordinates(file, process.cwd());
146
- }
147
- catch (error) {
148
- const message = error instanceof Error ? error.message : String(error);
149
- console.warn(`Warning: Failed to refresh symbol coordinates in ${file}: ${message}`);
143
+ if (options.refreshSymbolCoordinates) {
144
+ for (const file of manifestFiles) {
145
+ try {
146
+ await refreshManifestCoordinates(file, process.cwd(), { refreshSymbolCoordinates: options.refreshSymbolCoordinates });
147
+ }
148
+ catch (error) {
149
+ const message = error instanceof Error ? error.message : String(error);
150
+ console.warn(`Warning: Failed to refresh symbol coordinates in ${file}: ${message}`);
151
+ }
150
152
  }
151
153
  }
152
154
  }
@@ -0,0 +1,8 @@
1
+ import type { CommandResult } from "../cli.js";
2
+ interface UsageMetricsOptions {
3
+ format?: "json" | "table";
4
+ limit?: string;
5
+ }
6
+ export declare function usageMetricsCommand(options: UsageMetricsOptions): Promise<CommandResult | undefined>;
7
+ export {};
8
+ //# sourceMappingURL=usage-metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-metrics.d.ts","sourceRoot":"","sources":["../../src/commands/usage-metrics.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,UAAU,mBAAmB;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAkED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAuBpC"}