kibi-cli 2.24.0 → 2.26.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 (107) hide show
  1. package/dist/cli-errors.d.ts +14 -0
  2. package/dist/cli-errors.d.ts.map +1 -1
  3. package/dist/cli-errors.js +31 -0
  4. package/dist/commands/check.d.ts.map +1 -1
  5. package/dist/commands/check.js +28 -0
  6. package/dist/commands/migrate.d.ts +2 -0
  7. package/dist/commands/migrate.d.ts.map +1 -1
  8. package/dist/commands/migrate.js +115 -4
  9. package/dist/commands/sync/cache.d.ts +30 -0
  10. package/dist/commands/sync/cache.d.ts.map +1 -1
  11. package/dist/commands/sync/cache.js +61 -0
  12. package/dist/commands/sync/extraction.d.ts +6 -0
  13. package/dist/commands/sync/extraction.d.ts.map +1 -1
  14. package/dist/commands/sync/extraction.js +4 -1
  15. package/dist/commands/sync.d.ts.map +1 -1
  16. package/dist/commands/sync.js +27 -1
  17. package/dist/firm-decision-edit.d.ts +30 -0
  18. package/dist/firm-decision-edit.d.ts.map +1 -0
  19. package/dist/firm-decision-edit.js +90 -0
  20. package/dist/operations/bootstrap/claim-restatement.d.ts +37 -0
  21. package/dist/operations/bootstrap/claim-restatement.d.ts.map +1 -0
  22. package/dist/operations/bootstrap/claim-restatement.js +83 -0
  23. package/dist/operations/bootstrap/generate.d.ts.map +1 -1
  24. package/dist/operations/bootstrap/generate.js +23 -12
  25. package/dist/operations/bootstrap/intent-claims.d.ts +5 -2
  26. package/dist/operations/bootstrap/intent-claims.d.ts.map +1 -1
  27. package/dist/operations/bootstrap/intent-claims.js +44 -4
  28. package/dist/operations/migration/requirement-status.d.ts +41 -2
  29. package/dist/operations/migration/requirement-status.d.ts.map +1 -1
  30. package/dist/operations/migration/requirement-status.js +75 -7
  31. package/dist/operations/migration/schema6.d.ts +9 -5
  32. package/dist/operations/migration/schema6.d.ts.map +1 -1
  33. package/dist/operations/migration/schema6.js +82 -13
  34. package/dist/operations/migration/symbol-coordinates-refresh.d.ts +36 -0
  35. package/dist/operations/migration/symbol-coordinates-refresh.d.ts.map +1 -0
  36. package/dist/operations/migration/symbol-coordinates-refresh.js +99 -0
  37. package/dist/operations/mutation/firm-decision-guard.d.ts +10 -0
  38. package/dist/operations/mutation/firm-decision-guard.d.ts.map +1 -0
  39. package/dist/operations/mutation/firm-decision-guard.js +71 -0
  40. package/dist/operations/mutation/source-authoring.d.ts +9 -0
  41. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  42. package/dist/operations/mutation/source-authoring.js +20 -0
  43. package/dist/operations/mutation/stored-entity.d.ts +5 -0
  44. package/dist/operations/mutation/stored-entity.d.ts.map +1 -1
  45. package/dist/operations/mutation/uncompiled-source.d.ts +43 -0
  46. package/dist/operations/mutation/uncompiled-source.d.ts.map +1 -0
  47. package/dist/operations/mutation/uncompiled-source.js +115 -0
  48. package/dist/operations/mutation/upsert.d.ts +6 -0
  49. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  50. package/dist/operations/mutation/upsert.js +34 -5
  51. package/dist/public/impact/analyzer.d.ts.map +1 -1
  52. package/dist/public/impact/analyzer.js +13 -3
  53. package/dist/public/impact/diagnostics.js +1 -1
  54. package/dist/public/impact/unowned-files.d.ts +31 -0
  55. package/dist/public/impact/unowned-files.d.ts.map +1 -0
  56. package/dist/public/impact/unowned-files.js +171 -0
  57. package/dist/public/operations/catalog.d.ts +3 -3
  58. package/dist/public/operations/decision-firmness.d.ts +11 -1
  59. package/dist/public/operations/decision-firmness.d.ts.map +1 -1
  60. package/dist/public/operations/decision-firmness.js +78 -5
  61. package/dist/public/operations/discovery-entities.d.ts +11 -0
  62. package/dist/public/operations/discovery-entities.d.ts.map +1 -1
  63. package/dist/public/operations/discovery-entities.js +28 -0
  64. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  65. package/dist/public/operations/discovery-executors.js +13 -1
  66. package/dist/public/operations/result-envelope.d.ts +2 -0
  67. package/dist/public/operations/result-envelope.d.ts.map +1 -1
  68. package/dist/public/operations/result-envelope.js +1 -0
  69. package/dist/public/operations/source-relationship-parity.d.ts.map +1 -1
  70. package/dist/public/operations/source-relationship-parity.js +22 -7
  71. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  72. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  73. package/dist/public/operations/specs/check-rules.generated.js +2 -1
  74. package/dist/public/operations/specs/check.d.ts +2 -2
  75. package/dist/public/operations/specs/check.js +1 -1
  76. package/dist/public/operations/specs/mutation-schemas.d.ts +1 -1
  77. package/dist/public/operations/specs/mutation-schemas.js +1 -1
  78. package/dist/public/operations/specs/upsert.d.ts +1 -1
  79. package/dist/public/skills/kibi-bootstrap/SKILL.md +2 -2
  80. package/dist/public/skills/kibi-usage/SKILL.md +18 -7
  81. package/dist/traceability/git-change-snapshot.d.ts.map +1 -1
  82. package/dist/traceability/git-change-snapshot.js +44 -3
  83. package/dist/traceability/staged-diagnostics.d.ts +1 -1
  84. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  85. package/dist/traceability/staged-migration.d.ts +13 -0
  86. package/dist/traceability/staged-migration.d.ts.map +1 -0
  87. package/dist/traceability/staged-migration.js +59 -0
  88. package/dist/traceability/staged-symbols-manifest.d.ts +11 -0
  89. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  90. package/dist/traceability/staged-symbols-manifest.js +50 -16
  91. package/dist/utils/rule-registry.generated.d.ts +5 -0
  92. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  93. package/dist/utils/rule-registry.generated.js +6 -0
  94. package/package.json +2 -2
  95. package/src/public/impact/analyzer.ts +21 -7
  96. package/src/public/impact/diagnostics.ts +1 -1
  97. package/src/public/impact/unowned-files.ts +229 -0
  98. package/src/public/operations/decision-firmness.ts +110 -13
  99. package/src/public/operations/discovery-entities.ts +36 -0
  100. package/src/public/operations/discovery-executors.ts +17 -1
  101. package/src/public/operations/result-envelope.ts +3 -0
  102. package/src/public/operations/source-relationship-parity.ts +22 -7
  103. package/src/public/operations/specs/check-rules.generated.ts +2 -1
  104. package/src/public/operations/specs/check.ts +1 -1
  105. package/src/public/operations/specs/mutation-schemas.ts +1 -1
  106. package/src/public/skills/kibi-bootstrap/SKILL.md +2 -2
  107. package/src/public/skills/kibi-usage/SKILL.md +18 -7
