kibi-cli 2.22.0 → 2.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-register-foundation.d.ts.map +1 -1
- package/dist/cli-register-foundation.js +1 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +51 -3
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +40 -0
- package/dist/commands/init-helpers.d.ts +2 -1
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +3 -0
- package/dist/commands/migrate.d.ts +47 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +82 -15
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +15 -1
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +37 -35
- package/dist/intent-search.d.ts.map +1 -1
- package/dist/intent-search.js +32 -4
- package/dist/operations/bootstrap/activation.d.ts +7 -1
- package/dist/operations/bootstrap/activation.d.ts.map +1 -1
- package/dist/operations/bootstrap/activation.js +27 -2
- package/dist/operations/bootstrap/generate.d.ts.map +1 -1
- package/dist/operations/bootstrap/generate.js +56 -16
- package/dist/operations/bootstrap/guidance-actions.d.ts.map +1 -1
- package/dist/operations/bootstrap/guidance-actions.js +14 -2
- package/dist/operations/bootstrap/guidance.d.ts.map +1 -1
- package/dist/operations/bootstrap/guidance.js +6 -2
- package/dist/operations/bootstrap/presentation.d.ts.map +1 -1
- package/dist/operations/bootstrap/presentation.js +75 -9
- package/dist/operations/bootstrap/types.d.ts +6 -0
- package/dist/operations/bootstrap/types.d.ts.map +1 -1
- package/dist/operations/migration/kb-sources.d.ts +8 -5
- package/dist/operations/migration/kb-sources.d.ts.map +1 -1
- package/dist/operations/migration/kb-sources.js +50 -14
- package/dist/operations/migration/schema6.d.ts.map +1 -1
- package/dist/operations/migration/schema6.js +11 -2
- package/dist/operations/migration/source-paths.d.ts +18 -1
- package/dist/operations/migration/source-paths.d.ts.map +1 -1
- package/dist/operations/migration/source-paths.js +111 -16
- package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
- package/dist/operations/modeling/suggest-predicates.js +22 -7
- package/dist/operations/mutation/requirement-semantics.d.ts +16 -0
- package/dist/operations/mutation/requirement-semantics.d.ts.map +1 -1
- package/dist/operations/mutation/requirement-semantics.js +50 -0
- package/dist/operations/mutation/strict-fact.js +1 -1
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +2 -1
- package/dist/operations/mutation/validation.d.ts.map +1 -1
- package/dist/operations/mutation/validation.js +43 -1
- package/dist/operations/planning/apply-plan.d.ts.map +1 -1
- package/dist/operations/planning/apply-plan.js +38 -9
- package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analyze-prose.js +49 -17
- package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/ingestion-boundary.js +25 -1
- package/dist/operations/semantic-advisor/stored-ledger-drift.d.ts +19 -0
- package/dist/operations/semantic-advisor/stored-ledger-drift.d.ts.map +1 -0
- package/dist/operations/semantic-advisor/stored-ledger-drift.js +78 -0
- package/dist/public/impact/analyzer.d.ts.map +1 -1
- package/dist/public/impact/analyzer.js +7 -1
- package/dist/public/impact/diagnostics.d.ts.map +1 -1
- package/dist/public/impact/diagnostics.js +16 -3
- package/dist/public/impact/types.d.ts +2 -0
- package/dist/public/impact/types.d.ts.map +1 -1
- package/dist/public/operations/catalog.d.ts +7 -2
- 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 +10 -3
- package/dist/public/operations/check-helpers.d.ts +10 -0
- package/dist/public/operations/check-helpers.d.ts.map +1 -1
- package/dist/public/operations/check-helpers.js +54 -0
- package/dist/public/operations/contracts.d.ts.map +1 -1
- package/dist/public/operations/contracts.js +1 -0
- package/dist/public/operations/decision-firmness.d.ts +3 -1
- package/dist/public/operations/decision-firmness.d.ts.map +1 -1
- package/dist/public/operations/decision-firmness.js +13 -3
- package/dist/public/operations/discovery-executors.d.ts +18 -0
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +77 -3
- package/dist/public/operations/migration-plan.d.ts +13 -0
- package/dist/public/operations/migration-plan.d.ts.map +1 -1
- package/dist/public/operations/migration-plan.js +29 -1
- package/dist/public/operations/schema6-check-actions.d.ts +4 -0
- package/dist/public/operations/schema6-check-actions.d.ts.map +1 -1
- package/dist/public/operations/schema6-check-actions.js +39 -2
- package/dist/public/operations/source-path-dangling.d.ts +1 -1
- package/dist/public/operations/source-path-dangling.d.ts.map +1 -1
- package/dist/public/operations/source-path-dangling.js +11 -2
- package/dist/public/operations/specs/composite.d.ts +1 -1
- package/dist/public/operations/specs/discovery.d.ts +5 -0
- package/dist/public/operations/specs/discovery.d.ts.map +1 -1
- package/dist/public/operations/specs/discovery.js +5 -0
- package/dist/public/operations/specs/planning.d.ts +1 -1
- package/dist/public/operations/specs/planning.js +1 -1
- package/dist/public/operations/specs/reporting.d.ts.map +1 -1
- package/dist/public/operations/specs/reporting.js +6 -4
- package/dist/public/operations/specs/semantic.d.ts +1 -1
- package/dist/public/operations/specs/semantic.d.ts.map +1 -1
- package/dist/public/operations/specs/semantic.js +30 -3
- package/dist/public/skills/kibi-bootstrap/SKILL.md +3 -2
- package/dist/public/skills/kibi-usage/SKILL.md +13 -5
- package/dist/public/skills/kibi-usage/resources/workflows.md +3 -1
- package/dist/search-answer.d.ts +14 -0
- package/dist/search-answer.d.ts.map +1 -1
- package/dist/search-answer.js +42 -4
- package/dist/traceability/git-change-snapshot.d.ts +46 -0
- package/dist/traceability/git-change-snapshot.d.ts.map +1 -1
- package/dist/traceability/git-change-snapshot.js +187 -3
- package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
- package/dist/traceability/staged-diagnostics.js +5 -6
- package/dist/traceability/staged-inventory-drift.d.ts +24 -0
- package/dist/traceability/staged-inventory-drift.d.ts.map +1 -0
- package/dist/traceability/staged-inventory-drift.js +84 -0
- package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
- package/dist/traceability/staged-symbols-manifest.js +1 -2
- package/dist/utils/content-freshness.d.ts +9 -0
- package/dist/utils/content-freshness.d.ts.map +1 -1
- package/dist/utils/content-freshness.js +33 -0
- package/dist/utils/merge-driver-setup.d.ts +10 -0
- package/dist/utils/merge-driver-setup.d.ts.map +1 -0
- package/dist/utils/merge-driver-setup.js +69 -0
- package/dist/utils/rule-registry.generated.d.ts +1 -1
- package/dist/utils/rule-registry.generated.js +1 -1
- package/package.json +2 -2
- package/src/public/impact/analyzer.ts +13 -1
- package/src/public/impact/diagnostics.ts +20 -3
- package/src/public/impact/types.ts +2 -0
- package/src/public/operations/check-executor.ts +16 -4
- package/src/public/operations/check-helpers.ts +65 -0
- package/src/public/operations/contracts.ts +1 -0
- package/src/public/operations/decision-firmness.ts +18 -2
- package/src/public/operations/discovery-executors.ts +113 -3
- package/src/public/operations/migration-plan.ts +47 -1
- package/src/public/operations/schema6-check-actions.ts +43 -2
- package/src/public/operations/source-path-dangling.ts +12 -2
- package/src/public/operations/specs/discovery.ts +6 -0
- package/src/public/operations/specs/planning.ts +1 -1
- package/src/public/operations/specs/reporting.ts +6 -4
- package/src/public/operations/specs/semantic.ts +41 -4
- package/src/public/skills/kibi-bootstrap/SKILL.md +3 -2
- package/src/public/skills/kibi-usage/SKILL.md +13 -5
- package/src/public/skills/kibi-usage/resources/workflows.md +3 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { analyzeSemanticAdvisorInput } from "../semantic-advisor/analyze-prose.js";
|
|
2
|
+
import { validateSemanticInventoryBoundary, } from "../semantic-advisor/ingestion-boundary.js";
|
|
1
3
|
import { storedEntityLookup, } from "./stored-entity.js";
|
|
2
4
|
/** The fields that make up a requirement's proposition ledger. */
|
|
3
5
|
const SEMANTIC_CONTRACT_FIELDS = [
|
|
@@ -62,3 +64,51 @@ export async function withStoredRequirementSemantics(input, prolog, staged, look
|
|
|
62
64
|
return input;
|
|
63
65
|
return { ...input, properties: { ...preserved, ...properties } };
|
|
64
66
|
}
|
|
67
|
+
/** Stored fields that decide how sync reads a requirement's propositions. */
|
|
68
|
+
const BOUNDARY_FIELDS = [
|
|
69
|
+
"status",
|
|
70
|
+
"title",
|
|
71
|
+
"text_ref",
|
|
72
|
+
...SEMANTIC_CONTRACT_FIELDS,
|
|
73
|
+
];
|
|
74
|
+
function boundaryOf(input, properties, relationships) {
|
|
75
|
+
const payload = { type: input.type, id: input.id, properties };
|
|
76
|
+
const { receipt } = analyzeSemanticAdvisorInput({
|
|
77
|
+
payload: { ...payload, relationships },
|
|
78
|
+
});
|
|
79
|
+
return validateSemanticInventoryBoundary(payload, relationships, receipt);
|
|
80
|
+
}
|
|
81
|
+
// implements REQ-kibi-upsert-preserves-requirement-semantics, REQ-kibi-proposition-complete-ingestion
|
|
82
|
+
/**
|
|
83
|
+
* An update to a stored requirement is judged as its authored file will read
|
|
84
|
+
* afterwards: the stored status, prose and proposition ledger with the
|
|
85
|
+
* payload's fields on top (the source writer keeps every front matter key the
|
|
86
|
+
* payload leaves out and states the stored `semantic_text`). A payload such
|
|
87
|
+
* as `{status: "closed"}` passes the check on its own fields, yet the file it
|
|
88
|
+
* leaves is a current requirement whose prose has no inventory, which sync
|
|
89
|
+
* then refuses. Such a write is refused here with the error sync would give.
|
|
90
|
+
*
|
|
91
|
+
* A requirement that was already current with the same prose and no complete
|
|
92
|
+
* inventory is legacy content sync keeps accepting, so a write that changes
|
|
93
|
+
* neither keeps working. A write that makes the requirement current, or
|
|
94
|
+
* changes the prose sync checks, must carry the complete inventory.
|
|
95
|
+
*/
|
|
96
|
+
export async function assertStoredRequirementBoundary(input, relationships, prolog, staged, lookup = storedEntityLookup(prolog, input.id, input.type)) {
|
|
97
|
+
if (input.type !== "req")
|
|
98
|
+
return;
|
|
99
|
+
const stored = await storedRequirement(input.id, lookup, staged);
|
|
100
|
+
if (stored === undefined)
|
|
101
|
+
return;
|
|
102
|
+
const before = {};
|
|
103
|
+
for (const field of BOUNDARY_FIELDS) {
|
|
104
|
+
if (stored[field] !== undefined)
|
|
105
|
+
before[field] = stored[field];
|
|
106
|
+
}
|
|
107
|
+
const after = boundaryOf(input, { ...before, ...(input.properties ?? {}) }, relationships);
|
|
108
|
+
if (after.errors.length === 0)
|
|
109
|
+
return;
|
|
110
|
+
const previous = boundaryOf(input, before, relationships);
|
|
111
|
+
if (previous.applicable && previous.sourceHash === after.sourceHash)
|
|
112
|
+
return;
|
|
113
|
+
throw new Error(`Proposition-complete ingestion failed: ${input.id} would be written as a current requirement without a complete proposition inventory (${after.errors.join("; ")}). Run kb_model mode analyze (CLI: semantic-advisor) with id '${input.id}' and its complete semantic_text, then send the returned inventory contract (semantic_inventory, semantic_inventory_version, semantic_source_field, semantic_source_hash, logic_claims) in this kb_upsert, or retire the requirement through a superseding one instead.`);
|
|
114
|
+
}
|
|
@@ -90,7 +90,7 @@ export function factKindShapeHints(entity) {
|
|
|
90
90
|
if (missing.length !== 0)
|
|
91
91
|
return [
|
|
92
92
|
`fact_kind 'predicate' requires ${missing.join(", ")}.`,
|
|
93
|
-
"Next action: call
|
|
93
|
+
"Next action: call kb_model with mode predicates (CLI: kibi suggest-predicates) and apply its planned fact instead of hand-writing ontology predicate facts.",
|
|
94
94
|
];
|
|
95
95
|
return [];
|
|
96
96
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAexE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAexE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AA4B3E,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AAsEpB,wBAAgB,gCAAgC,CAC9C,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,GAChB,IAAI,CAkBN;AAwCD,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,aAAa,EAAE,SAAS,iBAAiB,EAAE,EAC3C,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CA8BvC;AAmCD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CACrC,CAAC,CAAC;AAGH,uFAAuF;AACvF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC,CAAC;AAGH;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,WAAW,GAAG;IACrB,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACtD,GACA,sBAAsB,CASxB;AAGD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;CAC3C,CAAC,CAAC;AAGH;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAiF3B;AAgDD,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C;;;;;;OAMG;IAEH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACxC,CAAC,CAAC;AAyBH,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CA2hBzC"}
|
|
@@ -19,7 +19,7 @@ import { resolveUpsertOrigin } from "./origin.js";
|
|
|
19
19
|
import { planRecoveryNotes, settlePendingPlanApplyJournals, withPlanRecoveryNotes, } from "./plan-apply-journal.js";
|
|
20
20
|
import { assertPredicateArgumentVocabulary } from "./predicate-vocabulary-guard.js";
|
|
21
21
|
import { existingRelationships, validateLiveRelationshipTargets, validateRelationshipSources, validateStrictLanePairing, validateSupersedesSourceHistory, } from "./relationships.js";
|
|
22
|
-
import { withStoredRequirementSemantics } from "./requirement-semantics.js";
|
|
22
|
+
import { assertStoredRequirementBoundary, withStoredRequirementSemantics, } from "./requirement-semantics.js";
|
|
23
23
|
import { MutationRollbackFailureError, MutationSaga } from "./saga.js";
|
|
24
24
|
import { readFileTimes, restoreFileTimes, writePendingSourceReceipt, writeSourceForUpsert, } from "./source-authoring.js";
|
|
25
25
|
import { storedEntityLookup, } from "./stored-entity.js";
|
|
@@ -244,6 +244,7 @@ export async function validateUpsertForCommit(payload, context, options = {}) {
|
|
|
244
244
|
await validateLiveRelationshipTargets(prolog, validated.entity, validated.relationships, staged);
|
|
245
245
|
await validateSupersedesSourceHistory(prolog, validated.entity, validated.relationships, context.workspaceRoot, undefined, staged);
|
|
246
246
|
const semantic = analyzeUpsertSemantics({ ...input, relationships });
|
|
247
|
+
await assertStoredRequirementBoundary(input, relationships, prolog, staged, lookup);
|
|
247
248
|
await assertLogicalGroundingClaimKeys(prolog, { ...input, relationships }, relationships, staged);
|
|
248
249
|
await assertPredicateArgumentVocabulary(prolog, validated.entity, staged);
|
|
249
250
|
// The origin the write records is added last: none of the checks above
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/validation.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,0BAA0B,EAC3B,MAAM,oCAAoC,CAAC;AAU5C,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/validation.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,0BAA0B,EAC3B,MAAM,oCAAoC,CAAC;AAU5C,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA8L/D,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,IAAI,GACR,eAAe,CA6DjB;AAED,OAAO,EAAE,0BAA0B,EAAE,CAAC"}
|
|
@@ -64,9 +64,51 @@ function unknownEntityProperties(entity) {
|
|
|
64
64
|
const known = upsertEntitySchema.properties;
|
|
65
65
|
return Object.keys(entity).filter((key) => !Object.hasOwn(known, key));
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* The fields a type-conditional `else: { not: ... }` branch of the entity
|
|
69
|
+
* schema forbids, and the types that may carry them; null for any other
|
|
70
|
+
* schema path.
|
|
71
|
+
*/
|
|
72
|
+
function typeRestrictedFields(schemaPath) {
|
|
73
|
+
const match = /^#\/allOf\/(\d+)\/else\/not$/.exec(schemaPath);
|
|
74
|
+
if (match === null)
|
|
75
|
+
return null;
|
|
76
|
+
const allOf = upsertEntitySchema.allOf;
|
|
77
|
+
const branch = Array.isArray(allOf) ? allOf[Number(match[1])] : undefined;
|
|
78
|
+
if (branch === null || typeof branch !== "object")
|
|
79
|
+
return null;
|
|
80
|
+
const record = branch;
|
|
81
|
+
const typeRule = record.if?.properties?.type;
|
|
82
|
+
const types = Array.isArray(typeRule?.enum)
|
|
83
|
+
? typeRule.enum.map(String)
|
|
84
|
+
: typeof typeRule?.const === "string"
|
|
85
|
+
? [typeRule.const]
|
|
86
|
+
: [];
|
|
87
|
+
const not = record.else?.not;
|
|
88
|
+
const requiredLists = [
|
|
89
|
+
not?.required,
|
|
90
|
+
...(Array.isArray(not?.anyOf)
|
|
91
|
+
? not.anyOf.map((entry) => entry?.required)
|
|
92
|
+
: []),
|
|
93
|
+
];
|
|
94
|
+
const fields = requiredLists.flatMap((list) => Array.isArray(list) ? list.map(String) : []);
|
|
95
|
+
return types.length > 0 && fields.length > 0 ? { fields, types } : null;
|
|
96
|
+
}
|
|
67
97
|
// implements REQ-upsert-unknown-field-guidance
|
|
68
98
|
function formatEntityErrors(entity, errors) {
|
|
69
|
-
|
|
99
|
+
// A field only some entity types carry fails a type-conditional `not`
|
|
100
|
+
// branch; AJV then reports "must NOT be valid" plus the "else" it chose.
|
|
101
|
+
const restricted = new Set(errors.flatMap((error) => error.keyword === "not" && typeRestrictedFields(error.schemaPath)
|
|
102
|
+
? [error.schemaPath.replace(/\/else\/not$/, "/if")]
|
|
103
|
+
: []));
|
|
104
|
+
const visible = errors.filter((error) => !(error.keyword === "if" && restricted.has(error.schemaPath)));
|
|
105
|
+
const messages = visible.map((error) => {
|
|
106
|
+
const restriction = error.keyword === "not" ? typeRestrictedFields(error.schemaPath) : null;
|
|
107
|
+
if (restriction !== null) {
|
|
108
|
+
const present = restriction.fields.filter((field) => Object.hasOwn(entity, field));
|
|
109
|
+
const named = present.length > 0 ? present : restriction.fields;
|
|
110
|
+
return `${named.join(", ")} ${named.length === 1 ? "is" : "are"} only valid on ${restriction.types.join(" and ")} entities; remove ${named.length === 1 ? "it" : "them"} from this ${String(entity.type ?? "entity")}`;
|
|
111
|
+
}
|
|
70
112
|
const params = error.params;
|
|
71
113
|
const path = error.instancePath || "root";
|
|
72
114
|
if (error.keyword === "additionalProperties" && params.additionalProperty) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAEV,gBAAgB,EAGjB,MAAM,0CAA0C,CAAC;AASlD,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AAiE/B,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EAGhB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAG/B,eAAO,MAAM,yBAAyB,EAAG,2BAAoC,CAAC;AAqI9E,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,IAAI,CAMN;AAED,wBAAgB,mCAAmC,CACjD,SAAS,EAAE,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,EACnE,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,IAAI,CAaN;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,SAAS,cAAoB,GAC5B,eAAe,EAAE,CA2CnB;AAi1BD;;;;;;GAMG;AAEH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAiCf;AAyqDD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB;AACzB;;;GAGG;AACH,WAAW,GAAE,MAAM,IAAsB,GACxC,OAAO,CAAC;IACT,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,iBAAiB,EAAE,eAAe,CAAC;CACpC,CAAC,CAmDD"}
|
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { OperationError } from "../../cli-errors.js";
|
|
4
4
|
import { branchEnsureCommand, branchMigrateCommand, branchRecoverCommand, } from "../../commands/branch.js";
|
|
5
|
-
import { migrateCommand } from "../../commands/migrate.js";
|
|
5
|
+
import { describeMigrationRun, migrateCommand, } from "../../commands/migrate.js";
|
|
6
6
|
import { syncCommand } from "../../commands/sync.js";
|
|
7
7
|
import { PROOF_INTEGRATION_CONFIGURE, applyProofIntegrationAction, } from "../../proof/integration-plan.js";
|
|
8
8
|
import { loadEntities } from "../../public/operations/discovery-entities.js";
|
|
@@ -84,13 +84,33 @@ function asUpsert(step) {
|
|
|
84
84
|
...(document !== undefined ? { document } : {}),
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
+
// implements REQ-kibi-change-to-proof-plan-compiler-v2
|
|
88
|
+
/**
|
|
89
|
+
* Why a plan that is not ready cannot be applied, with the remedy for its
|
|
90
|
+
* status. The leading sentence stays the same for every status.
|
|
91
|
+
*/
|
|
92
|
+
function notReadyPlanMessage(prefix, status) {
|
|
93
|
+
const base = `${prefix}: only ready plans may be applied; this plan's status is '${String(status)}'.`;
|
|
94
|
+
switch (status) {
|
|
95
|
+
case "needs_resolution":
|
|
96
|
+
return `${base} Resolve its ontology_gap and missing propositions first (model each with kb_model mode requirement or predicates, then call kb_compile_intent again and apply the ready plan it returns), and fix any step its diagnostics name. Or write the requirement with kb_upsert carrying the plan's inventory contract, with the unmodeled propositions kept as status ontology_gap entries, as the kibi-usage skill describes.`;
|
|
97
|
+
case "blocked":
|
|
98
|
+
return prefix === "Apply plan failed"
|
|
99
|
+
? `${base} Its contradiction analysis found a conflict with a current requirement: resolve the contradiction first (supersede or change the conflicting requirement, or change the intent), then compile again.`
|
|
100
|
+
: `${base} Resolve what its diagnostics name, then plan the bootstrap again.`;
|
|
101
|
+
case "needs_context":
|
|
102
|
+
return `${base} Supply the bootstrap context it asks for, then plan the bootstrap again.`;
|
|
103
|
+
default:
|
|
104
|
+
return `${base} Compile or plan again and apply the plan it returns once its status is 'ready'.`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
87
107
|
function validateCompilePlanShape(args) {
|
|
88
108
|
if (!isRecord(args.plan))
|
|
89
109
|
throw new Error("Apply plan failed: plan must be an object");
|
|
90
110
|
if (args.plan.version !== "kibi.compile-plan.v1")
|
|
91
111
|
throw new Error("Apply plan failed: unsupported plan version");
|
|
92
112
|
if (args.plan.status !== "ready")
|
|
93
|
-
throw new Error("Apply plan failed
|
|
113
|
+
throw new Error(notReadyPlanMessage("Apply plan failed", args.plan.status));
|
|
94
114
|
if (!/^[a-f0-9]{64}$/i.test(args.approvedPlanHash))
|
|
95
115
|
throw new Error("Apply plan failed: approvedPlanHash must be a SHA-256 hash");
|
|
96
116
|
if (args.approvedPlanHash !== args.plan.planHash)
|
|
@@ -163,7 +183,7 @@ export function orderBootstrapActions(actions, completed = new Set()) {
|
|
|
163
183
|
function validateBootstrapPlanShape(args) {
|
|
164
184
|
const { plan } = args;
|
|
165
185
|
if (plan.status !== "ready")
|
|
166
|
-
throw new Error("Bootstrap apply failed
|
|
186
|
+
throw new Error(notReadyPlanMessage("Bootstrap apply failed", plan.status));
|
|
167
187
|
if (!/^[a-f0-9]{64}$/i.test(args.approvedPlanHash))
|
|
168
188
|
throw new Error("Bootstrap apply failed: approvedPlanHash must be SHA-256");
|
|
169
189
|
if (args.approvedPlanHash !== plan.planHash)
|
|
@@ -2155,12 +2175,12 @@ async function applyMigrationPlan(args, context, onCommitted = () => undefined)
|
|
|
2155
2175
|
continue;
|
|
2156
2176
|
}
|
|
2157
2177
|
try {
|
|
2158
|
-
await applyMigrationAction(action, context);
|
|
2178
|
+
const detail = await applyMigrationAction(action, context);
|
|
2159
2179
|
onCommitted();
|
|
2160
2180
|
results.push({
|
|
2161
2181
|
actionId: action.id,
|
|
2162
2182
|
outcome: "applied",
|
|
2163
|
-
detail: `Applied ${action.code}.`,
|
|
2183
|
+
detail: detail ?? `Applied ${action.code}.`,
|
|
2164
2184
|
});
|
|
2165
2185
|
}
|
|
2166
2186
|
catch (error) {
|
|
@@ -2292,6 +2312,10 @@ async function applyMigrationPlan(args, context, onCommitted = () => undefined)
|
|
|
2292
2312
|
structuredContent: payload,
|
|
2293
2313
|
};
|
|
2294
2314
|
}
|
|
2315
|
+
/**
|
|
2316
|
+
* Run one approved migration action. Returns a detail line for the action
|
|
2317
|
+
* result when the executor reports what it changed.
|
|
2318
|
+
*/
|
|
2295
2319
|
async function applyMigrationAction(action, context) {
|
|
2296
2320
|
switch (action.code) {
|
|
2297
2321
|
case "legacy_branch_storage":
|
|
@@ -2342,14 +2366,19 @@ async function applyMigrationAction(action, context) {
|
|
|
2342
2366
|
return;
|
|
2343
2367
|
case "schema_version_upgrade":
|
|
2344
2368
|
case "invalid_schema_version":
|
|
2345
|
-
case "legacy_storage_migration":
|
|
2346
|
-
|
|
2369
|
+
case "legacy_storage_migration": {
|
|
2370
|
+
// implements REQ-agent-guided-migration-orchestration
|
|
2371
|
+
const run = await migrateCommand({
|
|
2347
2372
|
yes: true,
|
|
2348
2373
|
workspaceRoot: context.workspaceRoot,
|
|
2349
2374
|
initializeMissingConfig: true,
|
|
2350
|
-
})
|
|
2375
|
+
});
|
|
2376
|
+
if (run.exitCode !== 0)
|
|
2351
2377
|
throw new Error("Schema migration did not complete successfully.");
|
|
2352
|
-
return
|
|
2378
|
+
return run.summary !== undefined
|
|
2379
|
+
? describeMigrationRun(run.summary)
|
|
2380
|
+
: undefined;
|
|
2381
|
+
}
|
|
2353
2382
|
case "symbol_refresh_coordinates":
|
|
2354
2383
|
case "coverage_source_coordinates": {
|
|
2355
2384
|
const result = await syncCommand({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze-prose.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/analyze-prose.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analyze-prose.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/analyze-prose.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAiBxD,OAAO,KAAK,EACV,6BAA6B,EAC7B,oBAAoB,EAWrB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAI1D;AA4gBD,OAAO,EAAE,eAAe,EAAE,CAAC;AAwX3B,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,GAC1B,6BAA6B,CAgE/B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createBuiltinOntologyPack, detectSignals as detectBuiltinSignals, } from "kibi-plugin-builtin";
|
|
2
|
+
import { InputError } from "../../cli-errors.js";
|
|
2
3
|
import { utf8Span, validateLogicIr, } from "../../logic/ir.js";
|
|
3
4
|
import { logicRuleFactId } from "../modeling/logic-modeling.js";
|
|
4
5
|
import { buildAdvisorResult } from "./analysis-receipt.js";
|
|
@@ -15,12 +16,17 @@ export function stringLogicClaims(value) {
|
|
|
15
16
|
? value.filter((entry) => typeof entry === "string")
|
|
16
17
|
: [];
|
|
17
18
|
}
|
|
19
|
+
// implements REQ-mcp-semantic-advisor-preflight
|
|
18
20
|
function proseOf(payload) {
|
|
19
21
|
const properties = propertiesOf(payload);
|
|
22
|
+
// text_ref is the prose only while semantic_text is unset (the ingestion
|
|
23
|
+
// boundary's fallback); beside semantic_text it is a reference such as
|
|
24
|
+
// "docs/intake.md#batch-4", whose numbers and words are not claims.
|
|
25
|
+
const semanticText = stringValue(properties.semantic_text);
|
|
20
26
|
return [
|
|
21
27
|
properties.title,
|
|
22
28
|
properties.semantic_text,
|
|
23
|
-
properties.text_ref,
|
|
29
|
+
semanticText ? undefined : properties.text_ref,
|
|
24
30
|
properties.description,
|
|
25
31
|
]
|
|
26
32
|
.map(stringValue)
|
|
@@ -540,6 +546,16 @@ function shadowAnalysis(text, interpretations) {
|
|
|
540
546
|
}
|
|
541
547
|
function propositionSpan(text, clause, cursor) {
|
|
542
548
|
const located = locateSourceText(text, clause.text, cursor);
|
|
549
|
+
// A clause the prose does not contain has no source span: it keeps its own
|
|
550
|
+
// text over an empty span (which strict ingestion rejects) instead of an
|
|
551
|
+
// unrelated slice of the prose.
|
|
552
|
+
if (located === null) {
|
|
553
|
+
return {
|
|
554
|
+
span: utf8Span(text, cursor, cursor),
|
|
555
|
+
next: cursor,
|
|
556
|
+
sourceText: clause.text,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
543
559
|
return {
|
|
544
560
|
span: utf8Span(text, located.start, located.end),
|
|
545
561
|
next: Math.max(cursor, located.end),
|
|
@@ -552,23 +568,34 @@ function escapeRegExp(value) {
|
|
|
552
568
|
/**
|
|
553
569
|
* Locate canonical claim text while accepting source-only whitespace layout.
|
|
554
570
|
* The caller receives the authored slice so strict ingestion can compare the
|
|
555
|
-
* span with the claim text without rewriting the source document.
|
|
571
|
+
* span with the claim text without rewriting the source document. The search
|
|
572
|
+
* starts at `cursor` and then retries from the start of the prose; null means
|
|
573
|
+
* the prose does not contain the claim text at all.
|
|
556
574
|
*/
|
|
557
575
|
function locateSourceText(source, claimText, cursor) {
|
|
558
|
-
const
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
576
|
+
const pattern = new RegExp(escapeRegExp(claimText.trim()).replace(/\s+/g, "\\s+"));
|
|
577
|
+
for (const from of cursor > 0 ? [cursor, 0] : [0]) {
|
|
578
|
+
const match = pattern.exec(source.slice(from));
|
|
579
|
+
if (match !== null) {
|
|
580
|
+
const start = from + match.index;
|
|
581
|
+
return { start, end: start + match[0].length };
|
|
582
|
+
}
|
|
563
583
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Caller-supplied clauses split the requirement prose; they never restate
|
|
588
|
+
* it. A clause that does not occur in the prose (apart from whitespace
|
|
589
|
+
* layout, a list marker or closing punctuation) would get a span over
|
|
590
|
+
* unrelated text, so the request is refused naming each such clause.
|
|
591
|
+
*/
|
|
592
|
+
function assertVerbatimClauses(statement, clauses) {
|
|
593
|
+
const missing = clauses
|
|
594
|
+
.filter((clause) => locateSourceText(statement, clause.text, 0) === null)
|
|
595
|
+
.map((clause) => JSON.stringify(clause.text));
|
|
596
|
+
if (missing.length === 0)
|
|
597
|
+
return;
|
|
598
|
+
throw new InputError("VALIDATION_FAILED", `Semantic advisor clauses must be verbatim fragments of the requirement prose (the intent or semantic_text); ${missing.length === 1 ? "this clause does" : "these clauses do"} not occur in it: ${missing.join(", ")}. Copy each clause from the prose unchanged (whitespace may differ), or omit clauses to let Kibi split the prose.`);
|
|
572
599
|
}
|
|
573
600
|
function interpretationResults(interpretations, clauses, statement) {
|
|
574
601
|
if (interpretations === undefined)
|
|
@@ -592,8 +619,11 @@ function interpretationResults(interpretations, clauses, statement) {
|
|
|
592
619
|
errors.push("interpretation span is invalid");
|
|
593
620
|
if (clause && candidate.span) {
|
|
594
621
|
const candidateLocation = locateSourceText(statement, candidate.claim_text, 0);
|
|
595
|
-
const expectedSpan =
|
|
596
|
-
|
|
622
|
+
const expectedSpan = candidateLocation === null
|
|
623
|
+
? null
|
|
624
|
+
: utf8Span(statement, candidateLocation.start, candidateLocation.end);
|
|
625
|
+
if (expectedSpan === null ||
|
|
626
|
+
candidate.span.start !== expectedSpan.start ||
|
|
597
627
|
candidate.span.end !== expectedSpan.end)
|
|
598
628
|
errors.push(`interpretation span for ${candidate.claim_key} does not match the exact UTF-8 proposition span`);
|
|
599
629
|
}
|
|
@@ -690,6 +720,8 @@ export function analyzeSemanticAdvisorInput(input) {
|
|
|
690
720
|
const clauses = statement
|
|
691
721
|
? extractSemanticClauses(statement, input.clauses ?? semanticClausesOf(payload))
|
|
692
722
|
: [];
|
|
723
|
+
if (input.clauses !== undefined)
|
|
724
|
+
assertVerbatimClauses(statement, clauses);
|
|
693
725
|
const rawDeclaredClaims = propertiesOf(payload).logic_claims;
|
|
694
726
|
const declaredClaims = Array.isArray(rawDeclaredClaims)
|
|
695
727
|
? rawDeclaredClaims.filter((value) => typeof value === "string")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingestion-boundary.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/ingestion-boundary.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAG3E,OAAO,EACL,KAAK,OAAO,EAOb,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,sBAAsB,EAAuB,MAAM,YAAY,CAAC;AAgB9E,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,EAAE,CAAC,EAAE,OAAO,CAAC;CACd,CAAC,CAAC;AAuEH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,OAAO,EAAE,sBAAsB,GAC9B,+BAA+B,CA0IjC;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,OAAO,EAAE,sBAAsB,GAC9B,IAAI,CAWN;
|
|
1
|
+
{"version":3,"file":"ingestion-boundary.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/ingestion-boundary.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAG3E,OAAO,EACL,KAAK,OAAO,EAOb,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,sBAAsB,EAAuB,MAAM,YAAY,CAAC;AAgB9E,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,EAAE,CAAC,EAAE,OAAO,CAAC;CACd,CAAC,CAAC;AAuEH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,OAAO,EAAE,sBAAsB,GAC9B,+BAA+B,CA0IjC;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,OAAO,EAAE,sBAAsB,GAC9B,IAAI,CAWN;AAoFD;;;;GAIG;AAEH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GAC9D,MAAM,GAAG,IAAI,CAUf;AAGD,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,MAAM,CAAC,EAAE,QAAQ,CAAC;IAChB,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAClE,CAAC,GACD,OAAO,CAAC,IAAI,CAAC,CAwBf"}
|
|
@@ -192,9 +192,33 @@ function groundingClaimKeyError(modeledClaimKeys, grounded) {
|
|
|
192
192
|
return `Proposition-complete ingestion failed: claim '${duplicate}' has more than one logical grounding relationship.`;
|
|
193
193
|
if (groundedClaimKeys.length !== modeledClaimKeys.length ||
|
|
194
194
|
modeledClaimKeys.some((claimKey) => !groundedClaimKeys.includes(claimKey)))
|
|
195
|
-
return
|
|
195
|
+
return `Proposition-complete ingestion failed: modeled proposition claim_keys must match logical grounding target claim_keys exactly. ${claimKeyMismatchDetail(modeledClaimKeys, grounded)}`;
|
|
196
196
|
return null;
|
|
197
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Why the mismatch names both sides: a claim_key is derived from the clause
|
|
200
|
+
* text, so a requirement whose prose was reworded (or analyzed from another
|
|
201
|
+
* field than the stored semantic_text) carries keys none of its grounding
|
|
202
|
+
* facts declare. Listing the unmatched modeled keys and the facts with the
|
|
203
|
+
* keys they still declare lets the agent see the drift and pick the remedy
|
|
204
|
+
* without diffing the KB by hand.
|
|
205
|
+
*/
|
|
206
|
+
// implements REQ-kibi-proposition-complete-ingestion
|
|
207
|
+
function claimKeyMismatchDetail(modeledClaimKeys, grounded) {
|
|
208
|
+
const groundedClaimKeys = grounded.map(([, claimKey]) => claimKey);
|
|
209
|
+
const ungrounded = modeledClaimKeys.filter((claimKey) => !groundedClaimKeys.includes(claimKey));
|
|
210
|
+
const stale = grounded.filter(([, claimKey]) => !modeledClaimKeys.includes(claimKey));
|
|
211
|
+
const parts = [
|
|
212
|
+
ungrounded.length > 0
|
|
213
|
+
? `Modeled claims with no grounding fact declaring their claim_key: ${ungrounded.map((claimKey) => `'${claimKey}'`).join(", ")}.`
|
|
214
|
+
: "",
|
|
215
|
+
stale.length > 0
|
|
216
|
+
? `Grounding facts whose claim_key the inventory no longer models: ${stale.map(([target, claimKey]) => `${target} ('${claimKey}')`).join(", ")}.`
|
|
217
|
+
: "",
|
|
218
|
+
"A claim_key is derived from the clause text, so reworded prose (or prose analyzed from a field other than the stored semantic_text) yields new keys. Keep the stored semantic_text wording, or kb_upsert each grounding fact with the new claim_key and claim_text (same fact id) before this write; for a change of meaning, supersede the requirement instead.",
|
|
219
|
+
];
|
|
220
|
+
return parts.filter(Boolean).join(" ");
|
|
221
|
+
}
|
|
198
222
|
/**
|
|
199
223
|
* The write-time claim_key check, run against planned writes only. Returns
|
|
200
224
|
* null when it passes or when a target is outside the plan (the write-time
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PrologPort } from "../../public/operations/runtime-types.js";
|
|
2
|
+
import type { SemanticAdvisorReceipt } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Why `kb_model mode: analyze` with the `id` of a stored requirement compares
|
|
5
|
+
* its ledger with the stored one: a claim_key is derived from the clause
|
|
6
|
+
* text, so analyzing reworded prose, or the title instead of the stored
|
|
7
|
+
* `semantic_text`, yields keys that none of the requirement's grounding
|
|
8
|
+
* facts declare. The write that follows such a receipt is refused by the
|
|
9
|
+
* proposition-complete check, long after the agent picked the wording. The
|
|
10
|
+
* receipt therefore warns at analysis time, naming the stored modeled
|
|
11
|
+
* clauses the analysis no longer states, the facts that ground them, and the
|
|
12
|
+
* clauses the analysis adds, so the agent can keep the stored wording or
|
|
13
|
+
* re-ground the facts before writing.
|
|
14
|
+
*
|
|
15
|
+
* Returns null when the requirement is not stored, carries no recorded
|
|
16
|
+
* ledger, or every stored modeled clause is still among the analyzed ones.
|
|
17
|
+
*/
|
|
18
|
+
export declare function storedLedgerDriftWarning(prolog: Pick<PrologPort, "query">, id: string, text: string, receipt: SemanticAdvisorReceipt): Promise<string | null>;
|
|
19
|
+
//# sourceMappingURL=stored-ledger-drift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stored-ledger-drift.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/stored-ledger-drift.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAwCzD;;;;;;;;;;;;;;GAcG;AAEH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EACjC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkDxB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { escapeAtom, parseListOfLists, parsePrologValue, } from "../../prolog/codec.js";
|
|
2
|
+
import { loadEntities } from "../../public/operations/discovery-entities.js";
|
|
3
|
+
import { SEMANTIC_INVENTORY_VERSION, isRecord, stringValue } from "./shared.js";
|
|
4
|
+
const LOGICAL_RELATIONSHIPS = [
|
|
5
|
+
"requires_property",
|
|
6
|
+
"requires_predicate",
|
|
7
|
+
"requires_rule",
|
|
8
|
+
];
|
|
9
|
+
function atom(value) {
|
|
10
|
+
return stringValue(parsePrologValue(value ?? "")).replace(/^['"]|['"]$/g, "");
|
|
11
|
+
}
|
|
12
|
+
function quoted(text) {
|
|
13
|
+
return text ? ` ("${text}")` : "";
|
|
14
|
+
}
|
|
15
|
+
async function groundingFactsOf(prolog, id) {
|
|
16
|
+
const result = await prolog.query(`findall([Type,To,ClaimKey], (member(Type, [${LOGICAL_RELATIONSHIPS.join(",")}]), kb_relationship(Type, '${escapeAtom(id)}', To), ((kb_entity(To, fact, _DriftProps), memberchk(claim_key=_DriftRaw, _DriftProps), normalize_term_atom(_DriftRaw, ClaimKey)) -> true ; ClaimKey = '')), Rows)`);
|
|
17
|
+
if (!result.success)
|
|
18
|
+
return [];
|
|
19
|
+
return parseListOfLists(result.bindings.Rows ?? "[]").map(([type, target, claimKey]) => ({
|
|
20
|
+
type: atom(type),
|
|
21
|
+
target: atom(target),
|
|
22
|
+
claimKey: atom(claimKey),
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Why `kb_model mode: analyze` with the `id` of a stored requirement compares
|
|
27
|
+
* its ledger with the stored one: a claim_key is derived from the clause
|
|
28
|
+
* text, so analyzing reworded prose, or the title instead of the stored
|
|
29
|
+
* `semantic_text`, yields keys that none of the requirement's grounding
|
|
30
|
+
* facts declare. The write that follows such a receipt is refused by the
|
|
31
|
+
* proposition-complete check, long after the agent picked the wording. The
|
|
32
|
+
* receipt therefore warns at analysis time, naming the stored modeled
|
|
33
|
+
* clauses the analysis no longer states, the facts that ground them, and the
|
|
34
|
+
* clauses the analysis adds, so the agent can keep the stored wording or
|
|
35
|
+
* re-ground the facts before writing.
|
|
36
|
+
*
|
|
37
|
+
* Returns null when the requirement is not stored, carries no recorded
|
|
38
|
+
* ledger, or every stored modeled clause is still among the analyzed ones.
|
|
39
|
+
*/
|
|
40
|
+
// implements REQ-kibi-proposition-complete-ingestion
|
|
41
|
+
export async function storedLedgerDriftWarning(prolog, id, text, receipt) {
|
|
42
|
+
const [stored] = await loadEntities(prolog, { id, type: "req" });
|
|
43
|
+
if (!stored ||
|
|
44
|
+
stored.semantic_inventory_version !== SEMANTIC_INVENTORY_VERSION ||
|
|
45
|
+
!Array.isArray(stored.semantic_inventory))
|
|
46
|
+
return null;
|
|
47
|
+
const storedInventory = stored.semantic_inventory.filter(isRecord);
|
|
48
|
+
const storedKeys = new Set(storedInventory.map((entry) => stringValue(entry.claim_key)));
|
|
49
|
+
const analyzedKeys = new Set(receipt.propositions.map(({ claim_key }) => claim_key));
|
|
50
|
+
const drifted = storedInventory
|
|
51
|
+
.filter((entry) => stringValue(entry.status) === "modeled" &&
|
|
52
|
+
!analyzedKeys.has(stringValue(entry.claim_key)))
|
|
53
|
+
.map((entry) => ({
|
|
54
|
+
claimKey: stringValue(entry.claim_key),
|
|
55
|
+
claimText: stringValue(entry.claim_text),
|
|
56
|
+
}));
|
|
57
|
+
if (drifted.length === 0)
|
|
58
|
+
return null;
|
|
59
|
+
const facts = await groundingFactsOf(prolog, id);
|
|
60
|
+
const groundedBy = (claimKey) => {
|
|
61
|
+
const matches = facts.filter((fact) => fact.claimKey === claimKey);
|
|
62
|
+
return matches.length > 0
|
|
63
|
+
? `, grounded by ${matches.map((fact) => `${fact.target} via ${fact.type}`).join(" and ")}`
|
|
64
|
+
: "";
|
|
65
|
+
};
|
|
66
|
+
const missing = drifted
|
|
67
|
+
.map(({ claimKey, claimText }) => `'${claimKey}'${quoted(claimText)}${groundedBy(claimKey)}`)
|
|
68
|
+
.join("; ");
|
|
69
|
+
const added = receipt.propositions
|
|
70
|
+
.filter(({ claim_key }) => !storedKeys.has(claim_key))
|
|
71
|
+
.map(({ claim_key, claim_text }) => `'${claim_key}'${quoted(claim_text)}`)
|
|
72
|
+
.join("; ");
|
|
73
|
+
const storedText = stringValue(stored.semantic_text);
|
|
74
|
+
const cause = storedText && storedText !== text
|
|
75
|
+
? `The analyzed prose differs from the stored semantic_text of ${id}.`
|
|
76
|
+
: "The stored clause keys were derived from different prose.";
|
|
77
|
+
return `Claim-key drift against the stored ledger of ${id}: stored modeled clause(s) not stated by this analysis: ${missing}.${added ? ` Clause(s) this analysis adds without a stored grounding: ${added}.` : ""} ${cause} A kb_upsert of ${id} with this ledger fails the proposition-complete claim_key check unless each affected grounding fact is first re-written with the new claim_key and claim_text (same fact id). Keep the stored semantic_text wording to keep the keys, or supersede the requirement for a change of meaning.`;
|
|
78
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../../src/public/impact/analyzer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../../src/public/impact/analyzer.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CAsJlC"}
|
|
@@ -4,6 +4,7 @@ import { analyzeSourceChanges, assertSourceAnalysisGate, } from "../../plugins/s
|
|
|
4
4
|
import { captureStagedSnapshot } from "../../traceability/git-change-snapshot.js";
|
|
5
5
|
import { readSnapshotKnowledge } from "../../traceability/snapshot-knowledge.js";
|
|
6
6
|
import { extractSymbolsFromStagedFileAsync } from "../../traceability/symbol-extract.js";
|
|
7
|
+
import { isTraceabilityRelationshipType } from "../symbol-granularity.js";
|
|
7
8
|
import { createSemanticReviewDiagnostics, createSymbolGranularityDiagnostics, } from "./diagnostics.js";
|
|
8
9
|
import { createImpactManifestLookup, readImpactManifestResults, } from "./manifest.js";
|
|
9
10
|
import { normalizeSourceFile } from "./source-changes.js";
|
|
@@ -90,7 +91,12 @@ export async function analyzeChangedFileImpact(options) {
|
|
|
90
91
|
manifestResults: activeManifestResults,
|
|
91
92
|
symbolsByFile,
|
|
92
93
|
}),
|
|
93
|
-
...createSemanticReviewDiagnostics({
|
|
94
|
+
...createSemanticReviewDiagnostics({
|
|
95
|
+
symbolsByFile,
|
|
96
|
+
linkedSourceFiles: new Set(activeManifestResults
|
|
97
|
+
.filter((result) => result.relationships.some((relationship) => isTraceabilityRelationshipType(relationship.type)))
|
|
98
|
+
.map((result) => result.sourceFile)),
|
|
99
|
+
}),
|
|
94
100
|
];
|
|
95
101
|
const maxDiagnostics = options.maxDiagnostics;
|
|
96
102
|
const cappedDiagnostics = maxDiagnostics !== undefined && maxDiagnostics >= 0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../src/public/impact/diagnostics.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAarF,OAAO,KAAK,EACV,iBAAiB,EACjB,gCAAgC,EAChC,mCAAmC,EACpC,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../src/public/impact/diagnostics.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAarF,OAAO,KAAK,EACV,iBAAiB,EACjB,gCAAgC,EAChC,mCAAmC,EACpC,MAAM,YAAY,CAAC;AA0FpB,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,mCAAmC,GAC3C,oBAAoB,EAAE,CAmExB;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,oBAAoB,EAAE,CAiCxB;AAED,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,SAAS,oBAAoB,EAAE,GAC3C,OAAO,CAET;AAED,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,SAAS,iBAAiB,EAAE,GACxC,OAAO,CAIT"}
|
|
@@ -40,6 +40,9 @@ function getGranularSymbolsForSourceFile(sourceFile, options) {
|
|
|
40
40
|
sourceSymbols: sourceSymbols.length > 0 ? sourceSymbols : (changedSymbols ?? []),
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
+
function hasTraceabilityLinks(symbol) {
|
|
44
|
+
return (symbol.relationships ?? []).some((relationship) => isTraceabilityRelationshipType(relationship.type));
|
|
45
|
+
}
|
|
43
46
|
function formatRelationshipTargets(symbol) {
|
|
44
47
|
const targets = [
|
|
45
48
|
...new Set((symbol.relationships ?? [])
|
|
@@ -116,15 +119,25 @@ export function createSemanticReviewDiagnostics(options) {
|
|
|
116
119
|
if (!isChangedBehavioralReviewCandidate(symbol))
|
|
117
120
|
continue;
|
|
118
121
|
const linkedTargets = formatRelationshipTargets(symbol);
|
|
122
|
+
const file = symbol.location.file;
|
|
123
|
+
// In a file no requirement owns at all, the changed behavior has no
|
|
124
|
+
// recorded intent, so the review is "record it", not "compare it with
|
|
125
|
+
// its links". Elsewhere a coarser symbol of the file may still own it.
|
|
126
|
+
const unlinked = !hasTraceabilityLinks(symbol) &&
|
|
127
|
+
!(options.linkedSourceFiles?.has(file) ?? false);
|
|
119
128
|
diagnostics.push({
|
|
120
129
|
id: "symbol_semantic_review_needed",
|
|
121
130
|
severity: "warning",
|
|
122
131
|
blocking: false,
|
|
123
132
|
category: "symbol",
|
|
124
|
-
files: [
|
|
133
|
+
files: [file],
|
|
125
134
|
docs: ["docs/symbol-traceability-taxonomy.md"],
|
|
126
|
-
message:
|
|
127
|
-
|
|
135
|
+
message: unlinked
|
|
136
|
+
? `Changed behavioral symbol ${symbol.name} is in a file no requirement owns, so no recorded intent covers the behavior it has after this change`
|
|
137
|
+
: `Changed behavioral symbol ${symbol.name} needs semantic review against linked Kibi coverage: ${linkedTargets}`,
|
|
138
|
+
suggestion: unlinked
|
|
139
|
+
? `If the change adds or alters behavior, record it with the change: kb_search({query:"<the behavior>", sourceLocations:[{path:"${file}"}]}) for a requirement that already describes it and link ${symbol.name} to it; if none does, kb_upsert a requirement with a scenario and link the symbol with implements. If behavior is unchanged (a refactor, renaming), say so in the final report instead.`
|
|
140
|
+
: `Use kb_search and kb_query for ${file}, then verify that ${linkedTargets} actually describe and test the changed behavior or UI copy. Update Kibi links or requirements/tests while the edit context is fresh.`,
|
|
128
141
|
});
|
|
129
142
|
}
|
|
130
143
|
}
|
|
@@ -32,6 +32,8 @@ export type SymbolGranularityDiagnosticsOptions = {
|
|
|
32
32
|
};
|
|
33
33
|
export type SemanticReviewDiagnosticsOptions = {
|
|
34
34
|
readonly symbolsByFile: SymbolsByFile;
|
|
35
|
+
/** Source files where some manifest symbol carries a traceability link. */
|
|
36
|
+
readonly linkedSourceFiles?: ReadonlySet<string>;
|
|
35
37
|
};
|
|
36
38
|
export type SymbolQualityDiagnosticsOptions = {
|
|
37
39
|
readonly manifestResults: readonly ExtractionResult[];
|