kibi-cli 2.21.0 → 2.21.2

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 (105) hide show
  1. package/dist/cli-operation-metadata.d.ts +1 -1
  2. package/dist/cli-operation-metadata.js +1 -1
  3. package/dist/commands/doctor.js +13 -1
  4. package/dist/commands/init.d.ts.map +1 -1
  5. package/dist/commands/init.js +15 -3
  6. package/dist/commands/legacy-storage-migration.d.ts.map +1 -1
  7. package/dist/commands/legacy-storage-migration.js +26 -1
  8. package/dist/commands/migrate.d.ts.map +1 -1
  9. package/dist/commands/migrate.js +123 -5
  10. package/dist/commands/sync/persistence.d.ts.map +1 -1
  11. package/dist/commands/sync/persistence.js +4 -0
  12. package/dist/decision-firmness.d.ts +39 -0
  13. package/dist/decision-firmness.d.ts.map +1 -0
  14. package/dist/decision-firmness.js +115 -0
  15. package/dist/extractors/markdown.d.ts +2 -0
  16. package/dist/extractors/markdown.d.ts.map +1 -1
  17. package/dist/extractors/markdown.js +14 -0
  18. package/dist/operations/migration/schema6.d.ts +2 -0
  19. package/dist/operations/migration/schema6.d.ts.map +1 -1
  20. package/dist/operations/migration/schema6.js +44 -1
  21. package/dist/operations/migration/supersedes-reason.d.ts +26 -0
  22. package/dist/operations/migration/supersedes-reason.d.ts.map +1 -0
  23. package/dist/operations/migration/supersedes-reason.js +69 -0
  24. package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
  25. package/dist/operations/modeling/predicate-applyplan.js +10 -2
  26. package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
  27. package/dist/operations/modeling/predicate-loader.js +11 -0
  28. package/dist/operations/modeling/predicate-types.d.ts +32 -1
  29. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  30. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  31. package/dist/operations/modeling/suggest-predicates.js +175 -35
  32. package/dist/operations/mutation/delete.d.ts +5 -1
  33. package/dist/operations/mutation/delete.d.ts.map +1 -1
  34. package/dist/operations/mutation/delete.js +223 -17
  35. package/dist/operations/mutation/serialization.d.ts.map +1 -1
  36. package/dist/operations/mutation/serialization.js +1 -0
  37. package/dist/operations/mutation/types.d.ts +37 -0
  38. package/dist/operations/mutation/types.d.ts.map +1 -1
  39. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  40. package/dist/operations/mutation/upsert.js +54 -2
  41. package/dist/operations/planning/apply-plan.d.ts +6 -1
  42. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  43. package/dist/operations/planning/apply-plan.js +8 -2
  44. package/dist/public/operations/catalog.d.ts +12 -3
  45. package/dist/public/operations/catalog.d.ts.map +1 -1
  46. package/dist/public/operations/check-executor.d.ts.map +1 -1
  47. package/dist/public/operations/check-executor.js +3 -0
  48. package/dist/public/operations/contracts.d.ts.map +1 -1
  49. package/dist/public/operations/contracts.js +29 -1
  50. package/dist/public/operations/decision-firmness.d.ts +34 -0
  51. package/dist/public/operations/decision-firmness.d.ts.map +1 -0
  52. package/dist/public/operations/decision-firmness.js +216 -0
  53. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  54. package/dist/public/operations/discovery-executors.js +16 -2
  55. package/dist/public/operations/migration-plan.js +1 -1
  56. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  57. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  58. package/dist/public/operations/specs/check-rules.generated.js +5 -1
  59. package/dist/public/operations/specs/check.d.ts +1 -1
  60. package/dist/public/operations/specs/mutation-schemas.d.ts +5 -0
  61. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  62. package/dist/public/operations/specs/mutation-schemas.js +5 -0
  63. package/dist/public/operations/specs/mutation.d.ts +6 -2
  64. package/dist/public/operations/specs/mutation.d.ts.map +1 -1
  65. package/dist/public/operations/specs/mutation.js +6 -2
  66. package/dist/public/operations/specs/upsert.d.ts +5 -0
  67. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  68. package/dist/public/schemas/entity.d.ts.map +1 -1
  69. package/dist/public/schemas/entity.js +11 -0
  70. package/dist/public/skills/kibi-traceability/SKILL.md +6 -4
  71. package/dist/public/skills/kibi-usage/SKILL.md +50 -10
  72. package/dist/public/skills/kibi-usage/resources/source-authoring.md +4 -2
  73. package/dist/relationships/shards.d.ts +10 -0
  74. package/dist/relationships/shards.d.ts.map +1 -1
  75. package/dist/relationships/shards.js +27 -11
  76. package/dist/schemas/entity.schema.json +28 -0
  77. package/dist/search-answer.d.ts +31 -2
  78. package/dist/search-answer.d.ts.map +1 -1
  79. package/dist/search-answer.js +115 -17
  80. package/dist/types/entities.d.ts +4 -0
  81. package/dist/types/entities.d.ts.map +1 -1
  82. package/dist/utils/kb-manifest.d.ts +6 -0
  83. package/dist/utils/kb-manifest.d.ts.map +1 -1
  84. package/dist/utils/kb-manifest.js +6 -0
  85. package/dist/utils/rule-registry.generated.d.ts +21 -0
  86. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  87. package/dist/utils/rule-registry.generated.js +25 -0
  88. package/dist/utils/schema-version.d.ts +1 -1
  89. package/dist/utils/schema-version.d.ts.map +1 -1
  90. package/dist/utils/schema-version.js +3 -1
  91. package/package.json +2 -2
  92. package/schema/entities.pl +6 -0
  93. package/src/public/operations/check-executor.ts +6 -0
  94. package/src/public/operations/contracts.ts +33 -1
  95. package/src/public/operations/decision-firmness.ts +280 -0
  96. package/src/public/operations/discovery-executors.ts +17 -2
  97. package/src/public/operations/migration-plan.ts +1 -1
  98. package/src/public/operations/specs/check-rules.generated.ts +5 -1
  99. package/src/public/operations/specs/mutation-schemas.ts +6 -0
  100. package/src/public/operations/specs/mutation.ts +7 -2
  101. package/src/public/schemas/entity.ts +12 -0
  102. package/src/public/skills/kibi-traceability/SKILL.md +6 -4
  103. package/src/public/skills/kibi-usage/SKILL.md +50 -10
  104. package/src/public/skills/kibi-usage/resources/source-authoring.md +4 -2
  105. package/src/schemas/entity.schema.json +28 -0
