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.
- package/dist/cli-operation-metadata.d.ts +1 -1
- package/dist/cli-operation-metadata.js +1 -1
- package/dist/commands/doctor.js +13 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +15 -3
- package/dist/commands/legacy-storage-migration.d.ts.map +1 -1
- package/dist/commands/legacy-storage-migration.js +26 -1
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +123 -5
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +4 -0
- package/dist/decision-firmness.d.ts +39 -0
- package/dist/decision-firmness.d.ts.map +1 -0
- package/dist/decision-firmness.js +115 -0
- package/dist/extractors/markdown.d.ts +2 -0
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +14 -0
- package/dist/operations/migration/schema6.d.ts +2 -0
- package/dist/operations/migration/schema6.d.ts.map +1 -1
- package/dist/operations/migration/schema6.js +44 -1
- package/dist/operations/migration/supersedes-reason.d.ts +26 -0
- package/dist/operations/migration/supersedes-reason.d.ts.map +1 -0
- package/dist/operations/migration/supersedes-reason.js +69 -0
- package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-applyplan.js +10 -2
- package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-loader.js +11 -0
- package/dist/operations/modeling/predicate-types.d.ts +32 -1
- package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
- package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
- package/dist/operations/modeling/suggest-predicates.js +175 -35
- package/dist/operations/mutation/delete.d.ts +5 -1
- package/dist/operations/mutation/delete.d.ts.map +1 -1
- package/dist/operations/mutation/delete.js +223 -17
- package/dist/operations/mutation/serialization.d.ts.map +1 -1
- package/dist/operations/mutation/serialization.js +1 -0
- package/dist/operations/mutation/types.d.ts +37 -0
- package/dist/operations/mutation/types.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +54 -2
- package/dist/operations/planning/apply-plan.d.ts +6 -1
- package/dist/operations/planning/apply-plan.d.ts.map +1 -1
- package/dist/operations/planning/apply-plan.js +8 -2
- package/dist/public/operations/catalog.d.ts +12 -3
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/check-executor.d.ts.map +1 -1
- package/dist/public/operations/check-executor.js +3 -0
- package/dist/public/operations/contracts.d.ts.map +1 -1
- package/dist/public/operations/contracts.js +29 -1
- package/dist/public/operations/decision-firmness.d.ts +34 -0
- package/dist/public/operations/decision-firmness.d.ts.map +1 -0
- package/dist/public/operations/decision-firmness.js +216 -0
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +16 -2
- package/dist/public/operations/migration-plan.js +1 -1
- package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
- package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
- package/dist/public/operations/specs/check-rules.generated.js +5 -1
- package/dist/public/operations/specs/check.d.ts +1 -1
- package/dist/public/operations/specs/mutation-schemas.d.ts +5 -0
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +5 -0
- package/dist/public/operations/specs/mutation.d.ts +6 -2
- package/dist/public/operations/specs/mutation.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation.js +6 -2
- package/dist/public/operations/specs/upsert.d.ts +5 -0
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +11 -0
- package/dist/public/skills/kibi-traceability/SKILL.md +6 -4
- package/dist/public/skills/kibi-usage/SKILL.md +50 -10
- package/dist/public/skills/kibi-usage/resources/source-authoring.md +4 -2
- package/dist/relationships/shards.d.ts +10 -0
- package/dist/relationships/shards.d.ts.map +1 -1
- package/dist/relationships/shards.js +27 -11
- package/dist/schemas/entity.schema.json +28 -0
- package/dist/search-answer.d.ts +31 -2
- package/dist/search-answer.d.ts.map +1 -1
- package/dist/search-answer.js +115 -17
- package/dist/types/entities.d.ts +4 -0
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/utils/kb-manifest.d.ts +6 -0
- package/dist/utils/kb-manifest.d.ts.map +1 -1
- package/dist/utils/kb-manifest.js +6 -0
- package/dist/utils/rule-registry.generated.d.ts +21 -0
- package/dist/utils/rule-registry.generated.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.js +25 -0
- package/dist/utils/schema-version.d.ts +1 -1
- package/dist/utils/schema-version.d.ts.map +1 -1
- package/dist/utils/schema-version.js +3 -1
- package/package.json +2 -2
- package/schema/entities.pl +6 -0
- package/src/public/operations/check-executor.ts +6 -0
- package/src/public/operations/contracts.ts +33 -1
- package/src/public/operations/decision-firmness.ts +280 -0
- package/src/public/operations/discovery-executors.ts +17 -2
- package/src/public/operations/migration-plan.ts +1 -1
- package/src/public/operations/specs/check-rules.generated.ts +5 -1
- package/src/public/operations/specs/mutation-schemas.ts +6 -0
- package/src/public/operations/specs/mutation.ts +7 -2
- package/src/public/schemas/entity.ts +12 -0
- package/src/public/skills/kibi-traceability/SKILL.md +6 -4
- package/src/public/skills/kibi-usage/SKILL.md +50 -10
- package/src/public/skills/kibi-usage/resources/source-authoring.md +4 -2
- 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",
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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:
|
|
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":"
|
|
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"}
|
package/dist/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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,
|
|
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:
|
|
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":"
|
|
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"}
|
package/dist/commands/migrate.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
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":"
|
|
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. */
|