kibi-cli 2.5.1 → 2.6.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/README.md +41 -0
- package/dist/intent-search.d.ts.map +1 -1
- package/dist/intent-search.js +12 -32
- package/dist/operations/bootstrap/generate.d.ts.map +1 -1
- package/dist/operations/bootstrap/generate.js +108 -8
- package/dist/operations/bootstrap/intent-claims.d.ts.map +1 -1
- package/dist/operations/bootstrap/intent-claims.js +161 -4
- package/dist/operations/bootstrap/presentation.d.ts +8 -0
- package/dist/operations/bootstrap/presentation.d.ts.map +1 -1
- package/dist/operations/bootstrap/presentation.js +92 -8
- package/dist/operations/bootstrap/types.d.ts +21 -0
- package/dist/operations/bootstrap/types.d.ts.map +1 -1
- package/dist/operations/bootstrap/types.js +10 -0
- package/dist/operations/semantic-advisor/ingestion-boundary.d.ts +7 -2
- package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/ingestion-boundary.js +61 -24
- package/dist/plugins/approved-source-analyzers.generated.d.ts +5 -5
- package/dist/plugins/approved-source-analyzers.generated.js +5 -5
- package/dist/public/operations/catalog.d.ts +46 -4
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/discovery-entities.d.ts +5 -3
- package/dist/public/operations/discovery-entities.d.ts.map +1 -1
- package/dist/public/operations/discovery-entities.js +8 -4
- package/dist/public/operations/specs/bootstrap.d.ts +46 -4
- package/dist/public/operations/specs/bootstrap.d.ts.map +1 -1
- package/dist/public/operations/specs/bootstrap.js +41 -5
- package/dist/public/skills/kibi-bootstrap/SKILL.md +13 -7
- package/dist/public/skills/kibi-bootstrap/resources/bootstrap.md +4 -1
- package/package.json +11 -6
- package/src/public/operations/discovery-entities.ts +14 -5
- package/src/public/operations/specs/bootstrap.ts +44 -4
- package/src/public/skills/kibi-bootstrap/SKILL.md +13 -7
- package/src/public/skills/kibi-bootstrap/resources/bootstrap.md +4 -1
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# kibi-cli
|
|
2
|
+
|
|
3
|
+
The `kibi` command-line interface for [Kibi](https://looted.github.io/kibi/), an agent-native requirements compiler and enforcement layer. Kibi keeps a branch-local knowledge base of requirements, scenarios, tests, facts and code-symbol links in your repository's `.kb/` directory and checks it with Prolog.
|
|
4
|
+
|
|
5
|
+
Most people install `kibi-cli` together with `kibi-core` and `kibi-mcp`, then let their coding agent drive it.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Requires Node.js 22+. SWI-Prolog is bundled for Linux (x64/arm64, glibc 2.28+) and macOS (Apple silicon/Intel); elsewhere, install SWI-Prolog 9.0+ and put `swipl` on your `PATH`.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install --save-dev kibi-core kibi-cli kibi-mcp
|
|
13
|
+
npm exec -- kibi init
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Do not install with `--omit=optional`: the bundled SWI-Prolog runtime is an optional dependency. The [installation guide](https://looted.github.io/kibi/guide/install.html) covers pnpm, Yarn and Bun.
|
|
17
|
+
|
|
18
|
+
## Common commands
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
kibi init # create the .kb/ layout and Git hooks
|
|
22
|
+
kibi doctor # diagnose the environment, including which SWI-Prolog is used
|
|
23
|
+
kibi sync # rebuild the KB from the checkout
|
|
24
|
+
kibi status # freshness and health of the current branch's KB
|
|
25
|
+
kibi search # ask a question of the KB
|
|
26
|
+
kibi check # validate traceability, contradictions and semantics
|
|
27
|
+
kibi report # write the kibi-report/ health page and badge
|
|
28
|
+
kibi migrate # preview and apply KB schema upgrades
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Agents without MCP can call the same operations through JSON routes, for example `printf '%s\n' '{...}' | npx --no-install kibi <route> --input -`. See the [CLI reference](https://looted.github.io/kibi/reference/cli.html) for every command and route.
|
|
32
|
+
|
|
33
|
+
## Links
|
|
34
|
+
|
|
35
|
+
- [Documentation](https://looted.github.io/kibi/)
|
|
36
|
+
- [Source](https://github.com/Looted/kibi)
|
|
37
|
+
- [Issues](https://github.com/Looted/kibi/issues)
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
AGPL-3.0-or-later
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intent-search.d.ts","sourceRoot":"","sources":["../src/intent-search.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"intent-search.d.ts","sourceRoot":"","sources":["../src/intent-search.ts"],"names":[],"mappings":"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"}
|
package/dist/intent-search.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { escapeAtom, normalizeEntityId, parseTriples } from "./prolog/codec.js";
|
|
3
|
-
import {
|
|
3
|
+
import { listSearchCandidates, loadEntityRows, loadSearchCandidates, } from "./public/operations/discovery-entities.js";
|
|
4
4
|
import { loadMarkdownBody } from "./search-ranking.js";
|
|
5
5
|
const GRAPH_RELATIONSHIPS = [
|
|
6
6
|
"implements",
|
|
@@ -502,28 +502,18 @@ async function loadIntentCandidates(prolog, options) {
|
|
|
502
502
|
candidates.set(key, { ...entity });
|
|
503
503
|
}
|
|
504
504
|
};
|
|
505
|
+
// Source-located candidates are projected rows too, read through the
|
|
506
|
+
// same source filter kb_query_entities applies, so a file with many
|
|
507
|
+
// receipt-bearing tests does not ship their complete payloads.
|
|
505
508
|
const sourceLocations = options.sourceLocations ?? [];
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const page = await prolog.queryEntities({
|
|
515
|
-
...(options.type !== undefined ? { type: options.type } : {}),
|
|
516
|
-
sourceFile: location.path,
|
|
517
|
-
limit,
|
|
518
|
-
offset,
|
|
519
|
-
});
|
|
520
|
-
if (page.entities.length === 0)
|
|
521
|
-
break;
|
|
522
|
-
addCandidates(page.entities);
|
|
523
|
-
total = page.count;
|
|
524
|
-
offset += page.entities.length;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
509
|
+
for (const location of sourceLocations) {
|
|
510
|
+
if (candidates.size >= MAX_CANDIDATES)
|
|
511
|
+
break;
|
|
512
|
+
addCandidates(await listSearchCandidates(prolog, {
|
|
513
|
+
...(options.type !== undefined ? { type: options.type } : {}),
|
|
514
|
+
sourceFile: location.path,
|
|
515
|
+
maxCandidates: MAX_CANDIDATES - candidates.size,
|
|
516
|
+
}));
|
|
527
517
|
}
|
|
528
518
|
// Lexical candidates are projected rows (no receipt histories or other
|
|
529
519
|
// large structured properties); ports without the engine method run the
|
|
@@ -544,16 +534,6 @@ async function loadIntentCandidates(prolog, options) {
|
|
|
544
534
|
maxCandidates: MAX_CANDIDATES,
|
|
545
535
|
}));
|
|
546
536
|
}
|
|
547
|
-
else if (sourceLocations.length > 0 && !prolog.queryEntities) {
|
|
548
|
-
for (const location of sourceLocations) {
|
|
549
|
-
if (candidates.size >= MAX_CANDIDATES)
|
|
550
|
-
break;
|
|
551
|
-
addCandidates(await loadEntities(prolog, {
|
|
552
|
-
...(options.type !== undefined ? { type: options.type } : {}),
|
|
553
|
-
sourceFile: location.path,
|
|
554
|
-
}));
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
537
|
// An unfamiliar host-agent alias may not exist in the lexical index at all.
|
|
558
538
|
// For small facet-bearing corpora, scan the bounded entity set so a zero
|
|
559
539
|
// lexical hit does not turn a valid semantic query into a false abstention.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/operations/bootstrap/generate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/operations/bootstrap/generate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAUjF,OAAO,KAAK,EACV,SAAS,EACT,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAGpB,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GACzC,SAAS,gBAAgB,EAAE,CAI7B;AA+BD,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EAChC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EAC1C,OAAO,EAAE,MAAM,GACd;IACD,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,QAAQ,CAAC,UAAU,EAAE,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;IAClE,QAAQ,CAAC,iBAAiB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACxD,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC,CA6IA;AA+JD,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,mBAAmB,CAAC,CAqG9B"}
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { loadEntityIds } from "../../public/operations/discovery-entities.js";
|
|
4
4
|
import { executeStatus } from "../../public/operations/discovery-executors.js";
|
|
5
5
|
import { readWorkspaceSnapshot } from "../../public/operations/workspace-snapshot.js";
|
|
6
|
+
import { stagedLogicalGroundingError, } from "../semantic-advisor/ingestion-boundary.js";
|
|
6
7
|
import { buildBootstrapCandidates } from "./candidates.js";
|
|
7
8
|
import { discoverBootstrap } from "./discovery.js";
|
|
8
9
|
import { buildIntentClaimCandidates } from "./intent-claims.js";
|
|
@@ -35,6 +36,7 @@ function firstUpsertId(candidate) {
|
|
|
35
36
|
function sha256(value) {
|
|
36
37
|
return createHash("sha256").update(value).digest("hex");
|
|
37
38
|
}
|
|
39
|
+
// implements REQ-bootstrap-write-safety, REQ-bootstrap-intent-claim-accounting, REQ-bootstrap-discovered-candidate-budget
|
|
38
40
|
export function selectBootstrapCandidates(input, existingIds, entityTypes, maximum) {
|
|
39
41
|
const allowed = entityTypes?.length ? new Set(entityTypes) : null;
|
|
40
42
|
const lane = (candidate) => {
|
|
@@ -59,8 +61,12 @@ export function selectBootstrapCandidates(input, existingIds, entityTypes, maxim
|
|
|
59
61
|
};
|
|
60
62
|
const writable = input
|
|
61
63
|
.filter((candidate) => {
|
|
62
|
-
if (allowed && !allowed.has(candidate.entityType))
|
|
64
|
+
if (allowed && !allowed.has(candidate.entityType)) {
|
|
65
|
+
// A filtered claim is the caller's choice, not a dropped claim.
|
|
66
|
+
if (candidate.sourceKind === "intent_claim")
|
|
67
|
+
suppress(candidate, "filtered_by_entity_type");
|
|
63
68
|
return false;
|
|
69
|
+
}
|
|
64
70
|
if (existingIds.has(firstUpsertId(candidate))) {
|
|
65
71
|
suppress(candidate, "entity_exists");
|
|
66
72
|
return false;
|
|
@@ -105,16 +111,100 @@ export function selectBootstrapCandidates(input, existingIds, entityTypes, maxim
|
|
|
105
111
|
else
|
|
106
112
|
selected.set(key, candidate);
|
|
107
113
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
// Declared intent claims are never capped and sit outside the arithmetic:
|
|
115
|
+
// they are what the human pointed at, and the schema already bounds them.
|
|
116
|
+
// maxCandidates is the budget for candidates Kibi discovers itself, so a
|
|
117
|
+
// long list of declared intent never starves the code, test and document
|
|
118
|
+
// evidence that bootstrap is meant to link it to.
|
|
119
|
+
const written = new Map();
|
|
120
|
+
const staged = new Map();
|
|
121
|
+
const candidates = [];
|
|
122
|
+
let discoveredCount = 0;
|
|
123
|
+
let overLimit = 0;
|
|
124
|
+
for (const candidate of selected.values()) {
|
|
125
|
+
const discovered = candidate.sourceKind !== "intent_claim";
|
|
126
|
+
if (discovered && discoveredCount >= maximum) {
|
|
127
|
+
suppress(candidate, "over_limit");
|
|
128
|
+
overLimit += 1;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
// Two candidates that write one entity ID with different content would
|
|
132
|
+
// overwrite each other mid-apply and break the earlier one's grounding.
|
|
133
|
+
const conflict = candidate.applyPlan.find((payload) => {
|
|
134
|
+
const id = payloadId(payload);
|
|
135
|
+
const previous = id ? written.get(id) : undefined;
|
|
136
|
+
return previous !== undefined && previous !== JSON.stringify(payload);
|
|
137
|
+
});
|
|
138
|
+
if (conflict) {
|
|
139
|
+
suppress(candidate, "duplicate_entity", `Entity ${payloadId(conflict)} is already written by another candidate with different content.`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const groundingError = stagedGroundingError(candidate, staged);
|
|
143
|
+
if (groundingError) {
|
|
144
|
+
suppress(candidate, "invalid_write", groundingError);
|
|
145
|
+
diagnostics.push(`Invalid bootstrap candidate at ${candidate.sourcePath}: ${groundingError}`);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
for (const payload of candidate.applyPlan) {
|
|
149
|
+
const id = payloadId(payload);
|
|
150
|
+
if (!id)
|
|
151
|
+
continue;
|
|
152
|
+
written.set(id, JSON.stringify(payload));
|
|
153
|
+
staged.set(id, stagedEntity(payload));
|
|
154
|
+
}
|
|
155
|
+
if (discovered)
|
|
156
|
+
discoveredCount += 1;
|
|
157
|
+
candidates.push(candidate);
|
|
158
|
+
}
|
|
159
|
+
if (overLimit > 0)
|
|
160
|
+
diagnostics.push(`${overLimit} discovered candidate(s) exceeded maxCandidates ${maximum} and are suppressed as over_limit; declared intent claims do not count against it.`);
|
|
111
161
|
return {
|
|
112
|
-
candidates
|
|
162
|
+
candidates,
|
|
113
163
|
suppressed,
|
|
114
164
|
sourceOnlySignals,
|
|
115
165
|
diagnostics,
|
|
116
166
|
};
|
|
117
167
|
}
|
|
168
|
+
function payloadId(payload) {
|
|
169
|
+
if (typeof payload.id === "string")
|
|
170
|
+
return payload.id;
|
|
171
|
+
const properties = payload.properties;
|
|
172
|
+
return properties !== null &&
|
|
173
|
+
typeof properties === "object" &&
|
|
174
|
+
"id" in properties &&
|
|
175
|
+
typeof properties.id === "string"
|
|
176
|
+
? properties.id
|
|
177
|
+
: "";
|
|
178
|
+
}
|
|
179
|
+
function stagedEntity(payload) {
|
|
180
|
+
const properties = payload.properties !== null && typeof payload.properties === "object"
|
|
181
|
+
? payload.properties
|
|
182
|
+
: {};
|
|
183
|
+
return { ...properties, id: payloadId(payload), type: payload.type };
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Run the write-time claim_key grounding check against the plan's own
|
|
187
|
+
* earlier writes, so a mismatch is a suppressed candidate at plan time rather
|
|
188
|
+
* than a terminal partial apply. Targets outside the plan are left to apply.
|
|
189
|
+
*/
|
|
190
|
+
// implements REQ-bootstrap-intent-claim-accounting
|
|
191
|
+
function stagedGroundingError(candidate, planned) {
|
|
192
|
+
const local = new Map(planned);
|
|
193
|
+
for (const payload of candidate.applyPlan) {
|
|
194
|
+
const id = payloadId(payload);
|
|
195
|
+
if (id)
|
|
196
|
+
local.set(id, stagedEntity(payload));
|
|
197
|
+
}
|
|
198
|
+
for (const payload of candidate.applyPlan) {
|
|
199
|
+
const relationships = Array.isArray(payload.relationships)
|
|
200
|
+
? payload.relationships
|
|
201
|
+
: [];
|
|
202
|
+
const error = stagedLogicalGroundingError(payload, relationships, local);
|
|
203
|
+
if (error)
|
|
204
|
+
return error;
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
118
208
|
async function existingEntityIds(context) {
|
|
119
209
|
if (!context.prolog)
|
|
120
210
|
return new Set();
|
|
@@ -202,10 +292,15 @@ async function expectedSnapshots(context, evidencePaths) {
|
|
|
202
292
|
};
|
|
203
293
|
}
|
|
204
294
|
}
|
|
205
|
-
// implements REQ-KIBI-BOOTSTRAP-PLAN, REQ-kibi-operation-interface-parity
|
|
295
|
+
// implements REQ-KIBI-BOOTSTRAP-PLAN, REQ-kibi-operation-interface-parity, REQ-bootstrap-discovered-candidate-budget
|
|
206
296
|
// implements REQ-KIBI-BOOTSTRAP-PLAN
|
|
207
297
|
export async function executePlanBootstrap(args, context) {
|
|
208
|
-
const
|
|
298
|
+
const declared = normalizeBootstrapContext(args.bootstrapContext);
|
|
299
|
+
// Declared intent claims already carry the human's sources of truth, so
|
|
300
|
+
// generic Markdown would mostly restate them as duplicate_title noise.
|
|
301
|
+
const genericMarkdownDefaultOff = args.includeGenericMarkdown === undefined &&
|
|
302
|
+
(declared.intentClaims?.length ?? 0) > 0;
|
|
303
|
+
const includeGenericMarkdown = args.includeGenericMarkdown ?? !genericMarkdownDefaultOff;
|
|
209
304
|
const minConfidence = clamp(args.minConfidence ?? 0.8, 0.6, 0.95);
|
|
210
305
|
const maxCandidates = clamp(Math.trunc(args.maxCandidates ?? 50), 1, 200);
|
|
211
306
|
const entityBindingDiagnostics = [];
|
|
@@ -232,7 +327,7 @@ export async function executePlanBootstrap(args, context) {
|
|
|
232
327
|
// Intent harvested from declared knowledge sources joins the repository
|
|
233
328
|
// evidence under the same activation policy, filters, and selection.
|
|
234
329
|
const claimed = discovery.activation.allowCandidateGeneration
|
|
235
|
-
? buildIntentClaimCandidates(
|
|
330
|
+
? buildIntentClaimCandidates(declared, existingIds, minConfidence)
|
|
236
331
|
: {
|
|
237
332
|
candidates: [],
|
|
238
333
|
sourceOnlySignals: [],
|
|
@@ -270,6 +365,11 @@ export async function executePlanBootstrap(args, context) {
|
|
|
270
365
|
expected,
|
|
271
366
|
bindingDiagnostics: [...bindingDiagnostics, ...entityBindingDiagnostics],
|
|
272
367
|
contextDiagnostics: [
|
|
368
|
+
...(genericMarkdownDefaultOff
|
|
369
|
+
? [
|
|
370
|
+
"Generic Markdown candidates are off by default because intent claims were declared; pass includeGenericMarkdown: true to include them.",
|
|
371
|
+
]
|
|
372
|
+
: []),
|
|
273
373
|
...claimed.diagnostics,
|
|
274
374
|
...built.diagnostics,
|
|
275
375
|
...selected.diagnostics,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intent-claims.d.ts","sourceRoot":"","sources":["../../../src/operations/bootstrap/intent-claims.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"intent-claims.d.ts","sourceRoot":"","sources":["../../../src/operations/bootstrap/intent-claims.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,wBAAwB,EAGxB,SAAS,EACT,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAQpB,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACxD,QAAQ,CAAC,UAAU,EAAE,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;IAClE,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC,CAAC;AAuKF;;;;;;;GAOG;AAEH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,wBAAwB,EAClC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EAChC,aAAa,EAAE,MAAM,GACpB,sBAAsB,CA8HxB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { buildStrictWriteSet } from "../../utils/strict-modeling.js";
|
|
2
|
-
import { confidenceBand, strictPlan } from "./candidate-helpers.js";
|
|
3
|
+
import { confidenceBand, strictPlan, upsert } from "./candidate-helpers.js";
|
|
3
4
|
import { claimFor } from "./requirement-claims.js";
|
|
4
5
|
/** How much a claim is trusted follows the authority the human assigned. */
|
|
5
6
|
const AUTHORITY_CONFIDENCE = {
|
|
@@ -14,6 +15,132 @@ function citation(source, claim) {
|
|
|
14
15
|
...(source.connector ? [`connector:${source.connector}`] : []),
|
|
15
16
|
];
|
|
16
17
|
}
|
|
18
|
+
function digest(...parts) {
|
|
19
|
+
return createHash("sha256")
|
|
20
|
+
.update(parts.join("\u0000"))
|
|
21
|
+
.digest("hex")
|
|
22
|
+
.slice(0, 16)
|
|
23
|
+
.toUpperCase();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* An observation or open question from a declared source is evidence for the
|
|
27
|
+
* human, not a requirement: it becomes a cited, non-blocking observation fact
|
|
28
|
+
* so the plan keeps it without entering the contradiction lane.
|
|
29
|
+
*/
|
|
30
|
+
// implements REQ-bootstrap-claim-kinds-and-conflicts
|
|
31
|
+
function reviewClaimCandidate(source, claim, kind, confidence) {
|
|
32
|
+
const id = `FACT-BOOTSTRAP-${kind === "open_question" ? "QUESTION" : "OBSERVATION"}-${digest(source.id, claim.reference, claim.statement)}`;
|
|
33
|
+
const entity = {
|
|
34
|
+
type: "fact",
|
|
35
|
+
id,
|
|
36
|
+
title: claim.statement,
|
|
37
|
+
status: "active",
|
|
38
|
+
fact_kind: "observation",
|
|
39
|
+
source: `knowledge-source:${source.id}`,
|
|
40
|
+
text_ref: `${source.id}:${claim.reference}`,
|
|
41
|
+
tags: [
|
|
42
|
+
"bootstrap",
|
|
43
|
+
`knowledge-source:${source.id}`,
|
|
44
|
+
kind === "open_question" ? "review:open-question" : "claim:observation",
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
candidateId: `claim:${source.id}:${id.toLowerCase()}`,
|
|
49
|
+
entityType: "fact",
|
|
50
|
+
title: claim.statement,
|
|
51
|
+
sourceKind: "intent_claim",
|
|
52
|
+
sourcePath: claim.reference,
|
|
53
|
+
confidence,
|
|
54
|
+
confidenceBand: confidenceBand(confidence),
|
|
55
|
+
evidence: [
|
|
56
|
+
...citation(source, claim),
|
|
57
|
+
`claim_kind:${kind}`,
|
|
58
|
+
...(claim.excerpt ? [`excerpt:${claim.excerpt}`] : []),
|
|
59
|
+
],
|
|
60
|
+
relationships: [],
|
|
61
|
+
applyPlan: [upsert(entity)],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A conflict the agent declared between claims becomes an observation fact
|
|
66
|
+
* tagged review:conflict that cites every conflicting claim, so the
|
|
67
|
+
* disagreement stays explicit in the KB instead of an external report.
|
|
68
|
+
*/
|
|
69
|
+
// implements REQ-bootstrap-claim-kinds-and-conflicts
|
|
70
|
+
function conflictCandidates(declared, sources, existingIds, minConfidence) {
|
|
71
|
+
const claims = new Set((declared.intentClaims ?? []).map((claim) => `${claim.sourceId}\u0000${claim.reference}`));
|
|
72
|
+
const candidates = [];
|
|
73
|
+
const suppressed = [];
|
|
74
|
+
const diagnostics = [];
|
|
75
|
+
const confidence = AUTHORITY_CONFIDENCE.authoritative;
|
|
76
|
+
for (const conflict of declared.conflicts ?? []) {
|
|
77
|
+
const refs = conflict.claimReferences.map((ref) => `${ref.sourceId}:${ref.reference}`);
|
|
78
|
+
const undeclared = conflict.claimReferences.filter((ref) => !claims.has(`${ref.sourceId}\u0000${ref.reference}`));
|
|
79
|
+
if (undeclared.length > 0) {
|
|
80
|
+
diagnostics.push(`Conflict "${conflict.note}" cites claim(s) not declared in intentClaims: ${undeclared.map((ref) => `${ref.sourceId}:${ref.reference}`).join(", ")}; declare them or correct the reference.`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const id = `FACT-BOOTSTRAP-CONFLICT-${digest(...[...refs].sort(), conflict.note)}`;
|
|
84
|
+
const candidateId = `conflict:${id.toLowerCase()}`;
|
|
85
|
+
if (confidence < minConfidence) {
|
|
86
|
+
suppressed.push({
|
|
87
|
+
candidateId,
|
|
88
|
+
reason: "below_min_confidence",
|
|
89
|
+
sourcePath: refs.join(", "),
|
|
90
|
+
entityType: "fact",
|
|
91
|
+
});
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (existingIds.has(id)) {
|
|
95
|
+
suppressed.push({
|
|
96
|
+
candidateId,
|
|
97
|
+
reason: "entity_exists",
|
|
98
|
+
sourcePath: refs.join(", "),
|
|
99
|
+
entityType: "fact",
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const title = `Conflict between ${refs.join(" and ")}: ${conflict.note}`;
|
|
104
|
+
candidates.push({
|
|
105
|
+
candidateId,
|
|
106
|
+
entityType: "fact",
|
|
107
|
+
title,
|
|
108
|
+
sourceKind: "intent_claim",
|
|
109
|
+
sourcePath: refs.join(", "),
|
|
110
|
+
confidence,
|
|
111
|
+
confidenceBand: confidenceBand(confidence),
|
|
112
|
+
evidence: conflict.claimReferences.flatMap((ref) => {
|
|
113
|
+
const source = sources.get(ref.sourceId);
|
|
114
|
+
return [
|
|
115
|
+
`intent_claim:${ref.sourceId}:${ref.reference}`,
|
|
116
|
+
...(source
|
|
117
|
+
? [`knowledge_source:${source.kind}:${source.locator}`]
|
|
118
|
+
: []),
|
|
119
|
+
];
|
|
120
|
+
}),
|
|
121
|
+
relationships: [],
|
|
122
|
+
applyPlan: [
|
|
123
|
+
upsert({
|
|
124
|
+
type: "fact",
|
|
125
|
+
id,
|
|
126
|
+
title,
|
|
127
|
+
status: "active",
|
|
128
|
+
fact_kind: "observation",
|
|
129
|
+
source: "bootstrap:declared-conflict",
|
|
130
|
+
text_ref: refs.join("; "),
|
|
131
|
+
tags: [
|
|
132
|
+
"bootstrap",
|
|
133
|
+
"review:conflict",
|
|
134
|
+
...[
|
|
135
|
+
...new Set(conflict.claimReferences.map((ref) => `knowledge-source:${ref.sourceId}`)),
|
|
136
|
+
],
|
|
137
|
+
],
|
|
138
|
+
}),
|
|
139
|
+
],
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return { candidates, sourceOnlySignals: [], suppressed, diagnostics };
|
|
143
|
+
}
|
|
17
144
|
/**
|
|
18
145
|
* Turn intent the agent harvested from declared knowledge sources into cited
|
|
19
146
|
* bootstrap candidates. Kibi never contacts those sources: it only records
|
|
@@ -22,7 +149,7 @@ function citation(source, claim) {
|
|
|
22
149
|
* strict modeler cannot ground stays an explicit authoring follow-up instead
|
|
23
150
|
* of becoming unverifiable knowledge.
|
|
24
151
|
*/
|
|
25
|
-
// implements REQ-kibi-bootstrap-knowledge-sources
|
|
152
|
+
// implements REQ-kibi-bootstrap-knowledge-sources, REQ-bootstrap-claim-kinds-and-conflicts
|
|
26
153
|
export function buildIntentClaimCandidates(declared, existingIds, minConfidence) {
|
|
27
154
|
const sources = new Map((declared.knowledgeSources ?? []).map((source) => [source.id, source]));
|
|
28
155
|
const candidates = [];
|
|
@@ -35,18 +162,35 @@ export function buildIntentClaimCandidates(declared, existingIds, minConfidence)
|
|
|
35
162
|
diagnostics.push(`Intent claim cites an undeclared knowledge source "${claim.sourceId}" (${claim.reference}); declare it in knowledgeSources or drop the claim.`);
|
|
36
163
|
continue;
|
|
37
164
|
}
|
|
165
|
+
const kind = claim.kind ?? "intent";
|
|
38
166
|
if (source.authority === "stale") {
|
|
39
167
|
suppressed.push({
|
|
40
168
|
candidateId: "",
|
|
41
169
|
reason: "stale_knowledge_source",
|
|
42
170
|
sourcePath: claim.reference,
|
|
43
|
-
entityType: "req",
|
|
171
|
+
entityType: kind === "intent" ? "req" : "fact",
|
|
44
172
|
statement: claim.statement,
|
|
45
173
|
sourceId: source.id,
|
|
46
174
|
});
|
|
47
175
|
continue;
|
|
48
176
|
}
|
|
49
177
|
const confidence = AUTHORITY_CONFIDENCE[source.authority];
|
|
178
|
+
if (kind !== "intent") {
|
|
179
|
+
const review = reviewClaimCandidate(source, claim, kind, confidence);
|
|
180
|
+
const id = String(review.applyPlan[0]?.id ?? "");
|
|
181
|
+
if (confidence < minConfidence || existingIds.has(id))
|
|
182
|
+
suppressed.push({
|
|
183
|
+
candidateId: review.candidateId,
|
|
184
|
+
reason: confidence < minConfidence
|
|
185
|
+
? "below_min_confidence"
|
|
186
|
+
: "entity_exists",
|
|
187
|
+
sourcePath: claim.reference,
|
|
188
|
+
entityType: "fact",
|
|
189
|
+
});
|
|
190
|
+
else
|
|
191
|
+
candidates.push(review);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
50
194
|
const evidence = citation(source, claim);
|
|
51
195
|
const provenance = `${source.id}:${claim.reference}`;
|
|
52
196
|
let writeSet = null;
|
|
@@ -82,6 +226,13 @@ export function buildIntentClaimCandidates(declared, existingIds, minConfidence)
|
|
|
82
226
|
});
|
|
83
227
|
continue;
|
|
84
228
|
}
|
|
229
|
+
if (writeSet?.isStrict && confidence < minConfidence)
|
|
230
|
+
suppressed.push({
|
|
231
|
+
candidateId: `claim:${source.id}:${writeSet.req.id.toLowerCase()}`,
|
|
232
|
+
reason: "below_min_confidence",
|
|
233
|
+
sourcePath: claim.reference,
|
|
234
|
+
entityType: "req",
|
|
235
|
+
});
|
|
85
236
|
if (writeSet?.isStrict && existingIds.has(writeSet.req.id)) {
|
|
86
237
|
suppressed.push({
|
|
87
238
|
candidateId: `claim:${source.id}:${writeSet.req.id.toLowerCase()}`,
|
|
@@ -99,5 +250,11 @@ export function buildIntentClaimCandidates(declared, existingIds, minConfidence)
|
|
|
99
250
|
evidence,
|
|
100
251
|
});
|
|
101
252
|
}
|
|
102
|
-
|
|
253
|
+
const declaredConflicts = conflictCandidates(declared, sources, existingIds, minConfidence);
|
|
254
|
+
return {
|
|
255
|
+
candidates: [...candidates, ...declaredConflicts.candidates],
|
|
256
|
+
sourceOnlySignals,
|
|
257
|
+
suppressed: [...suppressed, ...declaredConflicts.suppressed],
|
|
258
|
+
diagnostics: [...diagnostics, ...declaredConflicts.diagnostics],
|
|
259
|
+
};
|
|
103
260
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { ActivationPolicy, BootstrapContext, BootstrapDeclaredContext, BootstrapPlanV1, Candidate, DiscoverySummary, PlanBootstrapResult, SourceOnlySignal } from "./types.js";
|
|
2
2
|
export declare function normalizeBootstrapContext(input?: BootstrapContext): BootstrapDeclaredContext;
|
|
3
|
+
/**
|
|
4
|
+
* Suppressions per reason, most frequent first, so a plan with hundreds of
|
|
5
|
+
* suppressed rows still reads as a handful of lines.
|
|
6
|
+
*/
|
|
7
|
+
export declare function suppressionCounts(rows: readonly Readonly<Record<string, unknown>>[]): readonly {
|
|
8
|
+
readonly reason: string;
|
|
9
|
+
readonly count: number;
|
|
10
|
+
}[];
|
|
3
11
|
export declare function presentBootstrap(input: {
|
|
4
12
|
readonly root: string;
|
|
5
13
|
readonly activation: ActivationPolicy;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation.d.ts","sourceRoot":"","sources":["../../../src/operations/bootstrap/presentation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,
|
|
1
|
+
{"version":3,"file":"presentation.d.ts","sourceRoot":"","sources":["../../../src/operations/bootstrap/presentation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAGhB,gBAAgB,EAChB,wBAAwB,EAGxB,eAAe,EACf,SAAS,EACT,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAuGpB,wBAAgB,yBAAyB,CACvC,KAAK,CAAC,EAAE,gBAAgB,GACvB,wBAAwB,CAe1B;AAgBD;;;GAGG;AAEH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,GACjD,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAYhE;AA0CD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACxD,QAAQ,CAAC,oBAAoB,EAAE,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;IAC5E,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;IAC/C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,wEAAwE;IACxE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjD,GAAG,mBAAmB,CAmNtB"}
|