kibi-cli 2.20.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 (126) 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 +2 -0
  9. package/dist/commands/migrate.d.ts.map +1 -1
  10. package/dist/commands/migrate.js +155 -11
  11. package/dist/commands/sync/discovery.d.ts.map +1 -1
  12. package/dist/commands/sync/discovery.js +14 -2
  13. package/dist/commands/sync/persistence.d.ts.map +1 -1
  14. package/dist/commands/sync/persistence.js +4 -0
  15. package/dist/decision-firmness.d.ts +39 -0
  16. package/dist/decision-firmness.d.ts.map +1 -0
  17. package/dist/decision-firmness.js +115 -0
  18. package/dist/extractors/markdown.d.ts +2 -0
  19. package/dist/extractors/markdown.d.ts.map +1 -1
  20. package/dist/extractors/markdown.js +16 -1
  21. package/dist/operations/migration/kb-sources.d.ts +5 -1
  22. package/dist/operations/migration/kb-sources.d.ts.map +1 -1
  23. package/dist/operations/migration/kb-sources.js +28 -1
  24. package/dist/operations/migration/schema6.d.ts +2 -0
  25. package/dist/operations/migration/schema6.d.ts.map +1 -1
  26. package/dist/operations/migration/schema6.js +44 -1
  27. package/dist/operations/migration/supersedes-reason.d.ts +26 -0
  28. package/dist/operations/migration/supersedes-reason.d.ts.map +1 -0
  29. package/dist/operations/migration/supersedes-reason.js +69 -0
  30. package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
  31. package/dist/operations/modeling/predicate-applyplan.js +10 -2
  32. package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
  33. package/dist/operations/modeling/predicate-loader.js +11 -0
  34. package/dist/operations/modeling/predicate-types.d.ts +32 -1
  35. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  36. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  37. package/dist/operations/modeling/suggest-predicates.js +175 -35
  38. package/dist/operations/mutation/delete.d.ts +5 -1
  39. package/dist/operations/mutation/delete.d.ts.map +1 -1
  40. package/dist/operations/mutation/delete.js +223 -17
  41. package/dist/operations/mutation/serialization.d.ts.map +1 -1
  42. package/dist/operations/mutation/serialization.js +1 -0
  43. package/dist/operations/mutation/source-authoring.d.ts +8 -0
  44. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  45. package/dist/operations/mutation/source-authoring.js +33 -0
  46. package/dist/operations/mutation/types.d.ts +37 -0
  47. package/dist/operations/mutation/types.d.ts.map +1 -1
  48. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  49. package/dist/operations/mutation/upsert.js +54 -2
  50. package/dist/operations/mutation/validation.d.ts.map +1 -1
  51. package/dist/operations/mutation/validation.js +2 -1
  52. package/dist/operations/planning/apply-plan.d.ts +6 -1
  53. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  54. package/dist/operations/planning/apply-plan.js +8 -2
  55. package/dist/operations/semantic-advisor/clauses.d.ts +9 -0
  56. package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
  57. package/dist/operations/semantic-advisor/clauses.js +24 -0
  58. package/dist/public/operations/catalog.d.ts +14 -5
  59. package/dist/public/operations/catalog.d.ts.map +1 -1
  60. package/dist/public/operations/check-executor.d.ts.map +1 -1
  61. package/dist/public/operations/check-executor.js +3 -0
  62. package/dist/public/operations/contracts.d.ts.map +1 -1
  63. package/dist/public/operations/contracts.js +29 -1
  64. package/dist/public/operations/decision-firmness.d.ts +34 -0
  65. package/dist/public/operations/decision-firmness.d.ts.map +1 -0
  66. package/dist/public/operations/decision-firmness.js +216 -0
  67. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  68. package/dist/public/operations/discovery-executors.js +16 -2
  69. package/dist/public/operations/migration-plan.d.ts.map +1 -1
  70. package/dist/public/operations/migration-plan.js +37 -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 +5 -1
  74. package/dist/public/operations/specs/check.d.ts +1 -1
  75. package/dist/public/operations/specs/mutation-schemas.d.ts +7 -2
  76. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  77. package/dist/public/operations/specs/mutation-schemas.js +13 -17
  78. package/dist/public/operations/specs/mutation.d.ts +6 -2
  79. package/dist/public/operations/specs/mutation.d.ts.map +1 -1
  80. package/dist/public/operations/specs/mutation.js +6 -2
  81. package/dist/public/operations/specs/upsert.d.ts +7 -2
  82. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  83. package/dist/public/schemas/entity.d.ts.map +1 -1
  84. package/dist/public/schemas/entity.js +13 -13
  85. package/dist/public/skills/kibi-traceability/SKILL.md +6 -4
  86. package/dist/public/skills/kibi-usage/SKILL.md +52 -10
  87. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +1 -1
  88. package/dist/public/skills/kibi-usage/resources/source-authoring.md +4 -2
  89. package/dist/public/skills/kibi-usage/resources/ui-requirements.md +2 -2
  90. package/dist/public/skills/kibi-usage/resources/workflows.md +1 -1
  91. package/dist/relationships/shards.d.ts +10 -0
  92. package/dist/relationships/shards.d.ts.map +1 -1
  93. package/dist/relationships/shards.js +27 -11
  94. package/dist/schemas/entity.schema.json +30 -25
  95. package/dist/search-answer.d.ts +31 -2
  96. package/dist/search-answer.d.ts.map +1 -1
  97. package/dist/search-answer.js +115 -17
  98. package/dist/types/entities.d.ts +4 -0
  99. package/dist/types/entities.d.ts.map +1 -1
  100. package/dist/utils/kb-manifest.d.ts +6 -0
  101. package/dist/utils/kb-manifest.d.ts.map +1 -1
  102. package/dist/utils/kb-manifest.js +6 -0
  103. package/dist/utils/rule-registry.generated.d.ts +21 -0
  104. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  105. package/dist/utils/rule-registry.generated.js +25 -0
  106. package/dist/utils/schema-version.d.ts +1 -1
  107. package/dist/utils/schema-version.d.ts.map +1 -1
  108. package/dist/utils/schema-version.js +3 -1
  109. package/package.json +2 -2
  110. package/schema/entities.pl +6 -0
  111. package/src/public/operations/check-executor.ts +6 -0
  112. package/src/public/operations/contracts.ts +33 -1
  113. package/src/public/operations/decision-firmness.ts +280 -0
  114. package/src/public/operations/discovery-executors.ts +17 -2
  115. package/src/public/operations/migration-plan.ts +41 -9
  116. package/src/public/operations/specs/check-rules.generated.ts +5 -1
  117. package/src/public/operations/specs/mutation-schemas.ts +14 -17
  118. package/src/public/operations/specs/mutation.ts +7 -2
  119. package/src/public/schemas/entity.ts +14 -13
  120. package/src/public/skills/kibi-traceability/SKILL.md +6 -4
  121. package/src/public/skills/kibi-usage/SKILL.md +52 -10
  122. package/src/public/skills/kibi-usage/resources/fact-lanes.md +1 -1
  123. package/src/public/skills/kibi-usage/resources/source-authoring.md +4 -2
  124. package/src/public/skills/kibi-usage/resources/ui-requirements.md +2 -2
  125. package/src/public/skills/kibi-usage/resources/workflows.md +1 -1
  126. package/src/schemas/entity.schema.json +30 -25
