kibi-cli 2.6.1 → 2.9.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.
Files changed (38) hide show
  1. package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
  2. package/dist/operations/modeling/predicate-applyplan.js +1 -1
  3. package/dist/operations/modeling/predicate-types.d.ts +16 -1
  4. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  5. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  6. package/dist/operations/modeling/suggest-predicates.js +136 -23
  7. package/dist/operations/mutation/validation.d.ts.map +1 -1
  8. package/dist/operations/mutation/validation.js +30 -0
  9. package/dist/operations/mutation/workspace-mutation-lock.d.ts +22 -0
  10. package/dist/operations/mutation/workspace-mutation-lock.d.ts.map +1 -1
  11. package/dist/operations/mutation/workspace-mutation-lock.js +67 -1
  12. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  13. package/dist/operations/planning/apply-plan.js +149 -7
  14. package/dist/public/operations/catalog.d.ts +7 -2
  15. package/dist/public/operations/catalog.d.ts.map +1 -1
  16. package/dist/public/operations/contracts.d.ts.map +1 -1
  17. package/dist/public/operations/contracts.js +36 -22
  18. package/dist/public/operations/runtime-types.d.ts +12 -0
  19. package/dist/public/operations/runtime-types.d.ts.map +1 -1
  20. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  21. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  22. package/dist/public/operations/specs/check-rules.generated.js +2 -1
  23. package/dist/public/operations/specs/check.d.ts +1 -1
  24. package/dist/public/operations/specs/planning.d.ts +6 -1
  25. package/dist/public/operations/specs/planning.d.ts.map +1 -1
  26. package/dist/public/operations/specs/planning.js +6 -1
  27. package/dist/public/skills/kibi-bootstrap/SKILL.md +4 -4
  28. package/dist/public/skills/kibi-usage/SKILL.md +9 -4
  29. package/dist/utils/rule-registry.generated.d.ts +5 -0
  30. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  31. package/dist/utils/rule-registry.generated.js +6 -0
  32. package/package.json +2 -2
  33. package/src/public/operations/contracts.ts +38 -22
  34. package/src/public/operations/runtime-types.ts +14 -0
  35. package/src/public/operations/specs/check-rules.generated.ts +2 -1
  36. package/src/public/operations/specs/planning.ts +7 -1
  37. package/src/public/skills/kibi-bootstrap/SKILL.md +4 -4
  38. package/src/public/skills/kibi-usage/SKILL.md +9 -4
@@ -1 +1 @@
1
- {"version":3,"file":"predicate-applyplan.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/predicate-applyplan.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAEV,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAQ9B,wBAAgB,eAAe,CAC7B,MAAM,EAAE,wBAAwB,EAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,gBAAgB,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EACvD,YAAY,CAAC,EAAE,QAAQ,GAAG,MAAM,EAChC,WAAW,CAAC,EAAE;IACZ,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACtC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GACA,mBAAmB,CA8FrB;AAGD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,mBAAmB,EAC/B,IAAI,EAAE,qBAAqB,GAC1B,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAqChC;AAGD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,SAAS,CAAC,EAAE,MAAM,EAClB,mBAAmB,GAAE,SAAS,MAAM,EAAO,GAC1C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAwBhC;AAGD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,qBAAqB,GAC1B,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA+BhC;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,0BAA0B,CAoE5B"}
1
+ {"version":3,"file":"predicate-applyplan.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/predicate-applyplan.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAEV,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAQ9B,wBAAgB,eAAe,CAC7B,MAAM,EAAE,wBAAwB,EAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,gBAAgB,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EACvD,YAAY,CAAC,EAAE,QAAQ,GAAG,MAAM,EAChC,WAAW,CAAC,EAAE;IACZ,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACtC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GACA,mBAAmB,CA8FrB;AAGD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,mBAAmB,EAC/B,IAAI,EAAE,qBAAqB,GAC1B,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAqChC;AAGD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,SAAS,CAAC,EAAE,MAAM,EAClB,mBAAmB,GAAE,SAAS,MAAM,EAAO,GAC1C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAuBhC;AAGD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,qBAAqB,GAC1B,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA+BhC;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,0BAA0B,CAoE5B"}
@@ -133,7 +133,7 @@ export function buildRelationshipPlan(factId, requirementId, claimText, existing
133
133
  logicClaims,
134
134
  }
