kibi-cli 2.21.2 → 2.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-register-foundation.d.ts.map +1 -1
- package/dist/cli-register-foundation.js +1 -0
- package/dist/commands/check.d.ts +0 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +47 -111
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +108 -6
- package/dist/commands/init-helpers.d.ts +16 -2
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +52 -10
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -1
- package/dist/commands/legacy-storage-migration.d.ts +46 -1
- package/dist/commands/legacy-storage-migration.d.ts.map +1 -1
- package/dist/commands/legacy-storage-migration.js +219 -9
- package/dist/commands/migrate.d.ts +37 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +72 -188
- package/dist/commands/sync/manifest.d.ts.map +1 -1
- package/dist/commands/sync/manifest.js +9 -0
- package/dist/extractors/symbols-coordinator.js +1 -0
- package/dist/intent-search.d.ts.map +1 -1
- package/dist/intent-search.js +28 -6
- package/dist/operations/migration/inventory-rederive.d.ts +40 -1
- package/dist/operations/migration/inventory-rederive.d.ts.map +1 -1
- package/dist/operations/migration/inventory-rederive.js +99 -8
- package/dist/operations/migration/requirement-status.d.ts +25 -0
- package/dist/operations/migration/requirement-status.d.ts.map +1 -0
- package/dist/operations/migration/requirement-status.js +105 -0
- package/dist/operations/migration/schema6.d.ts +25 -0
- package/dist/operations/migration/schema6.d.ts.map +1 -1
- package/dist/operations/migration/schema6.js +265 -7
- package/dist/operations/migration/symbol-legacy-links.d.ts +51 -0
- package/dist/operations/migration/symbol-legacy-links.d.ts.map +1 -0
- package/dist/operations/migration/symbol-legacy-links.js +319 -0
- package/dist/operations/mutation/context-warning.d.ts +12 -2
- package/dist/operations/mutation/context-warning.d.ts.map +1 -1
- package/dist/operations/mutation/context-warning.js +6 -6
- package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
- package/dist/operations/mutation/source-authoring.js +130 -35
- package/dist/operations/mutation/types.d.ts +2 -1
- package/dist/operations/mutation/types.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.d.ts +17 -2
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +28 -11
- package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
- package/dist/operations/mutation/validate-upsert.js +40 -8
- 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 +34 -10
- 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/diagnostics.d.ts.map +1 -1
- package/dist/public/impact/diagnostics.js +24 -14
- package/dist/public/operations/catalog.d.ts +3 -3
- 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 +6 -1
- 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 +2 -0
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +24 -2
- package/dist/public/operations/migration-plan.d.ts +15 -0
- package/dist/public/operations/migration-plan.d.ts.map +1 -1
- package/dist/public/operations/migration-plan.js +130 -17
- package/dist/public/operations/result-envelope.d.ts.map +1 -1
- package/dist/public/operations/result-envelope.js +27 -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 +2 -1
- package/dist/public/operations/specs/check.d.ts +1 -1
- package/dist/public/operations/specs/planning.d.ts +2 -2
- package/dist/public/operations/specs/planning.js +2 -2
- package/dist/public/operations/specs/semantic.d.ts.map +1 -1
- package/dist/public/operations/specs/semantic.js +26 -1
- package/dist/public/operations/symbol-granularity-check.d.ts +18 -0
- package/dist/public/operations/symbol-granularity-check.d.ts.map +1 -0
- package/dist/public/operations/symbol-granularity-check.js +175 -0
- package/dist/public/skills/kibi-usage/SKILL.md +8 -3
- package/dist/public/symbol-granularity.d.ts +17 -0
- package/dist/public/symbol-granularity.d.ts.map +1 -1
- package/dist/public/symbol-granularity.js +21 -0
- package/dist/traceability/evidence-model.d.ts +7 -3
- package/dist/traceability/evidence-model.d.ts.map +1 -1
- package/dist/traceability/staged-symbols-manifest.js +6 -5
- package/dist/traceability/validate.d.ts +14 -1
- package/dist/traceability/validate.d.ts.map +1 -1
- package/dist/traceability/validate.js +41 -0
- package/dist/utils/rule-registry.generated.d.ts +5 -0
- package/dist/utils/rule-registry.generated.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.js +6 -0
- package/package.json +3 -3
- package/src/public/impact/diagnostics.ts +29 -17
- package/src/public/operations/check-executor.ts +7 -0
- package/src/public/operations/contracts.ts +6 -1
- package/src/public/operations/decision-firmness.ts +18 -2
- package/src/public/operations/discovery-executors.ts +28 -2
- package/src/public/operations/migration-plan.ts +147 -20
- package/src/public/operations/result-envelope.ts +34 -1
- package/src/public/operations/specs/check-rules.generated.ts +2 -1
- package/src/public/operations/specs/planning.ts +2 -2
- package/src/public/operations/specs/semantic.ts +37 -2
- package/src/public/operations/symbol-granularity-check.ts +233 -0
- package/src/public/skills/kibi-usage/SKILL.md +8 -3
- package/src/public/symbol-granularity.ts +37 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { OperationContext } from "../../public/operations/runtime-types.js";
|
|
2
2
|
import type { OperationResult } from "../../public/operations/types.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { SemanticAdvisorReceipt } from "../semantic-advisor/types.js";
|
|
4
4
|
import { type StoredEntityLookup } from "./stored-entity.js";
|
|
5
5
|
import type { RelationshipInput, StagedUpsertState, UpsertInput, UpsertPayload, ValidatedUpsert } from "./types.js";
|
|
6
6
|
export declare function assertRelationshipShardContained(workspaceRoot: string, shardPath: string): void;
|
|
@@ -15,11 +15,26 @@ export type UpsertValidationOptions = Readonly<{
|
|
|
15
15
|
*/
|
|
16
16
|
readonly staged?: StagedUpsertState;
|
|
17
17
|
}>;
|
|
18
|
+
/** The semantic advisor result an upsert reports; no receipt for a non-requirement. */
|
|
19
|
+
export type UpsertSemanticAnalysis = Readonly<{
|
|
20
|
+
readonly receipt: SemanticAdvisorReceipt | null;
|
|
21
|
+
readonly warnings: readonly string[];
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* The semantic advisor analyses requirement prose: its propositions, claim
|
|
25
|
+
* keys and inventory contract describe a requirement. Any other entity has no
|
|
26
|
+
* prose inventory, so analysing its title (the fallback prose) would report
|
|
27
|
+
* claim keys nothing declares, for example a fact whose claim_key derives
|
|
28
|
+
* from claim_text. Its upsert carries no receipt.
|
|
29
|
+
*/
|
|
30
|
+
export declare function analyzeUpsertSemantics(payload: UpsertInput & {
|
|
31
|
+
readonly relationships: readonly RelationshipInput[];
|
|
32
|
+
}): UpsertSemanticAnalysis;
|
|
18
33
|
export type UpsertValidation = Readonly<{
|
|
19
34
|
readonly validated: ValidatedUpsert;
|
|
20
35
|
/** The upsert's relationships merged with the entity's existing ones. */
|
|
21
36
|
readonly relationships: readonly RelationshipInput[];
|
|
22
|
-
readonly semantic:
|
|
37
|
+
readonly semantic: UpsertSemanticAnalysis;
|
|
23
38
|
}>;
|
|
24
39
|
/**
|
|
25
40
|
* The complete validation an upsert passes before its first write: schema,
|
|
@@ -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;
|
|
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;AAyB3E,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,CA0E3B;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"}
|
|
@@ -59,6 +59,11 @@ function storedClaimKey(entity) {
|
|
|
59
59
|
? { claimKey: entity.claim_key }
|
|
60
60
|
: {};
|
|
61
61
|
}
|
|
62
|
+
// implements REQ-mcp-semantic-advisor-preflight
|
|
63
|
+
/** The written result's semantic advisor receipt, omitted for a non-requirement. */
|
|
64
|
+
function advisorReceipt(receipt) {
|
|
65
|
+
return receipt === null ? {} : { semanticAdvisor: receipt };
|
|
66
|
+
}
|
|
62
67
|
function fileHash(pathname) {
|
|
63
68
|
if (!existsSync(pathname))
|
|
64
69
|
return null;
|
|
@@ -194,6 +199,21 @@ function reextractCanonicalSymbolEntity(symbolId, authoredRelativePath, workspac
|
|
|
194
199
|
: { sourceFile: result.sourceFile }),
|
|
195
200
|
};
|
|
196
201
|
}
|
|
202
|
+
// implements REQ-mcp-semantic-advisor-preflight
|
|
203
|
+
/**
|
|
204
|
+
* The semantic advisor analyses requirement prose: its propositions, claim
|
|
205
|
+
* keys and inventory contract describe a requirement. Any other entity has no
|
|
206
|
+
* prose inventory, so analysing its title (the fallback prose) would report
|
|
207
|
+
* claim keys nothing declares, for example a fact whose claim_key derives
|
|
208
|
+
* from claim_text. Its upsert carries no receipt.
|
|
209
|
+
*/
|
|
210
|
+
export function analyzeUpsertSemantics(payload) {
|
|
211
|
+
if (payload.type !== "req")
|
|
212
|
+
return { receipt: null, warnings: [] };
|
|
213
|
+
const semantic = analyzeSemanticAdvisorInput({ payload });
|
|
214
|
+
assertSemanticInventoryBoundary(payload, payload.relationships, semantic.receipt);
|
|
215
|
+
return semantic;
|
|
216
|
+
}
|
|
197
217
|
// implements REQ-kibi-truthful-consistency
|
|
198
218
|
/**
|
|
199
219
|
* The complete validation an upsert passes before its first write: schema,
|
|
@@ -223,10 +243,7 @@ export async function validateUpsertForCommit(payload, context, options = {}) {
|
|
|
223
243
|
await validateStrictLanePairing(prolog, validated.relationships, staged);
|
|
224
244
|
await validateLiveRelationshipTargets(prolog, validated.entity, validated.relationships, staged);
|
|
225
245
|
await validateSupersedesSourceHistory(prolog, validated.entity, validated.relationships, context.workspaceRoot, undefined, staged);
|
|
226
|
-
const semantic =
|
|
227
|
-
payload: { ...input, relationships },
|
|
228
|
-
});
|
|
229
|
-
assertSemanticInventoryBoundary({ ...input, relationships }, relationships, semantic.receipt);
|
|
246
|
+
const semantic = analyzeUpsertSemantics({ ...input, relationships });
|
|
230
247
|
await assertLogicalGroundingClaimKeys(prolog, { ...input, relationships }, relationships, staged);
|
|
231
248
|
await assertPredicateArgumentVocabulary(prolog, validated.entity, staged);
|
|
232
249
|
// The origin the write records is added last: none of the checks above
|
|
@@ -321,7 +338,7 @@ export async function executeUpsert(input, context, options = {}) {
|
|
|
321
338
|
let commitEntity;
|
|
322
339
|
let relationshipCount = 0;
|
|
323
340
|
let changeKind = null;
|
|
324
|
-
let semanticAdvisor;
|
|
341
|
+
let semanticAdvisor = null;
|
|
325
342
|
let compilerLock;
|
|
326
343
|
let sourceMutationLock;
|
|
327
344
|
let operationFailure;
|
|
@@ -510,7 +527,7 @@ export async function executeUpsert(input, context, options = {}) {
|
|
|
510
527
|
updated: 0,
|
|
511
528
|
relationships_created: validated.relationships.length,
|
|
512
529
|
warnings: [...planRecoveryWarnings, ...semantic.warnings],
|
|
513
|
-
|
|
530
|
+
...advisorReceipt(semantic.receipt),
|
|
514
531
|
...storedClaimKey(commitEntity ?? validated.entity),
|
|
515
532
|
deferredCommit,
|
|
516
533
|
...(sourceWrite ? { sourceWrites: [sourceWrite.receipt] } : {}),
|
|
@@ -587,10 +604,10 @@ export async function executeUpsert(input, context, options = {}) {
|
|
|
587
604
|
...semantic.warnings,
|
|
588
605
|
...coverage,
|
|
589
606
|
...idStyleWarnings,
|
|
590
|
-
...entityContextWarnings(input, commitEntity ?? validated.entity, context.workspaceRoot, sourceWrite?.receipt.path),
|
|
607
|
+
...entityContextWarnings(input, commitEntity ?? validated.entity, context.workspaceRoot, { writtenPath: sourceWrite?.receipt.path }),
|
|
591
608
|
...shardWarnings,
|
|
592
609
|
],
|
|
593
|
-
|
|
610
|
+
...advisorReceipt(semantic.receipt),
|
|
594
611
|
...storedClaimKey(commitEntity ?? validated.entity),
|
|
595
612
|
...(shardWarnings.length > 0
|
|
596
613
|
? {
|
|
@@ -625,9 +642,9 @@ export async function executeUpsert(input, context, options = {}) {
|
|
|
625
642
|
? "skipped"
|
|
626
643
|
: "no-conflict",
|
|
627
644
|
checked_req_id: input.id,
|
|
628
|
-
strict_readiness: semantic.receipt
|
|
645
|
+
strict_readiness: semantic.receipt?.logic_readiness === "modeled"
|
|
629
646
|
? "modeled"
|
|
630
|
-
: semantic.receipt
|
|
647
|
+
: (semantic.receipt?.candidate_lane ?? "none"),
|
|
631
648
|
},
|
|
632
649
|
}
|
|
633
650
|
: {}),
|
|
@@ -662,7 +679,7 @@ export async function executeUpsert(input, context, options = {}) {
|
|
|
662
679
|
updated: changeKind === "updated" ? 1 : 0,
|
|
663
680
|
relationships_created: relationshipCount,
|
|
664
681
|
warnings: [...planRecoveryWarnings, detail],
|
|
665
|
-
semanticAdvisor
|
|
682
|
+
...advisorReceipt(semanticAdvisor),
|
|
666
683
|
status: "committed_with_repairs",
|
|
667
684
|
effectFailures: [
|
|
668
685
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/validate-upsert.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate-upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/validate-upsert.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,gBAAgB,EAEjB,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAWxE,OAAO,KAAK,EAIV,WAAW,EACX,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAiPpB;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC,CAkHjD"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// implements REQ-kibi-operation-interface-parity
|
|
2
2
|
import { escapeAtom } from "../../prolog/codec.js";
|
|
3
|
+
import { loadEntities } from "../../public/operations/discovery-entities.js";
|
|
3
4
|
import { entityIdStyleWarnings } from "../../utils/entity-id-style.js";
|
|
4
|
-
import { analyzeSemanticAdvisorInput } from "../semantic-advisor/analyze-prose.js";
|
|
5
|
-
import { assertSemanticInventoryBoundary } from "../semantic-advisor/ingestion-boundary.js";
|
|
6
5
|
import { entityContextWarnings } from "./context-warning.js";
|
|
7
6
|
import { buildUpsertContradictionPreviewGoal, buildWhatIfAnalysisGoal, formatUpsertError, } from "./contradictions.js";
|
|
8
7
|
import { validateRelationshipSources } from "./relationships.js";
|
|
8
|
+
import { canonicalSourcePath } from "./source-authoring.js";
|
|
9
9
|
import { validateSymbolGranularity } from "./symbol-granularity.js";
|
|
10
|
-
import { validateAppendOnlyProofReceipts, validateUpsertForCommit, } from "./upsert.js";
|
|
10
|
+
import { analyzeUpsertSemantics, validateAppendOnlyProofReceipts, validateUpsertForCommit, } from "./upsert.js";
|
|
11
11
|
import { validateUpsertInput } from "./validation.js";
|
|
12
12
|
import { symbolCoverageWarnings } from "./warnings.js";
|
|
13
13
|
import { isBlockingWitness, parseWhatIfAnalysis } from "./what-if.js";
|
|
@@ -16,6 +16,37 @@ async function entityExists(prolog, id) {
|
|
|
16
16
|
const result = await prolog.query(`once(kb_entity('${escapeAtom(id)}', _Type, _Props))`);
|
|
17
17
|
return result.success;
|
|
18
18
|
}
|
|
19
|
+
// implements REQ-kibi-operation-interface-parity, REQ-kb-entity-body-context
|
|
20
|
+
/**
|
|
21
|
+
* The authored document a real kb_upsert would update, resolved with the
|
|
22
|
+
* write's own rules (stored authored source, else the canonical lane path).
|
|
23
|
+
* The preview entity's `source` is the runtime marker, so without this the
|
|
24
|
+
* context warning would judge the statement instead of the existing body the
|
|
25
|
+
* write preserves. Undefined when the write would not touch a document.
|
|
26
|
+
*/
|
|
27
|
+
async function previewDocumentPath(input, entity, context) {
|
|
28
|
+
if (context.fs === undefined || context.sourceFirst === false) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const existing = context.prolog === undefined
|
|
33
|
+
? undefined
|
|
34
|
+
: (await loadEntities(context.prolog, {
|
|
35
|
+
id: input.id,
|
|
36
|
+
type: input.type,
|
|
37
|
+
}))[0];
|
|
38
|
+
const existingSource = typeof existing?.source === "string" ? existing.source : undefined;
|
|
39
|
+
if (input.document === undefined &&
|
|
40
|
+
existing !== undefined &&
|
|
41
|
+
!existingSource?.match(/\.(?:md|mdx|ya?ml|json)$/i)) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
return canonicalSourcePath(context, input, entity, existing);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
19
50
|
// implements REQ-kibi-operation-interface-parity, REQ-kibi-truthful-consistency
|
|
20
51
|
/**
|
|
21
52
|
* The commit-time contradiction check, run against a staged and rolled-back
|
|
@@ -130,11 +161,10 @@ async function validateWithoutStore(input, context) {
|
|
|
130
161
|
await validateAppendOnlyProofReceipts(validated.entity, context);
|
|
131
162
|
validateRelationshipSources(input.id, validated.relationships);
|
|
132
163
|
await validateSymbolGranularity(validated.entity, validated.relationships, context);
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
164
|
+
const semantic = analyzeUpsertSemantics({
|
|
165
|
+
...input,
|
|
166
|
+
relationships: validated.relationships,
|
|
136
167
|
});
|
|
137
|
-
assertSemanticInventoryBoundary({ ...input, relationships }, relationships, semantic.receipt);
|
|
138
168
|
return { entity: validated.entity, semantic };
|
|
139
169
|
}
|
|
140
170
|
// implements REQ-kibi-operation-interface-parity, REQ-kibi-truthful-consistency
|
|
@@ -194,7 +224,9 @@ export async function executeValidateUpsert(input, context) {
|
|
|
194
224
|
...semantic.warnings,
|
|
195
225
|
...coverageWarnings,
|
|
196
226
|
...idStyleWarnings,
|
|
197
|
-
...entityContextWarnings(input, entity, context.workspaceRoot
|
|
227
|
+
...entityContextWarnings(input, entity, context.workspaceRoot, {
|
|
228
|
+
targetPath: await previewDocumentPath(input, entity, context),
|
|
229
|
+
}),
|
|
198
230
|
],
|
|
199
231
|
semanticAdvisor: semantic.receipt,
|
|
200
232
|
normalizedPreview: entity,
|
|
@@ -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;AA6G9E,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";
|
|
@@ -16,7 +16,7 @@ import { canonicalFilesystemPath } from "../../utils/canonical-path.js";
|
|
|
16
16
|
import { isDerivedKbPath } from "../../utils/kb-paths.js";
|
|
17
17
|
import { bootstrapEmptyKbSnapshotId, bootstrapPlanHash, } from "../bootstrap/types.js";
|
|
18
18
|
import { validateBootstrapPayload } from "../bootstrap/validation.js";
|
|
19
|
-
import { SCHEMA6_AUTOMATIC_CODES, applySchema6MigrationAction, } from "../migration/schema6.js";
|
|
19
|
+
import { SCHEMA6_APPROVABLE_REVIEW_CODES, SCHEMA6_AUTOMATIC_CODES, applySchema6MigrationAction, } from "../migration/schema6.js";
|
|
20
20
|
import { buildUpsertBatchCommitGoal, formatUpsertError, } from "../mutation/contradictions.js";
|
|
21
21
|
import { executeDelete } from "../mutation/delete.js";
|
|
22
22
|
import { PLAN_APPLY_JOURNAL_VERSION, contentHash, createPlanApplyJournal, isPlanApplyJournalId, openPlanApplyJournal, openPlanApplyJournalById, planRecoveryNotes, publishJournaledFiles, reconcilePendingSourceReceipts, recordPlanApplyJournal, recoverPlanApplyJournal, restoreJournaledFiles, settlePendingPlanApplyJournals, storeFingerprint, withPlanRecoveryNotes, } from "../mutation/plan-apply-journal.js";
|
|
@@ -273,7 +273,8 @@ function validateMigrationPlanShape(args) {
|
|
|
273
273
|
for (const action of actions) {
|
|
274
274
|
if (action.state !== "ready")
|
|
275
275
|
throw new Error(`Migration apply failed: action '${action.id}' is blocked`);
|
|
276
|
-
if (action.safety
|
|
276
|
+
if (!(action.safety === "automatic" && action.autoApplicable === true) &&
|
|
277
|
+
!isApprovableReviewAction(action))
|
|
277
278
|
throw new Error(`Migration apply failed: action '${action.id}' is not automatic`);
|
|
278
279
|
for (const dependency of action.dependsOn) {
|
|
279
280
|
if (!selected.has(dependency))
|
|
@@ -282,6 +283,19 @@ function validateMigrationPlanShape(args) {
|
|
|
282
283
|
}
|
|
283
284
|
return actions;
|
|
284
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* A review action Kibi can execute once a person approves it by id. Callers
|
|
288
|
+
* pass the approved ids explicitly; `kibi migrate --apply-safe` without
|
|
289
|
+
* `--approved-action` selects only automatic actions, so these never run on
|
|
290
|
+
* their own.
|
|
291
|
+
*/
|
|
292
|
+
// implements REQ-agent-guided-migration-orchestration, REQ-cli-schema-migration
|
|
293
|
+
function isApprovableReviewAction(action) {
|
|
294
|
+
return (action.safety === "review" &&
|
|
295
|
+
action.autoApplicable === false &&
|
|
296
|
+
action.invocation.kind === "cli" &&
|
|
297
|
+
SCHEMA6_APPROVABLE_REVIEW_CODES.has(action.code));
|
|
298
|
+
}
|
|
285
299
|
function topologicalActions(actions) {
|
|
286
300
|
const byId = new Map(actions.map((action) => [action.id, action]));
|
|
287
301
|
const result = [];
|
|
@@ -2141,12 +2155,12 @@ async function applyMigrationPlan(args, context, onCommitted = () => undefined)
|
|
|
2141
2155
|
continue;
|
|
2142
2156
|
}
|
|
2143
2157
|
try {
|
|
2144
|
-
await applyMigrationAction(action, context);
|
|
2158
|
+
const detail = await applyMigrationAction(action, context);
|
|
2145
2159
|
onCommitted();
|
|
2146
2160
|
results.push({
|
|
2147
2161
|
actionId: action.id,
|
|
2148
2162
|
outcome: "applied",
|
|
2149
|
-
detail: `Applied ${action.code}.`,
|
|
2163
|
+
detail: detail ?? `Applied ${action.code}.`,
|
|
2150
2164
|
});
|
|
2151
2165
|
}
|
|
2152
2166
|
catch (error) {
|
|
@@ -2278,6 +2292,10 @@ async function applyMigrationPlan(args, context, onCommitted = () => undefined)
|
|
|
2278
2292
|
structuredContent: payload,
|
|
2279
2293
|
};
|
|
2280
2294
|
}
|
|
2295
|
+
/**
|
|
2296
|
+
* Run one approved migration action. Returns a detail line for the action
|
|
2297
|
+
* result when the executor reports what it changed.
|
|
2298
|
+
*/
|
|
2281
2299
|
async function applyMigrationAction(action, context) {
|
|
2282
2300
|
switch (action.code) {
|
|
2283
2301
|
case "legacy_branch_storage":
|
|
@@ -2328,14 +2346,19 @@ async function applyMigrationAction(action, context) {
|
|
|
2328
2346
|
return;
|
|
2329
2347
|
case "schema_version_upgrade":
|
|
2330
2348
|
case "invalid_schema_version":
|
|
2331
|
-
case "legacy_storage_migration":
|
|
2332
|
-
|
|
2349
|
+
case "legacy_storage_migration": {
|
|
2350
|
+
// implements REQ-agent-guided-migration-orchestration
|
|
2351
|
+
const run = await migrateCommand({
|
|
2333
2352
|
yes: true,
|
|
2334
2353
|
workspaceRoot: context.workspaceRoot,
|
|
2335
2354
|
initializeMissingConfig: true,
|
|
2336
|
-
})
|
|
2355
|
+
});
|
|
2356
|
+
if (run.exitCode !== 0)
|
|
2337
2357
|
throw new Error("Schema migration did not complete successfully.");
|
|
2338
|
-
return
|
|
2358
|
+
return run.summary !== undefined
|
|
2359
|
+
? describeMigrationRun(run.summary)
|
|
2360
|
+
: undefined;
|
|
2361
|
+
}
|
|
2339
2362
|
case "symbol_refresh_coordinates":
|
|
2340
2363
|
case "coverage_source_coordinates": {
|
|
2341
2364
|
const result = await syncCommand({
|
|
@@ -2354,7 +2377,8 @@ async function applyMigrationAction(action, context) {
|
|
|
2354
2377
|
applyProofIntegrationAction(action, context.workspaceRoot);
|
|
2355
2378
|
return;
|
|
2356
2379
|
default:
|
|
2357
|
-
if (SCHEMA6_AUTOMATIC_CODES.has(action.code)
|
|
2380
|
+
if (SCHEMA6_AUTOMATIC_CODES.has(action.code) ||
|
|
2381
|
+
SCHEMA6_APPROVABLE_REVIEW_CODES.has(action.code)) {
|
|
2358
2382
|
await applySchema6MigrationAction(action, context);
|
|
2359
2383
|
return;
|
|
2360
2384
|
}
|
|
@@ -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":"diagnostics.d.ts","sourceRoot":"","sources":["../../../src/public/impact/diagnostics.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,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;AAoFpB,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,mCAAmC,GAC3C,oBAAoB,EAAE,CAmExB;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,oBAAoB,EAAE,CAsBxB;AAED,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,SAAS,oBAAoB,EAAE,GAC3C,OAAO,CAET;AAED,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,SAAS,iBAAiB,EAAE,GACxC,OAAO,CAIT"}
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { extractSymbolsFromStagedFile, } from "../../traceability/symbol-extract.js";
|
|
4
|
-
import { ALLOWED_GRANULARITY_REASONS_PROSE, getBehavioralSymbolNames, getNonBehavioralSymbolNames, isAllowedGranularityReason, isTraceabilityRelationshipType, } from "../symbol-granularity.js";
|
|
5
|
-
function isNarrowerSymbol(manifestSymbolName, candidate, sourceSymbols) {
|
|
6
|
-
if (candidate.name === manifestSymbolName)
|
|
7
|
-
return false;
|
|
8
|
-
if (candidate.name.startsWith(`${manifestSymbolName}.`))
|
|
9
|
-
return true;
|
|
10
|
-
return !sourceSymbols.some((symbol) => symbol.name === manifestSymbolName);
|
|
11
|
-
}
|
|
4
|
+
import { ALLOWED_GRANULARITY_REASONS_PROSE, getBehavioralSymbolNames, getNonBehavioralSymbolNames, isAllowedGranularityReason, isTraceabilityRelationshipType, narrowerSymbols, } from "../symbol-granularity.js";
|
|
12
5
|
function hasTraceabilityRelationship(result) {
|
|
13
6
|
return result.relationships.some((relationship) => isTraceabilityRelationshipType(relationship.type));
|
|
14
7
|
}
|
|
@@ -62,6 +55,7 @@ function isChangedBehavioralReviewCandidate(symbol) {
|
|
|
62
55
|
return false;
|
|
63
56
|
return symbol.hunkRanges.length > 0;
|
|
64
57
|
}
|
|
58
|
+
// implements REQ-cli-staged-impact-enforcement, REQ-cli-check
|
|
65
59
|
export function createSymbolGranularityDiagnostics(options) {
|
|
66
60
|
const diagnostics = [];
|
|
67
61
|
for (const result of options.manifestResults) {
|
|
@@ -76,25 +70,41 @@ export function createSymbolGranularityDiagnostics(options) {
|
|
|
76
70
|
if (hasValidGranularityReason(result))
|
|
77
71
|
continue;
|
|
78
72
|
const { candidateSymbols, sourceSymbols } = getGranularSymbolsForSourceFile(result.sourceFile, options);
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
73
|
+
const narrower = narrowerSymbols(result.entity.title, candidateSymbols, sourceSymbols);
|
|
74
|
+
if (narrower.length === 0)
|
|
81
75
|
continue;
|
|
82
|
-
const behavioralNames = getBehavioralSymbolNames(
|
|
76
|
+
const behavioralNames = getBehavioralSymbolNames(narrower);
|
|
83
77
|
if (behavioralNames.length === 0)
|
|
84
78
|
continue;
|
|
85
|
-
const nonBehavioralNames = getNonBehavioralSymbolNames(
|
|
79
|
+
const nonBehavioralNames = getNonBehavioralSymbolNames(narrower);
|
|
86
80
|
const ignoredSymbolsSuggestion = nonBehavioralNames.length > 0
|
|
87
81
|
? ` Non-behavioral symbols ignored for this decision: ${nonBehavioralNames.join(", ")}.`
|
|
88
82
|
: "";
|
|
83
|
+
// A title the extractor does not produce (a private class member, a
|
|
84
|
+
// renamed or removed declaration) can never become granular by refresh:
|
|
85
|
+
// say so instead of only calling the link coarse.
|
|
86
|
+
const titleExtracted = sourceSymbols.some((symbol) => symbol.name === result.entity.title);
|
|
89
87
|
diagnostics.push({
|
|
90
88
|
id: "symbol_granularity_violation",
|
|
91
89
|
severity: "error",
|
|
92
90
|
blocking: true,
|
|
93
91
|
category: "symbol",
|
|
92
|
+
entityId: result.entity.id,
|
|
93
|
+
...(result.entity.source ? { source: result.entity.source } : {}),
|
|
94
94
|
files: [result.entity.source, result.sourceFile],
|
|
95
95
|
docs: ["docs/symbol-traceability-taxonomy.md"],
|
|
96
|
-
message:
|
|
97
|
-
|
|
96
|
+
message: titleExtracted
|
|
97
|
+
? `Symbol ${result.entity.id} links ${result.sourceFile} coarsely while granular symbols are available (behavioral only): ${behavioralNames.join(", ")}`
|
|
98
|
+
: `Symbol ${result.entity.id} names '${result.entity.title}', which the source extractor does not produce for ${result.sourceFile} (private class members and removed or renamed declarations are not tracked), so it links ${result.sourceFile} coarsely while granular symbols are available (behavioral only): ${behavioralNames.join(", ")}`,
|
|
99
|
+
suggestion: titleExtracted
|
|
100
|
+
? `Move ownership/coverage/test relationships to the narrow behavioral symbol, add a manifest behavioral anchor, or add granularity_reason with ${ALLOWED_GRANULARITY_REASONS_PROSE} when the coarse symbol is intentional.${ignoredSymbolsSuggestion}`
|
|
101
|
+
: `Re-point this symbol's relationships to the public declaration that owns the behavior (one of: ${behavioralNames.join(", ")}) or remove the symbol; add granularity_reason with ${ALLOWED_GRANULARITY_REASONS_PROSE} only when coarse ownership is intentional.${ignoredSymbolsSuggestion}`,
|
|
102
|
+
evidence: {
|
|
103
|
+
sourceFile: result.sourceFile,
|
|
104
|
+
title: result.entity.title,
|
|
105
|
+
titleExtracted,
|
|
106
|
+
granularSymbols: behavioralNames,
|
|
107
|
+
},
|
|
98
108
|
});
|
|
99
109
|
}
|
|
100
110
|
return diagnostics;
|