@@ -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);
@@ -5,6 +5,8 @@ interface MigrateOptions {
5
5
  applySafe?: boolean;
6
6
  approvedPlanHash?: string;
7
7
  approvedActionIds?: string[];
8
+ /** `--approved-action <id>` as the CLI parser spells it; same as approvedActionIds. */
9
+ approvedAction?: string[];
8
10
  workspaceRoot?: string;
9
11
  /** Internal migration-plan executor: create only the canonical baseline manifest. */
10
12
  initializeMissingConfig?: boolean;
@@ -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,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,22 +795,131 @@ 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
+ });
845
+ }
846
+ /**
847
+ * The action ids approved on the command line. The CLI parser stores
848
+ * `--approved-action` as `approvedAction`; in-process callers pass
849
+ * `approvedActionIds`. Either spelling limits `--apply-safe` to those ids.
850
+ */
851
+ // implements REQ-cli-schema-migration
852
+ function approvedActionIdsOf(options) {
853
+ const ids = [
854
+ ...(options.approvedActionIds ?? []),
855
+ ...(options.approvedAction ?? []),
856
+ ]
857
+ .map((id) => id.trim())
858
+ .filter((id) => id.length > 0);
859
+ return [...new Set(ids)];
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.");
763
896
  }
764
897
  async function migratePlanCommand(options) {
765
898
  const workspaceRoot = path.resolve(options.workspaceRoot ?? process.cwd());
766
899
  const plan = await buildWorkspaceMigrationPlan(workspaceRoot);
767
- if (options.applySafe === true) {
768
- if (!options.approvedPlanHash) {
900
+ // `--yes` with JSON output applies every ready automatic action of the plan
901
+ // it would otherwise only print; `--dry-run` still only prints it.
902
+ const approveWholePlan = options.applySafe !== true &&
903
+ options.yes === true &&
904
+ options.dryRun !== true &&
905
+ options.format === "json";
906
+ if (options.applySafe === true || approveWholePlan) {
907
+ const approvedPlanHash = approveWholePlan
908
+ ? plan.planHash
909
+ : options.approvedPlanHash;
910
+ if (!approvedPlanHash) {
769
911
  console.error("--apply-safe requires --approved-plan-hash <sha256>.");
770
912
  return { exitCode: 2 };
771
913
  }
772
- if (options.approvedPlanHash !== plan.planHash) {
914
+ if (approvedPlanHash !== plan.planHash) {
773
915
  console.error("Migration plan changed; regenerate the plan and approve its current hash.");
774
916
  return { exitCode: 2 };
775
917
  }
776
- const approvedActionIds = options.approvedActionIds && options.approvedActionIds.length > 0
777
- ? options.approvedActionIds
918
+ const explicitActionIds = approveWholePlan
919
+ ? []
920
+ : approvedActionIdsOf(options);
921
+ const approvedActionIds = explicitActionIds.length > 0
922
+ ? explicitActionIds
778
923
  : plan.actions
779
924
  .filter((action) => action.state === "ready" && action.autoApplicable)
780
925
  .map((action) => action.id);
@@ -788,7 +933,6 @@ async function migratePlanCommand(options) {
788
933
  import("../public/operations/specs/planning.js"),
789
934
  import("../cli-json-command.js"),
790
935
  ]);
791
- const approvedPlanHash = options.approvedPlanHash;
792
936
  const execute = () => executeOperation(createCliRuntime({ workspaceRoot }), applyPlanSpec, {
793
937
  plan,
794
938
  approvedPlanHash,
@@ -804,7 +948,7 @@ async function migratePlanCommand(options) {
804
948
  }
805
949
  else {
806
950
  console.log(result.content[0]?.text ?? "Migration applied.");
807
- console.log(JSON.stringify(result.structuredContent, null, 2));
951
+ printApplySummary(result.structuredContent);
808
952
  }
809
953
  return {
810
954
  exitCode: result.structuredContent?.outcome === "applied" ? 0 : 1,
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/discovery.ts"],"names":[],"mappings":"AAkCA,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,+EAA+E;IAC/E,6BAA6B,CAAC,EAAE,OAAO,CAAC;CACzC,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AA2FH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOlD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAShE;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAUxD;AAED,wBAAgB,mCAAmC,CACjD,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,SAAS,4BAA4B,EAAE,GAChD,IAAI,CA6CN;AAsBD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B,MAAM,GAAG,IAAI,CAIf;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC;IACT,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,4BAA4B,EAAE,4BAA4B,EAAE,CAAC;CAC9D,CAAC,CA+FD"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/discovery.ts"],"names":[],"mappings":"AAkCA,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,+EAA+E;IAC/E,6BAA6B,CAAC,EAAE,OAAO,CAAC;CACzC,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAwGH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOlD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAShE;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAUxD;AAED,wBAAgB,mCAAmC,CACjD,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,SAAS,4BAA4B,EAAE,GAChD,IAAI,CA6CN;AAsBD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B,MAAM,GAAG,IAAI,CAIf;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC;IACT,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,4BAA4B,EAAE,4BAA4B,EAAE,CAAC;CAC9D,CAAC,CAgGD"}
@@ -34,7 +34,14 @@ async function gitTrackedPaths(cwd) {
34
34
  .filter(Boolean)
35
35
  .map((value) => value.replaceAll("\\", "/")));
36
36
  }
37
- function pendingSourcePaths(cwd, recoverMissing) {
37
+ /**
38
+ * The sources bound by pending receipts. A receipt whose file Git now tracks
39
+ * is kept as an input but not compared, whatever its hash says: the tracked
40
+ * bytes are the authored input, and the caller retires that receipt. Only a
41
+ * receipt for a still-untracked file can refuse the sync on hash drift.
42
+ */
43
+ // implements REQ-core-atomic-upsert-persistence
44
+ function pendingSourcePaths(cwd, recoverMissing, tracked = new Set()) {
38
45
  const root = path.join(cwd, ".kb", "recovery", "pending-sources");
39
46
  if (!existsSync(root)) {
40
47
  return { paths: new Map(), recoveredReceipts: [] };
@@ -53,6 +60,11 @@ function pendingSourcePaths(cwd, recoverMissing) {
53
60
  typeof receipt.afterHash === "string" &&
54
61
  /^[a-f0-9]{64}$/i.test(receipt.afterHash)) {
55
62
  const relative = receipt.path.replaceAll("\\", "/");
63
+ if (tracked.has(relative)) {
64
+ // Still an authored input; only the stale hash is ignored.
65
+ paths.set(relative, receipt.afterHash);
66
+ continue;
67
+ }
56
68
  const absolute = path.resolve(cwd, relative);
57
69
  if (!absolute.startsWith(`${path.resolve(cwd)}${path.sep}`)) {
58
70
  throw new Error(`Pending source receipt escapes workspace: ${relative}`);
@@ -199,7 +211,7 @@ export async function discoverSourceFiles(cwd, options = {}) {
199
211
  const recoveredPendingReceiptPaths = [];
200
212
  if (options.trackedOnly) {
201
213
  const tracked = await gitTrackedPaths(cwd);
202
- const pending = pendingSourcePaths(cwd, options.recoverMissingPendingSources === true);
214
+ const pending = pendingSourcePaths(cwd, options.recoverMissingPendingSources === true, tracked);
203
215
  recoveredPendingReceiptPaths.push(...pending.recoveredReceipts);
204
216
  entityMarkdownFiles = filterTracked(cwd, entityMarkdownFiles, tracked, new Set(pending.paths.keys()));
205
217
  manifestFiles = filterTracked(cwd, manifestFiles, tracked, new Set(pending.paths.keys()));
@@ -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"}