135
135
  : {}),
136
- instructions: "Apply the predicate fact first, update the requirement with the returned merged logicClaims manifest, then attach this relationship without overwriting other requirement metadata.",
136
+ 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-...).`,
137
137
  };
138
138
  }
139
139
  // implements REQ-mcp-suggest-predicates
@@ -86,6 +86,17 @@ export interface RecommendedPredicateSchema {
86
86
  rationale: string;
87
87
  reuse_scope: string;
88
88
  }
89
+ /** A logical grounding relationship an existing requirement has for a claim. */
90
+ export interface ExistingClaimGrounding {
91
+ relationship: {
92
+ type: string;
93
+ from: string;
94
+ to: string;
95
+ };
96
+ factId: string;
97
+ factKind: string | null;
98
+ claimKey: string;
99
+ }
89
100
  export interface SuggestPredicatesResult {
90
101
  content: Array<{
91
102
  type: "text";
@@ -99,10 +110,14 @@ export interface SuggestPredicatesResult {
99
110
  requirementId: string | null;
100
111
  subject: string;
101
112
  candidates: PredicateSuggestion[];
102
- recommendedAction: "apply_requires_predicate" | "provide_argument_bindings" | "resolve_schema_reference" | "record_ontology_gap" | "review_nonlogical";
113
+ recommendedAction: "apply_requires_predicate" | "provide_argument_bindings" | "resolve_schema_reference" | "record_ontology_gap" | "review_nonlogical" | "already_grounded";
103
114
  recommendedPredicateSchema: RecommendedPredicateSchema | null;
104
115
  applyPlan: Array<Record<string, unknown>>;
105
116
  relationshipPlan: Record<string, unknown> | null;
117
+ /** The planned predicate fact id a requires_predicate link must target (never a candidate id). */
118
+ relationshipTarget?: string | null;
119
+ existingGrounding?: ExistingClaimGrounding[];
120
+ replacementPlan?: Record<string, unknown> | null;
106
121
  warnings: string[];
107
122
  };
108
123
  applyPlan: Array<Record<string, unknown>>;
@@ -1 +1 @@
1
- {"version":3,"file":"predicate-types.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/predicate-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"predicate-types.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/predicate-types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"suggest-predicates.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/suggest-predicates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,0CAA0C,CAAC;AAoBlD,OAAO,KAAK,EAEV,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EACV,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AA6H9B,wBAAgB,sCAAsC,CACpD,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,EACzE,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,GACzE,MAAM,CAIR;AAED,wBAAgB,2CAA2C,CACzD,IAAI,EAAE,IAAI,CACR,mBAAmB,EACnB,qBAAqB,GAAG,OAAO,GAAG,gBAAgB,CACnD,EACD,KAAK,EAAE,IAAI,CACT,mBAAmB,EACnB,qBAAqB,GAAG,OAAO,GAAG,gBAAgB,CACnD,GACA,MAAM,CAKR;AAqCD,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,IAAI,EAAE,qBAAqB,EAC3B,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,uBAAuB,CAAC,CA+LlC;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,uBAAuB,CAAC,CAElC"}
1
+ {"version":3,"file":"suggest-predicates.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/suggest-predicates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,0CAA0C,CAAC;AAqBlD,OAAO,KAAK,EAGV,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EACV,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AA6H9B,wBAAgB,sCAAsC,CACpD,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,EACzE,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,GACzE,MAAM,CAIR;AAED,wBAAgB,2CAA2C,CACzD,IAAI,EAAE,IAAI,CACR,mBAAmB,EACnB,qBAAqB,GAAG,OAAO,GAAG,gBAAgB,CACnD,EACD,KAAK,EAAE,IAAI,CACT,mBAAmB,EACnB,qBAAqB,GAAG,OAAO,GAAG,gBAAgB,CACnD,GACA,MAAM,CAKR;AA0FD,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,IAAI,EAAE,qBAAqB,EAC3B,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,uBAAuB,CAAC,CA2OlC;AA8CD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,uBAAuB,CAAC,CAElC"}
@@ -1,3 +1,5 @@
1
+ import { escapeAtom } from "../../prolog/codec.js";
2
+ import { parsePrologList } from "../mutation/serialization.js";
1
3
  import { analyzeSemanticAdvisorInput } from "../semantic-advisor/analyze-prose.js";
2
4
  import { semanticClaimKey } from "../semantic-advisor/clauses.js";
3
5
  import { composeOntologyCatalogForOperation } from "../semantic-advisor/plugin-orchestration.js";
@@ -143,6 +145,50 @@ function withMarginRejection(candidates) {
143
145
  };
144
146
  });
145
147
  }
148
+ const GROUNDING_RELATIONSHIP_TYPES = [
149
+ "requires_property",
150
+ "requires_predicate",
151
+ "requires_rule",
152
+ ];
153
+ function unquoted(value) {
154
+ return typeof value === "string" ? value.replace(/^['"]|['"]$/g, "") : "";
155
+ }
156
+ /**
157
+ * The logical grounding relationships an existing requirement already has for
158
+ * this claim. A modeled claim takes exactly one grounding relationship, so a
159
+ * second link (for example requires_predicate beside a bootstrap
160
+ * requires_property) fails the proposition-complete rule.
161
+ */
162
+ // implements REQ-model-predicates-grounding-aware
163
+ async function existingClaimGrounding(prolog, requirementId, claimKey, warnings) {
164
+ if (prolog === null || !requirementId)
165
+ return [];
166
+ const found = [];
167
+ try {
168
+ for (const type of GROUNDING_RELATIONSHIP_TYPES) {
169
+ const targets = await prolog.query(`findall(To, kb_relationship(${type}, '${escapeAtom(requirementId)}', To), Targets)`);
170
+ if (!targets.success)
171
+ continue;
172
+ for (const target of parsePrologList(targets.bindings.Targets ?? "[]")) {
173
+ 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 = '')))`);
174
+ if (!fact.success)
175
+ continue;
176
+ if (unquoted(fact.bindings.ClaimKey) !== claimKey)
177
+ continue;
178
+ found.push({
179
+ relationship: { type, from: requirementId, to: target },
180
+ factId: target,
181
+ factKind: unquoted(fact.bindings.FactKind) || null,
182
+ claimKey,
183
+ });
184
+ }
185
+ }
186
+ }
187
+ catch (error) {
188
+ 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.`);
189
+ }
190
+ return found;
191
+ }
146
192
  // implements REQ-mcp-suggest-predicates
147
193
  export async function handleKbSuggestPredicates(prolog, args, context) {
148
194
  const text = normalizeText(args.text);
@@ -235,32 +281,59 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
235
281
  }
236
282
  // Stage 3/4: bind only after eligibility, then decide conservatively.
237
283
  const unavailableSchema = Boolean(args.schemaId && selectedSchemas.length === 0);
238
- const recommendedAction = !recommendedCandidate
239
- ? unavailableSchema
240
- ? "resolve_schema_reference"
241
- : "record_ontology_gap"
242
- : recommendedCandidate.binding_status === "complete"
243
- ? "apply_requires_predicate"
244
- : "provide_argument_bindings";
245
- const applyPlan = recommendedCandidate && recommendedCandidate.binding_status === "complete"
246
- ? buildPredicateApplyPlan(recommendedCandidate, args)
247
- : !recommendedCandidate && !unavailableSchema
248
- ? buildGapApplyPlan(text, args)
249
- : [];
250
- const relationshipPlan = recommendedCandidate && recommendedCandidate.binding_status === "complete"
251
- ? buildRelationshipPlan(String(applyPlan[0]?.id ?? ""), args.requirementId, text, args.existingLogicClaims)
284
+ const claimKey = semanticClaimKey(text);
285
+ const existingGrounding = await existingClaimGrounding(prolog, args.requirementId, claimKey, warnings);
286
+ const alreadyGrounded = existingGrounding.length > 0;
287
+ const completeCandidate = recommendedCandidate && recommendedCandidate.binding_status === "complete"
288
+ ? recommendedCandidate
289
+ : undefined;
290
+ const recommendedAction = alreadyGrounded
291
+ ? "already_grounded"
292
+ : !recommendedCandidate
293
+ ? unavailableSchema
294
+ ? "resolve_schema_reference"
295
+ : "record_ontology_gap"
296
+ : completeCandidate
297
+ ? "apply_requires_predicate"
298
+ : "provide_argument_bindings";
299
+ const predicatePlan = completeCandidate
300
+ ? buildPredicateApplyPlan(completeCandidate, args)
301
+ : [];
302
+ // An already grounded claim gets no write plan: a second grounding link
303
+ // would fail the proposition-complete rule. The replacement plan below
304
+ // swaps the grounding instead.
305
+ const applyPlan = alreadyGrounded
306
+ ? []
307
+ : completeCandidate
308
+ ? predicatePlan
309
+ : !recommendedCandidate && !unavailableSchema
310
+ ? buildGapApplyPlan(text, args)
311
+ : [];
312
+ const plannedFactId = typeof predicatePlan[0]?.id === "string" ? predicatePlan[0].id : null;
313
+ const relationshipPlan = completeCandidate && !alreadyGrounded
314
+ ? buildRelationshipPlan(plannedFactId ?? "", args.requirementId, text, args.existingLogicClaims)
315
+ : null;
316
+ const replacementPlan = alreadyGrounded && plannedFactId !== null && args.requirementId
317
+ ? buildGroundingReplacementPlan(args.requirementId, plannedFactId, predicatePlan, existingGrounding)
318
+ : null;
319
+ const relationshipTarget = relationshipPlan !== null || replacementPlan !== null
320
+ ? plannedFactId
252
321
  : null;
253
- const recommendedPredicateSchema = !recommendedCandidate && !unavailableSchema
322
+ if (alreadyGrounded) {
323
+ 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.`);
324
+ }
325
+ const recommendedPredicateSchema = !recommendedCandidate && !unavailableSchema && !alreadyGrounded
254
326
  ? buildPredicateSchemaDraft(text, subject)