@@ -75,7 +75,7 @@ export declare const CLI_OPERATION_METADATA: readonly [{
75
75
  }, {
76
76
  readonly name: "kb_delete";
77
77
  readonly cliName: "delete";
78
- readonly description: "Delete entities by ID. Use only for intentional removals after dependency checks. Do not use as a bulk cleanup shortcut. Side effects: mutates and saves KB; skips entities with dependents.";
78
+ readonly description: "Delete entities by ID, or exact relationship triples. Use only for intentional removals after dependency checks. Do not use as a bulk cleanup shortcut. An entity authored in a Markdown or YAML source is not deleted directly: the call writes nothing and returns outcome plan_returned with a hash-bound deletionPlan and a nextActions entry holding the exact kb_apply_plan input ({plan, approvedPlanHash}); apply that unmodified, or repeat this call with approvedPlanHash = deletionPlan.planHash to apply the freshly computed plan in one step (a stale or different hash fails with DELETE_PLAN_HASH_MISMATCH and writes nothing). Authored requirements are never deleted: outcome supersession_required points to kb_upsert with a supersedes relationship. dryRun:true previews any delete and writes nothing. Side effects: mutates and saves KB and authored sources; skips entities with dependents; dryRun and plan_returned write nothing.";
79
79
  }, {
80
80
  readonly name: "kb_check";
81
81
  readonly cliName: "check";
@@ -90,7 +90,7 @@ export const CLI_OPERATION_METADATA = [
90
90
  {
91
91
  name: "kb_delete",
92
92
  cliName: "delete",
93
- description: "Delete entities by ID. Use only for intentional removals after dependency checks. Do not use as a bulk cleanup shortcut. Side effects: mutates and saves KB; skips entities with dependents.",
93
+ description: "Delete entities by ID, or exact relationship triples. Use only for intentional removals after dependency checks. Do not use as a bulk cleanup shortcut. An entity authored in a Markdown or YAML source is not deleted directly: the call writes nothing and returns outcome plan_returned with a hash-bound deletionPlan and a nextActions entry holding the exact kb_apply_plan input ({plan, approvedPlanHash}); apply that unmodified, or repeat this call with approvedPlanHash = deletionPlan.planHash to apply the freshly computed plan in one step (a stale or different hash fails with DELETE_PLAN_HASH_MISMATCH and writes nothing). Authored requirements are never deleted: outcome supersession_required points to kb_upsert with a supersedes relationship. dryRun:true previews any delete and writes nothing. Side effects: mutates and saves KB and authored sources; skips entities with dependents; dryRun and plan_returned write nothing.",
94
94
  },
95
95
  {
96
96
  name: "kb_check",
@@ -598,10 +598,15 @@ function checkKbDirectory() {
598
598
  function checkKbManifest() {
599
599
  const status = readKbManifestStatus(process.cwd());
600
600
  if (status.state === "missing") {
601
+ // 'kibi init' on a legacy layout would claim the latest schema and skip
602
+ // the legacy migration steps; migrate writes the manifest itself.
603
+ const legacy = planLegacyStorageMigration(process.cwd());
601
604
  return {
602
605
  passed: false,
603
606
  message: "Not found",
604
- remediation: "Run: kibi init",
607
+ remediation: legacy.legacyConfig === "absent"
608
+ ? "Run: kibi init"
609
+ : "Run: kibi migrate --yes (it creates the manifest from the legacy .kb/config.json; do not run kibi init first)",
605
610
  };
606
611
  }
607
612
  if (status.state !== "ok") {
@@ -626,6 +631,13 @@ function checkLegacyStorage() {
626
631
  "Repair or remove the legacy .kb/config.json, then run: kibi migrate --yes",
627
632
  };
628
633
  }
634
+ if (plan.blockers.length > 0) {
635
+ return {
636
+ passed: false,
637
+ message: `${plan.moves.length} legacy knowledge file(s) outside .kb/ cannot be moved automatically`,
638
+ remediation: `${plan.blockers.join(" ")} Compare each legacy copy with its .kb/ copy and keep the current one: replace the .kb/ copy when the legacy one is newer, or remove (git rm) the legacy copy when it is a stale duplicate. Then run: kibi migrate --yes`,
639
+ };
640
+ }
629
641
  if (plan.legacyConfig === "present" || plan.moves.length > 0) {
630
642
  return {
631
643
  passed: false,
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAmGA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAoDD,wBAAsB,WAAW,CAC/B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAoI/B"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAoGA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAoDD,wBAAsB,WAAW,CAC/B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAkJ/B"}
@@ -21,6 +21,7 @@ import { fileURLToPath } from "node:url";
21
21
  import { classifyActivation } from "../operations/bootstrap/activation.js";
22
22
  import { nodeFilesystem } from "../public/operations/node-ports.js";
23
23
  import { resolveBranchAttachment } from "../utils/branch-resolver.js";
24
+ import { readLegacyKbConfig } from "../utils/config.js";
24
25
  import { resolveGitRepository, } from "../utils/git-repository-context.js";
25
26
  import { scaffoldGitHubIntegration } from "./github-init.js";
26
27
  import { copySchemaFiles, createKbDirectoryStructure, createManifestFile, ensureSymbolsManifestFile, installGitHooks, updateGitIgnore, } from "./init-helpers.js";
@@ -172,14 +173,23 @@ export async function initCommand(options) {
172
173
  }
173
174
  // A directory left by a tool or interrupted setup is not a complete KB.
174
175
  // Reconcile missing infrastructure on every invocation, preserving sources.
176
+ // A legacy .kb/config.json belongs to 'kibi migrate': a fresh manifest
177
+ // would claim the latest schema (skipping the legacy steps) and a fresh
178
+ // symbols manifest would block the move of the real one.
179
+ const legacyConfig = readLegacyKbConfig(projectRoot).kind !== "none";
175
180
  createKbDirectoryStructure(kbDir, currentBranch);
176
- if (!existsSync(path.join(kbDir, "manifest.json"))) {
181
+ if (!legacyConfig && !existsSync(path.join(kbDir, "manifest.json"))) {
177
182
  createManifestFile(kbDir);
178
183
  }
179
184
  updateGitIgnore(projectRoot);
180
185
  const schemaSourceDir = path.resolve(__dirname, "..", "..", "schema");
181
186
  await copySchemaFiles(kbDir, schemaSourceDir);
182
- ensureSymbolsManifestFile(projectRoot);
187
+ if (legacyConfig) {
188
+ console.log("! Legacy .kb/config.json found; left .kb/manifest.json and .kb/symbols.yaml to 'kibi migrate --yes', which moves the existing knowledge and runs every schema step.");
189
+ }
190
+ else {
191
+ ensureSymbolsManifestFile(projectRoot);
192
+ }
183
193
  if (options.hooks) {
184
194
  if (!repoContext) {
185
195
  console.error("Warning: No git repository found, skipping hooks");
@@ -209,7 +219,9 @@ export async function initCommand(options) {
209
219
  if (kbExists) {
210
220
  console.log(" Existing Kibi source knowledge was preserved.");
211
221
  }
212
- console.log((await initNextAction(projectRoot)).message);
222
+ console.log(legacyConfig
223
+ ? " Next: run 'kibi migrate --yes' to move the legacy knowledge into .kb/."
224
+ : (await initNextAction(projectRoot)).message);
213
225
  console.log(" Optional diagnostic: run 'kibi doctor' if setup appears degraded.");
214
226
  if (options.github === true) {
215
227
  scaffoldGitHubIntegration({
@@ -1 +1 @@
1
- {"version":3,"file":"legacy-storage-migration.d.ts","sourceRoot":"","sources":["../../src/commands/legacy-storage-migration.ts"],"names":[],"mappings":"AAoCA,OAAO,EACL,KAAK,uBAAuB,EAI7B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAGL,KAAK,UAAU,EAEhB,MAAM,sBAAsB,CAAC;AAI9B;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,4CAA4C;IAC5C,YAAY,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,uBAAuB,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACxD,kEAAkE;IAClE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,GAAG,SAAS,CAAC;CAC9B;AA2BD,6DAA6D;AAC7D,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,CAqH5E;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,uBAAuB,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,oBAAoB,GACzB,sBAAsB,CAqDxB;AA+BD,wEAAwE;AACxE,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,oBAAoB,EAAE,GACrC,MAAM,CAwCR;AAED,wEAAwE;AACxE,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOhE;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,CAI/D"}
1
+ {"version":3,"file":"legacy-storage-migration.d.ts","sourceRoot":"","sources":["../../src/commands/legacy-storage-migration.ts"],"names":[],"mappings":"AAoCA,OAAO,EACL,KAAK,uBAAuB,EAI7B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAGL,KAAK,UAAU,EAEhB,MAAM,sBAAsB,CAAC;AAI9B;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,4CAA4C;IAC5C,YAAY,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,uBAAuB,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACxD,kEAAkE;IAClE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,GAAG,SAAS,CAAC;CAC9B;AA2BD,6DAA6D;AAC7D,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,CAwH5E;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,uBAAuB,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,oBAAoB,GACzB,sBAAsB,CAwDxB;AA+BD,wEAAwE;AACxE,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,oBAAoB,EAAE,GACrC,MAAM,CAwCR;AA8BD,wEAAwE;AACxE,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOhE;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,CAI/D"}
@@ -128,6 +128,9 @@ export function planLegacyStorageMigration(cwd) {
128
128
  const destinationAbsolute = path.resolve(cwd, move.to);
129
129
  if (!existsSync(destinationAbsolute))
130
130
  continue;
131
+ // 'kibi init' seeds an empty symbols manifest; the legacy one replaces it.
132
+ if (move.lane === "symbols" && isEmptySymbolsStub(destinationAbsolute))
133
+ continue;
131
134
  blockers.push(`Destination ${move.to} already exists; resolve the conflict manually before migrating ${move.from}.`);
132
135
  }
133
136
  // Duplicate destinations (two legacy sources collapsing onto one target).
@@ -174,10 +177,13 @@ export function applyLegacyStorageMigration(cwd, plan) {
174
177
  const backfill = plan.semanticAdvisorBackfill ??
175
178
  existing?.semanticAdvisorBackfill ??
176
179
  "pending";
180
+ // Record the generation the knowledge was written for, not the latest
181
+ // one: 'kibi migrate' raises it after the schema steps, so a run
182
+ // interrupted between the move and those steps still replays them.
177
183
  const manifest = {
178
184
  ...(existing ?? defaultKbManifest()),
179
185
  manifestVersion: defaultKbManifest().manifestVersion,
180
- schemaVersion: LATEST_KB_SCHEMA_VERSION,
186
+ schemaVersion: sourceSchemaVersion(plan.schemaVersion, existing),
181
187
  semanticAdvisorBackfill: backfill,
182
188
  };
183
189
  const manifestPath = writeKbManifest(cwd, manifest);
@@ -264,6 +270,25 @@ export function rewritePendingSourceReceiptPaths(cwd, moves) {
264
270
  }
265
271
  return rewritten;
266
272
  }
273
+ // implements REQ-cli-schema-migration
274
+ function isEmptySymbolsStub(filePath) {
275
+ try {
276
+ const statements = readFileSync(filePath, "utf8")
277
+ .split(/\r?\n/)
278
+ .map((line) => line.trim())
279
+ .filter((line) => line.length > 0 && !line.startsWith("#"));
280
+ return (statements.length === 1 &&
281
+ /^symbols:\s*\[\s*\]$/.test(statements[0] ?? ""));
282
+ }
283
+ catch {
284
+ return false;
285
+ }
286
+ }
287
+ // implements REQ-cli-schema-migration
288
+ function sourceSchemaVersion(legacyVersion, existing) {
289
+ const known = [legacyVersion, existing?.schemaVersion].filter((version) => typeof version === "number" && Number.isInteger(version) && version > 0);
290
+ return known.length > 0 ? Math.min(...known) : LATEST_KB_SCHEMA_VERSION;
291
+ }
267
292
  /** True when the workspace still needs the legacy storage migration. */
268
293
  export function needsLegacyStorageMigration(cwd) {
269
294
  const plan = planLegacyStorageMigration(cwd);
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAiEA,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,uFAAuF;IACvF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAuKD,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,OAAO,EACzB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,CAUR;AAsXD,wBAAgB,+BAA+B,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAKtE;AAGD,wBAAsB,cAAc,CAClC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAiW/B"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAkEA,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,uFAAuF;IACvF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AA8KD,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,OAAO,EACzB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,CAUR;AAsXD,wBAAgB,+BAA+B,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAKtE;AAGD,wBAAsB,cAAc,CAClC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgZ/B"}
@@ -15,6 +15,7 @@
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
+ import { execFileSync } from "node:child_process";
18
19
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
19
20
  import * as path from "node:path";
20
21
  import { load as parseYAML } from "js-yaml";
@@ -79,6 +80,12 @@ const SCHEMA_MIGRATION_STEPS = [
79
80
  to: 8,
80
81
  description: "Pin semantic_text on requirements that lack it (computed from the body before context sections stop counting), and tag current entities without body context review:context-missing; front matter only, bodies are preserved.",
81
82
  },
83
+ {
84
+ id: "decision-firmness-v9",
85
+ from: 8,
86
+ to: 9,
87
+ description: "Accept firmness on requirements and ADRs, and acknowledge in the manifest the current supersedes recorded without a 'Why this changed' section; no source file is rewritten and no reason is invented.",
88
+ },
82
89
  ];
83
90
  function migrationStepsFor(fromVersion) {
84
91
  const start = Math.max(0, fromVersion ?? 0);
@@ -421,9 +428,13 @@ export async function migrateCommand(options = {}) {
421
428
  }
422
429
  const manifestStatus = readKbManifestStatus(cwd);
423
430
  const storagePlan = planLegacyStorageMigration(cwd);
424
- // Baseline for fresh workspaces driven by the internal plan executor.
431
+ // Baseline for fresh workspaces driven by the internal plan executor. A
432
+ // legacy layout carries its own schema generation; a baseline would claim
433
+ // the latest one and skip every legacy step.
425
434
  if (options.initializeMissingConfig === true &&
426
- manifestStatus.state === "missing") {
435
+ manifestStatus.state === "missing" &&
436
+ storagePlan.moves.length === 0 &&
437
+ storagePlan.legacyConfig === "absent") {
427
438
  writeKbManifest(cwd, defaultKbManifest());
428
439
  }
429
440
  if (manifestStatus.state === "missing" &&
@@ -435,7 +446,12 @@ export async function migrateCommand(options = {}) {
435
446
  const { branch, warnings: branchWarnings } = branchResult;
436
447
  const currentManifest = readKbManifest(cwd);
437
448
  const legacySchemaVersion = normalizeSchemaVersion(storagePlan.schemaVersion);
438
- const currentVersion = currentManifest?.schemaVersion ?? legacySchemaVersion;
449
+ // While a legacy config is still present, its schemaVersion is the
450
+ // generation the knowledge was written for; a manifest created beside it
451
+ // (by an older 'kibi init') must not skip the steps in between.
452
+ const currentVersion = storagePlan.legacyConfig !== "absent" && legacySchemaVersion !== null
453
+ ? Math.min(currentManifest?.schemaVersion ?? legacySchemaVersion, legacySchemaVersion)
454
+ : (currentManifest?.schemaVersion ?? legacySchemaVersion);
439
455
  const normalizedVersion = normalizeSchemaVersion(currentVersion);
440
456
  const configStatus = getSchemaVersionStatus(normalizedVersion === null
441
457
  ? undefined
@@ -503,6 +519,8 @@ export async function migrateCommand(options = {}) {
503
519
  const { planPolarityValueBackfill, applyPolarityValueBackfill } = await import("../operations/migration/polarity-values.js");
504
520
  const polarityBackfill = planPolarityValueBackfill(cwd);
505
521
  const bodyContextStep = migrationSteps.some((step) => step.id === "entity-body-context-v8");
522
+ const decisionFirmnessStep = migrationSteps.some((step) => step.id === "decision-firmness-v9");
523
+ const { mergeSupersedesAcknowledgements, planSupersedesReasonAcknowledgement, } = await import("../operations/migration/supersedes-reason.js");
506
524
  const { acknowledgedContextIds, applyContextMissingTags, applySemanticTextPins, countByType, planContextMissingTags, planSemanticTextPins, } = await import("../operations/migration/entity-body-context.js");
507
525
  if (options.dryRun) {
508
526
  if (polarityBackfill.length > 0)
@@ -519,6 +537,11 @@ export async function migrateCommand(options = {}) {
519
537
  for (const skip of [...pins.skipped, ...tags.skipped])
520
538
  printWarning(`Cannot edit ${skip.path}: ${skip.reason}.`);
521
539
  }
540
+ if (decisionFirmnessStep) {
541
+ const unexplained = planSupersedesReasonAcknowledgement(cwd);
542
+ if (unexplained.length > 0)
543
+ console.log(`dry run: would acknowledge ${unexplained.length} supersede(s) recorded without a 'Why this changed' section (manifest only; no reason is written).`);
544
+ }
522
545
  if (storagePlan.moves.length > 0) {
523
546
  console.log(`dry run: would move ${storagePlan.moves.length} legacy knowledge file(s) into .kb/:`);
524
547
  for (const move of storagePlan.moves) {
@@ -558,6 +581,9 @@ export async function migrateCommand(options = {}) {
558
581
  const storageResult = applyLegacyStorageMigration(cwd, storagePlan);
559
582
  if (storageResult.movedFiles.length > 0) {
560
583
  console.log(`Moved ${storageResult.movedFiles.length} knowledge file(s) into the canonical .kb/ layout.`);
584
+ const untracked = untrackedFiles(cwd, storageResult.movedFiles);
585
+ if (untracked > 0)
586
+ printWarning(`${untracked} moved file(s) are not tracked by Git yet, and kibi sync compiles only tracked sources: until you stage them (git add .kb, plus the removals under the legacy folders) the KB compiles without them and plans report them as missing.`);
561
587
  }
562
588
  if (storageResult.retiredLegacyConfig) {
563
589
  console.log("Retired legacy .kb/config.json.");
@@ -599,6 +625,9 @@ export async function migrateCommand(options = {}) {
599
625
  : { written: [], skipped: [], previouslyStamped: 0 };
600
626
  rederiveDriftedInventories(cwd, false);
601
627
  const lifecycle = repairLifecycleFindings(cwd, false);
628
+ const supersedesUnexplained = decisionFirmnessStep
629
+ ? planSupersedesReasonAcknowledgement(cwd)
630
+ : [];
602
631
  const existingManifest = readKbManifest(cwd) ?? defaultKbManifest();
603
632
  writeKbManifest(cwd, {
604
633
  ...existingManifest,
@@ -614,7 +643,14 @@ export async function migrateCommand(options = {}) {
614
643
  ].sort(),
615
644
  }
616
645
  : {}),
646
+ ...(supersedesUnexplained.length > 0
647
+ ? {
648
+ supersedesReasonAcknowledged: mergeSupersedesAcknowledgements(existingManifest.supersedesReasonAcknowledged, supersedesUnexplained),
649
+ }
650
+ : {}),
617
651
  });
652
+ if (supersedesUnexplained.length > 0)
653
+ console.log(`Acknowledged ${supersedesUnexplained.length} supersede(s) recorded without a 'Why this changed' section; new supersedes must explain themselves.`);
618
654
  writeJsonAtomically(auditPath, buildMigrationAuditRecord({
619
655
  branch,
620
656
  manifestPath: KB_MANIFEST_RELATIVE,
@@ -759,7 +795,53 @@ async function buildWorkspaceMigrationPlan(workspaceRoot = process.cwd()) {
759
795
  ],
760
796
  });
761
797
  }
762
- return mergeMigrationPlans(plans);
798
+ return orderStoreSyncAfterSourceRewrites({ buildMigrationPlan, migrationAction }, mergeMigrationPlans(plans), fragment.sourceRewriteActionIds, fragment.actions
799
+ .filter((action) => action.code === "semantic_inventory_review")
800
+ .map((action) => action.id));
801
+ }
802
+ /** Status actions that run 'kibi sync' over the authored sources. */
803
+ const STORE_SYNC_ACTION_IDS = new Set([
804
+ "branch-store-compile",
805
+ "freshness-resync",
806
+ ]);
807
+ /**
808
+ * A sync compiles the authored sources, so it must run after the source
809
+ * rewrites that make them ingestible. While a requirement still needs a
810
+ * manual inventory review the sync would fail on it, so it waits on that
811
+ * review just as the migration sync does.
812
+ */
813
+ // implements REQ-cli-schema-migration
814
+ function orderStoreSyncAfterSourceRewrites(builders, plan, rewriteActionIds, reviewActionIds) {
815
+ const affected = plan.actions.some((action) => STORE_SYNC_ACTION_IDS.has(action.id) && action.state === "ready");
816
+ if (!affected ||
817
+ (rewriteActionIds.length === 0 && reviewActionIds.length === 0))
818
+ return plan;
819
+ const actions = plan.actions.map((action) => {
820
+ if (!STORE_SYNC_ACTION_IDS.has(action.id) || action.state !== "ready")
821
+ return action;
822
+ if (reviewActionIds.length > 0)
823
+ return builders.migrationAction({
824
+ ...action,
825
+ state: "blocked",
826
+ autoApplicable: false,
827
+ evidence: {
828
+ ...action.evidence,
829
+ blockedBy: [...reviewActionIds],
830
+ reason: "kibi sync rejects a requirement until its semantic inventory review is fixed; rerun kibi migrate afterwards.",
831
+ },
832
+ });
833
+ return builders.migrationAction({
834
+ ...action,
835
+ dependsOn: [...action.dependsOn, ...rewriteActionIds],
836
+ });
837
+ });
838
+ return builders.buildMigrationPlan({
839
+ expected: plan.expected,
840
+ evaluatedDomains: plan.scope.evaluatedDomains,
841
+ incompleteDomains: plan.scope.incompleteDomains,
842
+ actions,
843
+ diagnostics: plan.diagnostics,
844
+ });
763
845
  }
764
846
  /**
765
847
  * The action ids approved on the command line. The CLI parser stores
@@ -776,6 +858,42 @@ function approvedActionIdsOf(options) {
776
858
  .filter((id) => id.length > 0);
777
859
  return [...new Set(ids)];
778
860
  }
861
+ /** How many of `files` Git does not track; 0 outside a repository. */
862
+ // implements REQ-cli-schema-migration
863
+ function untrackedFiles(cwd, files) {
864
+ let tracked;
865
+ try {
866
+ tracked = new Set(execFileSync("git", ["-C", cwd, "ls-files", "-z", "--", ".kb"], {
867
+ encoding: "utf8",
868
+ stdio: ["ignore", "pipe", "ignore"],
869
+ timeout: 10_000,
870
+ })
871
+ .split("\0")
872
+ .filter((file) => file.length > 0));
873
+ }
874
+ catch {
875
+ return 0;
876
+ }
877
+ return files.filter((file) => !tracked.has(file)).length;
878
+ }
879
+ /** The table rendering of an apply result; --format json prints it whole. */
880
+ // implements REQ-cli-schema-migration
881
+ function printApplySummary(content) {
882
+ if (content === null || typeof content !== "object")
883
+ return;
884
+ const result = content;
885
+ if (result.outcome !== undefined)
886
+ console.log(`Outcome: ${result.outcome}`);
887
+ for (const action of result.actionResults ?? []) {
888
+ console.log(`- ${action.outcome} ${action.actionId}: ${action.detail}`);
889
+ }
890
+ const remaining = result.remainingPlan;
891
+ if (remaining !== undefined) {
892
+ const ready = remaining.actions.filter((action) => action.state === "ready" && action.autoApplicable).length;
893
+ console.log(`Remaining plan ${remaining.planHash}: ${remaining.status}; actions: ${remaining.summary.actionCount}; automatic-ready: ${ready}`);
894
+ }
895
+ console.log("Run 'kibi migrate' for the remaining actions, or add --format json for the full result.");
896
+ }
779
897
  async function migratePlanCommand(options) {
780
898
  const workspaceRoot = path.resolve(options.workspaceRoot ?? process.cwd());
781
899
  const plan = await buildWorkspaceMigrationPlan(workspaceRoot);
@@ -830,7 +948,7 @@ async function migratePlanCommand(options) {
830
948
  }
831
949
  else {
832
950
  console.log(result.content[0]?.text ?? "Migration applied.");
833
- console.log(JSON.stringify(result.structuredContent, null, 2));
951
+ printApplySummary(result.structuredContent);
834
952
  }
835
953
  return {
836
954
  exitCode: result.structuredContent?.outcome === "applied" ? 0 : 1,
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/persistence.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA2HrD,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;CACrB;AAuFD,iFAAiF;AACjF,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,MAAM,CAAC,CA+BjB;AAED,0EAA0E;AAC1E,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,MAAM,CAAC,CAmBjB;AAED,8EAA8E;AAC9E,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,aAAa,EAAE,SAAS,qBAAqB,EAAE,GAC9C,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAmCD,wBAAsB,eAAe,CAEnC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,OAAO,GAAE;IAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAO,GAChD,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CA8QvD;AA4BD,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,MAAM,CAER;AAGD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,kBAAkB,EAAE,qBAAqB,EAAE,EAC3C,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,OAAO,CAAC;IAAE,iBAAiB,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAmO7D"}
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/persistence.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA2HrD,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;CACrB;AAuFD,iFAAiF;AACjF,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,MAAM,CAAC,CA+BjB;AAED,0EAA0E;AAC1E,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,MAAM,CAAC,CAmBjB;AAED,8EAA8E;AAC9E,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,aAAa,EAAE,SAAS,qBAAqB,EAAE,GAC9C,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAmCD,wBAAsB,eAAe,CAEnC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,OAAO,GAAE;IAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAO,GAChD,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAoRvD;AA4BD,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,MAAM,CAER;AAGD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,kBAAkB,EAAE,qBAAqB,EAAE,EAC3C,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,OAAO,CAAC;IAAE,iBAAiB,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAmO7D"}
@@ -377,6 +377,10 @@ prolog, results, entityIds, options = {}) {
377
377
  if (entity.type === "req" && entity.proof_exempt !== undefined) {
378
378
  props.push(`proof_exempt=${entity.proof_exempt}`);
379
379
  }
380
+ if ((entity.type === "req" || entity.type === "adr") &&
381
+ entity.firmness !== undefined) {
382
+ props.push(`firmness=${toPrologAtom(entity.firmness)}`);
383
+ }
380
384
  if (entity.type === "scenario" && entity.expects !== undefined) {
381
385
  props.push(`expects=${toPrologAtom(entity.expects)}`);
382
386
  }
@@ -0,0 +1,39 @@
1
+ /** Firmness values, firmest first. */
2
+ export declare const FIRMNESS_VALUES: readonly ["firm", "default", "exploratory"];
3
+ export type Firmness = (typeof FIRMNESS_VALUES)[number];
4
+ /** The firmness of a decision that does not state one. */
5
+ export declare const DEFAULT_FIRMNESS: Firmness;
6
+ /** Entity types that carry firmness. */
7
+ export declare const FIRMNESS_TYPES: readonly ["req", "adr"];
8
+ /** A supersede must explain itself under a heading starting with this. */
9
+ export declare const WHY_THIS_CHANGED_HEADING = "Why this changed";
10
+ /** The explanation needs at least this many words of prose. */
11
+ export declare const MIN_WHY_CHANGED_WORDS = 12;
12
+ /**
13
+ * How the manifest records one acknowledged supersede edge. Edges, not ids,
14
+ * are acknowledged, so a supersede added later to the same entity still has
15
+ * to explain itself.
16
+ */
17
+ export declare function supersedeAcknowledgement(from: string, to: string): string;
18
+ export declare function isFirmness(value: unknown): value is Firmness;
19
+ export declare function carriesFirmness(type: unknown): boolean;
20
+ /**
21
+ * The firmness a decision is read with: the authored value, else `default`.
22
+ * Undefined for types that carry no firmness.
23
+ */
24
+ export declare function effectiveFirmness(type: unknown, value: unknown): Firmness | undefined;
25
+ /**
26
+ * The reference that makes a firm decision traceable to a human's words:
27
+ * `origin.ref`, or `approval_ref` on a requirement.
28
+ */
29
+ export declare function firmnessRef(entity: {
30
+ type?: unknown;
31
+ origin?: unknown;
32
+ approval_ref?: unknown;
33
+ }): string | undefined;
34
+ /**
35
+ * Words of prose under the body's "Why this changed" heading (nested
36
+ * headings included), or 0 when the body has no such section.
37
+ */
38
+ export declare function whyThisChangedWords(body: string): number;
39
+ //# sourceMappingURL=decision-firmness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-firmness.d.ts","sourceRoot":"","sources":["../src/decision-firmness.ts"],"names":[],"mappings":"AA4BA,sCAAsC;AAEtC,eAAO,MAAM,eAAe,6CAA8C,CAAC;AAG3E,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,0DAA0D;AAE1D,eAAO,MAAM,gBAAgB,EAAE,QAAoB,CAAC;AAEpD,wCAAwC;AAExC,eAAO,MAAM,cAAc,yBAA0B,CAAC;AAEtD,0EAA0E;AAE1E,eAAO,MAAM,wBAAwB,qBAAqB,CAAC;AAE3D,+DAA+D;AAE/D,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAIxC;;;;GAIG;AAEH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAEzE;AAGD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAK5D;AAGD,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAKtD;AAED;;;GAGG;AAEH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,OAAO,GACb,QAAQ,GAAG,SAAS,CAGtB;AAED;;;GAGG;AAEH,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GAAG,MAAM,GAAG,SAAS,CAcrB;AAMD;;;GAGG;AAEH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBxD"}
@@ -0,0 +1,115 @@
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
+ /**
19
+ * Decision firmness: how firmly a requirement or ADR was decided, which tells
20
+ * a later agent whether it may supersede the decision on evidence or must ask
21
+ * a human first. This module is the one place that knows the values, the
22
+ * default and what a supersede must explain, so extraction, checks, search,
23
+ * query and migration agree.
24
+ */
25
+ import { parseBodySections } from "./entity-body-context.js";
26
+ /** Firmness values, firmest first. */
27
+ // implements REQ-kb-decision-firmness
28
+ export const FIRMNESS_VALUES = ["firm", "default", "exploratory"];
29
+ /** The firmness of a decision that does not state one. */
30
+ // implements REQ-kb-decision-firmness
31
+ export const DEFAULT_FIRMNESS = "default";
32
+ /** Entity types that carry firmness. */
33
+ // implements REQ-kb-decision-firmness
34
+ export const FIRMNESS_TYPES = ["req", "adr"];
35
+ /** A supersede must explain itself under a heading starting with this. */
36
+ // implements REQ-kb-decision-firmness
37
+ export const WHY_THIS_CHANGED_HEADING = "Why this changed";
38
+ /** The explanation needs at least this many words of prose. */
39
+ // implements REQ-kb-decision-firmness
40
+ export const MIN_WHY_CHANGED_WORDS = 12;
41
+ const WHY_THIS_CHANGED = /^why\s+this\s+changed\b/i;
42
+ /**
43
+ * How the manifest records one acknowledged supersede edge. Edges, not ids,
44
+ * are acknowledged, so a supersede added later to the same entity still has
45
+ * to explain itself.
46
+ */
47
+ // implements REQ-kb-decision-firmness
48
+ export function supersedeAcknowledgement(from, to) {
49
+ return `${from} -> ${to}`;
50
+ }
51
+ // implements REQ-kb-decision-firmness
52
+ export function isFirmness(value) {
53
+ return (typeof value === "string" &&
54
+ FIRMNESS_VALUES.includes(value));
55
+ }
56
+ // implements REQ-kb-decision-firmness
57
+ export function carriesFirmness(type) {
58
+ return (typeof type === "string" &&
59
+ FIRMNESS_TYPES.includes(type));
60
+ }
61
+ /**
62
+ * The firmness a decision is read with: the authored value, else `default`.
63
+ * Undefined for types that carry no firmness.
64
+ */
65
+ // implements REQ-kb-decision-firmness
66
+ export function effectiveFirmness(type, value) {
67
+ if (!carriesFirmness(type))
68
+ return undefined;
69
+ return isFirmness(value) ? value : DEFAULT_FIRMNESS;
70
+ }
71
+ /**
72
+ * The reference that makes a firm decision traceable to a human's words:
73
+ * `origin.ref`, or `approval_ref` on a requirement.
74
+ */
75
+ // implements REQ-kb-decision-firmness
76
+ export function firmnessRef(entity) {
77
+ const origin = entity.origin;
78
+ if (origin !== null && typeof origin === "object") {
79
+ const ref = origin.ref;
80
+ if (typeof ref === "string" && ref.trim() !== "")
81
+ return ref.trim();
82
+ }
83
+ if (entity.type === "req" &&
84
+ typeof entity.approval_ref === "string" &&
85
+ entity.approval_ref.trim() !== "") {
86
+ return entity.approval_ref.trim();
87
+ }
88
+ return undefined;
89
+ }
90
+ function wordCount(text) {
91
+ return text.split(/\s+/).filter((word) => /[\p{L}\p{N}]/u.test(word)).length;
92
+ }
93
+ /**
94
+ * Words of prose under the body's "Why this changed" heading (nested
95
+ * headings included), or 0 when the body has no such section.
96
+ */
97
+ // implements REQ-kb-decision-firmness
98
+ export function whyThisChangedWords(body) {
99
+ const sections = parseBodySections(body);
100
+ let level = null;
101
+ let words = 0;
102
+ for (const section of sections) {
103
+ if (level !== null && section.level > 0 && section.level <= level) {
104
+ level = null;
105
+ }
106
+ if (level === null &&
107
+ section.level > 0 &&
108
+ WHY_THIS_CHANGED.test(section.heading.replace(/^[*_`\s]+/, ""))) {
109
+ level = section.level;
110
+ }
111
+ if (level !== null)
112
+ words += wordCount(section.text);
113
+ }
114
+ return words;
115
+ }
@@ -1,3 +1,4 @@
1
+ import { type Firmness } from "../decision-firmness.js";
1
2
  import { type EntityOrigin } from "../public/entity-origin.js";
2
3
  import { type ProofBinding, type ProofContract } from "../public/proof-protocol.js";
3
4
  import { type ProofReceipt } from "../public/proof-receipt.js";
@@ -15,6 +16,7 @@ export interface ExtractedEntity {
15
16
  severity?: string;
16
17
  text_ref?: string;
17
18
  origin?: EntityOrigin;
19
+ firmness?: Firmness;
18
20
  semantic_text?: string;
19
21
  logic_claims?: string[];
20
22
  semantic_clauses?: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/extractors/markdown.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/extractors/markdown.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,7 @@ import { createHash } from "node:crypto";
19
19
  import { readFileSync } from "node:fs";
20
20
  import Ajv from "ajv";
21
21
  import { load as yamlLoad } from "js-yaml";
22
+ import { FIRMNESS_VALUES, carriesFirmness, isFirmness, } from "../decision-firmness.js";
22
23
  import { withoutContextSections } from "../entity-body-context.js";
23
24
  import { deriveFactClaimKey, semanticClaimKey, } from "../operations/semantic-advisor/clauses.js";
24
25
  import { normalizeEntityOrigin, } from "../public/entity-origin.js";
@@ -511,6 +512,19 @@ function extractFromMarkdownContent(content, filePath) {
511
512
  }
512
513
  entity.rationale = data.rationale;
513
514
  }
515
+ // implements REQ-kb-decision-firmness
516
+ // How firmly a requirement or ADR was decided; absent reads as default.
517
+ if (data.firmness !== undefined) {
518
+ if (!carriesFirmness(type) || !isFirmness(data.firmness)) {
519
+ throw new FrontmatterError(carriesFirmness(type)
520
+ ? `Invalid firmness; expected one of ${FIRMNESS_VALUES.join(", ")}`
521
+ : "firmness is only allowed on type: req or adr", filePath, {
522
+ classification: "Invalid Firmness",
523
+ hint: "Use firm only for a rule a human stated (with origin.ref to their words), default for a delegated or agent choice, exploratory for a flagged guess; remove the field from other entity types.",
524
+ });
525
+ }
526
+ entity.firmness = data.firmness;
527
+ }
514
528
  if (type === "req" && Array.isArray(data.exempts_claims)) {
515
529
  entity.exempts_claims = data.exempts_claims.filter((value) => typeof value === "string");
516
530
  }
@@ -4,6 +4,8 @@ import { CLOSE_SUPERSEDED_REQUIREMENTS_CODE, MIGRATION_SYNC_ACTION_ID, MIGRATION
4
4
  export declare const ORIGIN_SCHEMA_VERSION = 6;
5
5
  /** The KB schema generation that requires body context on current entities. */
6
6
  export declare const BODY_CONTEXT_SCHEMA_VERSION = 8;
7
+ /** The KB schema generation that adds decision firmness. */
8
+ export declare const DECISION_FIRMNESS_SCHEMA_VERSION = 9;
7
9
  /** Automatic: re-derive a drifted inventory with the current advisor. */
8
10
  export declare const SEMANTIC_INVENTORY_REDERIVE_CODE = "semantic_inventory_rederive";
9
11
  /** Review: a drifted inventory Kibi cannot re-derive safely. */