kibi-cli 2.22.1 → 2.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-operation-metadata.d.ts +1 -1
- package/dist/cli-operation-metadata.js +1 -1
- package/dist/cli-register-foundation.d.ts.map +1 -1
- package/dist/cli-register-foundation.js +1 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +51 -3
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +28 -0
- package/dist/commands/migrate.d.ts +10 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +81 -9
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +15 -1
- package/dist/decision-firmness.d.ts +16 -4
- package/dist/decision-firmness.d.ts.map +1 -1
- package/dist/decision-firmness.js +29 -6
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +39 -36
- package/dist/intent-search.d.ts +2 -0
- package/dist/intent-search.d.ts.map +1 -1
- package/dist/intent-search.js +43 -4
- package/dist/operations/bootstrap/activation.d.ts +7 -1
- package/dist/operations/bootstrap/activation.d.ts.map +1 -1
- package/dist/operations/bootstrap/activation.js +27 -2
- package/dist/operations/bootstrap/candidate-helpers.d.ts +6 -0
- package/dist/operations/bootstrap/candidate-helpers.d.ts.map +1 -1
- package/dist/operations/bootstrap/candidate-helpers.js +19 -0
- package/dist/operations/bootstrap/generate.d.ts.map +1 -1
- package/dist/operations/bootstrap/generate.js +56 -16
- package/dist/operations/bootstrap/guidance-actions.d.ts.map +1 -1
- package/dist/operations/bootstrap/guidance-actions.js +14 -2
- package/dist/operations/bootstrap/guidance.d.ts.map +1 -1
- package/dist/operations/bootstrap/guidance.js +6 -2
- package/dist/operations/bootstrap/intent-claims.d.ts.map +1 -1
- package/dist/operations/bootstrap/intent-claims.js +2 -2
- package/dist/operations/bootstrap/markdown-candidates.d.ts.map +1 -1
- package/dist/operations/bootstrap/markdown-candidates.js +2 -2
- package/dist/operations/bootstrap/presentation.d.ts.map +1 -1
- package/dist/operations/bootstrap/presentation.js +75 -9
- package/dist/operations/bootstrap/types.d.ts +6 -0
- package/dist/operations/bootstrap/types.d.ts.map +1 -1
- package/dist/operations/migration/firm-supersede.d.ts +19 -0
- package/dist/operations/migration/firm-supersede.d.ts.map +1 -0
- package/dist/operations/migration/firm-supersede.js +60 -0
- package/dist/operations/migration/kb-sources.d.ts +8 -5
- package/dist/operations/migration/kb-sources.d.ts.map +1 -1
- package/dist/operations/migration/kb-sources.js +50 -14
- package/dist/operations/migration/schema6.d.ts.map +1 -1
- package/dist/operations/migration/schema6.js +52 -2
- package/dist/operations/migration/source-paths.d.ts +18 -1
- package/dist/operations/migration/source-paths.d.ts.map +1 -1
- package/dist/operations/migration/source-paths.js +111 -16
- package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
- package/dist/operations/modeling/suggest-predicates.js +22 -7
- package/dist/operations/mutation/requirement-semantics.d.ts +19 -1
- package/dist/operations/mutation/requirement-semantics.d.ts.map +1 -1
- package/dist/operations/mutation/requirement-semantics.js +63 -2
- package/dist/operations/mutation/strict-fact.js +1 -1
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +2 -1
- package/dist/operations/planning/apply-plan.d.ts.map +1 -1
- package/dist/operations/planning/apply-plan.js +22 -2
- package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analyze-prose.js +49 -17
- package/dist/public/impact/analyzer.d.ts.map +1 -1
- package/dist/public/impact/analyzer.js +7 -1
- package/dist/public/impact/diagnostics.d.ts.map +1 -1
- package/dist/public/impact/diagnostics.js +16 -3
- package/dist/public/impact/types.d.ts +2 -0
- package/dist/public/impact/types.d.ts.map +1 -1
- package/dist/public/operations/catalog.d.ts +8 -3
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/check-executor.d.ts.map +1 -1
- package/dist/public/operations/check-executor.js +10 -3
- package/dist/public/operations/check-helpers.d.ts +10 -0
- package/dist/public/operations/check-helpers.d.ts.map +1 -1
- package/dist/public/operations/check-helpers.js +54 -0
- package/dist/public/operations/contracts.d.ts.map +1 -1
- package/dist/public/operations/contracts.js +1 -0
- package/dist/public/operations/decision-firmness.d.ts +14 -0
- package/dist/public/operations/decision-firmness.d.ts.map +1 -1
- package/dist/public/operations/decision-firmness.js +60 -7
- package/dist/public/operations/discovery-executors.d.ts +18 -0
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +80 -8
- package/dist/public/operations/schema6-check-actions.d.ts +4 -0
- package/dist/public/operations/schema6-check-actions.d.ts.map +1 -1
- package/dist/public/operations/schema6-check-actions.js +39 -2
- package/dist/public/operations/source-path-dangling.d.ts +1 -1
- package/dist/public/operations/source-path-dangling.d.ts.map +1 -1
- package/dist/public/operations/source-path-dangling.js +11 -2
- package/dist/public/operations/specs/composite.d.ts +1 -1
- package/dist/public/operations/specs/discovery.d.ts +5 -0
- package/dist/public/operations/specs/discovery.d.ts.map +1 -1
- package/dist/public/operations/specs/discovery.js +5 -0
- package/dist/public/operations/specs/planning.d.ts +1 -1
- package/dist/public/operations/specs/planning.js +1 -1
- package/dist/public/operations/specs/reporting.d.ts.map +1 -1
- package/dist/public/operations/specs/reporting.js +6 -4
- package/dist/public/operations/specs/semantic.d.ts +1 -1
- package/dist/public/operations/specs/semantic.d.ts.map +1 -1
- package/dist/public/operations/specs/semantic.js +4 -2
- package/dist/public/operations/specs/upsert.d.ts +1 -1
- package/dist/public/operations/specs/upsert.js +1 -1
- package/dist/public/schemas/entity.js +1 -1
- package/dist/public/skills/kibi-bootstrap/SKILL.md +5 -4
- package/dist/public/skills/kibi-usage/SKILL.md +36 -16
- package/dist/public/skills/kibi-usage/resources/workflows.md +3 -1
- package/dist/schemas/entity.schema.json +1 -1
- package/dist/search-answer.d.ts +14 -0
- package/dist/search-answer.d.ts.map +1 -1
- package/dist/search-answer.js +51 -5
- package/dist/traceability/git-change-snapshot.d.ts +46 -0
- package/dist/traceability/git-change-snapshot.d.ts.map +1 -1
- package/dist/traceability/git-change-snapshot.js +187 -3
- package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
- package/dist/traceability/staged-diagnostics.js +5 -6
- package/dist/traceability/staged-inventory-drift.d.ts +24 -0
- package/dist/traceability/staged-inventory-drift.d.ts.map +1 -0
- package/dist/traceability/staged-inventory-drift.js +84 -0
- package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
- package/dist/traceability/staged-symbols-manifest.js +1 -2
- package/dist/types/entities.d.ts +2 -2
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/utils/content-freshness.d.ts +9 -0
- package/dist/utils/content-freshness.d.ts.map +1 -1
- package/dist/utils/content-freshness.js +33 -0
- package/dist/utils/kb-manifest.d.ts +7 -0
- package/dist/utils/kb-manifest.d.ts.map +1 -1
- package/dist/utils/kb-manifest.js +6 -0
- package/dist/utils/merge-driver-setup.d.ts +10 -0
- package/dist/utils/merge-driver-setup.d.ts.map +1 -0
- package/dist/utils/merge-driver-setup.js +69 -0
- package/dist/utils/rule-registry.generated.d.ts +1 -1
- package/dist/utils/rule-registry.generated.js +1 -1
- package/dist/utils/schema-version.d.ts +1 -1
- package/dist/utils/schema-version.d.ts.map +1 -1
- package/dist/utils/schema-version.js +3 -1
- package/package.json +2 -2
- package/src/public/impact/analyzer.ts +13 -1
- package/src/public/impact/diagnostics.ts +20 -3
- package/src/public/impact/types.ts +2 -0
- package/src/public/operations/check-executor.ts +16 -4
- package/src/public/operations/check-helpers.ts +65 -0
- package/src/public/operations/contracts.ts +1 -0
- package/src/public/operations/decision-firmness.ts +83 -10
- package/src/public/operations/discovery-executors.ts +124 -7
- package/src/public/operations/schema6-check-actions.ts +43 -2
- package/src/public/operations/source-path-dangling.ts +12 -2
- package/src/public/operations/specs/discovery.ts +6 -0
- package/src/public/operations/specs/planning.ts +1 -1
- package/src/public/operations/specs/reporting.ts +6 -4
- package/src/public/operations/specs/semantic.ts +4 -2
- package/src/public/operations/specs/upsert.ts +1 -1
- package/src/public/schemas/entity.ts +1 -1
- package/src/public/skills/kibi-bootstrap/SKILL.md +5 -4
- package/src/public/skills/kibi-usage/SKILL.md +36 -16
- package/src/public/skills/kibi-usage/resources/workflows.md +3 -1
- package/src/schemas/entity.schema.json +1 -1
|
@@ -23,6 +23,21 @@ const URL_PATTERN = /^https?:\/\/\S+$/i;
|
|
|
23
23
|
const LEGACY_ROOT = "documentation/";
|
|
24
24
|
const SYMBOL_ID_LINE = /^[ \t]*-?[ \t]*id[ \t]*:[ \t]*(.+?)[ \t]*$/gm;
|
|
25
25
|
const SOURCE_EDIT_REFUSED = "the source field spans several lines, or editing it would change other frontmatter fields";
|
|
26
|
+
const TEXT_REF_EDIT_REFUSED = "the text_ref field spans several lines, or editing it would change other frontmatter fields";
|
|
27
|
+
const TEXT_REF_UNMAPPED = "no file under .kb/ matches it, so Kibi cannot tell what evidence it meant";
|
|
28
|
+
const TEXT_REF_SEMANTIC_SOURCE = "text_ref is this requirement's semantic source, so changing it changes its semantic inventory hash; re-derive the inventory after editing it";
|
|
29
|
+
/** One workspace file path: a directory, a file name with an extension, no spaces. */
|
|
30
|
+
const FILE_PATH_SHAPE = /^(?:\.\/)?[^\s;,:]+\/[^\s;,:/]+\.[A-Za-z0-9]+(?:#\S*)?$/;
|
|
31
|
+
function editRefusedReason(field) {
|
|
32
|
+
return field === "text_ref" ? TEXT_REF_EDIT_REFUSED : SOURCE_EDIT_REFUSED;
|
|
33
|
+
}
|
|
34
|
+
/** Whether a `text_ref` value reads as one workspace file path. */
|
|
35
|
+
// implements REQ-core-validation-rules, REQ-kibi-kb-lifecycle-integrity
|
|
36
|
+
export function isPathShapedTextRef(value) {
|
|
37
|
+
return (typeof value === "string" &&
|
|
38
|
+
!URL_PATTERN.test(value.trim()) &&
|
|
39
|
+
FILE_PATH_SHAPE.test(value.trim()));
|
|
40
|
+
}
|
|
26
41
|
function scalarText(value) {
|
|
27
42
|
if (typeof value === "string")
|
|
28
43
|
return value.trim();
|
|
@@ -58,11 +73,33 @@ export function scanAuthoredSources(workspaceRoot) {
|
|
|
58
73
|
path.basename(file.relativePath, ".md");
|
|
59
74
|
entityIds.add(id);
|
|
60
75
|
const value = readTopLevelField(slice.text, "source");
|
|
61
|
-
if (value
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
if (value !== undefined &&
|
|
77
|
+
value !== null &&
|
|
78
|
+
!(typeof value === "string" && value.trim() === "")) {
|
|
79
|
+
refs.push({
|
|
80
|
+
entityId: id,
|
|
81
|
+
field: "source",
|
|
82
|
+
file: file.relativePath,
|
|
83
|
+
value,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const textRef = readTopLevelField(slice.text, "text_ref");
|
|
87
|
+
if (isPathShapedTextRef(textRef)) {
|
|
88
|
+
const declared = readTopLevelField(slice.text, "semantic_source_field");
|
|
89
|
+
const requirement = readTopLevelField(slice.text, "type") === "req" ||
|
|
90
|
+
file.relativePath.startsWith(`${KB_PATHS.lanes.requirements}/`);
|
|
91
|
+
const semanticSource = declared === "text_ref" ||
|
|
92
|
+
(requirement &&
|
|
93
|
+
declared === undefined &&
|
|
94
|
+
readTopLevelField(slice.text, "semantic_text") === undefined);
|
|
95
|
+
refs.push({
|
|
96
|
+
entityId: id,
|
|
97
|
+
field: "text_ref",
|
|
98
|
+
...(semanticSource ? { semanticSource } : {}),
|
|
99
|
+
file: file.relativePath,
|
|
100
|
+
value: textRef,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
66
103
|
}
|
|
67
104
|
const symbols = readText(path.join(workspaceRoot, KB_PATHS.symbolsManifest));
|
|
68
105
|
if (symbols !== null) {
|
|
@@ -148,6 +185,34 @@ export function workspacePathExists(workspaceRoot) {
|
|
|
148
185
|
};
|
|
149
186
|
}
|
|
150
187
|
function repairFor(ref, resolution) {
|
|
188
|
+
if (ref.field === "text_ref") {
|
|
189
|
+
// A text_ref naming its own existing file is evidence, not redundancy.
|
|
190
|
+
if (resolution.kind === "self" && resolution.resolves)
|
|
191
|
+
return undefined;
|
|
192
|
+
if (resolution.kind !== "dangling" && resolution.kind !== "self") {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
// A pre-canonical spelling of the entity's own file moved with it.
|
|
196
|
+
const rewrite = resolution.kind === "dangling" ? resolution.rewrite : ref.file;
|
|
197
|
+
if (ref.semanticSource === true) {
|
|
198
|
+
return {
|
|
199
|
+
...ref,
|
|
200
|
+
dangling: true,
|
|
201
|
+
fix: rewrite !== undefined
|
|
202
|
+
? { kind: "rewrite", to: rewrite }
|
|
203
|
+
: { kind: "remove", reason: "dangling" },
|
|
204
|
+
refused: TEXT_REF_SEMANTIC_SOURCE,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return rewrite !== undefined
|
|
208
|
+
? { ...ref, dangling: true, fix: { kind: "rewrite", to: rewrite } }
|
|
209
|
+
: {
|
|
210
|
+
...ref,
|
|
211
|
+
dangling: true,
|
|
212
|
+
fix: { kind: "remove", reason: "dangling" },
|
|
213
|
+
refused: TEXT_REF_UNMAPPED,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
151
216
|
if (resolution.kind === "self") {
|
|
152
217
|
return {
|
|
153
218
|
...ref,
|
|
@@ -165,10 +230,10 @@ function repairFor(ref, resolution) {
|
|
|
165
230
|
: { kind: "remove", reason: "dangling" },
|
|
166
231
|
};
|
|
167
232
|
}
|
|
168
|
-
function editedContent(content, fix) {
|
|
233
|
+
function editedContent(content, field, fix) {
|
|
169
234
|
return fix.kind === "rewrite"
|
|
170
|
-
? withTopLevelField(content,
|
|
171
|
-
: withoutTopLevelField(content,
|
|
235
|
+
? withTopLevelField(content, field, fix.to)
|
|
236
|
+
: withoutTopLevelField(content, field);
|
|
172
237
|
}
|
|
173
238
|
/** Every repairable source value, sorted by entity id. Read-only. */
|
|
174
239
|
function classifySources(workspaceRoot) {
|
|
@@ -186,20 +251,24 @@ function classifySources(workspaceRoot) {
|
|
|
186
251
|
repairs.push(repair);
|
|
187
252
|
}
|
|
188
253
|
return repairs.sort((left, right) => left.entityId.localeCompare(right.entityId) ||
|
|
189
|
-
left.file.localeCompare(right.file)
|
|
254
|
+
left.file.localeCompare(right.file) ||
|
|
255
|
+
left.field.localeCompare(right.field));
|
|
190
256
|
}
|
|
191
257
|
/** Mark the repairs whose edit would not read back as exactly that change. */
|
|
192
258
|
function withEditability(workspaceRoot, repairs) {
|
|
193
259
|
return repairs.map((repair) => {
|
|
260
|
+
if (repair.refused !== undefined)
|
|
261
|
+
return repair;
|
|
194
262
|
const content = readText(path.join(workspaceRoot, repair.file));
|
|
195
|
-
if (content !== null &&
|
|
263
|
+
if (content !== null &&
|
|
264
|
+
editedContent(content, repair.field, repair.fix) !== null) {
|
|
196
265
|
return repair;
|
|
197
266
|
}
|
|
198
267
|
return {
|
|
199
268
|
...repair,
|
|
200
269
|
refused: content === null
|
|
201
270
|
? "the entity file cannot be read"
|
|
202
|
-
:
|
|
271
|
+
: editRefusedReason(repair.field),
|
|
203
272
|
};
|
|
204
273
|
});
|
|
205
274
|
}
|
|
@@ -225,6 +294,7 @@ function plannedRepair(repair) {
|
|
|
225
294
|
const base = {
|
|
226
295
|
entityId: repair.entityId,
|
|
227
296
|
file: repair.file,
|
|
297
|
+
...(repair.field === "text_ref" ? { field: "text_ref" } : {}),
|
|
228
298
|
from: shownSourceValue(repair.value),
|
|
229
299
|
};
|
|
230
300
|
return repair.fix.kind === "rewrite"
|
|
@@ -250,8 +320,18 @@ export function plannedSourcePathRepairs(repairs) {
|
|
|
250
320
|
}
|
|
251
321
|
return { rewrites, removals };
|
|
252
322
|
}
|
|
323
|
+
function plannedField(repair) {
|
|
324
|
+
return "field" in repair && repair.field === "text_ref"
|
|
325
|
+
? "text_ref"
|
|
326
|
+
: "source";
|
|
327
|
+
}
|
|
328
|
+
function plannedKey(file, field) {
|
|
329
|
+
return `${field}\0${file}`;
|
|
330
|
+
}
|
|
253
331
|
function samePlannedRepair(left, right) {
|
|
254
|
-
if (left.entityId !== right.entityId ||
|
|
332
|
+
if (left.entityId !== right.entityId ||
|
|
333
|
+
left.from !== right.from ||
|
|
334
|
+
plannedField(left) !== plannedField(right)) {
|
|
255
335
|
return false;
|
|
256
336
|
}
|
|
257
337
|
if ("to" in left)
|
|
@@ -271,16 +351,26 @@ export function applySourcePathRepairs(workspaceRoot, planned) {
|
|
|
271
351
|
const wanted = planned === undefined
|
|
272
352
|
? undefined
|
|
273
353
|
: new Map([...planned.rewrites, ...planned.removals].map((repair) => [
|
|
274
|
-
repair.file,
|
|
354
|
+
plannedKey(repair.file, plannedField(repair)),
|
|
275
355
|
repair,
|
|
276
356
|
]));
|
|
277
357
|
const rewritten = [];
|
|
278
358
|
const removed = [];
|
|
279
359
|
const skipped = [];
|
|
280
360
|
for (const repair of classifySources(workspaceRoot)) {
|
|
361
|
+
// A text_ref Kibi cannot map needs a person; never remove it.
|
|
362
|
+
if (repair.refused !== undefined) {
|
|
363
|
+
if (wanted === undefined) {
|
|
364
|
+
skipped.push({
|
|
365
|
+
file: repair.file,
|
|
366
|
+
reason: `text_ref '${shownSourceValue(repair.value)}': ${repair.refused}`,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
281
371
|
const current = plannedRepair(repair);
|
|
282
372
|
if (wanted !== undefined) {
|
|
283
|
-
const plan = wanted.get(repair.file);
|
|
373
|
+
const plan = wanted.get(plannedKey(repair.file, repair.field));
|
|
284
374
|
if (plan === undefined)
|
|
285
375
|
continue;
|
|
286
376
|
if (!samePlannedRepair(plan, current)) {
|
|
@@ -293,9 +383,14 @@ export function applySourcePathRepairs(workspaceRoot, planned) {
|
|
|
293
383
|
}
|
|
294
384
|
const absolute = path.join(workspaceRoot, repair.file);
|
|
295
385
|
const content = readText(absolute);
|
|
296
|
-
const next = content === null
|
|
386
|
+
const next = content === null
|
|
387
|
+
? null
|
|
388
|
+
: editedContent(content, repair.field, repair.fix);
|
|
297
389
|
if (next === null) {
|
|
298
|
-
skipped.push({
|
|
390
|
+
skipped.push({
|
|
391
|
+
file: repair.file,
|
|
392
|
+
reason: editRefusedReason(repair.field),
|
|
393
|
+
});
|
|
299
394
|
continue;
|
|
300
395
|
}
|
|
301
396
|
writeFileAtomically(absolute, next);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suggest-predicates.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/suggest-predicates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,0CAA0C,CAAC;AA2BlD,OAAO,KAAK,EAIV,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;AA+H9B,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;AA+ID,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,IAAI,EAAE,qBAAqB,EAC3B,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,uBAAuB,CAAC,
|
|
1
|
+
{"version":3,"file":"suggest-predicates.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/suggest-predicates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,0CAA0C,CAAC;AA2BlD,OAAO,KAAK,EAIV,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;AA+H9B,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;AA+ID,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,IAAI,EAAE,qBAAqB,EAC3B,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,uBAAuB,CAAC,CA8ZlC;AA+WD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,uBAAuB,CAAC,CAElC"}
|
|
@@ -409,8 +409,19 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
|
|
|
409
409
|
? buildGapApplyPlan(text, args)
|
|
410
410
|
: [];
|
|
411
411
|
const plannedFactId = typeof predicatePlan[0]?.id === "string" ? predicatePlan[0].id : null;
|
|
412
|
+
// The manifest the relationship plan calls "merged" starts from what the
|
|
413
|
+
// stored requirement already declares, so an agent that passes no
|
|
414
|
+
// existingLogicClaims is not handed a manifest that drops its other
|
|
415
|
+
// clauses.
|
|
416
|
+
const existingLogicClaims = Array.from(new Set([
|
|
417
|
+
...(storedRequirement !== null &&
|
|
418
|
+
Array.isArray(storedRequirement.logic_claims)
|
|
419
|
+
? storedRequirement.logic_claims.filter((claim) => typeof claim === "string")
|
|
420
|
+
: []),
|
|
421
|
+
...(args.existingLogicClaims ?? []),
|
|
422
|
+
]));
|
|
412
423
|
const relationshipPlan = completeCandidate && !alreadyGrounded && !ledgerMissing
|
|
413
|
-
? buildRelationshipPlan(plannedFactId ?? "", args.requirementId, text,
|
|
424
|
+
? buildRelationshipPlan(plannedFactId ?? "", args.requirementId, text, existingLogicClaims)
|
|
414
425
|
: null;
|
|
415
426
|
const replacementPlan = alreadyGrounded &&
|
|
416
427
|
!keepExistingGrounding &&
|
|
@@ -471,7 +482,7 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
|
|
|
471
482
|
: alreadyGrounded
|
|
472
483
|
? "No predicate candidate passed the semantic applicability gate; keep the existing grounding and record the ontology-gap observation in applyPlan so the missing schema stays visible."
|
|
473
484
|
: "No predicate candidate passed the semantic applicability gate; record an ontology gap and review the generated schema draft instead of silently writing prose.");
|
|
474
|
-
const logicClaims = Array.from(new Set([...
|
|
485
|
+
const logicClaims = Array.from(new Set([...existingLogicClaims, claimKey]));
|
|
475
486
|
const plan = relationshipPlan !== null && plannedFactId !== null && args.requirementId
|
|
476
487
|
? await groundingPlan(prolog, context, {
|
|
477
488
|
requirementId: args.requirementId,
|
|
@@ -486,17 +497,21 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
|
|
|
486
497
|
: null;
|
|
487
498
|
// Without a plan nothing ran the contradiction analysis, so the decision
|
|
488
499
|
// conflicts a complete candidate would introduce are looked up directly.
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
args.requirementId
|
|
500
|
+
// A claim analyzed without a requirement asks the same question ("what
|
|
501
|
+
// would this decision conflict with?"), so it gets the same lookup.
|
|
502
|
+
const decisionConflicts = plan === null && completeCandidate !== undefined && !keepExistingGrounding
|
|
493
503
|
? await candidateDecisionConflicts(prolog, completeCandidate, warnings)
|
|
494
504
|
: [];
|
|
495
505
|
for (const conflict of decisionConflicts) {
|
|
496
506
|
warnings.push(`The proposed ${conflict.canonicalKey} (${conflict.proposedEffect}) contradicts ${conflict.factId} (${conflict.effect}) under ${conflict.requirementId}${conflict.requirementId === args.requirementId ? ", the requirement this claim would ground" : ""}: both decide ${completeCandidate?.predicate_name} on the same key arguments. kb_upsert refuses a grounding link that introduces this conflict; supersede or close the conflicting requirement, or correct the decision, before applying.`);
|
|
497
507
|
}
|
|
508
|
+
const conflictSummary = decisionConflicts.length > 0
|
|
509
|
+
? ` It would contradict ${decisionConflicts
|
|
510
|
+
.map((conflict) => `${conflict.factId} (${conflict.effect}) under ${conflict.requirementId}`)
|
|
511
|
+
.join(", ")}: see decisionConflicts before applying anything.`
|
|
512
|
+
: "";
|
|
498
513
|
return {
|
|
499
|
-
content: [{ type: "text", text: textSummary }],
|
|
514
|
+
content: [{ type: "text", text: textSummary + conflictSummary }],
|
|
500
515
|
structuredContent: {
|
|
501
516
|
text,
|
|
502
517
|
claimKey,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PrologPort } from "../../public/operations/runtime-types.js";
|
|
2
|
+
import { type SemanticRelationship } from "../semantic-advisor/ingestion-boundary.js";
|
|
2
3
|
import { type StoredEntityLookup } from "./stored-entity.js";
|
|
3
4
|
import type { StagedUpsertState, UpsertInput } from "./types.js";
|
|
4
5
|
/**
|
|
@@ -7,10 +8,27 @@ import type { StagedUpsertState, UpsertInput } from "./types.js";
|
|
|
7
8
|
* relationship (for example `specified_by`) and carries no `semantic_*` or
|
|
8
9
|
* `logic_claims` field, and no `title` or `text_ref` that differs from the
|
|
9
10
|
* stored value, is merged over the stored semantic contract (and the stored
|
|
10
|
-
* `text_ref` it omits) before validation.
|
|
11
|
+
* `text_ref` it omits) before validation. When the stored ledger reads its
|
|
12
|
+
* prose from `semantic_text`, a changed `text_ref` is merged the same way,
|
|
13
|
+
* since it is not the prose. The
|
|
11
14
|
* proposition-complete check then runs against the merged requirement, which
|
|
12
15
|
* is also what the write records. A payload that supplies any ledger field or
|
|
13
16
|
* changes the prose is validated exactly as given.
|
|
14
17
|
*/
|
|
15
18
|
export declare function withStoredRequirementSemantics(input: UpsertInput, prolog: Pick<PrologPort, "query">, staged?: StagedUpsertState, lookup?: StoredEntityLookup): Promise<UpsertInput>;
|
|
19
|
+
/**
|
|
20
|
+
* An update to a stored requirement is judged as its authored file will read
|
|
21
|
+
* afterwards: the stored status, prose and proposition ledger with the
|
|
22
|
+
* payload's fields on top (the source writer keeps every front matter key the
|
|
23
|
+
* payload leaves out and states the stored `semantic_text`). A payload such
|
|
24
|
+
* as `{status: "closed"}` passes the check on its own fields, yet the file it
|
|
25
|
+
* leaves is a current requirement whose prose has no inventory, which sync
|
|
26
|
+
* then refuses. Such a write is refused here with the error sync would give.
|
|
27
|
+
*
|
|
28
|
+
* A requirement that was already current with the same prose and no complete
|
|
29
|
+
* inventory is legacy content sync keeps accepting, so a write that changes
|
|
30
|
+
* neither keeps working. A write that makes the requirement current, or
|
|
31
|
+
* changes the prose sync checks, must carry the complete inventory.
|
|
32
|
+
*/
|
|
33
|
+
export declare function assertStoredRequirementBoundary(input: UpsertInput, relationships: readonly SemanticRelationship[], prolog: Pick<PrologPort, "query">, staged?: StagedUpsertState, lookup?: StoredEntityLookup): Promise<void>;
|
|
16
34
|
//# sourceMappingURL=requirement-semantics.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"requirement-semantics.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/requirement-semantics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"requirement-semantics.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/requirement-semantics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E,OAAO,EAEL,KAAK,oBAAoB,EAE1B,MAAM,2CAA2C,CAAC;AAEnD,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAwCjE;;;;;;;;;;;;GAYG;AACH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EACjC,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,GAAE,kBAAqE,GAC5E,OAAO,CAAC,WAAW,CAAC,CAsCtB;AAuBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,+BAA+B,CACnD,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EACjC,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,GAAE,kBAAqE,GAC5E,OAAO,CAAC,IAAI,CAAC,CAmBf"}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { analyzeSemanticAdvisorInput } from "../semantic-advisor/analyze-prose.js";
|
|
2
|
+
import { validateSemanticInventoryBoundary, } from "../semantic-advisor/ingestion-boundary.js";
|
|
3
|
+
import { stringValue } from "../semantic-advisor/shared.js";
|
|
1
4
|
import { storedEntityLookup, } from "./stored-entity.js";
|
|
2
5
|
/** The fields that make up a requirement's proposition ledger. */
|
|
3
6
|
const SEMANTIC_CONTRACT_FIELDS = [
|
|
@@ -30,7 +33,9 @@ async function storedRequirement(id, lookup, staged) {
|
|
|
30
33
|
* relationship (for example `specified_by`) and carries no `semantic_*` or
|
|
31
34
|
* `logic_claims` field, and no `title` or `text_ref` that differs from the
|
|
32
35
|
* stored value, is merged over the stored semantic contract (and the stored
|
|
33
|
-
* `text_ref` it omits) before validation.
|
|
36
|
+
* `text_ref` it omits) before validation. When the stored ledger reads its
|
|
37
|
+
* prose from `semantic_text`, a changed `text_ref` is merged the same way,
|
|
38
|
+
* since it is not the prose. The
|
|
34
39
|
* proposition-complete check then runs against the merged requirement, which
|
|
35
40
|
* is also what the write records. A payload that supplies any ledger field or
|
|
36
41
|
* changes the prose is validated exactly as given.
|
|
@@ -44,7 +49,15 @@ export async function withStoredRequirementSemantics(input, prolog, staged, look
|
|
|
44
49
|
const stored = await storedRequirement(input.id, lookup, staged);
|
|
45
50
|
if (stored === undefined)
|
|
46
51
|
return input;
|
|
47
|
-
|
|
52
|
+
// text_ref is prose only while it is the semantic source. Once the stored
|
|
53
|
+
// ledger reads semantic_text, changing it (for example clearing a text_ref
|
|
54
|
+
// whose file is gone) changes no proposition. A changed title stays a
|
|
55
|
+
// prose change: it is what a reader takes the requirement to say.
|
|
56
|
+
const readsSemanticText = stored.semantic_source_field === "semantic_text" &&
|
|
57
|
+
stringValue(stored.semantic_text).length > 0;
|
|
58
|
+
if (PROSE_FIELDS.some((field) => !(field === "text_ref" && readsSemanticText) &&
|
|
59
|
+
properties[field] !== undefined &&
|
|
60
|
+
properties[field] !== stored[field]))
|
|
48
61
|
return input;
|
|
49
62
|
const preserved = {};
|
|
50
63
|
for (const field of [
|
|
@@ -62,3 +75,51 @@ export async function withStoredRequirementSemantics(input, prolog, staged, look
|
|
|
62
75
|
return input;
|
|
63
76
|
return { ...input, properties: { ...preserved, ...properties } };
|
|
64
77
|
}
|
|
78
|
+
/** Stored fields that decide how sync reads a requirement's propositions. */
|
|
79
|
+
const BOUNDARY_FIELDS = [
|
|
80
|
+
"status",
|
|
81
|
+
"title",
|
|
82
|
+
"text_ref",
|
|
83
|
+
...SEMANTIC_CONTRACT_FIELDS,
|
|
84
|
+
];
|
|
85
|
+
function boundaryOf(input, properties, relationships) {
|
|
86
|
+
const payload = { type: input.type, id: input.id, properties };
|
|
87
|
+
const { receipt } = analyzeSemanticAdvisorInput({
|
|
88
|
+
payload: { ...payload, relationships },
|
|
89
|
+
});
|
|
90
|
+
return validateSemanticInventoryBoundary(payload, relationships, receipt);
|
|
91
|
+
}
|
|
92
|
+
// implements REQ-kibi-upsert-preserves-requirement-semantics, REQ-kibi-proposition-complete-ingestion
|
|
93
|
+
/**
|
|
94
|
+
* An update to a stored requirement is judged as its authored file will read
|
|
95
|
+
* afterwards: the stored status, prose and proposition ledger with the
|
|
96
|
+
* payload's fields on top (the source writer keeps every front matter key the
|
|
97
|
+
* payload leaves out and states the stored `semantic_text`). A payload such
|
|
98
|
+
* as `{status: "closed"}` passes the check on its own fields, yet the file it
|
|
99
|
+
* leaves is a current requirement whose prose has no inventory, which sync
|
|
100
|
+
* then refuses. Such a write is refused here with the error sync would give.
|
|
101
|
+
*
|
|
102
|
+
* A requirement that was already current with the same prose and no complete
|
|
103
|
+
* inventory is legacy content sync keeps accepting, so a write that changes
|
|
104
|
+
* neither keeps working. A write that makes the requirement current, or
|
|
105
|
+
* changes the prose sync checks, must carry the complete inventory.
|
|
106
|
+
*/
|
|
107
|
+
export async function assertStoredRequirementBoundary(input, relationships, prolog, staged, lookup = storedEntityLookup(prolog, input.id, input.type)) {
|
|
108
|
+
if (input.type !== "req")
|
|
109
|
+
return;
|
|
110
|
+
const stored = await storedRequirement(input.id, lookup, staged);
|
|
111
|
+
if (stored === undefined)
|
|
112
|
+
return;
|
|
113
|
+
const before = {};
|
|
114
|
+
for (const field of BOUNDARY_FIELDS) {
|
|
115
|
+
if (stored[field] !== undefined)
|
|
116
|
+
before[field] = stored[field];
|
|
117
|
+
}
|
|
118
|
+
const after = boundaryOf(input, { ...before, ...(input.properties ?? {}) }, relationships);
|
|
119
|
+
if (after.errors.length === 0)
|
|
120
|
+
return;
|
|
121
|
+
const previous = boundaryOf(input, before, relationships);
|
|
122
|
+
if (previous.applicable && previous.sourceHash === after.sourceHash)
|
|
123
|
+
return;
|
|
124
|
+
throw new Error(`Proposition-complete ingestion failed: ${input.id} would be written as a current requirement without a complete proposition inventory (${after.errors.join("; ")}). Run kb_model mode analyze (CLI: semantic-advisor) with id '${input.id}' and its complete semantic_text, then send the returned inventory contract (semantic_inventory, semantic_inventory_version, semantic_source_field, semantic_source_hash, logic_claims) in this kb_upsert, or retire the requirement through a superseding one instead.`);
|
|
125
|
+
}
|
|
@@ -90,7 +90,7 @@ export function factKindShapeHints(entity) {
|
|
|
90
90
|
if (missing.length !== 0)
|
|
91
91
|
return [
|
|
92
92
|
`fact_kind 'predicate' requires ${missing.join(", ")}.`,
|
|
93
|
-
"Next action: call
|
|
93
|
+
"Next action: call kb_model with mode predicates (CLI: kibi suggest-predicates) and apply its planned fact instead of hand-writing ontology predicate facts.",
|
|
94
94
|
];
|
|
95
95
|
return [];
|
|
96
96
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAexE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAexE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AA4B3E,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AAsEpB,wBAAgB,gCAAgC,CAC9C,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,GAChB,IAAI,CAkBN;AAwCD,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,aAAa,EAAE,SAAS,iBAAiB,EAAE,EAC3C,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CA8BvC;AAmCD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CACrC,CAAC,CAAC;AAGH,uFAAuF;AACvF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC,CAAC;AAGH;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,WAAW,GAAG;IACrB,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACtD,GACA,sBAAsB,CASxB;AAGD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;CAC3C,CAAC,CAAC;AAGH;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAiF3B;AAgDD,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C;;;;;;OAMG;IAEH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACxC,CAAC,CAAC;AAyBH,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CA2hBzC"}
|
|
@@ -19,7 +19,7 @@ import { resolveUpsertOrigin } from "./origin.js";
|
|
|
19
19
|
import { planRecoveryNotes, settlePendingPlanApplyJournals, withPlanRecoveryNotes, } from "./plan-apply-journal.js";
|
|
20
20
|
import { assertPredicateArgumentVocabulary } from "./predicate-vocabulary-guard.js";
|
|
21
21
|
import { existingRelationships, validateLiveRelationshipTargets, validateRelationshipSources, validateStrictLanePairing, validateSupersedesSourceHistory, } from "./relationships.js";
|
|
22
|
-
import { withStoredRequirementSemantics } from "./requirement-semantics.js";
|
|
22
|
+
import { assertStoredRequirementBoundary, withStoredRequirementSemantics, } from "./requirement-semantics.js";
|
|
23
23
|
import { MutationRollbackFailureError, MutationSaga } from "./saga.js";
|
|
24
24
|
import { readFileTimes, restoreFileTimes, writePendingSourceReceipt, writeSourceForUpsert, } from "./source-authoring.js";
|
|
25
25
|
import { storedEntityLookup, } from "./stored-entity.js";
|
|
@@ -244,6 +244,7 @@ export async function validateUpsertForCommit(payload, context, options = {}) {
|
|
|
244
244
|
await validateLiveRelationshipTargets(prolog, validated.entity, validated.relationships, staged);
|
|
245
245
|
await validateSupersedesSourceHistory(prolog, validated.entity, validated.relationships, context.workspaceRoot, undefined, staged);
|
|
246
246
|
const semantic = analyzeUpsertSemantics({ ...input, relationships });
|
|
247
|
+
await assertStoredRequirementBoundary(input, relationships, prolog, staged, lookup);
|
|
247
248
|
await assertLogicalGroundingClaimKeys(prolog, { ...input, relationships }, relationships, staged);
|
|
248
249
|
await assertPredicateArgumentVocabulary(prolog, validated.entity, staged);
|
|
249
250
|
// The origin the write records is added last: none of the checks above
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAEV,gBAAgB,EAGjB,MAAM,0CAA0C,CAAC;AASlD,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AAiE/B,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EAGhB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAG/B,eAAO,MAAM,yBAAyB,EAAG,2BAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAEV,gBAAgB,EAGjB,MAAM,0CAA0C,CAAC;AASlD,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AAiE/B,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EAGhB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAG/B,eAAO,MAAM,yBAAyB,EAAG,2BAAoC,CAAC;AAqI9E,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,IAAI,CAMN;AAED,wBAAgB,mCAAmC,CACjD,SAAS,EAAE,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,EACnE,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,IAAI,CAaN;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,SAAS,cAAoB,GAC5B,eAAe,EAAE,CA2CnB;AAi1BD;;;;;;GAMG;AAEH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAiCf;AAyqDD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB;AACzB;;;GAGG;AACH,WAAW,GAAE,MAAM,IAAsB,GACxC,OAAO,CAAC;IACT,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,iBAAiB,EAAE,eAAe,CAAC;CACpC,CAAC,CAmDD"}
|
|
@@ -84,13 +84,33 @@ function asUpsert(step) {
|
|
|
84
84
|
...(document !== undefined ? { document } : {}),
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
+
// implements REQ-kibi-change-to-proof-plan-compiler-v2
|
|
88
|
+
/**
|
|
89
|
+
* Why a plan that is not ready cannot be applied, with the remedy for its
|
|
90
|
+
* status. The leading sentence stays the same for every status.
|
|
91
|
+
*/
|
|
92
|
+
function notReadyPlanMessage(prefix, status) {
|
|
93
|
+
const base = `${prefix}: only ready plans may be applied; this plan's status is '${String(status)}'.`;
|
|
94
|
+
switch (status) {
|
|
95
|
+
case "needs_resolution":
|
|
96
|
+
return `${base} Resolve its ontology_gap and missing propositions first (model each with kb_model mode requirement or predicates, then call kb_compile_intent again and apply the ready plan it returns), and fix any step its diagnostics name. Or write the requirement with kb_upsert carrying the plan's inventory contract, with the unmodeled propositions kept as status ontology_gap entries, as the kibi-usage skill describes.`;
|
|
97
|
+
case "blocked":
|
|
98
|
+
return prefix === "Apply plan failed"
|
|
99
|
+
? `${base} Its contradiction analysis found a conflict with a current requirement: resolve the contradiction first (supersede or change the conflicting requirement, or change the intent), then compile again.`
|
|
100
|
+
: `${base} Resolve what its diagnostics name, then plan the bootstrap again.`;
|
|
101
|
+
case "needs_context":
|
|
102
|
+
return `${base} Supply the bootstrap context it asks for, then plan the bootstrap again.`;
|
|
103
|
+
default:
|
|
104
|
+
return `${base} Compile or plan again and apply the plan it returns once its status is 'ready'.`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
87
107
|
function validateCompilePlanShape(args) {
|
|
88
108
|
if (!isRecord(args.plan))
|
|
89
109
|
throw new Error("Apply plan failed: plan must be an object");
|
|
90
110
|
if (args.plan.version !== "kibi.compile-plan.v1")
|
|
91
111
|
throw new Error("Apply plan failed: unsupported plan version");
|
|
92
112
|
if (args.plan.status !== "ready")
|
|
93
|
-
throw new Error("Apply plan failed
|
|
113
|
+
throw new Error(notReadyPlanMessage("Apply plan failed", args.plan.status));
|
|
94
114
|
if (!/^[a-f0-9]{64}$/i.test(args.approvedPlanHash))
|
|
95
115
|
throw new Error("Apply plan failed: approvedPlanHash must be a SHA-256 hash");
|
|
96
116
|
if (args.approvedPlanHash !== args.plan.planHash)
|
|
@@ -163,7 +183,7 @@ export function orderBootstrapActions(actions, completed = new Set()) {
|
|
|
163
183
|
function validateBootstrapPlanShape(args) {
|
|
164
184
|
const { plan } = args;
|
|
165
185
|
if (plan.status !== "ready")
|
|
166
|
-
throw new Error("Bootstrap apply failed
|
|
186
|
+
throw new Error(notReadyPlanMessage("Bootstrap apply failed", plan.status));
|
|
167
187
|
if (!/^[a-f0-9]{64}$/i.test(args.approvedPlanHash))
|
|
168
188
|
throw new Error("Bootstrap apply failed: approvedPlanHash must be SHA-256");
|
|
169
189
|
if (args.approvedPlanHash !== plan.planHash)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze-prose.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/analyze-prose.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analyze-prose.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/analyze-prose.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAiBxD,OAAO,KAAK,EACV,6BAA6B,EAC7B,oBAAoB,EAWrB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAI1D;AA4gBD,OAAO,EAAE,eAAe,EAAE,CAAC;AAwX3B,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,GAC1B,6BAA6B,CAgE/B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createBuiltinOntologyPack, detectSignals as detectBuiltinSignals, } from "kibi-plugin-builtin";
|
|
2
|
+
import { InputError } from "../../cli-errors.js";
|
|
2
3
|
import { utf8Span, validateLogicIr, } from "../../logic/ir.js";
|
|
3
4
|
import { logicRuleFactId } from "../modeling/logic-modeling.js";
|
|
4
5
|
import { buildAdvisorResult } from "./analysis-receipt.js";
|
|
@@ -15,12 +16,17 @@ export function stringLogicClaims(value) {
|
|
|
15
16
|
? value.filter((entry) => typeof entry === "string")
|
|
16
17
|
: [];
|
|
17
18
|
}
|
|
19
|
+
// implements REQ-mcp-semantic-advisor-preflight
|
|
18
20
|
function proseOf(payload) {
|
|
19
21
|
const properties = propertiesOf(payload);
|
|
22
|
+
// text_ref is the prose only while semantic_text is unset (the ingestion
|
|
23
|
+
// boundary's fallback); beside semantic_text it is a reference such as
|
|
24
|
+
// "docs/intake.md#batch-4", whose numbers and words are not claims.
|
|
25
|
+
const semanticText = stringValue(properties.semantic_text);
|
|
20
26
|
return [
|
|
21
27
|
properties.title,
|
|
22
28
|
properties.semantic_text,
|
|
23
|
-
properties.text_ref,
|
|
29
|
+
semanticText ? undefined : properties.text_ref,
|
|
24
30
|
properties.description,
|
|
25
31
|
]
|
|
26
32
|
.map(stringValue)
|
|
@@ -540,6 +546,16 @@ function shadowAnalysis(text, interpretations) {
|
|
|
540
546
|
}
|
|
541
547
|
function propositionSpan(text, clause, cursor) {
|
|
542
548
|
const located = locateSourceText(text, clause.text, cursor);
|
|
549
|
+
// A clause the prose does not contain has no source span: it keeps its own
|
|
550
|
+
// text over an empty span (which strict ingestion rejects) instead of an
|
|
551
|
+
// unrelated slice of the prose.
|
|
552
|
+
if (located === null) {
|
|
553
|
+
return {
|
|
554
|
+
span: utf8Span(text, cursor, cursor),
|
|
555
|
+
next: cursor,
|
|
556
|
+
sourceText: clause.text,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
543
559
|
return {
|
|
544
560
|
span: utf8Span(text, located.start, located.end),
|
|
545
561
|
next: Math.max(cursor, located.end),
|
|
@@ -552,23 +568,34 @@ function escapeRegExp(value) {
|
|
|
552
568
|
/**
|
|
553
569
|
* Locate canonical claim text while accepting source-only whitespace layout.
|
|
554
570
|
* The caller receives the authored slice so strict ingestion can compare the
|
|
555
|
-
* span with the claim text without rewriting the source document.
|
|
571
|
+
* span with the claim text without rewriting the source document. The search
|
|
572
|
+
* starts at `cursor` and then retries from the start of the prose; null means
|
|
573
|
+
* the prose does not contain the claim text at all.
|
|
556
574
|
*/
|
|
557
575
|
function locateSourceText(source, claimText, cursor) {
|
|
558
|
-
const
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
576
|
+
const pattern = new RegExp(escapeRegExp(claimText.trim()).replace(/\s+/g, "\\s+"));
|
|
577
|
+
for (const from of cursor > 0 ? [cursor, 0] : [0]) {
|
|
578
|
+
const match = pattern.exec(source.slice(from));
|
|
579
|
+
if (match !== null) {
|
|
580
|
+
const start = from + match.index;
|
|
581
|
+
return { start, end: start + match[0].length };
|
|
582
|
+
}
|
|
563
583
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Caller-supplied clauses split the requirement prose; they never restate
|
|
588
|
+
* it. A clause that does not occur in the prose (apart from whitespace
|
|
589
|
+
* layout, a list marker or closing punctuation) would get a span over
|
|
590
|
+
* unrelated text, so the request is refused naming each such clause.
|
|
591
|
+
*/
|
|
592
|
+
function assertVerbatimClauses(statement, clauses) {
|
|
593
|
+
const missing = clauses
|
|
594
|
+
.filter((clause) => locateSourceText(statement, clause.text, 0) === null)
|
|
595
|
+
.map((clause) => JSON.stringify(clause.text));
|
|
596
|
+
if (missing.length === 0)
|
|
597
|
+
return;
|
|
598
|
+
throw new InputError("VALIDATION_FAILED", `Semantic advisor clauses must be verbatim fragments of the requirement prose (the intent or semantic_text); ${missing.length === 1 ? "this clause does" : "these clauses do"} not occur in it: ${missing.join(", ")}. Copy each clause from the prose unchanged (whitespace may differ), or omit clauses to let Kibi split the prose.`);
|
|
572
599
|
}
|
|
573
600
|
function interpretationResults(interpretations, clauses, statement) {
|
|
574
601
|
if (interpretations === undefined)
|
|
@@ -592,8 +619,11 @@ function interpretationResults(interpretations, clauses, statement) {
|
|
|
592
619
|
errors.push("interpretation span is invalid");
|
|
593
620
|
if (clause && candidate.span) {
|
|
594
621
|
const candidateLocation = locateSourceText(statement, candidate.claim_text, 0);
|
|
595
|
-
const expectedSpan =
|
|
596
|
-
|
|
622
|
+
const expectedSpan = candidateLocation === null
|
|
623
|
+
? null
|
|
624
|
+
: utf8Span(statement, candidateLocation.start, candidateLocation.end);
|
|
625
|
+
if (expectedSpan === null ||
|
|
626
|
+
candidate.span.start !== expectedSpan.start ||
|
|
597
627
|
candidate.span.end !== expectedSpan.end)
|
|
598
628
|
errors.push(`interpretation span for ${candidate.claim_key} does not match the exact UTF-8 proposition span`);
|
|
599
629
|
}
|
|
@@ -690,6 +720,8 @@ export function analyzeSemanticAdvisorInput(input) {
|
|
|
690
720
|
const clauses = statement
|
|
691
721
|
? extractSemanticClauses(statement, input.clauses ?? semanticClausesOf(payload))
|
|
692
722
|
: [];
|
|
723
|
+
if (input.clauses !== undefined)
|
|
724
|
+
assertVerbatimClauses(statement, clauses);
|
|
693
725
|
const rawDeclaredClaims = propertiesOf(payload).logic_claims;
|
|
694
726
|
const declaredClaims = Array.isArray(rawDeclaredClaims)
|
|
695
727
|
? rawDeclaredClaims.filter((value) => typeof value === "string")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../../src/public/impact/analyzer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../../src/public/impact/analyzer.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CAsJlC"}
|