255
327
  : null;
256
- const textSummary = recommendedCandidate && recommendedCandidate.binding_status === "complete"
257
- ? `Suggested ${candidates.length} predicate candidate(s). Top applicable match: ${recommendedCandidate.predicate_name}. Apply structured predicate facts before falling back to prose.`
258
- : recommendedCandidate
259
- ? `Matched ${recommendedCandidate.predicate_name}, but exact reviewed values are still required for: ${recommendedCandidate.unbound_arguments.join(", ")}. No apply plan was generated.`
260
- : unavailableSchema
261
- ? `Requested predicate schema ${args.schemaId} is unavailable or semantically inapplicable. No apply plan was generated.`
262
- : "No predicate candidate passed the semantic applicability gate; record an ontology gap and review the generated schema draft instead of silently writing prose.";
263
- const claimKey = semanticClaimKey(text);
328
+ const textSummary = alreadyGrounded
329
+ ? `${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}.` : ""}`
330
+ : completeCandidate
331
+ ? `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).`
332
+ : recommendedCandidate
333
+ ? `Matched ${recommendedCandidate.predicate_name}, but exact reviewed values are still required for: ${recommendedCandidate.unbound_arguments.join(", ")}. No apply plan was generated.`
334
+ : unavailableSchema
335
+ ? `Requested predicate schema ${args.schemaId} is unavailable or semantically inapplicable. No apply plan was generated.`
336
+ : "No predicate candidate passed the semantic applicability gate; record an ontology gap and review the generated schema draft instead of silently writing prose.";
264
337
  const logicClaims = Array.from(new Set([...(args.existingLogicClaims ?? []), claimKey]));
265
338
  return {
266
339
  content: [{ type: "text", text: textSummary }],
@@ -276,11 +349,51 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
276
349
  recommendedPredicateSchema,
277
350
  applyPlan,
278
351
  relationshipPlan,
352
+ relationshipTarget,
353
+ existingGrounding,
354
+ replacementPlan,
279
355
  warnings,
280
356
  },
281
357
  applyPlan,
282
358
  };
283
359
  }
360
+ /**
361
+ * Ordered steps that swap an existing grounding for the planned predicate
362
+ * fact while keeping one grounding relationship per modeled claim: write the
363
+ * fact, retract the old grounding link, then link requires_predicate.
364
+ */
365
+ // implements REQ-model-predicates-grounding-aware
366
+ function buildGroundingReplacementPlan(requirementId, factId, predicatePlan, existing) {
367
+ return {
368
+ relationshipTarget: factId,
369
+ steps: [
370
+ {
371
+ operation: "kb_upsert",
372
+ input: predicatePlan[0],
373
+ reason: "Write the predicate fact that will ground the claim.",
374
+ },
375
+ {
376
+ operation: "kb_delete",
377
+ input: {
378
+ relationships: existing.map((row) => row.relationship),
379
+ },
380
+ reason: "Retract the existing grounding link so the claim keeps exactly one logical grounding relationship.",
381
+ },
382
+ {
383
+ operation: "kb_upsert",
384
+ input: {
385
+ type: "req",
386
+ id: requirementId,
387
+ relationships: [
388
+ { type: "requires_predicate", from: requirementId, to: factId },
389
+ ],
390
+ },
391
+ reason: `Link requires_predicate to ${factId}; upsert the requirement with its stored title and status and this relationship only.`,
392
+ },
393
+ ],
394
+ 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.`,
395
+ };
396
+ }
284
397
  export async function executeSuggestPredicates(args, context) {
285
398
  return handleKbSuggestPredicates(context.prolog ?? null, args, context);
286
399
  }
