kibi-runtime 2.5.0 → 2.7.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/index.d.ts +1 -1
- package/dist/index.js +335 -54
- package/dist/skills/kibi-bootstrap/SKILL.md +4 -4
- package/dist/skills/kibi-usage/SKILL.md +9 -4
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* intentionally explicit: adding a CLI module does not silently widen the
|
|
5
5
|
* runtime package contract.
|
|
6
6
|
*/
|
|
7
|
-
export type { Clock, OperationContext, OperationPlugins, OperationRuntime, RuntimeOptions, RuntimeOperationSpec, PrologPort, PrologQueryResult, FilesystemPort, GitPort, NetworkPort, EngineCommandV1 as CliEngineCommandV1, EnginePort as CliEnginePort, } from "kibi-cli/operations/runtime-types";
|
|
7
|
+
export type { Clock, OperationContext, OperationPlugins, OperationRuntime, OperationProgress, ProgressReporter, RuntimeOptions, RuntimeOperationSpec, PrologPort, PrologQueryResult, FilesystemPort, GitPort, NetworkPort, EngineCommandV1 as CliEngineCommandV1, EnginePort as CliEnginePort, } from "kibi-cli/operations/runtime-types";
|
|
8
8
|
export type { EngineCommandV1, EnginePort } from "./engine.js";
|
|
9
9
|
export { executeOperation } from "kibi-cli/operations/runtime-types";
|
|
10
10
|
export type { KibiResult, KibiResultStatus, OperationContent, OperationEffect, OperationName, OperationResult, OperationSpec, } from "kibi-cli/operations";
|
package/dist/index.js
CHANGED
|
@@ -563,12 +563,20 @@ var init_contracts = __esm(() => {
|
|
|
563
563
|
"apply_requires_predicate",
|
|
564
564
|
"provide_argument_bindings",
|
|
565
565
|
"resolve_schema_reference",
|
|
566
|
-
"record_ontology_gap"
|
|
566
|
+
"record_ontology_gap",
|
|
567
|
+
"review_nonlogical",
|
|
568
|
+
"already_grounded"
|
|
567
569
|
]
|
|
568
570
|
},
|
|
569
571
|
recommendedPredicateSchema: { type: ["object", "null"] },
|
|
570
572
|
applyPlan: recordArray,
|
|
571
573
|
relationshipPlan: { type: ["object", "null"] },
|
|
574
|
+
relationshipTarget: {
|
|
575
|
+
type: ["string", "null"],
|
|
576
|
+
description: "The planned predicate fact id (FACT-PRED-...) that a requires_predicate link must target. Candidate ids (SUGGEST-...) are never relationship targets."
|
|
577
|
+
},
|
|
578
|
+
existingGrounding: recordArray,
|
|
579
|
+
replacementPlan: { type: ["object", "null"] },
|
|
572
580
|
warnings: stringArray
|
|
573
581
|
}),
|
|
574
582
|
kb_plan_bootstrap: objectData({
|
|
@@ -703,26 +711,32 @@ var init_contracts = __esm(() => {
|
|
|
703
711
|
diagnostics: stringArray,
|
|
704
712
|
capabilityPlugins: recordValue
|
|
705
713
|
}),
|
|
706
|
-
kb_apply_plan:
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
714
|
+
kb_apply_plan: {
|
|
715
|
+
description: "Synchronous apply result, or a kibi.job.v1 receipt when async:true detached the apply into a background job (poll kb_job_status with the jobId).",
|
|
716
|
+
anyOf: [
|
|
717
|
+
objectData({
|
|
718
|
+
version: stringValue,
|
|
719
|
+
outcome: stringValue,
|
|
720
|
+
planHash: stringValue,
|
|
721
|
+
changedEntities: integerValue,
|
|
722
|
+
changedRelationships: integerValue,
|
|
723
|
+
changedPaths: stringArray,
|
|
724
|
+
finalSnapshots: recordValue,
|
|
725
|
+
validationSummary: recordValue,
|
|
726
|
+
recoveryJournalId: { type: ["string", "null"] },
|
|
727
|
+
deleted: integerValue,
|
|
728
|
+
sourcePaths: stringArray,
|
|
729
|
+
actionResults: recordArray,
|
|
730
|
+
notes: stringArray,
|
|
731
|
+
remainingPlan: recordValue,
|
|
732
|
+
closeout: recordValue,
|
|
733
|
+
status: stringValue,
|
|
734
|
+
effectFailures: recordArray,
|
|
735
|
+
nextActions: recordArray
|
|
736
|
+
}),
|
|
737
|
+
jobReceiptData
|
|
738
|
+
]
|
|
739
|
+
},
|
|
726
740
|
kb_ingest_proof: objectData({
|
|
727
741
|
artifactDigest: stringValue,
|
|
728
742
|
environmentHash: stringValue,
|
|
@@ -23292,6 +23306,12 @@ var init_rule_registry_generated = __esm(() => {
|
|
|
23292
23306
|
enforcementClass: "migration",
|
|
23293
23307
|
category: "integrity"
|
|
23294
23308
|
},
|
|
23309
|
+
{
|
|
23310
|
+
name: "related-requirement-unmodeled",
|
|
23311
|
+
description: "A current requirement with unresolved (missing) propositions that relates_to a current requirement modeled with strict property or ground predicate facts; its claims are never compared by domain-contradictions until they are modeled against the same subject or predicates, or it supersedes that requirement",
|
|
23312
|
+
enforcementClass: "canonical",
|
|
23313
|
+
category: "integrity"
|
|
23314
|
+
},
|
|
23295
23315
|
{
|
|
23296
23316
|
name: "proof-contract-symbols",
|
|
23297
23317
|
description: "Detect unresolved required_proofs.symbol_id values, type-shape required proofs, and proof_bindings.source_file disagreement with the named symbol sourceFile",
|
|
@@ -29834,6 +29854,10 @@ function relationshipSchemaValidator() {
|
|
|
29834
29854
|
relationshipValidator ??= ajv2.compile(relationship_default);
|
|
29835
29855
|
return relationshipValidator;
|
|
29836
29856
|
}
|
|
29857
|
+
function unknownEntityProperties(entity) {
|
|
29858
|
+
const known = upsertEntitySchema.properties;
|
|
29859
|
+
return Object.keys(entity).filter((key) => !Object.hasOwn(known, key));
|
|
29860
|
+
}
|
|
29837
29861
|
function formatEntityErrors(entity, errors) {
|
|
29838
29862
|
const messages = errors.map((error) => {
|
|
29839
29863
|
const params = error.params;
|
|
@@ -29847,6 +29871,17 @@ function formatEntityErrors(entity, errors) {
|
|
|
29847
29871
|
if (canonical) {
|
|
29848
29872
|
return `${path}: unknown property '${property}'. Did you mean '${canonical}'? kb_upsert.properties uses snake_case typed fact fields.`;
|
|
29849
29873
|
}
|
|
29874
|
+
if (path === "root" && DOCUMENT_TEXT_PROPERTIES.has(property)) {
|
|
29875
|
+
const others = unknownEntityProperties(entity).filter((key) => key !== property);
|
|
29876
|
+
return `${path}: unknown property '${property}' (${error.message}). Entity prose (scenario, requirement, test, ADR or observation text) belongs in kb_upsert.document.body, not in kb_upsert.properties.${others.length > 0 ? ` Other unknown properties: ${others.map((key) => `'${key}'`).join(", ")}.` : ""}`;
|
|
29877
|
+
}
|
|
29878
|
+
if (path === "root") {
|
|
29879
|
+
const unknown = unknownEntityProperties(entity);
|
|
29880
|
+
const listed = unknown.length > 0 ? unknown : [property];
|
|
29881
|
+
const proseHint = listed.some((key) => DOCUMENT_TEXT_PROPERTIES.has(key)) ? " Entity prose belongs in kb_upsert.document.body." : "";
|
|
29882
|
+
return `${path}: unknown propert${listed.length === 1 ? "y" : "ies"} ${listed.map((key) => `'${key}'`).join(", ")} (${error.message}). Remove ${listed.length === 1 ? "it" : "them"} from kb_upsert.properties or use fields the ${String(entity.type ?? "entity")} schema defines.${proseHint}`;
|
|
29883
|
+
}
|
|
29884
|
+
return `${path}: unknown property '${property}' (${error.message}).`;
|
|
29850
29885
|
}
|
|
29851
29886
|
if (error.keyword === "enum" && params.allowedValues) {
|
|
29852
29887
|
return `${path}: ${error.message}. Allowed values: ${params.allowedValues.map(String).join(", ")}`;
|
|
@@ -29931,7 +29966,7 @@ function validateProofExemptionPairing(entity) {
|
|
|
29931
29966
|
throw new Error("Entity validation failed: proof_exempt requires a non-empty proof_exempt_reason explaining why the requirement is outside E2E-proof scope");
|
|
29932
29967
|
}
|
|
29933
29968
|
}
|
|
29934
|
-
var import_ajv2, ajv2, schema2, properties, entityValidator, relationshipValidator, upsertEntitySchema, ALIASES;
|
|
29969
|
+
var import_ajv2, ajv2, schema2, properties, entityValidator, relationshipValidator, upsertEntitySchema, ALIASES, DOCUMENT_TEXT_PROPERTIES;
|
|
29935
29970
|
var init_validation = __esm(() => {
|
|
29936
29971
|
init_proof_receipt();
|
|
29937
29972
|
init_entity();
|
|
@@ -29968,6 +30003,16 @@ var init_validation = __esm(() => {
|
|
|
29968
30003
|
["canonicalKey", "canonical_key"],
|
|
29969
30004
|
["closedWorld", "closed_world"]
|
|
29970
30005
|
]);
|
|
30006
|
+
DOCUMENT_TEXT_PROPERTIES = new Set([
|
|
30007
|
+
"body",
|
|
30008
|
+
"text",
|
|
30009
|
+
"description",
|
|
30010
|
+
"content",
|
|
30011
|
+
"markdown",
|
|
30012
|
+
"prose",
|
|
30013
|
+
"summary",
|
|
30014
|
+
"details"
|
|
30015
|
+
]);
|
|
29971
30016
|
});
|
|
29972
30017
|
|
|
29973
30018
|
// ../cli/dist/operations/semantic-advisor/analysis-receipt.js
|
|
@@ -52715,7 +52760,7 @@ var init_check_executor = __esm(() => {
|
|
|
52715
52760
|
});
|
|
52716
52761
|
|
|
52717
52762
|
// ../cli/dist/public/operations/specs/check-rules.generated.js
|
|
52718
|
-
var SELECTABLE_RULE_NAMES, SELECTABLE_RULES_ALLOWED = "must-priority-coverage, symbol-coverage, symbol-traceability, no-dangling-refs, source-relationship-parity, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged";
|
|
52763
|
+
var SELECTABLE_RULE_NAMES, SELECTABLE_RULES_ALLOWED = "must-priority-coverage, symbol-coverage, symbol-traceability, no-dangling-refs, source-relationship-parity, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged";
|
|
52719
52764
|
var init_check_rules_generated = __esm(() => {
|
|
52720
52765
|
SELECTABLE_RULE_NAMES = [
|
|
52721
52766
|
"must-priority-coverage",
|
|
@@ -52744,6 +52789,7 @@ var init_check_rules_generated = __esm(() => {
|
|
|
52744
52789
|
"predicate-verifiability",
|
|
52745
52790
|
"strict-readiness",
|
|
52746
52791
|
"semantic-completeness",
|
|
52792
|
+
"related-requirement-unmodeled",
|
|
52747
52793
|
"proof-contract-symbols",
|
|
52748
52794
|
"entity-id-style",
|
|
52749
52795
|
"domain-redundancy",
|
|
@@ -55289,7 +55335,7 @@ function buildRelationshipPlan(factId, requirementId, claimText, existingLogicCl
|
|
|
55289
55335
|
claimText: claimText.trim(),
|
|
55290
55336
|
logicClaims
|
|
55291
55337
|
} : {},
|
|
55292
|
-
instructions:
|
|
55338
|
+
instructions: `Apply the predicate fact ${factId} first, update the requirement with the returned merged logicClaims manifest, then attach this relationship without overwriting other requirement metadata. The relationship target is the planned fact id ${factId}, never a candidates[].id (SUGGEST-...).`
|
|
55293
55339
|
};
|
|
55294
55340
|
}
|
|
55295
55341
|
function buildGapApplyPlan(text, args) {
|
|
@@ -55505,6 +55551,37 @@ function withMarginRejection(candidates) {
|
|
|
55505
55551
|
};
|
|
55506
55552
|
});
|
|
55507
55553
|
}
|
|
55554
|
+
function unquoted(value) {
|
|
55555
|
+
return typeof value === "string" ? value.replace(/^['"]|['"]$/g, "") : "";
|
|
55556
|
+
}
|
|
55557
|
+
async function existingClaimGrounding(prolog, requirementId, claimKey, warnings) {
|
|
55558
|
+
if (prolog === null || !requirementId)
|
|
55559
|
+
return [];
|
|
55560
|
+
const found = [];
|
|
55561
|
+
try {
|
|
55562
|
+
for (const type of GROUNDING_RELATIONSHIP_TYPES) {
|
|
55563
|
+
const targets = await prolog.query(`findall(To, kb_relationship(${type}, '${escapeAtom(requirementId)}', To), Targets)`);
|
|
55564
|
+
if (!targets.success)
|
|
55565
|
+
continue;
|
|
55566
|
+
for (const target of parsePrologList(targets.bindings.Targets ?? "[]")) {
|
|
55567
|
+
const fact = await prolog.query(`once((kb_entity('${escapeAtom(target)}', fact, _GroundProps), memberchk(claim_key=_GroundClaimRaw, _GroundProps), normalize_term_atom(_GroundClaimRaw, ClaimKey), (memberchk(fact_kind=_GroundKindRaw, _GroundProps) -> normalize_term_atom(_GroundKindRaw, FactKind) ; FactKind = '')))`);
|
|
55568
|
+
if (!fact.success)
|
|
55569
|
+
continue;
|
|
55570
|
+
if (unquoted(fact.bindings.ClaimKey) !== claimKey)
|
|
55571
|
+
continue;
|
|
55572
|
+
found.push({
|
|
55573
|
+
relationship: { type, from: requirementId, to: target },
|
|
55574
|
+
factId: target,
|
|
55575
|
+
factKind: unquoted(fact.bindings.FactKind) || null,
|
|
55576
|
+
claimKey
|
|
55577
|
+
});
|
|
55578
|
+
}
|
|
55579
|
+
}
|
|
55580
|
+
} catch (error) {
|
|
55581
|
+
warnings.push(`Existing grounding of ${requirementId} could not be read (${error instanceof Error ? error.message : String(error)}); check its requires_property, requires_predicate and requires_rule links before linking a predicate.`);
|
|
55582
|
+
}
|
|
55583
|
+
return found;
|
|
55584
|
+
}
|
|
55508
55585
|
async function handleKbSuggestPredicates(prolog, args, context) {
|
|
55509
55586
|
const text = normalizeText2(args.text);
|
|
55510
55587
|
const subject = inferSubject(text, args.subjectHint);
|
|
@@ -55569,12 +55646,22 @@ async function handleKbSuggestPredicates(prolog, args, context) {
|
|
|
55569
55646
|
warnings.push("No predicate candidate met minScore. If this is recurring domain language, create a fact_kind=predicate_schema fact; otherwise keep the generated review:ontology-gap observation. Do not invent unsupported predicate names without a predicate_schema.");
|
|
55570
55647
|
}
|
|
55571
55648
|
const unavailableSchema = Boolean(args.schemaId && selectedSchemas.length === 0);
|
|
55572
|
-
const recommendedAction = !recommendedCandidate ? unavailableSchema ? "resolve_schema_reference" : "record_ontology_gap" : recommendedCandidate.binding_status === "complete" ? "apply_requires_predicate" : "provide_argument_bindings";
|
|
55573
|
-
const applyPlan = recommendedCandidate && recommendedCandidate.binding_status === "complete" ? buildPredicateApplyPlan(recommendedCandidate, args) : !recommendedCandidate && !unavailableSchema ? buildGapApplyPlan(text, args) : [];
|
|
55574
|
-
const relationshipPlan = recommendedCandidate && recommendedCandidate.binding_status === "complete" ? buildRelationshipPlan(String(applyPlan[0]?.id ?? ""), args.requirementId, text, args.existingLogicClaims) : null;
|
|
55575
|
-
const recommendedPredicateSchema = !recommendedCandidate && !unavailableSchema ? buildPredicateSchemaDraft(text, subject) : null;
|
|
55576
|
-
const textSummary = recommendedCandidate && recommendedCandidate.binding_status === "complete" ? `Suggested ${candidates.length} predicate candidate(s). Top applicable match: ${recommendedCandidate.predicate_name}. Apply structured predicate facts before falling back to prose.` : recommendedCandidate ? `Matched ${recommendedCandidate.predicate_name}, but exact reviewed values are still required for: ${recommendedCandidate.unbound_arguments.join(", ")}. No apply plan was generated.` : unavailableSchema ? `Requested predicate schema ${args.schemaId} is unavailable or semantically inapplicable. No apply plan was generated.` : "No predicate candidate passed the semantic applicability gate; record an ontology gap and review the generated schema draft instead of silently writing prose.";
|
|
55577
55649
|
const claimKey = semanticClaimKey(text);
|
|
55650
|
+
const existingGrounding = await existingClaimGrounding(prolog, args.requirementId, claimKey, warnings);
|
|
55651
|
+
const alreadyGrounded = existingGrounding.length > 0;
|
|
55652
|
+
const completeCandidate = recommendedCandidate && recommendedCandidate.binding_status === "complete" ? recommendedCandidate : undefined;
|
|
55653
|
+
const recommendedAction = alreadyGrounded ? "already_grounded" : !recommendedCandidate ? unavailableSchema ? "resolve_schema_reference" : "record_ontology_gap" : completeCandidate ? "apply_requires_predicate" : "provide_argument_bindings";
|
|
55654
|
+
const predicatePlan = completeCandidate ? buildPredicateApplyPlan(completeCandidate, args) : [];
|
|
55655
|
+
const applyPlan = alreadyGrounded ? [] : completeCandidate ? predicatePlan : !recommendedCandidate && !unavailableSchema ? buildGapApplyPlan(text, args) : [];
|
|
55656
|
+
const plannedFactId = typeof predicatePlan[0]?.id === "string" ? predicatePlan[0].id : null;
|
|
55657
|
+
const relationshipPlan = completeCandidate && !alreadyGrounded ? buildRelationshipPlan(plannedFactId ?? "", args.requirementId, text, args.existingLogicClaims) : null;
|
|
55658
|
+
const replacementPlan = alreadyGrounded && plannedFactId !== null && args.requirementId ? buildGroundingReplacementPlan(args.requirementId, plannedFactId, predicatePlan, existingGrounding) : null;
|
|
55659
|
+
const relationshipTarget = relationshipPlan !== null || replacementPlan !== null ? plannedFactId : null;
|
|
55660
|
+
if (alreadyGrounded) {
|
|
55661
|
+
warnings.push(`${args.requirementId} already grounds this claim (${claimKey}) through ${existingGrounding.map((row) => `${row.relationship.type} -> ${row.factId}`).join(", ")}. A modeled claim takes exactly one logical grounding relationship, so adding requires_predicate beside it fails the proposition-complete rule. Keep the existing grounding, or follow replacementPlan to swap it for the predicate.`);
|
|
55662
|
+
}
|
|
55663
|
+
const recommendedPredicateSchema = !recommendedCandidate && !unavailableSchema && !alreadyGrounded ? buildPredicateSchemaDraft(text, subject) : null;
|
|
55664
|
+
const textSummary = alreadyGrounded ? `${args.requirementId} already grounds this claim through ${existingGrounding.map((row) => `${row.relationship.type} -> ${row.factId}`).join(", ")}; no write plan was generated.${replacementPlan ? ` To use ${completeCandidate?.predicate_name} instead, follow replacementPlan and link requires_predicate to ${plannedFactId}.` : ""}` : completeCandidate ? `Suggested ${candidates.length} predicate candidate(s). Top applicable match: ${completeCandidate.predicate_name}. Apply the predicate fact ${plannedFactId}, then link requires_predicate to that fact id (not a candidate id).` : recommendedCandidate ? `Matched ${recommendedCandidate.predicate_name}, but exact reviewed values are still required for: ${recommendedCandidate.unbound_arguments.join(", ")}. No apply plan was generated.` : unavailableSchema ? `Requested predicate schema ${args.schemaId} is unavailable or semantically inapplicable. No apply plan was generated.` : "No predicate candidate passed the semantic applicability gate; record an ontology gap and review the generated schema draft instead of silently writing prose.";
|
|
55578
55665
|
const logicClaims = Array.from(new Set([...args.existingLogicClaims ?? [], claimKey]));
|
|
55579
55666
|
return {
|
|
55580
55667
|
content: [{ type: "text", text: textSummary }],
|
|
@@ -55590,16 +55677,51 @@ async function handleKbSuggestPredicates(prolog, args, context) {
|
|
|
55590
55677
|
recommendedPredicateSchema,
|
|
55591
55678
|
applyPlan,
|
|
55592
55679
|
relationshipPlan,
|
|
55680
|
+
relationshipTarget,
|
|
55681
|
+
existingGrounding,
|
|
55682
|
+
replacementPlan,
|
|
55593
55683
|
warnings
|
|
55594
55684
|
},
|
|
55595
55685
|
applyPlan
|
|
55596
55686
|
};
|
|
55597
55687
|
}
|
|
55688
|
+
function buildGroundingReplacementPlan(requirementId, factId, predicatePlan, existing) {
|
|
55689
|
+
return {
|
|
55690
|
+
relationshipTarget: factId,
|
|
55691
|
+
steps: [
|
|
55692
|
+
{
|
|
55693
|
+
operation: "kb_upsert",
|
|
55694
|
+
input: predicatePlan[0],
|
|
55695
|
+
reason: "Write the predicate fact that will ground the claim."
|
|
55696
|
+
},
|
|
55697
|
+
{
|
|
55698
|
+
operation: "kb_delete",
|
|
55699
|
+
input: {
|
|
55700
|
+
relationships: existing.map((row) => row.relationship)
|
|
55701
|
+
},
|
|
55702
|
+
reason: "Retract the existing grounding link so the claim keeps exactly one logical grounding relationship."
|
|
55703
|
+
},
|
|
55704
|
+
{
|
|
55705
|
+
operation: "kb_upsert",
|
|
55706
|
+
input: {
|
|
55707
|
+
type: "req",
|
|
55708
|
+
id: requirementId,
|
|
55709
|
+
relationships: [
|
|
55710
|
+
{ type: "requires_predicate", from: requirementId, to: factId }
|
|
55711
|
+
]
|
|
55712
|
+
},
|
|
55713
|
+
reason: `Link requires_predicate to ${factId}; upsert the requirement with its stored title and status and this relationship only.`
|
|
55714
|
+
}
|
|
55715
|
+
],
|
|
55716
|
+
instructions: `Only replace the grounding when the predicate states the claim at least as precisely as ${existing.map((row) => row.factId).join(", ")}. Run kb_check after the last step.`
|
|
55717
|
+
};
|
|
55718
|
+
}
|
|
55598
55719
|
async function executeSuggestPredicates(args, context) {
|
|
55599
55720
|
return handleKbSuggestPredicates(context.prolog ?? null, args, context);
|
|
55600
55721
|
}
|
|
55601
|
-
var DEFAULT_MAX_CANDIDATES = 5, DEFAULT_MIN_SCORE2 = 0.35, NON_ASSERTIVE_PROPOSITION_ROLES;
|
|
55722
|
+
var DEFAULT_MAX_CANDIDATES = 5, DEFAULT_MIN_SCORE2 = 0.35, NON_ASSERTIVE_PROPOSITION_ROLES, GROUNDING_RELATIONSHIP_TYPES;
|
|
55602
55723
|
var init_suggest_predicates = __esm(() => {
|
|
55724
|
+
init_serialization();
|
|
55603
55725
|
init_analyze_prose();
|
|
55604
55726
|
init_clauses();
|
|
55605
55727
|
init_plugin_orchestration();
|
|
@@ -55615,6 +55737,11 @@ var init_suggest_predicates = __esm(() => {
|
|
|
55615
55737
|
"example",
|
|
55616
55738
|
"subjective"
|
|
55617
55739
|
]);
|
|
55740
|
+
GROUNDING_RELATIONSHIP_TYPES = [
|
|
55741
|
+
"requires_property",
|
|
55742
|
+
"requires_predicate",
|
|
55743
|
+
"requires_rule"
|
|
55744
|
+
];
|
|
55618
55745
|
});
|
|
55619
55746
|
|
|
55620
55747
|
// ../cli/dist/public/operations/specs/modeling.js
|
|
@@ -62458,7 +62585,7 @@ async function saveAtomicMutation(prolog, goals, operation) {
|
|
|
62458
62585
|
}
|
|
62459
62586
|
|
|
62460
62587
|
// ../cli/dist/operations/mutation/workspace-mutation-lock.js
|
|
62461
|
-
import { mkdirSync as mkdirSync9, readFileSync as readFileSync28, rmdirSync, unlinkSync as unlinkSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
62588
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync28, renameSync as renameSync6, rmdirSync, unlinkSync as unlinkSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
62462
62589
|
import * as path52 from "node:path";
|
|
62463
62590
|
function lockPath(workspaceRoot, lockFileRelative) {
|
|
62464
62591
|
return path52.join(workspaceRoot, lockFileRelative);
|
|
@@ -62569,9 +62696,40 @@ function observeExistingLock(fileSystem, target, ownerPath, isProcessAlive) {
|
|
|
62569
62696
|
}
|
|
62570
62697
|
return {
|
|
62571
62698
|
kind: "recovery",
|
|
62572
|
-
description: `holder pid ${record.pid} is not live
|
|
62699
|
+
description: `holder pid ${record.pid} is not live`,
|
|
62700
|
+
...source === "lock owner metadata" ? { deadHolder: record } : {}
|
|
62573
62701
|
};
|
|
62574
62702
|
}
|
|
62703
|
+
function reclaimDeadLock(fileSystem, target, holder, token) {
|
|
62704
|
+
const rename = fileSystem.renameSync;
|
|
62705
|
+
if (rename === undefined)
|
|
62706
|
+
return "changed";
|
|
62707
|
+
const tombstone = `${target}.reclaimed-${token}`;
|
|
62708
|
+
try {
|
|
62709
|
+
rename(target, tombstone);
|
|
62710
|
+
} catch (error) {
|
|
62711
|
+
const code = errorCode(error);
|
|
62712
|
+
if (code === "ENOENT" || code === "EEXIST" || code === "ENOTEMPTY")
|
|
62713
|
+
return "changed";
|
|
62714
|
+
throw new WorkspaceMutationLockError(`failed to reclaim workspace mutation lock at ${target} from dead pid ${holder.pid}: ${errorDetail(error)}`);
|
|
62715
|
+
}
|
|
62716
|
+
const tombstoneOwner = path52.join(tombstone, OWNER_FILE);
|
|
62717
|
+
let moved = null;
|
|
62718
|
+
try {
|
|
62719
|
+
moved = readLockRecord(fileSystem.readFileSync(tombstoneOwner));
|
|
62720
|
+
} catch {
|
|
62721
|
+
moved = null;
|
|
62722
|
+
}
|
|
62723
|
+
if (moved === null || moved.token !== holder.token) {
|
|
62724
|
+
try {
|
|
62725
|
+
rename(tombstone, target);
|
|
62726
|
+
} catch {}
|
|
62727
|
+
throw new OperationError("SOURCE_MUTATION_LOCK_RECOVERY_REQUIRED", `workspace mutation lock at ${target} changed owner while it was reclaimed from dead pid ${holder.pid}. Quiesce all Kibi and source-mutating writers, then verify ${target} and ${tombstone} before removing either`, false);
|
|
62728
|
+
}
|
|
62729
|
+
removeLockOwner(fileSystem, tombstone, tombstoneOwner, true);
|
|
62730
|
+
removeLockDirectory(fileSystem, tombstone);
|
|
62731
|
+
return "reclaimed";
|
|
62732
|
+
}
|
|
62575
62733
|
function defaultIsProcessAlive(pid) {
|
|
62576
62734
|
try {
|
|
62577
62735
|
process.kill(pid, 0);
|
|
@@ -62625,6 +62783,7 @@ async function acquireWorkspaceMutationLock(workspaceRoot, options2 = {}) {
|
|
|
62625
62783
|
throw new WorkspaceMutationLockError(`failed to create workspace mutation lock parent at ${parentPath} after ${parentResult.attempts} attempt(s): ${errorDetail(parentResult.error)}`);
|
|
62626
62784
|
}
|
|
62627
62785
|
let acquired = false;
|
|
62786
|
+
const reclaimed = [];
|
|
62628
62787
|
let lastBlocker = "unknown";
|
|
62629
62788
|
let lastObservationMissing = false;
|
|
62630
62789
|
let missingOwnerRereads = 0;
|
|
@@ -62650,6 +62809,21 @@ async function acquireWorkspaceMutationLock(workspaceRoot, options2 = {}) {
|
|
|
62650
62809
|
}
|
|
62651
62810
|
throw new OperationError("SOURCE_MUTATION_LOCK_RECOVERY_REQUIRED", `workspace mutation lock at ${target} requires operator recovery: ${observation.description} after ${missingOwnerRereads} reread(s). Quiesce all Kibi and source-mutating writers, then manually verify the owner metadata and remove the lock only after confirming no operation is active`, false);
|
|
62652
62811
|
}
|
|
62812
|
+
if (observation.kind === "recovery" && observation.deadHolder !== undefined && fileSystem.renameSync !== undefined && options2.reclaimDeadHolder?.(observation.deadHolder) === true) {
|
|
62813
|
+
const holder = observation.deadHolder;
|
|
62814
|
+
if (reclaimDeadLock(fileSystem, target, holder, token) === "reclaimed") {
|
|
62815
|
+
reclaimed.push({
|
|
62816
|
+
pid: holder.pid,
|
|
62817
|
+
token: holder.token,
|
|
62818
|
+
acquiredAt: holder.acquiredAt,
|
|
62819
|
+
reclaimedAt: now(),
|
|
62820
|
+
reclaimedByPid: process.pid
|
|
62821
|
+
});
|
|
62822
|
+
} else {
|
|
62823
|
+
await sleep(RETRY_INTERVAL_MS);
|
|
62824
|
+
}
|
|
62825
|
+
continue;
|
|
62826
|
+
}
|
|
62653
62827
|
if (observation.kind === "recovery") {
|
|
62654
62828
|
throw new OperationError("SOURCE_MUTATION_LOCK_RECOVERY_REQUIRED", `workspace mutation lock at ${target} requires operator recovery: ${observation.description}. Quiesce all Kibi and source-mutating writers, then manually verify the owner metadata and remove the lock only after confirming no operation is active`, false);
|
|
62655
62829
|
}
|
|
@@ -62698,6 +62872,7 @@ async function acquireWorkspaceMutationLock(workspaceRoot, options2 = {}) {
|
|
|
62698
62872
|
}
|
|
62699
62873
|
let releaseState = "owned";
|
|
62700
62874
|
return {
|
|
62875
|
+
...reclaimed.length > 0 ? { reclaimed } : {},
|
|
62701
62876
|
release: () => {
|
|
62702
62877
|
if (releaseState === "released")
|
|
62703
62878
|
return;
|
|
@@ -62735,7 +62910,8 @@ var init_workspace_mutation_lock = __esm(() => {
|
|
|
62735
62910
|
writeFileSync8(target, data, options2);
|
|
62736
62911
|
},
|
|
62737
62912
|
unlinkSync: (target) => unlinkSync6(target),
|
|
62738
|
-
rmdirSync: (target) => rmdirSync(target)
|
|
62913
|
+
rmdirSync: (target) => rmdirSync(target),
|
|
62914
|
+
renameSync: (from, to) => renameSync6(from, to)
|
|
62739
62915
|
};
|
|
62740
62916
|
WorkspaceMutationLockError = class WorkspaceMutationLockError extends Error {
|
|
62741
62917
|
name = "WorkspaceMutationLockError";
|
|
@@ -64090,7 +64266,7 @@ var init_symbols_coordinator2 = __esm(() => {
|
|
|
64090
64266
|
|
|
64091
64267
|
// ../cli/dist/operations/mutation/symbol-refresh.js
|
|
64092
64268
|
import { createHash as createHash29, randomUUID as randomUUID5 } from "node:crypto";
|
|
64093
|
-
import { existsSync as existsSync29, readFileSync as readFileSync33, renameSync as
|
|
64269
|
+
import { existsSync as existsSync29, readFileSync as readFileSync33, renameSync as renameSync7, unlinkSync as unlinkSync8, writeFileSync as writeFileSync11 } from "node:fs";
|
|
64094
64270
|
import * as path58 from "node:path";
|
|
64095
64271
|
function record2(value) {
|
|
64096
64272
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -64129,7 +64305,7 @@ function replaceArtifactAtomically(targetPath, content) {
|
|
|
64129
64305
|
const temporary = `${targetPath}.kibi-tmp-${process.pid}-${randomUUID5()}`;
|
|
64130
64306
|
try {
|
|
64131
64307
|
writeFileSync11(temporary, content, "utf8");
|
|
64132
|
-
|
|
64308
|
+
renameSync7(temporary, targetPath);
|
|
64133
64309
|
} catch (error) {
|
|
64134
64310
|
try {
|
|
64135
64311
|
unlinkSync8(temporary);
|
|
@@ -65520,7 +65696,7 @@ function formatSyncSummary(summary) {
|
|
|
65520
65696
|
}
|
|
65521
65697
|
|
|
65522
65698
|
// ../cli/dist/commands/sync/manifest.js
|
|
65523
|
-
import { existsSync as existsSync31, readFileSync as readFileSync35, renameSync as
|
|
65699
|
+
import { existsSync as existsSync31, readFileSync as readFileSync35, renameSync as renameSync8, unlinkSync as unlinkSync10, writeFileSync as writeFileSync13 } from "node:fs";
|
|
65524
65700
|
import * as path60 from "node:path";
|
|
65525
65701
|
function resolveDeps2(overrides) {
|
|
65526
65702
|
return {
|
|
@@ -65529,7 +65705,7 @@ function resolveDeps2(overrides) {
|
|
|
65529
65705
|
existsSync: existsSync31,
|
|
65530
65706
|
parseYAML: load,
|
|
65531
65707
|
readFileSync: readFileSync35,
|
|
65532
|
-
renameSync:
|
|
65708
|
+
renameSync: renameSync8,
|
|
65533
65709
|
unlinkSync: unlinkSync10,
|
|
65534
65710
|
writeFileSync: writeFileSync13,
|
|
65535
65711
|
writeCoordinateArtifact,
|
|
@@ -66427,7 +66603,7 @@ var init_persistence = __esm(() => {
|
|
|
66427
66603
|
});
|
|
66428
66604
|
|
|
66429
66605
|
// ../cli/dist/commands/sync/staging.js
|
|
66430
|
-
import { existsSync as existsSync33, mkdirSync as mkdirSync11, renameSync as
|
|
66606
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync11, renameSync as renameSync9, rmSync as rmSync4, writeFileSync as writeFileSync14 } from "node:fs";
|
|
66431
66607
|
import { copyFileSync as copyFileSync2 } from "node:fs";
|
|
66432
66608
|
import * as path63 from "node:path";
|
|
66433
66609
|
function resolveDeps3(overrides) {
|
|
@@ -66447,7 +66623,7 @@ function resolveDeps3(overrides) {
|
|
|
66447
66623
|
},
|
|
66448
66624
|
mkdirSync: mkdirSync11,
|
|
66449
66625
|
moduleDir: import.meta.dirname,
|
|
66450
|
-
renameSync:
|
|
66626
|
+
renameSync: renameSync9,
|
|
66451
66627
|
rmSync: rmSync4,
|
|
66452
66628
|
writeFileSync: writeFileSync14,
|
|
66453
66629
|
...overrides
|
|
@@ -66635,7 +66811,7 @@ var init_staging = __esm(() => {
|
|
|
66635
66811
|
// ../cli/dist/commands/sync.js
|
|
66636
66812
|
import { execSync as execSync4 } from "node:child_process";
|
|
66637
66813
|
import { createHash as createHash31 } from "node:crypto";
|
|
66638
|
-
import { existsSync as existsSync34, mkdirSync as mkdirSync12, readFileSync as readFileSync37, readdirSync as readdirSync9, renameSync as
|
|
66814
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync12, readFileSync as readFileSync37, readdirSync as readdirSync9, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "node:fs";
|
|
66639
66815
|
import * as path64 from "node:path";
|
|
66640
66816
|
function relationshipKey2(relationship) {
|
|
66641
66817
|
return `${relationship.type}\x00${relationship.from}\x00${relationship.to}`;
|
|
@@ -67449,11 +67625,11 @@ async function syncCommand(options2 = {}, runtime = {}) {
|
|
|
67449
67625
|
throw new SyncError(`Recovery backup path already exists: ${recoveryBackupPath}`);
|
|
67450
67626
|
}
|
|
67451
67627
|
mkdirSync12(path64.dirname(recoveryBackupPath), { recursive: true });
|
|
67452
|
-
|
|
67628
|
+
renameSync10(livePath, recoveryBackupPath);
|
|
67453
67629
|
try {
|
|
67454
|
-
|
|
67630
|
+
renameSync10(stagingPath, livePath);
|
|
67455
67631
|
} catch (error) {
|
|
67456
|
-
|
|
67632
|
+
renameSync10(recoveryBackupPath, livePath);
|
|
67457
67633
|
throw error;
|
|
67458
67634
|
}
|
|
67459
67635
|
} else if (rebuild && journaledLive) {
|
|
@@ -68686,7 +68862,7 @@ ${KIBI_BIN_RESOLVER}
|
|
|
68686
68862
|
|
|
68687
68863
|
// ../cli/dist/commands/legacy-storage-migration.js
|
|
68688
68864
|
import { createHash as createHash34 } from "node:crypto";
|
|
68689
|
-
import { existsSync as existsSync37, mkdirSync as mkdirSync15, readFileSync as readFileSync40, readdirSync as readdirSync11, renameSync as
|
|
68865
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync15, readFileSync as readFileSync40, readdirSync as readdirSync11, renameSync as renameSync12, rmSync as rmSync5, statSync as statSync12, writeFileSync as writeFileSync18 } from "node:fs";
|
|
68690
68866
|
import * as path70 from "node:path";
|
|
68691
68867
|
function stripGlobToRoot(pattern) {
|
|
68692
68868
|
const normalized = pattern.replaceAll("\\", "/").replace(/\/+$/, "");
|
|
@@ -68801,7 +68977,7 @@ function applyLegacyStorageMigration(cwd, plan) {
|
|
|
68801
68977
|
continue;
|
|
68802
68978
|
}
|
|
68803
68979
|
mkdirSync15(path70.dirname(destinationAbsolute), { recursive: true });
|
|
68804
|
-
|
|
68980
|
+
renameSync12(sourceAbsolute, destinationAbsolute);
|
|
68805
68981
|
movedFiles.push(move.to);
|
|
68806
68982
|
}
|
|
68807
68983
|
const existing = readKbManifest(cwd);
|
|
@@ -72941,7 +73117,7 @@ var init_cli_json_command = __esm(() => {
|
|
|
72941
73117
|
});
|
|
72942
73118
|
|
|
72943
73119
|
// ../cli/dist/commands/migrate.js
|
|
72944
|
-
import { existsSync as existsSync40, mkdirSync as mkdirSync16, readFileSync as readFileSync44, renameSync as
|
|
73120
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync16, readFileSync as readFileSync44, renameSync as renameSync13, writeFileSync as writeFileSync19 } from "node:fs";
|
|
72945
73121
|
import * as path76 from "node:path";
|
|
72946
73122
|
function migrationStepsFor(fromVersion) {
|
|
72947
73123
|
const start = Math.max(0, fromVersion ?? 0);
|
|
@@ -72982,7 +73158,7 @@ function writeTextAtomically(filePath, content) {
|
|
|
72982
73158
|
mkdirSync16(path76.dirname(filePath), { recursive: true });
|
|
72983
73159
|
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
72984
73160
|
writeFileSync19(tempPath, content, "utf8");
|
|
72985
|
-
|
|
73161
|
+
renameSync13(tempPath, filePath);
|
|
72986
73162
|
}
|
|
72987
73163
|
function formatSchemaVersion(rawSchemaVersion, normalized) {
|
|
72988
73164
|
if (normalized === null) {
|
|
@@ -74929,7 +75105,7 @@ async function executeSourceRecovery(args, context, onCommitted = () => {
|
|
|
74929
75105
|
}
|
|
74930
75106
|
async function executeBootstrapPlan(args, context, recovery = false, remainingActions, priorResults = [], onCommitted = () => {
|
|
74931
75107
|
return;
|
|
74932
|
-
}) {
|
|
75108
|
+
}, carry = {}) {
|
|
74933
75109
|
const actions = recovery ? [...remainingActions ?? args.plan.actions] : validateBootstrapPlanShape(args);
|
|
74934
75110
|
preflightBootstrapActions(actions, context);
|
|
74935
75111
|
const prolog = context.prolog ?? await context.ensureProlog?.();
|
|
@@ -74990,10 +75166,31 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
|
|
|
74990
75166
|
if (!context.fs)
|
|
74991
75167
|
return;
|
|
74992
75168
|
await context.fs.mkdir(path78.dirname(journalPath));
|
|
74993
|
-
await context.fs.writeFile(journalPath, `${JSON.stringify({
|
|
75169
|
+
await context.fs.writeFile(journalPath, `${JSON.stringify({
|
|
75170
|
+
version: 2,
|
|
75171
|
+
kind: "bootstrap",
|
|
75172
|
+
plan: args.plan,
|
|
75173
|
+
state,
|
|
75174
|
+
checkpoint: currentCheckpoint,
|
|
75175
|
+
...activeActionId ? { activeActionId } : {},
|
|
75176
|
+
results,
|
|
75177
|
+
...carry.lockReclaims?.length ? { lockReclaims: carry.lockReclaims } : {},
|
|
75178
|
+
...carry.interruptedActions?.length ? { interruptedActions: carry.interruptedActions } : {}
|
|
75179
|
+
}, null, 2)}
|
|
74994
75180
|
`);
|
|
74995
75181
|
};
|
|
74996
75182
|
await writeJournal("applying", undefined, initialCheckpoint);
|
|
75183
|
+
const totalActions = priorResults.length + actions.length;
|
|
75184
|
+
const reportProgress = (message) => {
|
|
75185
|
+
try {
|
|
75186
|
+
context.onProgress?.({
|
|
75187
|
+
progress: results.filter((row) => row.outcome === "applied").length,
|
|
75188
|
+
total: totalActions,
|
|
75189
|
+
message
|
|
75190
|
+
});
|
|
75191
|
+
} catch {}
|
|
75192
|
+
};
|
|
75193
|
+
reportProgress(`Bootstrap plan ${args.plan.planHash.slice(0, 12)}: applying ${actions.length} of ${totalActions} action(s).`);
|
|
74997
75194
|
try {
|
|
74998
75195
|
for (const action of actions) {
|
|
74999
75196
|
try {
|
|
@@ -75044,6 +75241,7 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
|
|
|
75044
75241
|
stepsApplied: results.filter((row) => row.outcome === "applied").length,
|
|
75045
75242
|
sourceHashesChecked: Object.keys(args.plan.expected.sourceHashes).length,
|
|
75046
75243
|
notes: [
|
|
75244
|
+
...carry.notes ?? [],
|
|
75047
75245
|
"Bootstrap stopped after an authoritative action committed with derived repair effects."
|
|
75048
75246
|
]
|
|
75049
75247
|
},
|
|
@@ -75070,6 +75268,7 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
|
|
|
75070
75268
|
lastCheckpoint = await checkpoint();
|
|
75071
75269
|
activeActionId = undefined;
|
|
75072
75270
|
await writeJournal("applying", undefined, lastCheckpoint);
|
|
75271
|
+
reportProgress(`Applied ${action.id}.`);
|
|
75073
75272
|
} catch (error) {
|
|
75074
75273
|
const detail = error instanceof Error ? error.message : String(error);
|
|
75075
75274
|
const deterministic = isDeterministicBootstrapFailure(error);
|
|
@@ -75102,6 +75301,7 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
|
|
|
75102
75301
|
stepsApplied: results.filter((row) => row.outcome === "applied").length,
|
|
75103
75302
|
sourceHashesChecked: Object.keys(args.plan.expected.sourceHashes).length,
|
|
75104
75303
|
notes: [
|
|
75304
|
+
...carry.notes ?? [],
|
|
75105
75305
|
deterministic ? "Bootstrap stopped at a deterministic action failure; the journal is terminal. Re-plan from the current state." : "Bootstrap application stopped at a repairable action failure."
|
|
75106
75306
|
]
|
|
75107
75307
|
},
|
|
@@ -75149,6 +75349,7 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
|
|
|
75149
75349
|
stepsApplied: results.filter((row) => row.outcome === "applied").length,
|
|
75150
75350
|
sourceHashesChecked: Object.keys(args.plan.expected.sourceHashes).length,
|
|
75151
75351
|
notes: [
|
|
75352
|
+
...carry.notes ?? [],
|
|
75152
75353
|
"Bootstrap application stopped before its full action graph completed."
|
|
75153
75354
|
]
|
|
75154
75355
|
},
|
|
@@ -75194,6 +75395,7 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
|
|
|
75194
75395
|
stepsApplied: results.filter((row) => row.outcome === "applied").length,
|
|
75195
75396
|
sourceHashesChecked: Object.keys(args.plan.expected.sourceHashes).length,
|
|
75196
75397
|
notes: [
|
|
75398
|
+
...carry.notes ?? [],
|
|
75197
75399
|
"Bootstrap actions applied sequentially through the shared plan executor."
|
|
75198
75400
|
]
|
|
75199
75401
|
}
|
|
@@ -75214,6 +75416,16 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => {
|
|
|
75214
75416
|
}
|
|
75215
75417
|
return withRecoveryReport(result, recovered);
|
|
75216
75418
|
}
|
|
75419
|
+
function interruptedBootstrapAction(journal, actionIds) {
|
|
75420
|
+
const active = journal.activeActionId;
|
|
75421
|
+
if (journal.state !== "applying" || typeof active !== "string")
|
|
75422
|
+
return;
|
|
75423
|
+
if (!actionIds.has(active))
|
|
75424
|
+
return;
|
|
75425
|
+
if ((journal.results ?? []).some((row) => row.actionId === active))
|
|
75426
|
+
return;
|
|
75427
|
+
return active;
|
|
75428
|
+
}
|
|
75217
75429
|
async function dispatchApplyPlan(args, context, onCommitted, recovered) {
|
|
75218
75430
|
if ("recoveryJournalId" in args) {
|
|
75219
75431
|
if (isPlanApplyJournalId(args.recoveryJournalId))
|
|
@@ -75248,9 +75460,30 @@ async function dispatchApplyPlan(args, context, onCommitted, recovered) {
|
|
|
75248
75460
|
}) : recoveryStatus?.snapshotId ?? "unavailable",
|
|
75249
75461
|
workspaceSnapshot: recoveryWorkspace.available ? recoveryWorkspace.snapshot.hash : "unavailable"
|
|
75250
75462
|
};
|
|
75251
|
-
if (liveCheckpoint.branch !== journal.checkpoint.branch || liveCheckpoint.kbSnapshotId !== journal.checkpoint.kbSnapshotId || liveCheckpoint.workspaceSnapshot !== journal.checkpoint.workspaceSnapshot)
|
|
75252
|
-
throw new Error("Bootstrap recovery refused: repository state changed since the last action checkpoint");
|
|
75253
75463
|
const actionIds = new Set(ordered.map((action) => action.id));
|
|
75464
|
+
const drifted = liveCheckpoint.branch !== journal.checkpoint.branch || liveCheckpoint.kbSnapshotId !== journal.checkpoint.kbSnapshotId || liveCheckpoint.workspaceSnapshot !== journal.checkpoint.workspaceSnapshot;
|
|
75465
|
+
const interruptedActionId = drifted ? interruptedBootstrapAction(journal, actionIds) : undefined;
|
|
75466
|
+
if (drifted && (interruptedActionId === undefined || liveCheckpoint.branch !== journal.checkpoint.branch))
|
|
75467
|
+
throw new Error("Bootstrap recovery refused: repository state changed since the last action checkpoint");
|
|
75468
|
+
const recordsOf = (value) => Array.isArray(value) ? value.filter((row) => row !== null && typeof row === "object" && !Array.isArray(row)) : [];
|
|
75469
|
+
const interruptedActions = [
|
|
75470
|
+
...recordsOf(journal.interruptedActions),
|
|
75471
|
+
...interruptedActionId === undefined ? [] : [
|
|
75472
|
+
{
|
|
75473
|
+
actionId: interruptedActionId,
|
|
75474
|
+
recoveredAt: context.clock().toISOString(),
|
|
75475
|
+
checkpoint: journal.checkpoint,
|
|
75476
|
+
liveCheckpoint
|
|
75477
|
+
}
|
|
75478
|
+
]
|
|
75479
|
+
];
|
|
75480
|
+
const carry = {
|
|
75481
|
+
lockReclaims: recordsOf(journal.lockReclaims),
|
|
75482
|
+
interruptedActions,
|
|
75483
|
+
notes: interruptedActionId === undefined ? [] : [
|
|
75484
|
+
`Action ${interruptedActionId} was interrupted before its checkpoint; the state drift since the last checkpoint is attributed to it and the action was re-applied idempotently.`
|
|
75485
|
+
]
|
|
75486
|
+
};
|
|
75254
75487
|
const applied = new Set;
|
|
75255
75488
|
for (const row of journal.results ?? []) {
|
|
75256
75489
|
if (typeof row.actionId !== "string" || !actionIds.has(row.actionId))
|
|
@@ -75266,7 +75499,7 @@ async function dispatchApplyPlan(args, context, onCommitted, recovered) {
|
|
|
75266
75499
|
outcome: row.outcome,
|
|
75267
75500
|
detail: row.detail
|
|
75268
75501
|
}
|
|
75269
|
-
] : [] : []), onCommitted);
|
|
75502
|
+
] : [] : []), onCommitted, carry);
|
|
75270
75503
|
}
|
|
75271
75504
|
return executeSourceRecovery(args, context, onCommitted);
|
|
75272
75505
|
}
|
|
@@ -75903,12 +76136,55 @@ function withRecoveryReport(result, recovered) {
|
|
|
75903
76136
|
}
|
|
75904
76137
|
return { content, structuredContent: result.structuredContent };
|
|
75905
76138
|
}
|
|
76139
|
+
async function applyingBootstrapJournal(args, context) {
|
|
76140
|
+
if (!context.fs || !("recoveryJournalId" in args))
|
|
76141
|
+
return;
|
|
76142
|
+
if (!/^bootstrap-[a-f0-9]{16}$/.test(args.recoveryJournalId))
|
|
76143
|
+
return;
|
|
76144
|
+
const journalPath = path78.join(context.workspaceRoot, ".kb", "recovery", `${args.recoveryJournalId}.json`);
|
|
76145
|
+
try {
|
|
76146
|
+
const journal = JSON.parse(await context.fs.readFile(journalPath));
|
|
76147
|
+
return journal.kind === "bootstrap" && journal.state === "applying" ? journalPath : undefined;
|
|
76148
|
+
} catch {
|
|
76149
|
+
return;
|
|
76150
|
+
}
|
|
76151
|
+
}
|
|
76152
|
+
async function recordBootstrapLockReclaims(context, journalPath, reclaimed) {
|
|
76153
|
+
if (!context.fs)
|
|
76154
|
+
return;
|
|
76155
|
+
const journal = JSON.parse(await context.fs.readFile(journalPath));
|
|
76156
|
+
const earlier = Array.isArray(journal.lockReclaims) ? journal.lockReclaims : [];
|
|
76157
|
+
await context.fs.writeFile(journalPath, `${JSON.stringify({
|
|
76158
|
+
...journal,
|
|
76159
|
+
lockReclaims: [
|
|
76160
|
+
...earlier,
|
|
76161
|
+
...reclaimed.map((row) => ({
|
|
76162
|
+
holderPid: row.pid,
|
|
76163
|
+
holderAcquiredAt: new Date(row.acquiredAt).toISOString(),
|
|
76164
|
+
reclaimedAt: new Date(row.reclaimedAt).toISOString(),
|
|
76165
|
+
reclaimedByPid: row.reclaimedByPid
|
|
76166
|
+
}))
|
|
76167
|
+
]
|
|
76168
|
+
}, null, 2)}
|
|
76169
|
+
`);
|
|
76170
|
+
}
|
|
75906
76171
|
async function executeApplyPlan(args, context) {
|
|
75907
76172
|
if (isBootstrapApplyArgs(args))
|
|
75908
76173
|
preflightBootstrapActions(validateBootstrapPlanShape(args), context);
|
|
75909
76174
|
if (!context.fs || context.sourceMutationLockHeld === true)
|
|
75910
76175
|
return executeApplyPlanUnlocked(args, context);
|
|
75911
|
-
const
|
|
76176
|
+
const applyingJournal = await applyingBootstrapJournal(args, context);
|
|
76177
|
+
const lock = await acquireWorkspaceMutationLock(context.workspaceRoot, applyingJournal === undefined ? {} : {
|
|
76178
|
+
reclaimDeadHolder: () => true
|
|
76179
|
+
});
|
|
76180
|
+
if (applyingJournal !== undefined && lock.reclaimed !== undefined) {
|
|
76181
|
+
try {
|
|
76182
|
+
await recordBootstrapLockReclaims(context, applyingJournal, lock.reclaimed);
|
|
76183
|
+
} catch (error) {
|
|
76184
|
+
releaseWorkspaceMutationLock(lock, { error });
|
|
76185
|
+
throw error;
|
|
76186
|
+
}
|
|
76187
|
+
}
|
|
75912
76188
|
let operationFailure;
|
|
75913
76189
|
let committed = false;
|
|
75914
76190
|
try {
|
|
@@ -76380,7 +76656,12 @@ var init_planning = __esm(() => {
|
|
|
76380
76656
|
recoveryJournalId: {
|
|
76381
76657
|
type: "string",
|
|
76382
76658
|
pattern: "^[A-Za-z0-9._-]+$",
|
|
76383
|
-
description: "Typed recovery journal identifier returned by a committed_with_repairs result or a PLAN_APPLY_RECOVERY_REQUIRED error. Recovery replays only the immutable journal and never the original plan request; a compile-plan journal (plan-apply-*) is completed or rolled back deterministically and reports which."
|
|
76659
|
+
description: "Typed recovery journal identifier returned by a committed_with_repairs result or a PLAN_APPLY_RECOVERY_REQUIRED error. Recovery replays only the immutable journal and never the original plan request; a compile-plan journal (plan-apply-*) is completed or rolled back deterministically and reports which. A bootstrap journal left `applying` by a process that died mid-action is resumed without edits: the interrupted action is re-applied and a source lock held by the dead process is reclaimed and recorded in the journal."
|
|
76660
|
+
},
|
|
76661
|
+
async: {
|
|
76662
|
+
type: "boolean",
|
|
76663
|
+
default: false,
|
|
76664
|
+
description: "MCP only. When true, start the apply as a background job and return a kibi.job.v1 receipt immediately instead of holding the request open; poll kb_job_status with the returned jobId for the final result. Use for large bootstrap plans (hundreds of actions) that can outlast a client's request timeout. The MCP server registers kb_job_status only when KIBI_MCP_OPTIONAL_TOOLS includes it; otherwise async applies synchronously. A synchronous apply sends notifications/progress after each bootstrap action when the request carries a progressToken. The CLI ignores this field."
|
|
76384
76665
|
}
|
|
76385
76666
|
}
|
|
76386
76667
|
},
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
id: kibi-bootstrap
|
|
3
3
|
name: kibi-bootstrap
|
|
4
4
|
description: "Use for any Kibi bootstrap or onboarding task: seeding a new or thin knowledge base; reviewing a bootstrap or onboarding plan, preview, or evidence; judging approval readiness or coverage; diagnosing a blocked, partial, or failed bootstrap or its repair; applying an approved bootstrap plan; or asking the human which knowledge sources hold intent. Covers kb_status first, source interview, cited intent claims, read-only kb_plan_bootstrap preview, explicit approval, exact-plan kb_apply_plan, and repair-safe close-out."
|
|
5
|
-
version: 3.
|
|
5
|
+
version: 3.5.0
|
|
6
6
|
kibiCompatibility: ">=1.0.0"
|
|
7
7
|
tags:
|
|
8
8
|
- kibi
|
|
@@ -38,13 +38,13 @@ Seed branch-local Kibi knowledge from cited product intent, without creating a p
|
|
|
38
38
|
5. **Narrow with filters, not caps.** Read the `tldr` and the `Suppressed candidates by reason` diagnostic before individual rows. If the output is too large or mostly tooling or metadata, re-preview with `entityTypes` or declared `sourceOfTruthPaths`. Generic Markdown is off by default once you declare intent claims; set `includeGenericMarkdown: true` only for repository docs the human named. Keep confirmed intent claims: they are never capped and never use `maxCandidates`, which is the separate budget for candidates Kibi discovers (symbols, tests, repository docs). Raise it only when discovered candidates you need are `over_limit`. Read the per-source `Knowledge source …` diagnostics; if a source has claims not planned, report them per source and restate them before approval, or carry them to step 11 with their citation.
|
|
39
39
|
6. **Request approval.** Show the complete `structuredContent.plan`, the full hash (never abbreviated), which candidates cite which sources, what was omitted, the open questions and declared conflicts it records for the human to decide, and any claim that contradicts code. If you find an undeclared contradiction now, add it to `conflicts` and preview again rather than resolving it. With no answer, stop as "awaiting approval." If declined, replan and ask again with the new hash.
|
|
40
40
|
7. **Pre-apply check.** The plan you send must equal the returned plan: every top-level field, including `suppressedCandidates`, `diagnostics`, `candidates`, `actions`, and `expected`, with every array the same length and order. Never rebuild it from preview fields. If the host cannot pass the exact object, stop and say so.
|
|
41
|
-
8. **Apply once** with `kb_apply_plan`, the exact plan, and `approvedPlanHash`. The operation owns dependency ordering, source-first writes, sequential mutation, and recovery journaling.
|
|
41
|
+
8. **Apply once** with `kb_apply_plan`, the exact plan, and `approvedPlanHash`. The operation owns dependency ordering, source-first writes, sequential mutation, and recovery journaling. A plan with hundreds of actions can run for minutes: pass a `progressToken` (Kibi sends progress after each action) with a client that resets its timeout on progress, or pass `async: true` and poll `kb_job_status` when the host has it. If the apply is cut off, call `kb_apply_plan` with only the `recoveryJournalId` from `kb_status` or the error; it resumes an action interrupted mid-write and reclaims a lock left by a dead process. Never edit or delete `.kb/recovery` files; if recovery is refused, stop and report it.
|
|
42
42
|
9. **Read back** with `kb_query` and `kb_search` to confirm writes and citations.
|
|
43
43
|
10. **Close out** with `kb_check`, then `kb_status`.
|
|
44
44
|
11. **Deepen.** Bootstrap is complete; the KB is now half-way, holding cited `req` entries and review facts but no scenarios, tests or predicates. Load `kibi-usage` and continue in the normal Kibi workflow, one area at a time, telling the human what you author:
|
|
45
45
|
- **Unplanned claims.** For every claim suppressed as `invalid_write` and every `sourceOnlySignals` entry ("Author requirement …"), restate the claim's statement and citation (`sourceId:reference`), model it with `kb_model` (`mode: "requirement"`, or `mode: "predicates"` for domain claims), and write the result with `kb_upsert` (`dryRun: true` first).
|
|
46
|
-
- **Scenarios.** Write scenarios from the cited ticket's acceptance criteria, one scenario per criterion, not bulk drafts from the requirement title; ask the human when the source states none. Give each a plain title, `expects`, and `assumes` links to the `property_value` facts whose values the scenario relies on, then link the requirement with `specified_by` (upsert the `req` with its stored `title`, a `status` and that relationship only). Example: criterion "Given an annotation in draft state, when the user deletes it, the draft is cleared" becomes `{"type":"scenario","id":"SCEN-annotation-delete-clears-draft","properties":{"title":"Deleting a draft annotation clears the draft","status":"active","expects":"success"},"relationships":[{"type":"assumes","from":"SCEN-annotation-delete-clears-draft","to":"FACT-annotation-state-draft"}]}`, where the assumed fact is a `property_value` (`annotation.state` `eq` `draft`). Link existing tests that exercise it; never invent test evidence.
|
|
47
|
-
- **Predicates.** Run `kb_model` with `mode: "predicates"` on each persisted requirement and apply a returned plan with `requires_predicate
|
|
46
|
+
- **Scenarios.** Write scenarios from the cited ticket's acceptance criteria, one scenario per criterion, not bulk drafts from the requirement title; ask the human when the source states none. Give each a plain title, `expects`, the scenario prose in `document.body` (never in `properties`), and `assumes` links to the `property_value` facts whose values the scenario relies on, then link the requirement with `specified_by` (upsert the `req` with its stored `title`, a `status` and that relationship only). Example: criterion "Given an annotation in draft state, when the user deletes it, the draft is cleared" becomes `{"type":"scenario","id":"SCEN-annotation-delete-clears-draft","properties":{"title":"Deleting a draft annotation clears the draft","status":"active","expects":"success"},"document":{"body":"Given an annotation in draft state, when the user deletes it, then the draft is cleared and nothing is published.\n\nThe acceptance criterion comes from the cited ticket; reviewers asked for it so a deleted draft never reappears after reload.\n"},"relationships":[{"type":"assumes","from":"SCEN-annotation-delete-clears-draft","to":"FACT-annotation-state-draft"}]}`, where the assumed fact is a `property_value` (`annotation.state` `eq` `draft`). Link existing tests that exercise it; never invent test evidence.
|
|
47
|
+
- **Predicates.** Run `kb_model` with `mode: "predicates"` and `requirementId` on each persisted requirement and apply a returned plan with `requires_predicate` to its `relationshipTarget` (the planned `FACT-PRED-…` id, never a `SUGGEST-…` candidate id), or record the `review:ontology-gap` observation it returns, with its prose in `document.body`. `already_grounded` means the bootstrap already grounded that claim; leave it unless the human wants the `replacementPlan` swap. Count what `kb_query` shows afterwards, not the calls you made.
|
|
48
48
|
- **Unrecorded ambiguity.** Record any conflict or open question you found but did not declare in step 3 as a cited `fact_kind: observation` tagged `review:conflict` or `review:open-question`.
|
|
49
49
|
Finish with `kb_check` and `kb_status`.
|
|
50
50
|
12. **Report** calls made, result statuses, applied scope, what step 11 authored, gaps, and follow-ups. Classify as complete, partial, awaiting approval, or blocked.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
id: kibi-usage
|
|
3
3
|
name: Kibi Usage
|
|
4
4
|
description: Use Kibi's source-first, exact-Git, migration-aware, proof-aware operations safely across MCP or the trusted local CLI, including partial completion repair.
|
|
5
|
-
version: 2.
|
|
5
|
+
version: 2.5.0
|
|
6
6
|
kibiCompatibility: ">=1.0.0"
|
|
7
7
|
tags:
|
|
8
8
|
- kibi
|
|
@@ -263,10 +263,15 @@ prose. Bind them from the claim's own words and call again with the same
|
|
|
263
263
|
```
|
|
264
264
|
|
|
265
265
|
When the retry returns `apply_requires_predicate`, write its `applyPlan` fact,
|
|
266
|
-
then the `relationshipPlan` `requires_predicate` row
|
|
266
|
+
then the `relationshipPlan` `requires_predicate` row; its target is
|
|
267
|
+
`relationshipTarget` (the planned `FACT-PRED-…` id), never a candidate's
|
|
268
|
+
`SUGGEST-…` id. `already_grounded` means the requirement already grounds the
|
|
269
|
+
claim (for example a bootstrap `requires_property` fact): a second grounding
|
|
270
|
+
link would fail, so keep it or follow `replacementPlan`. Record an ontology gap
|
|
267
271
|
only when no returned schema fits the claim, not because bindings were missing.
|
|
268
|
-
A review note is an observation that quotes its claim without `claim_key
|
|
269
|
-
|
|
272
|
+
A review note is an observation that quotes its claim without `claim_key`, with
|
|
273
|
+
its prose in `document.body`:
|
|
274
|
+
`{"type":"fact","id":"FACT-review-<area>-<behavior>","properties":{"title":"Review: ...","status":"active","fact_kind":"observation","text_ref":"<sourceId>:<reference>","tags":["review:invalid-write"],"claim_text":"<the claim>"},"document":{"body":"<why it needs review, who raised it, the source>"}}`.
|
|
270
275
|
|
|
271
276
|
To add `specified_by` (or another relationship) to an existing requirement,
|
|
272
277
|
upsert it with its stored `title`, a `status` and the relationship only; Kibi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-runtime",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "Narrow first-party runtime contracts and adapters for Kibi operations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"gray-matter": "^4.0.3",
|
|
21
|
-
"kibi-core": "^0.
|
|
21
|
+
"kibi-core": "^0.17.0",
|
|
22
22
|
"kibi-plugin-builtin": "^0.4.2",
|
|
23
23
|
"kibi-swipl": "^1.0.1"
|
|
24
24
|
},
|