@@ -16,6 +16,7 @@
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
18
 
19
+ import { execFileSync } from "node:child_process";
19
20
  import path from "node:path";
20
21
  import {
21
22
  MIN_WHY_CHANGED_WORDS,
@@ -27,6 +28,10 @@ import {
27
28
  supersedeAcknowledgement,
28
29
  whyThisChangedWords,
29
30
  } from "../../decision-firmness.js";
31
+ import {
32
+ firmEditGuidance,
33
+ unapprovedFirmEdit,
34
+ } from "../../firm-decision-edit.js";
30
35
  import {
31
36
  listLaneMarkdownFiles,
32
37
  readText,
@@ -46,6 +51,9 @@ export const DECISION_FIRM_UNREFERENCED_RULE = "decision-firm-unreferenced";
46
51
  /** Only a firm decision may supersede a firm decision (blocking). */
47
52
  // implements REQ-kb-decision-firmness
48
53
  export const DECISION_FIRM_SUPERSEDED_RULE = "decision-firm-superseded";
54
+ /** A firm decision's claim must not change in place without new human words (blocking). */
55
+ // implements REQ-kb-firm-decision-edit
56
+ export const DECISION_FIRM_EDITED_RULE = "decision-firm-edited";
49
57
  /** A supersede must say why under "Why this changed" (blocking). */
50
58
  // implements REQ-kb-decision-firmness
51
59
  export const SUPERSEDES_REASON_MISSING_RULE = "supersedes-reason-missing";
@@ -58,6 +66,7 @@ export const SUPERSEDES_REASON_ACKNOWLEDGED_RULE =
58
66
  export const DECISION_FIRMNESS_RULES = [
59
67
  DECISION_FIRM_UNREFERENCED_RULE,
60
68
  DECISION_FIRM_SUPERSEDED_RULE,
69
+ DECISION_FIRM_EDITED_RULE,
61
70
  SUPERSEDES_REASON_MISSING_RULE,
62
71
  SUPERSEDES_REASON_ACKNOWLEDGED_RULE,
63
72
  ] as const;
@@ -345,8 +354,81 @@ export function evaluateDecisionFirmness(
345
354
  return findings;
346
355
  }
347
356
 
357
+ /** Reads a workspace-relative file as committed at HEAD; null when absent. */
358
+ // implements REQ-kb-firm-decision-edit
359
+ export type CommittedFileReader = (relativePath: string) => string | null;
360
+
361
+ /**
362
+ * The decision files whose working copy differs from HEAD, with a reader for
363
+ * their committed content; undefined outside a Git checkout or before the
364
+ * first commit, where there is nothing to compare against.
365
+ */
366
+ function committedDecisionChanges(
367
+ workspaceRoot: string,
368
+ ): { changed: Set<string>; read: CommittedFileReader } | undefined {
369
+ try {
370
+ const listed = execFileSync(
371
+ "git",
372
+ ["-C", workspaceRoot, "diff", "--name-only", "--relative", "-z", "HEAD"],
373
+ { encoding: "utf8", maxBuffer: 64 * 1024 * 1024, stdio: "pipe" },
374
+ );
375
+ const changed = new Set(listed.split("\0").filter((name) => name !== ""));
376
+ const read: CommittedFileReader = (relativePath) => {
377
+ try {
378
+ return execFileSync(
379
+ "git",
380
+ ["-C", workspaceRoot, "show", `HEAD:./${relativePath}`],
381
+ { encoding: "utf8", maxBuffer: 64 * 1024 * 1024, stdio: "pipe" },
382
+ );
383
+ } catch {
384
+ return null;
385
+ }
386
+ };
387
+ return { changed, read };
388
+ } catch {
389
+ return undefined;
390
+ }
391
+ }
392
+
393
+ /**
394
+ * Current firm requirements and ADRs whose claim changed since HEAD without
395
+ * citing new human words: an in-place edit that bypassed kb_upsert's guard.
396
+ * A decision new since HEAD, or one that was not firm there, is not judged.
397
+ */
398
+ // implements REQ-kb-firm-decision-edit
399
+ export function evaluateFirmDecisionEdits(
400
+ entities: readonly AuthoredEntity[],
401
+ edges: ReadonlyMap<string, Set<string>>,
402
+ changed: ReadonlySet<string>,
403
+ readCommitted: CommittedFileReader,
404
+ ): Violation[] {
405
+ const superseded = supersededIds(edges);
406
+ const findings: Violation[] = [];
407
+ for (const entity of [...entities].sort((left, right) =>
408
+ left.id.localeCompare(right.id),
409
+ )) {
410
+ if (!changed.has(entity.path)) continue;
411
+ if (!isCurrentAuthoredEntity(entity, superseded)) continue;
412
+ const committed = readCommitted(entity.path);
413
+ if (committed === null) continue;
414
+ const before = readAuthoredEntity(committed, entity.path);
415
+ if (before === null || before.type !== entity.type) continue;
416
+ const fields = unapprovedFirmEdit(before, entity);
417
+ if (fields.length === 0) continue;
418
+ findings.push({
419
+ rule: DECISION_FIRM_EDITED_RULE,
420
+ entityId: entity.id,
421
+ description: `${entity.id} is a firm decision whose ${fields.join(", ")} changed since HEAD without citing new human words`,
422
+ suggestion: `${firmEditGuidance(entity.type)}. To undo the edit, restore ${entity.path} from HEAD`,
423
+ source: entity.path,
424
+ evidence: { type: entity.type, changed: fields },
425
+ });
426
+ }
427
+ return findings;
428
+ }
429
+
348
430
  /** Read the authored decisions once and evaluate the selected rules. */
349
- // implements REQ-kb-decision-firmness
431
+ // implements REQ-kb-decision-firmness, REQ-kb-firm-decision-edit
350
432
  export function collectDecisionFirmnessViolations(
351
433
  rules: ReadonlySet<string>,
352
434
  workspaceRoot: string,
@@ -354,16 +436,31 @@ export function collectDecisionFirmnessViolations(
354
436
  if (!DECISION_FIRMNESS_RULES.some((rule) => rules.has(rule))) return [];
355
437
  const entities = listDecisionEntities(workspaceRoot);
356
438
  const manifest = readKbManifest(workspaceRoot);
357
- return evaluateDecisionFirmness(
358
- entities,
359
- authoredSupersedes(workspaceRoot, entities),
360
- rules,
361
- new Set(manifest?.supersedesReasonAcknowledged ?? []),
362
- {
363
- schemaVersion: manifest?.schemaVersion ?? null,
364
- firmSupersedeAcknowledged: new Set(
365
- manifest?.firmSupersedeAcknowledged ?? [],
366
- ),
367
- },
368
- );
439
+ const edges = authoredSupersedes(workspaceRoot, entities);
440
+ const committed = rules.has(DECISION_FIRM_EDITED_RULE)
441
+ ? committedDecisionChanges(workspaceRoot)
442
+ : undefined;
443
+ const edited = committed
444
+ ? evaluateFirmDecisionEdits(
445
+ entities,
446
+ edges,
447
+ committed.changed,
448
+ committed.read,
449
+ )
450
+ : [];
451
+ return [
452
+ ...edited,
453
+ ...evaluateDecisionFirmness(
454
+ entities,
455
+ edges,
456
+ rules,
457
+ new Set(manifest?.supersedesReasonAcknowledged ?? []),
458
+ {
459
+ schemaVersion: manifest?.schemaVersion ?? null,
460
+ firmSupersedeAcknowledged: new Set(
461
+ manifest?.firmSupersedeAcknowledged ?? [],
462
+ ),
463
+ },
464
+ ),
465
+ ];
369
466
  }
@@ -413,6 +413,42 @@ export async function loadEntityIds(
413
413
  return parseAtomList(result.bindings.Ids ?? "[]").map(normalizeEntityId);
414
414
  }
415
415
 
416
+ /**
417
+ * Id, title and semantic text of every current requirement (open,
418
+ * in_progress, closed and the legacy active/approved statuses, not superseded),
419
+ * in id order. Projected rows only: semantic inventories and other large
420
+ * properties stay in the store.
421
+ */
422
+ // implements REQ-bootstrap-claim-restatement
423
+ export async function loadCurrentRequirementTexts(
424
+ prolog: Pick<PrologPort, "query">,
425
+ ): Promise<{ id: string; title: string; semanticText: string }[]> {
426
+ const result = await prolog.query("findall(R, kb:current_req(R), Ids)");
427
+ if (!result.success) {
428
+ throw new Error(
429
+ `Current requirement enumeration failed: ${result.error ?? "Unknown error"}`,
430
+ );
431
+ }
432
+ const ids = parseAtomList(result.bindings.Ids ?? "[]").map(normalizeEntityId);
433
+ const rows = await loadEntityRows(prolog, ids, [
434
+ "id",
435
+ "title",
436
+ "semantic_text",
437
+ ]);
438
+ return rows
439
+ .filter((row) => row.type === "req")
440
+ .map((row) => ({
441
+ id: String(row.id ?? ""),
442
+ title: typeof row.title === "string" ? row.title : "",
443
+ semanticText:
444
+ typeof row.semantic_text === "string" ? row.semantic_text : "",
445
+ }))
446
+ .filter((row) => row.id !== "")
447
+ .sort((left, right) =>
448
+ left.id < right.id ? -1 : left.id > right.id ? 1 : 0,
449
+ );
450
+ }
451
+
416
452
  /**
417
453
  * Properties a projected entity row keeps: what intent ranking, result
418
454
  * summaries and the search answer layer read. Large structured properties
@@ -1,6 +1,9 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { compilerFingerprintReason } from "../../commands/sync/cache.js";
3
+ import {
4
+ compilerFingerprintReason,
5
+ sourceExtractionFailureReasons,
6
+ } from "../../commands/sync/cache.js";
4
7
  import { effectiveFirmness } from "../../decision-firmness.js";
5
8
  import { EngineClient } from "../../engine.js";
6
9
  import {
@@ -1175,6 +1178,13 @@ export async function executeStatus(
1175
1178
  // A stale-lock takeover heals itself; report it without making the KB stale.
1176
1179
  const takeoverNotice = storeLockTakeoverReason(store.path);
1177
1180
  const compilerReason = compilerFingerprintReason(store.path);
1181
+ // implements REQ-kibi-uncompiled-source-visibility
1182
+ // A source the last sync could not extract is missing from the store
1183
+ // even when every compiled file is current.
1184
+ const extractionReasons =
1185
+ store.state === "healthy"
1186
+ ? sourceExtractionFailureReasons(context.workspaceRoot, store.path)
1187
+ : [];
1178
1188
  const engineReason = engineStatus
1179
1189
  ? {
1180
1190
  code: engineStatus.errorCode ?? "engine_status_unavailable",
@@ -1199,6 +1209,7 @@ export async function executeStatus(
1199
1209
  ...(storeReason ? [storeReason] : []),
1200
1210
  ...(lockReason ? [lockReason] : []),
1201
1211
  ...(compilerReason ? [compilerReason] : []),
1212
+ ...extractionReasons,
1202
1213
  ...(engineReason ? [engineReason] : []),
1203
1214
  ].sort((left, right) =>
1204
1215
  String(left.path ?? "").localeCompare(String(right.path ?? "")),
@@ -1208,6 +1219,11 @@ export async function executeStatus(
1208
1219
  // Source hashes can all match while the compilation itself is lossy, so
1209
1220
  // a compiler contract change alone makes the store stale.
1210
1221
  ...(compilerReason ? { syncState: "stale", dirty: true } : {}),
1222
+ // An authored source sync cannot extract leaves its entities out of
1223
+ // the store, so the KB is not current until the file is fixed.
1224
+ ...(extractionReasons.length > 0
1225
+ ? { syncState: "stale", dirty: true }
1226
+ : {}),
1211
1227
  // A missing or empty store has no stale file to report, yet nothing in
1212
1228
  // it is current: the authored sources were never compiled. (A missing
1213
1229
  // store without authored sources, and an unreadable or incomplete one,
@@ -1,6 +1,9 @@
1
1
  import { MIGRATION_PLAN_REFUSED } from "../../operations/planning/migration-refusal.js";
2
2
  import type { KibiResult, OperationEffect, OperationName } from "./types.js";
3
3
 
4
+ export { codedErrorOf } from "../../cli-errors.js";
5
+ export type { CodedErrorFields } from "../../cli-errors.js";
6
+
4
7
  export const KIBI_PROTOCOL_VERSION = 1 as const;
5
8
 
6
9
  function record(value: unknown): value is Record<string, unknown> {
@@ -1,5 +1,6 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import path from "node:path";
3
+ import { sourceExtractionRemediation } from "../../commands/sync/cache.js";
3
4
  import { discoverSourceFiles } from "../../commands/sync/discovery.js";
4
5
  import { processExtractions } from "../../commands/sync/extraction.js";
5
6
  import { findUntrackedDocumentMatches } from "../../commands/sync/untracked-targets.js";
@@ -210,14 +211,28 @@ export async function collectSourceRelationshipParityViolations(
210
211
  true,
211
212
  workspaceRoot,
212
213
  );
214
+ // implements REQ-kibi-uncompiled-source-visibility
215
+ // A source sync cannot extract is missing from the compiled KB with every
216
+ // entity and relationship it declares: one blocking finding per file names
217
+ // it, the extraction error and the fix.
213
218
  if (extraction.errors.length > 0) {
214
- return extraction.errors.map(({ file, message }) => ({
215
- rule: "source-relationship-parity",
216
- entityId: path.relative(workspaceRoot, file).replaceAll(path.sep, "/"),
217
- source: path.relative(workspaceRoot, file).replaceAll(path.sep, "/"),
218
- description: `Authored relationship parity could not inspect source: ${message}`,
219
- suggestion: "Repair the authored source, then rerun kb_check.",
220
- }));
219
+ return extraction.errors.map(({ file, message }) => {
220
+ const relative = path
221
+ .relative(workspaceRoot, file)
222
+ .replaceAll(path.sep, "/");
223
+ return {
224
+ rule: "source-relationship-parity",
225
+ entityId: relative,
226
+ source: relative,
227
+ description: `${relative} cannot be compiled, so the entities and relationships it declares are missing from the KB: ${message}`,
228
+ suggestion: `${sourceExtractionRemediation(relative, message)} Then rerun kb_check.`,
229
+ evidence: {
230
+ direction: "source_extraction",
231
+ file: relative,
232
+ error: message,
233
+ },
234
+ };
235
+ });
221
236
  }
222
237
 
223
238
  const authored: RelationshipParityRecord[] = extraction.results.flatMap(
@@ -58,6 +58,7 @@ export const SELECTABLE_RULE_NAMES: readonly string[] = [
58
58
  "entity-context-acknowledged",
59
59
  "decision-firm-unreferenced",
60
60
  "decision-firm-superseded",
61
+ "decision-firm-edited",
61
62
  "supersedes-reason-missing",
62
63
  "supersedes-reason-acknowledged",
63
64
  "policy-ownership",
@@ -67,4 +68,4 @@ export const SELECTABLE_RULE_NAMES: readonly string[] = [
67
68
  // implements REQ-006
68
69
  /** Comma-separated "Allowed:" fragment for the kb_check rules description. */
69
70
  export const SELECTABLE_RULES_ALLOWED =
70
- "must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers";
71
+ "must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, decision-firm-edited, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers";
@@ -40,7 +40,7 @@ export const checkSpec = {
40
40
  includeImpactDiagnostics: {
41
41
  type: "boolean",
42
42
  description:
43
- "When true, include changed-file impact diagnostics such as symbol_granularity_violation and symbol_semantic_review_needed in structured output.",
43
+ "When true, include changed-file impact diagnostics such as symbol_granularity_violation, symbol_semantic_review_needed and source_file_unowned in structured output.",
44
44
  },
45
45
  maxDiagnostics: {
46
46
  type: "integer",
@@ -129,7 +129,7 @@ export const ENTITY_PROPERTIES_SCHEMA = {
129
129
  type: "string",
130
130
  enum: ["firm", "default", "exploratory"],
131
131
  description:
132
- "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words); default (when absent) = delegated or agent choice; exploratory = flagged guess.",
132
+ "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words); its title, meaning or firmness changes only with new human words (a new origin.ref) or by supersede. default = delegated or agent choice; exploratory = flagged guess. Absent = default if an agent wrote it, else firm.",
133
133
  },
134
134
  rationale: {
135
135
  type: "string",
@@ -2,7 +2,7 @@
2
2
  id: kibi-bootstrap
3
3
  name: kibi-bootstrap
4
4
  description: "Use for any Kibi bootstrap or onboarding task: seeding a new or thin knowledge base; reviewing a bootstrap or onboarding plan, preview, or evidence; judging approval readiness or coverage; diagnosing a blocked, partial, or failed bootstrap or its repair; applying an approved bootstrap plan; or asking the human which knowledge sources hold intent. Covers kb_status first, source interview, cited intent claims, read-only kb_plan_bootstrap preview, explicit approval, exact-plan kb_apply_plan, and repair-safe close-out."
5
- version: 3.13.0
5
+ version: 3.14.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -24,7 +24,7 @@ Make `kb_status` your first tool call on every bootstrap task. This includes rev
24
24
  - **Review a supplied plan or preview, or judge approval readiness or coverage:** call `kb_status`, then run your own read-only `kb_plan_bootstrap` preview. Compare it with the supplied one: hash, candidates, citations, `suppressedCandidates`, `sourceOnlySignals`, and diagnostics. Report what is ready, what is missing or uncited, and any conflicts for the human. Do not apply. End with `kb_status`.
25
25
  - **Blocked, partial, failed, or repair:** never conclude from `kb_status` alone. Run a read-only `kb_plan_bootstrap` preview and report its eligibility and next-action fields (for example `planEligible`, `nextAction`). If it names operator-only repair such as `doctor`, report it and stop. Read `resources/branch-lifecycle.md` before migration or repair. End with `kb_status`.
26
26
  - **New or thin knowledge base, or apply an approved plan:** follow the procedure below.
27
- - **Knowledge base already seeded** (`activationMode: attached_seeded_handoff`): follow the same procedure. Kibi generates no discovered candidates here, but it still plans the intent claims you declare, deduplicated against the existing KB: claims already in the KB are counted as already in the KB, and a plan of the rest is `ready` and applies with `kb_apply_plan` after approval. Never fall back to one `kb_compile_intent` plus one write per claim while such a plan exists. Use `kb_compile_intent` only for claims the plan could not write (`sourceOnlySignals`, `suppressed_by_activation`, `invalid_write`), and `kb_search`/`kb_query` for existing knowledge.
27
+ - **Knowledge base already seeded** (`activationMode: attached_seeded_handoff`): follow the same procedure. Kibi generates no discovered candidates here, but it still plans the intent claims you declare, deduplicated against the existing KB: a claim whose entity exists, or whose statement or excerpt restates a current requirement (same words once case and punctuation are ignored), is counted as already in the KB with the matched id, and a plan of the rest is `ready` and applies with `kb_apply_plan` after approval. A diagnostic saying a claim "closely restates" a requirement means that claim is still planned: show both to the human before approval, and drop the claim or link the two with `restates` or `supersedes`. Never fall back to one `kb_compile_intent` plus one write per claim while such a plan exists. Use `kb_compile_intent` only for claims the plan could not write (`sourceOnlySignals`, `suppressed_by_activation`, `invalid_write`), and `kb_search`/`kb_query` for existing knowledge.
28
28
 
29
29
  ## Goal
30
30
 
@@ -2,7 +2,7 @@
2
2
  id: kibi-usage
3
3
  name: Kibi Usage
4
4
  description: Use Kibi's source-first, exact-Git, migration-aware, proof-aware operations safely across MCP or the trusted local CLI, including partial completion repair.
5
- version: 2.17.0
5
+ version: 2.19.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -112,9 +112,11 @@ it is a pointer to the knowledge, never proof. Each line answers one question:
112
112
  A file already shown this session gets only the one-line `Kibi: this edit to
113
113
  <path> is inside <symbol>` update; the facts and tests from its first snippet
114
114
  still apply. `Kibi: no symbol in <path> is linked to a requirement yet` means
115
- the file is unowned: find the governing requirement with `kb_search` and
116
- `sourceLocations`, then link a symbol to it with `implements` through
117
- `kb_upsert`. Without a readable manifest (`Kibi: the hook could not read
115
+ the file is unowned, not that no rule covers it. When the note names
116
+ requirements whose titles share the path's words, `kb_query` each one and
117
+ treat a match as the governing rule; otherwise find it with `kb_search` and
118
+ `sourceLocations`. Link the symbol that implements it (for markup, the
119
+ component that renders it) with `implements` through `kb_upsert`. Without a readable manifest (`Kibi: the hook could not read
118
120
  .kb/symbols.yaml`) snippets are off for this session; `kb_query({sourceFile})`
119
121
  still answers and `kibi sync` regenerates the manifest. The search tip and the
120
122
  `.kb/` note appear once per session and need no action.
@@ -136,7 +138,11 @@ still answers and `kibi sync` regenerates the manifest. The search tip and the
136
138
  2. Resolve genuine ambiguity with the human; use `kb_model` for deterministic
137
139
  interpretation and typed facts.
138
140
  3. Create endpoints before relationships and upsert small batches sequentially.
139
- Keep the canonical `REQ-* -> SCEN-* -> TEST-*` chain.
141
+ Keep the canonical `REQ-* -> SCEN-* -> TEST-*` chain. Recording the
142
+ behavior a change adds (a requirement with a scenario, and an `implements`
143
+ link from the symbol) is part of finishing that change, not a separate
144
+ request: do it without asking. Ask only when the change conflicts with a
145
+ recorded rule.
140
146
  4. Author tracked Markdown/YAML/manifests and relationship shards through Kibi;
141
147
  Kibi never Git-stages or commits those files. Do not read or edit `.kb`
142
148
  directly.
@@ -368,7 +374,11 @@ Search answers, `kb_query` rows and hook snippets show it (snippets label only
368
374
  wrote it (`origin.kind: agent`): rules people wrote, imported or carried
369
375
  over from before origins existed are theirs. `kb_check` blocks an explicit
370
376
  `firmness: firm` without `origin.ref` to their words, and a replacement of
371
- a firm decision that is not itself firm.
377
+ a firm decision that is not itself firm. Never edit a firm decision's
378
+ title, statement or (ADR) body in place, by `kb_upsert` or on disk:
379
+ `kb_upsert` refuses it and `kb_check` reports `decision-firm-edited`
380
+ unless the edit cites new human words in `origin.ref`. Context sections,
381
+ tags, status and links stay editable.
372
382
  - `default`: a delegated or agent choice (`firmness: default`, or an
373
383
  agent-written decision without `firmness`). Follow it; supersede it only on
374
384
  evidence that it is wrong (a failing test, a measured cost, a constraint it
@@ -381,7 +391,8 @@ conflict a person has to decide. Name the rule (id and what it requires) and
381
391
  ask whether to change it; do not implement the request and do not supersede
382
392
  the rule. Go ahead only when the person has said to change that rule, in this
383
393
  task or in reply: then supersede it with `firmness: firm` and `origin.ref` to
384
- their words, and say in the final answer which rule changed.
394
+ their words (or, for a correction they asked for in place, update it with
395
+ `origin.ref` to those words), and say in the final answer which rule changed.
385
396
 
386
397
  Set `firmness` from what a human actually said: only their stated rule is
387
398
  `firm` (with `origin.ref`); "do whatever" or your own pick is `default`; a