@@ -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;AAO5C,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA6F/D,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,IAAI,GACR,eAAe,CA4DjB;AAED,OAAO,EAAE,0BAA0B,EAAE,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;AAO5C,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAqI/D,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,IAAI,GACR,eAAe,CA4DjB;AAED,OAAO,EAAE,0BAA0B,EAAE,CAAC"}
@@ -48,6 +48,23 @@ const ALIASES = new Map([
48
48
  ["canonicalKey", "canonical_key"],
49
49
  ["closedWorld", "closed_world"],
50
50
  ]);
51
+ /** Property names agents use for entity prose that belongs in document.body. */
52
+ const DOCUMENT_TEXT_PROPERTIES = new Set([
53
+ "body",
54
+ "text",
55
+ "description",
56
+ "content",
57
+ "markdown",
58
+ "prose",
59
+ "summary",
60
+ "details",
61
+ ]);
62
+ /** Every top-level entity key the upsert entity schema does not define. */
63
+ function unknownEntityProperties(entity) {
64
+ const known = upsertEntitySchema.properties;
65
+ return Object.keys(entity).filter((key) => !Object.hasOwn(known, key));
66
+ }
67
+ // implements REQ-upsert-unknown-field-guidance
51
68
  function formatEntityErrors(entity, errors) {
52
69
  const messages = errors.map((error) => {
53
70
  const params = error.params;
@@ -61,6 +78,19 @@ function formatEntityErrors(entity, errors) {
61
78
  if (canonical) {
62
79
  return `${path}: unknown property '${property}'. Did you mean '${canonical}'? kb_upsert.properties uses snake_case typed fact fields.`;
63
80
  }
81
+ if (path === "root" && DOCUMENT_TEXT_PROPERTIES.has(property)) {
82
+ const others = unknownEntityProperties(entity).filter((key) => key !== property);
83
+ 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(", ")}.` : ""}`;
84
+ }
85
+ if (path === "root") {
86
+ const unknown = unknownEntityProperties(entity);
87
+ const listed = unknown.length > 0 ? unknown : [property];
88
+ const proseHint = listed.some((key) => DOCUMENT_TEXT_PROPERTIES.has(key))
89
+ ? " Entity prose belongs in kb_upsert.document.body."
90
+ : "";
91
+ 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}`;
92
+ }
93
+ return `${path}: unknown property '${property}' (${error.message}).`;
64
94
  }
65
95
  if (error.keyword === "enum" && params.allowedValues) {
66
96
  return `${path}: ${error.message}. Allowed values: ${params.allowedValues.map(String).join(", ")}`;
@@ -9,6 +9,8 @@ export interface WorkspaceMutationLockFileSystem {
9
9
  }) => void;
10
10
  readonly unlinkSync: (target: string) => void;
11
11
  readonly rmdirSync: (target: string) => void;
12
+ /** Atomic rename; required to reclaim a lock from a dead holder. */
13
+ readonly renameSync?: (from: string, to: string) => void;
12
14
  }
13
15
  export interface WorkspaceMutationLockOptions {
14
16
  readonly lockFileRelative?: string;
@@ -17,6 +19,24 @@ export interface WorkspaceMutationLockOptions {
17
19
  readonly sleep?: (ms: number) => Promise<void>;
18
20
  readonly fileSystem?: WorkspaceMutationLockFileSystem;
19
21
  readonly isProcessAlive?: (pid: number) => boolean;
22
+ /**
23
+ * Decide whether a lock whose holder pid is verifiably not alive may be
24
+ * reclaimed instead of failing closed with operator recovery. Called only
25
+ * for a readable owner record whose pid is dead; a live, unverifiable,
26
+ * corrupt or missing owner always keeps the fail-closed refusal.
27
+ */
28
+ readonly reclaimDeadHolder?: (holder: DeadLockHolder) => boolean;
29
+ }
30
+ /** The owner record of a lock whose holder process is no longer alive. */
31
+ export interface DeadLockHolder {
32
+ readonly pid: number;
33
+ readonly token: string;
34
+ readonly acquiredAt: number;
35
+ }
36
+ /** A dead holder's lock this acquisition reclaimed. */
37
+ export interface WorkspaceMutationLockReclaim extends DeadLockHolder {
38
+ readonly reclaimedAt: number;
39
+ readonly reclaimedByPid: number;
20
40
  }
21
41
  export declare class WorkspaceMutationLockError extends Error {
22
42
  readonly name = "WorkspaceMutationLockError";
@@ -30,6 +50,8 @@ export interface WorkspaceMutationLockHandle {
30
50
  * removes a different owner token.
31
51
  */
32
52
  readonly release: () => void;
53
+ /** Dead holders this acquisition reclaimed the lock from, oldest first. */
54
+ readonly reclaimed?: readonly WorkspaceMutationLockReclaim[];
33
55
  }
34
56
  export declare function releaseWorkspaceMutationLock(handle: WorkspaceMutationLockHandle | undefined, operationFailure?: {
35
57
  readonly error: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-mutation-lock.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/workspace-mutation-lock.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,SAAS,EAAE,CAClB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,KACvC,IAAI,CAAC;IACV,QAAQ,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD,QAAQ,CAAC,aAAa,EAAE,CACtB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;KAAE,KACxD,IAAI,CAAC;IACV,QAAQ,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAeD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,+BAA+B,CAAC;IACtD,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACpD;AAGD,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,SAAkB,IAAI,gCAAgC;CACvD;AAwMD,MAAM,WAAW,2BAA2B;IAC1C;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B;AAGD,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,2BAA2B,GAAG,SAAS,EAC/C,gBAAgB,CAAC,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAC9C,SAAS,UAAQ,GAChB,IAAI,CAoBN;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,OAAO,GACjB,OAAO,CAWT;AAED;;;;GAIG;AAEH,wBAAsB,4BAA4B,CAChD,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,2BAA2B,CAAC,CA4KtC;AAED;;;;;;;;GAQG;AAEH,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,CAAC,CAAC,CAWZ"}
1
+ {"version":3,"file":"workspace-mutation-lock.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/workspace-mutation-lock.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { mkdirSync, readFileSync, rmdirSync, unlinkSync, writeFileSync, } from "node:fs";
1
+ import { mkdirSync, readFileSync, renameSync, rmdirSync, unlinkSync, writeFileSync, } from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { InputError, OperationError } from "../../cli-errors.js";
4
4
  /**
@@ -23,6 +23,7 @@ const NODE_FILE_SYSTEM = {
23
23
  },
24
24
  unlinkSync: (target) => unlinkSync(target),
25
25
  rmdirSync: (target) => rmdirSync(target),
26
+ renameSync: (from, to) => renameSync(from, to),
26
27
  };
27
28
  // implements REQ-generated-coordinate-persistence
28
29
  export class WorkspaceMutationLockError extends Error {
@@ -156,8 +157,52 @@ function observeExistingLock(fileSystem, target, ownerPath, isProcessAlive) {
156
157
  return {
157
158
  kind: "recovery",
158
159
  description: `holder pid ${record.pid} is not live`,
160
+ // Legacy file locks are never reclaimed automatically.
161
+ ...(source === "lock owner metadata" ? { deadHolder: record } : {}),
159
162
  };
160
163
  }
164
+ /**
165
+ * Move a dead holder's lock directory aside atomically, confirm the moved
166
+ * owner is still that dead holder, then delete it. A concurrent reclaimer
167
+ * loses the rename and simply retries acquisition. If another writer replaced
168
+ * the lock between observation and rename, the lock is put back and the
169
+ * acquisition fails closed.
170
+ */
171
+ function reclaimDeadLock(fileSystem, target, holder, token) {
172
+ const rename = fileSystem.renameSync;
173
+ if (rename === undefined)
174
+ return "changed";
175
+ const tombstone = `${target}.reclaimed-${token}`;
176
+ try {
177
+ rename(target, tombstone);
178
+ }
179
+ catch (error) {
180
+ const code = errorCode(error);
181
+ if (code === "ENOENT" || code === "EEXIST" || code === "ENOTEMPTY")
182
+ return "changed";
183
+ throw new WorkspaceMutationLockError(`failed to reclaim workspace mutation lock at ${target} from dead pid ${holder.pid}: ${errorDetail(error)}`);
184
+ }
185
+ const tombstoneOwner = path.join(tombstone, OWNER_FILE);
186
+ let moved = null;
187
+ try {
188
+ moved = readLockRecord(fileSystem.readFileSync(tombstoneOwner));
189
+ }
190
+ catch {
191
+ moved = null;
192
+ }
193
+ if (moved === null || moved.token !== holder.token) {
194
+ try {
195
+ rename(tombstone, target);
196
+ }
197
+ catch {
198
+ // The original path was taken again; the tombstone stays for an operator.
199
+ }
200
+ 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);
201
+ }
202
+ removeLockOwner(fileSystem, tombstone, tombstoneOwner, true);
203
+ removeLockDirectory(fileSystem, tombstone);
204
+ return "reclaimed";
205
+ }
161
206
  function defaultIsProcessAlive(pid) {
162
207
  try {
163
208
  process.kill(pid, 0);
@@ -228,6 +273,7 @@ export async function acquireWorkspaceMutationLock(workspaceRoot, options = {})
228
273
  throw new WorkspaceMutationLockError(`failed to create workspace mutation lock parent at ${parentPath} after ${parentResult.attempts} attempt(s): ${errorDetail(parentResult.error)}`);
229
274
  }
230
275
  let acquired = false;
276
+ const reclaimed = [];
231
277
  let lastBlocker = "unknown";
232
278
  let lastObservationMissing = false;
233
279
  let missingOwnerRereads = 0;
@@ -257,6 +303,25 @@ export async function acquireWorkspaceMutationLock(workspaceRoot, options = {})
257
303
  }
258
304
  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);
259
305
  }
306
+ if (observation.kind === "recovery" &&
307
+ observation.deadHolder !== undefined &&
308
+ fileSystem.renameSync !== undefined &&
309
+ options.reclaimDeadHolder?.(observation.deadHolder) === true) {
310
+ const holder = observation.deadHolder;
311
+ if (reclaimDeadLock(fileSystem, target, holder, token) === "reclaimed") {
312
+ reclaimed.push({
313
+ pid: holder.pid,
314
+ token: holder.token,
315
+ acquiredAt: holder.acquiredAt,
316
+ reclaimedAt: now(),
317
+ reclaimedByPid: process.pid,
318
+ });
319
+ }
320
+ else {
321
+ await sleep(RETRY_INTERVAL_MS);
322
+ }
323
+ continue;
324
+ }
260
325
  if (observation.kind === "recovery") {
261
326
  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);
262
327
  }
@@ -312,6 +377,7 @@ export async function acquireWorkspaceMutationLock(workspaceRoot, options = {})
312
377
  }
313
378
  let releaseState = "owned";
314
379
  return {
380
+ ...(reclaimed.length > 0 ? { reclaimed } : {}),
315
381
  release: () => {
316
382
  if (releaseState === "released")
317
383
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAEV,gBAAgB,EAGjB,MAAM,0CAA0C,CAAC;AAQlD,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AA8D/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;AA6xED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB,GACxB,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,CA6BD"}
1
+ {"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAEV,gBAAgB,EAGjB,MAAM,0CAA0C,CAAC;AAQlD,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AA+D/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;AAo9ED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB,GACxB,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,CAkDD"}