kibi-cli 2.1.0 → 2.2.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-protocol.d.ts.map +1 -1
- package/dist/cli-protocol.js +3 -2
- package/dist/commands/aggregated-checks.d.ts +7 -0
- package/dist/commands/aggregated-checks.d.ts.map +1 -1
- package/dist/commands/aggregated-checks.js +25 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +21 -13
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +105 -24
- package/dist/commands/github-init.d.ts +1 -1
- package/dist/commands/github-init.d.ts.map +1 -1
- package/dist/commands/github-init.js +1 -1
- package/dist/commands/init-helpers.d.ts +17 -2
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +123 -23
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +110 -15
- package/dist/commands/prove.d.ts.map +1 -1
- package/dist/commands/prove.js +10 -18
- package/dist/commands/sync/cache.d.ts +20 -1
- package/dist/commands/sync/cache.d.ts.map +1 -1
- package/dist/commands/sync/cache.js +51 -2
- package/dist/commands/sync/discovery.d.ts.map +1 -1
- package/dist/commands/sync/discovery.js +1 -1
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +8 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +11 -3
- package/dist/engine.d.ts +26 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +126 -29
- package/dist/extractors/manifest.d.ts.map +1 -1
- package/dist/extractors/manifest.js +66 -28
- package/dist/extractors/markdown.d.ts +2 -0
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +14 -3
- package/dist/extractors/relationships.d.ts.map +1 -1
- package/dist/extractors/relationships.js +1 -0
- package/dist/intent-search.d.ts.map +1 -1
- package/dist/intent-search.js +68 -29
- package/dist/operations/bootstrap/generate.d.ts.map +1 -1
- package/dist/operations/bootstrap/generate.js +4 -3
- package/dist/operations/modeling/model-requirement.d.ts +1 -1
- package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
- package/dist/operations/modeling/model-requirement.js +176 -10
- package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-applyplan.js +14 -2
- package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-loader.js +24 -0
- package/dist/operations/modeling/predicate-types.d.ts +4 -0
- package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-vocabulary.d.ts +54 -0
- package/dist/operations/modeling/predicate-vocabulary.d.ts.map +1 -0
- package/dist/operations/modeling/predicate-vocabulary.js +169 -0
- package/dist/operations/modeling/requirement-types.d.ts +5 -0
- package/dist/operations/modeling/requirement-types.d.ts.map +1 -1
- package/dist/operations/modeling/vocabulary-alignment.d.ts +60 -0
- package/dist/operations/modeling/vocabulary-alignment.d.ts.map +1 -0
- package/dist/operations/modeling/vocabulary-alignment.js +178 -0
- package/dist/operations/mutation/contradictions.d.ts +3 -0
- package/dist/operations/mutation/contradictions.d.ts.map +1 -1
- package/dist/operations/mutation/contradictions.js +29 -0
- package/dist/operations/mutation/delete.d.ts.map +1 -1
- package/dist/operations/mutation/delete.js +33 -1
- package/dist/operations/mutation/predicate-vocabulary-guard.d.ts +8 -0
- package/dist/operations/mutation/predicate-vocabulary-guard.d.ts.map +1 -0
- package/dist/operations/mutation/predicate-vocabulary-guard.js +64 -0
- package/dist/operations/mutation/relationships.d.ts +1 -1
- package/dist/operations/mutation/relationships.d.ts.map +1 -1
- package/dist/operations/mutation/relationships.js +1 -0
- package/dist/operations/mutation/saga.d.ts +5 -0
- package/dist/operations/mutation/saga.d.ts.map +1 -1
- package/dist/operations/mutation/saga.js +10 -0
- package/dist/operations/mutation/serialization.d.ts.map +1 -1
- package/dist/operations/mutation/serialization.js +2 -0
- package/dist/operations/mutation/source-authoring.d.ts +6 -0
- package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
- package/dist/operations/mutation/source-authoring.js +65 -26
- package/dist/operations/mutation/strict-fact.d.ts.map +1 -1
- package/dist/operations/mutation/strict-fact.js +5 -0
- package/dist/operations/mutation/types.d.ts +17 -0
- package/dist/operations/mutation/types.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.d.ts +6 -0
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +72 -4
- package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
- package/dist/operations/mutation/validate-upsert.js +24 -1
- package/dist/operations/mutation/validation.d.ts.map +1 -1
- package/dist/operations/mutation/validation.js +16 -3
- package/dist/operations/planning/apply-plan.d.ts.map +1 -1
- package/dist/operations/planning/apply-plan.js +61 -17
- package/dist/operations/planning/compile-intent.d.ts.map +1 -1
- package/dist/operations/planning/compile-intent.js +3 -1
- package/dist/operations/proof/code-scope.d.ts +17 -0
- package/dist/operations/proof/code-scope.d.ts.map +1 -0
- package/dist/operations/proof/code-scope.js +60 -0
- package/dist/operations/proof/ingest-proof.d.ts +4 -2
- package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
- package/dist/operations/proof/ingest-proof.js +207 -74
- package/dist/operations/proof/migrate-legacy-receipts.d.ts.map +1 -1
- package/dist/operations/proof/migrate-legacy-receipts.js +7 -5
- package/dist/operations/proof/prune-receipts.d.ts.map +1 -1
- package/dist/operations/proof/prune-receipts.js +7 -5
- package/dist/plugins/compose-semantic-classifier.d.ts +6 -1
- package/dist/plugins/compose-semantic-classifier.d.ts.map +1 -1
- package/dist/plugins/compose-semantic-classifier.js +8 -3
- package/dist/plugins/compose-vocabulary-alignment.d.ts +23 -0
- package/dist/plugins/compose-vocabulary-alignment.d.ts.map +1 -0
- package/dist/plugins/compose-vocabulary-alignment.js +141 -0
- package/dist/plugins/external-allowlist.d.ts +7 -0
- package/dist/plugins/external-allowlist.d.ts.map +1 -1
- package/dist/plugins/external-allowlist.js +13 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +4 -0
- package/dist/plugins/registry.d.ts +2 -1
- package/dist/plugins/registry.d.ts.map +1 -1
- package/dist/plugins/registry.js +22 -1
- package/dist/prolog/codec.d.ts.map +1 -1
- package/dist/prolog/codec.js +2 -0
- package/dist/prolog.d.ts +36 -0
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +78 -9
- package/dist/public/diagnostic-usage.d.ts.map +1 -1
- package/dist/public/diagnostic-usage.js +7 -25
- package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
- package/dist/public/impact/full-kb-quality.js +4 -12
- package/dist/public/operations/catalog.d.ts +36 -7
- 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 -2
- package/dist/public/operations/check-helpers.d.ts +6 -0
- package/dist/public/operations/check-helpers.d.ts.map +1 -1
- package/dist/public/operations/check-helpers.js +21 -1
- package/dist/public/operations/contracts.d.ts.map +1 -1
- package/dist/public/operations/contracts.js +16 -0
- package/dist/public/operations/discovery-entities.d.ts +42 -2
- package/dist/public/operations/discovery-entities.d.ts.map +1 -1
- package/dist/public/operations/discovery-entities.js +195 -22
- package/dist/public/operations/discovery-executors.d.ts +1 -0
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +75 -46
- package/dist/public/operations/migration-plan.d.ts +2 -0
- package/dist/public/operations/migration-plan.d.ts.map +1 -1
- package/dist/public/operations/migration-plan.js +50 -0
- package/dist/public/operations/predicate-schema-conformance.d.ts +41 -0
- package/dist/public/operations/predicate-schema-conformance.d.ts.map +1 -0
- package/dist/public/operations/predicate-schema-conformance.js +242 -0
- package/dist/public/operations/prolog-json.d.ts.map +1 -1
- package/dist/public/operations/prolog-json.js +9 -3
- package/dist/public/operations/result-envelope.d.ts +34 -1
- package/dist/public/operations/result-envelope.d.ts.map +1 -1
- package/dist/public/operations/result-envelope.js +78 -2
- package/dist/public/operations/runtime-types.d.ts +1 -1
- package/dist/public/operations/runtime-types.d.ts.map +1 -1
- package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
- package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
- package/dist/public/operations/specs/check-rules.generated.js +8 -1
- package/dist/public/operations/specs/check.d.ts +1 -1
- package/dist/public/operations/specs/discovery.d.ts +7 -1
- package/dist/public/operations/specs/discovery.d.ts.map +1 -1
- package/dist/public/operations/specs/discovery.js +7 -1
- package/dist/public/operations/specs/mutation-schemas.d.ts +27 -4
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +21 -3
- package/dist/public/operations/specs/reporting.d.ts +8 -0
- package/dist/public/operations/specs/reporting.d.ts.map +1 -1
- package/dist/public/operations/specs/reporting.js +46 -15
- package/dist/public/operations/specs/upsert.d.ts +28 -5
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/operations/specs/upsert.js +1 -1
- package/dist/public/operations/symbol-repair-plan.d.ts.map +1 -1
- package/dist/public/operations/symbol-repair-plan.js +8 -3
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +22 -0
- package/dist/public/schemas/relationship.d.ts.map +1 -1
- package/dist/public/schemas/relationship.js +1 -0
- package/dist/public/skills/kibi-usage/SKILL.md +7 -3
- package/dist/public/skills/kibi-usage/resources/fact-lanes.md +4 -0
- package/dist/public/skills/kibi-usage/resources/relationship-directions.md +35 -17
- package/dist/public/skills/kibi-usage/resources/source-authoring.md +36 -0
- package/dist/public/skills/kibi-usage/resources/workflows.md +52 -1
- package/dist/public/telemetry-acceptance.d.ts.map +1 -1
- package/dist/public/telemetry-acceptance.js +21 -5
- package/dist/relationships/shards.d.ts +6 -0
- package/dist/relationships/shards.d.ts.map +1 -1
- package/dist/relationships/shards.js +26 -19
- package/dist/schemas/entity.schema.json +32 -0
- package/dist/traceability/staged-diagnostics.d.ts +1 -1
- package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
- package/dist/traceability/staged-entity-id-style.d.ts +12 -0
- package/dist/traceability/staged-entity-id-style.d.ts.map +1 -0
- package/dist/traceability/staged-entity-id-style.js +68 -0
- package/dist/traceability/temp-kb.d.ts.map +1 -1
- package/dist/traceability/temp-kb.js +8 -0
- package/dist/traceability/validate.js +1 -1
- package/dist/types/entities.d.ts +2 -0
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/types/relationships.d.ts +1 -1
- package/dist/types/relationships.d.ts.map +1 -1
- package/dist/utils/entity-id-style.d.ts +27 -0
- package/dist/utils/entity-id-style.d.ts.map +1 -0
- package/dist/utils/entity-id-style.js +81 -0
- package/dist/utils/git-repository-context.d.ts +50 -0
- package/dist/utils/git-repository-context.d.ts.map +1 -0
- package/dist/utils/git-repository-context.js +259 -0
- package/dist/utils/rule-registry.d.ts +5 -0
- package/dist/utils/rule-registry.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.d.ts +37 -0
- package/dist/utils/rule-registry.generated.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.js +44 -0
- package/dist/utils/strict-modeling.d.ts +7 -0
- package/dist/utils/strict-modeling.d.ts.map +1 -1
- package/dist/utils/strict-modeling.js +11 -0
- package/package.json +4 -4
- package/schema/entities.pl +60 -1
- package/schema/relationships.pl +17 -4
- package/schema/validation.pl +118 -20
- package/src/public/diagnostic-usage.ts +16 -24
- package/src/public/impact/full-kb-quality.ts +4 -16
- package/src/public/operations/check-executor.ts +12 -1
- package/src/public/operations/check-helpers.ts +27 -1
- package/src/public/operations/contracts.ts +19 -0
- package/src/public/operations/discovery-entities.ts +292 -23
- package/src/public/operations/discovery-executors.ts +91 -55
- package/src/public/operations/migration-plan.ts +57 -0
- package/src/public/operations/predicate-schema-conformance.ts +362 -0
- package/src/public/operations/prolog-json.ts +9 -3
- package/src/public/operations/result-envelope.ts +114 -2
- package/src/public/operations/runtime-types.ts +1 -1
- package/src/public/operations/specs/check-rules.generated.ts +8 -1
- package/src/public/operations/specs/discovery.ts +8 -1
- package/src/public/operations/specs/mutation-schemas.ts +23 -3
- package/src/public/operations/specs/reporting.ts +69 -22
- package/src/public/operations/specs/upsert.ts +1 -1
- package/src/public/operations/symbol-repair-plan.ts +13 -3
- package/src/public/schemas/entity.ts +22 -0
- package/src/public/schemas/relationship.ts +1 -0
- package/src/public/skills/kibi-usage/SKILL.md +7 -3
- package/src/public/skills/kibi-usage/resources/fact-lanes.md +4 -0
- package/src/public/skills/kibi-usage/resources/relationship-directions.md +35 -17
- package/src/public/skills/kibi-usage/resources/source-authoring.md +36 -0
- package/src/public/skills/kibi-usage/resources/workflows.md +52 -1
- package/src/public/telemetry-acceptance.ts +37 -6
- package/src/schemas/entity.schema.json +32 -0
- package/src/schemas/relationship.schema.json +1 -0
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
escapeAtomContent,
|
|
3
|
+
normalizeEntityId,
|
|
4
|
+
parseAtomList,
|
|
3
5
|
parseEntityFromBinding,
|
|
4
6
|
parseEntityFromList,
|
|
5
7
|
parseListOfLists,
|
|
6
8
|
} from "../../prolog/codec.js";
|
|
7
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
PrologEntityQueryInput,
|
|
11
|
+
PrologEntityQueryResult,
|
|
12
|
+
PrologPort,
|
|
13
|
+
PrologSearchQueryInput,
|
|
14
|
+
PrologSearchQueryResult,
|
|
15
|
+
} from "./runtime-types.js";
|
|
8
16
|
|
|
9
17
|
export type EntityQueryInput = {
|
|
10
18
|
readonly type?: string;
|
|
@@ -14,6 +22,11 @@ export type EntityQueryInput = {
|
|
|
14
22
|
/** Page size for index-backed queries; bounds each query's output. */
|
|
15
23
|
readonly limit?: number;
|
|
16
24
|
readonly offset?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Proof selection only needs the contract. Omit receipt histories so a
|
|
27
|
+
* campaign does not page every full test entity.
|
|
28
|
+
*/
|
|
29
|
+
readonly projection?: "proof_contract";
|
|
17
30
|
};
|
|
18
31
|
|
|
19
32
|
export const VALID_ENTITY_TYPES = [
|
|
@@ -36,9 +49,22 @@ export function validateEntityType(type?: string): void {
|
|
|
36
49
|
}
|
|
37
50
|
}
|
|
38
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Proof campaign selection must not materialize every contracted test in a
|
|
54
|
+
* single Prolog result. The specialized projection excludes receipt histories
|
|
55
|
+
* and returns one bounded page at a time.
|
|
56
|
+
*/
|
|
57
|
+
export const PROOF_CONTRACT_PAGE_SIZE = 100;
|
|
58
|
+
|
|
39
59
|
// implements REQ-002, REQ-013
|
|
40
60
|
export function buildEntityGoal(input: EntityQueryInput): string {
|
|
41
61
|
const { type, id, tags, sourceFile } = input;
|
|
62
|
+
if (type === "test" && input.projection === "proof_contract") {
|
|
63
|
+
const idTerm = id ? `some('${escapeAtomContent(id)}')` : "none";
|
|
64
|
+
const limit = input.limit ?? PROOF_CONTRACT_PAGE_SIZE;
|
|
65
|
+
const offset = input.offset ?? 0;
|
|
66
|
+
return `kb_query_proof_contracts(${idTerm},${limit},${offset},Results)`;
|
|
67
|
+
}
|
|
42
68
|
if (sourceFile) {
|
|
43
69
|
const safeSource = escapeAtomContent(sourceFile);
|
|
44
70
|
if (type) {
|
|
@@ -75,24 +101,72 @@ export function buildEntityGoal(input: EntityQueryInput): string {
|
|
|
75
101
|
return "findall([Id,Type,Props], kb_entity(Id, Type, Props), Results)";
|
|
76
102
|
}
|
|
77
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Full test entities carry their receipt histories, which only grow. Callers
|
|
106
|
+
* that must see every full entity of a type page through the indexed query so
|
|
107
|
+
* no single Prolog answer can approach the bounded output capacity.
|
|
108
|
+
*/
|
|
109
|
+
const FULL_ENTITY_PAGE_SIZE = 25;
|
|
110
|
+
|
|
111
|
+
// implements REQ-002, REQ-013
|
|
112
|
+
export async function loadEntitiesPaged(
|
|
113
|
+
prolog: Pick<PrologPort, "query">,
|
|
114
|
+
type: (typeof VALID_ENTITY_TYPES)[number],
|
|
115
|
+
pageSize: number = FULL_ENTITY_PAGE_SIZE,
|
|
116
|
+
): Promise<Record<string, unknown>[]> {
|
|
117
|
+
if (!Number.isInteger(pageSize) || pageSize <= 0) {
|
|
118
|
+
throw new Error("loadEntitiesPaged pageSize must be a positive integer");
|
|
119
|
+
}
|
|
120
|
+
const entities: Record<string, unknown>[] = [];
|
|
121
|
+
for (let offset = 0; ; offset += pageSize) {
|
|
122
|
+
const page = await loadEntities(prolog, { type, limit: pageSize, offset });
|
|
123
|
+
entities.push(...page);
|
|
124
|
+
if (page.length < pageSize) break;
|
|
125
|
+
}
|
|
126
|
+
return dedupeEntities(entities);
|
|
127
|
+
}
|
|
128
|
+
|
|
78
129
|
// implements REQ-002, REQ-013
|
|
79
130
|
export async function loadEntities(
|
|
80
131
|
prolog: Pick<PrologPort, "query">,
|
|
81
132
|
input: EntityQueryInput,
|
|
82
133
|
): Promise<Record<string, unknown>[]> {
|
|
83
134
|
validateEntityType(input.type);
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
135
|
+
const readPage = async (
|
|
136
|
+
pageInput: EntityQueryInput,
|
|
137
|
+
): Promise<Record<string, unknown>[]> => {
|
|
138
|
+
const queryResult = await prolog.query(buildEntityGoal(pageInput));
|
|
139
|
+
if (!queryResult.success) {
|
|
140
|
+
throw new Error(queryResult.error || "Query failed with unknown error");
|
|
141
|
+
}
|
|
142
|
+
const resultsBinding = queryResult.bindings.Results;
|
|
143
|
+
const resultBinding = queryResult.bindings.Result;
|
|
144
|
+
if (resultsBinding) {
|
|
145
|
+
return parseListOfLists(resultsBinding).map(parseEntityFromList);
|
|
146
|
+
}
|
|
147
|
+
return resultBinding ? [parseEntityFromBinding(resultBinding)] : [];
|
|
148
|
+
};
|
|
88
149
|
|
|
89
|
-
let entities: Record<string, unknown>[]
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
150
|
+
let entities: Record<string, unknown>[];
|
|
151
|
+
if (
|
|
152
|
+
input.type === "test" &&
|
|
153
|
+
input.projection === "proof_contract" &&
|
|
154
|
+
input.id === undefined &&
|
|
155
|
+
input.limit === undefined &&
|
|
156
|
+
input.offset === undefined
|
|
157
|
+
) {
|
|
158
|
+
entities = [];
|
|
159
|
+
for (let offset = 0; ; offset += PROOF_CONTRACT_PAGE_SIZE) {
|
|
160
|
+
const page = await readPage({
|
|
161
|
+
...input,
|
|
162
|
+
limit: PROOF_CONTRACT_PAGE_SIZE,
|
|
163
|
+
offset,
|
|
164
|
+
});
|
|
165
|
+
entities.push(...page);
|
|
166
|
+
if (page.length < PROOF_CONTRACT_PAGE_SIZE) break;
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
entities = await readPage(input);
|
|
96
170
|
}
|
|
97
171
|
if (input.tags && input.tags.length > 0) {
|
|
98
172
|
const requested = new Set(input.tags.map((tag) => tag.trim()));
|
|
@@ -125,34 +199,98 @@ export function paginateResults<T>(
|
|
|
125
199
|
// implements REQ-kibi-operation-interface-parity, REQ-mcp-search-discovery
|
|
126
200
|
export const SEARCH_CANDIDATE_PAGE_SIZE = 500;
|
|
127
201
|
|
|
202
|
+
function isPrologOutputOverflow(error: unknown): boolean {
|
|
203
|
+
return error instanceof Error && error.message.includes("ENOBUFS");
|
|
204
|
+
}
|
|
205
|
+
|
|
128
206
|
// implements REQ-kibi-operation-interface-parity, REQ-mcp-search-discovery
|
|
129
207
|
export async function loadSearchCandidates(
|
|
130
|
-
prolog: Pick<PrologPort, "searchEntities"
|
|
208
|
+
prolog: Pick<PrologPort, "searchEntities"> &
|
|
209
|
+
Partial<Pick<PrologPort, "query">>,
|
|
131
210
|
input: {
|
|
132
211
|
readonly query: string;
|
|
133
212
|
readonly type?: string;
|
|
134
213
|
readonly maxCandidates?: number;
|
|
135
214
|
},
|
|
136
215
|
signal?: AbortSignal,
|
|
216
|
+
): Promise<Record<string, unknown>[]> {
|
|
217
|
+
// Call through the port property, never via a detached local: EngineClient
|
|
218
|
+
// searchEntities depends on `this` (this.command), and the PrologPort
|
|
219
|
+
// contract does not promise a pre-bound method. Ports without the indexed
|
|
220
|
+
// engine method run the same bounded Prolog search through `query`.
|
|
221
|
+
const readPage = (
|
|
222
|
+
page: PrologSearchQueryInput,
|
|
223
|
+
): Promise<PrologSearchQueryResult> => {
|
|
224
|
+
if (prolog.searchEntities) return prolog.searchEntities(page, signal);
|
|
225
|
+
const query = prolog.query;
|
|
226
|
+
if (query === undefined) return Promise.resolve({ entities: [], count: 0 });
|
|
227
|
+
return searchEntitiesViaQuery(
|
|
228
|
+
{ query: (goal, querySignal) => query.call(prolog, goal, querySignal) },
|
|
229
|
+
page,
|
|
230
|
+
signal,
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
return readCandidatePages(readPage, input);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Projected candidate rows for every entity (optionally of one type), in id
|
|
238
|
+
* order, bounded by `maxCandidates`. For semantic discovery that has no
|
|
239
|
+
* lexical token to pre-filter candidates.
|
|
240
|
+
*/
|
|
241
|
+
// implements REQ-kibi-operation-interface-parity, REQ-mcp-search-discovery
|
|
242
|
+
export async function listSearchCandidates(
|
|
243
|
+
prolog: Pick<PrologPort, "query">,
|
|
244
|
+
input: { readonly type?: string; readonly maxCandidates: number },
|
|
245
|
+
signal?: AbortSignal,
|
|
246
|
+
): Promise<Record<string, unknown>[]> {
|
|
247
|
+
const type = input.type ? `'${escapeAtomContent(input.type)}'` : "none";
|
|
248
|
+
return readCandidatePages(
|
|
249
|
+
(page) =>
|
|
250
|
+
readRowsAndCount(
|
|
251
|
+
prolog,
|
|
252
|
+
`kb_list_search_candidates(${type}, ${page.limit}, ${page.offset}, Rows, Count)`,
|
|
253
|
+
"Search candidate listing failed",
|
|
254
|
+
signal,
|
|
255
|
+
),
|
|
256
|
+
{ query: "", maxCandidates: input.maxCandidates },
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function readCandidatePages(
|
|
261
|
+
readPage: (page: PrologSearchQueryInput) => Promise<PrologSearchQueryResult>,
|
|
262
|
+
input: {
|
|
263
|
+
readonly query: string;
|
|
264
|
+
readonly type?: string;
|
|
265
|
+
readonly maxCandidates?: number;
|
|
266
|
+
},
|
|
137
267
|
): Promise<Record<string, unknown>[]> {
|
|
138
268
|
const maxCandidates = input.maxCandidates ?? Number.POSITIVE_INFINITY;
|
|
139
269
|
const candidates: Record<string, unknown>[] = [];
|
|
140
270
|
let offset = 0;
|
|
141
271
|
let total = Number.POSITIVE_INFINITY;
|
|
272
|
+
let pageSize = SEARCH_CANDIDATE_PAGE_SIZE;
|
|
142
273
|
while (offset < total && candidates.length < maxCandidates) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const page = await prolog.searchEntities?.(
|
|
147
|
-
{
|
|
274
|
+
let page: PrologSearchQueryResult;
|
|
275
|
+
try {
|
|
276
|
+
page = await readPage({
|
|
148
277
|
query: input.query,
|
|
149
278
|
...(input.type !== undefined ? { type: input.type } : {}),
|
|
150
|
-
limit:
|
|
279
|
+
limit: pageSize,
|
|
151
280
|
offset,
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
281
|
+
});
|
|
282
|
+
} catch (error) {
|
|
283
|
+
// A page of unusually large rows can still exceed the bounded Prolog
|
|
284
|
+
// output. Halve the page and retry the same offset; the smaller size is
|
|
285
|
+
// kept for the rest of the scan. Only a single row that overflows on its
|
|
286
|
+
// own is a real failure.
|
|
287
|
+
if (pageSize > 1 && isPrologOutputOverflow(error)) {
|
|
288
|
+
pageSize = Math.max(1, Math.floor(pageSize / 2));
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
if (page.entities.length === 0) break;
|
|
156
294
|
candidates.push(...page.entities);
|
|
157
295
|
total = page.count;
|
|
158
296
|
offset += page.entities.length;
|
|
@@ -162,6 +300,137 @@ export async function loadSearchCandidates(
|
|
|
162
300
|
: candidates;
|
|
163
301
|
}
|
|
164
302
|
|
|
303
|
+
function searchEntitiesViaQuery(
|
|
304
|
+
prolog: Pick<PrologPort, "query">,
|
|
305
|
+
input: PrologSearchQueryInput,
|
|
306
|
+
signal?: AbortSignal,
|
|
307
|
+
): Promise<PrologSearchQueryResult> {
|
|
308
|
+
const type = input.type ? `'${escapeAtomContent(input.type)}'` : "none";
|
|
309
|
+
return readRowsAndCount(
|
|
310
|
+
prolog,
|
|
311
|
+
`kb_search_entities(${type}, '${escapeAtomContent(input.query)}', ${input.limit}, ${input.offset}, Rows, Count)`,
|
|
312
|
+
"Indexed search candidate query failed",
|
|
313
|
+
signal,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function readRowsAndCount(
|
|
318
|
+
prolog: Pick<PrologPort, "query">,
|
|
319
|
+
goal: string,
|
|
320
|
+
errorLabel: string,
|
|
321
|
+
signal?: AbortSignal,
|
|
322
|
+
): Promise<PrologEntityQueryResult> {
|
|
323
|
+
const result = await prolog.query(goal, signal);
|
|
324
|
+
if (!result.success) {
|
|
325
|
+
// Same message as the engine client's indexed methods, so both surfaces
|
|
326
|
+
// report a failure identically.
|
|
327
|
+
throw new Error(result.error ?? errorLabel);
|
|
328
|
+
}
|
|
329
|
+
const entities = result.bindings.Rows
|
|
330
|
+
? parseListOfLists(result.bindings.Rows).map(parseEntityFromList)
|
|
331
|
+
: [];
|
|
332
|
+
const count = Number.parseInt(result.bindings.Count ?? "0", 10);
|
|
333
|
+
return { entities, count: Number.isFinite(count) ? count : entities.length };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Rows per `kb_query_entities` request. Full entities carry receipt
|
|
338
|
+
* histories, so a caller-sized page (up to 100k rows) is split into bounded
|
|
339
|
+
* requests and concatenated; the reported count is the total match count.
|
|
340
|
+
*/
|
|
341
|
+
export const ENTITY_QUERY_CHUNK_SIZE = 25;
|
|
342
|
+
|
|
343
|
+
// implements REQ-kibi-operation-interface-parity
|
|
344
|
+
export async function queryEntityChunks(
|
|
345
|
+
readChunk: (
|
|
346
|
+
input: PrologEntityQueryInput,
|
|
347
|
+
) => Promise<PrologEntityQueryResult>,
|
|
348
|
+
input: PrologEntityQueryInput,
|
|
349
|
+
): Promise<PrologEntityQueryResult> {
|
|
350
|
+
const entities: Record<string, unknown>[] = [];
|
|
351
|
+
let count = 0;
|
|
352
|
+
let first = true;
|
|
353
|
+
while (first || entities.length < input.limit) {
|
|
354
|
+
const chunkLimit = Math.min(
|
|
355
|
+
ENTITY_QUERY_CHUNK_SIZE,
|
|
356
|
+
input.limit - entities.length,
|
|
357
|
+
);
|
|
358
|
+
const chunk = await readChunk({
|
|
359
|
+
...input,
|
|
360
|
+
limit: chunkLimit,
|
|
361
|
+
offset: input.offset + entities.length,
|
|
362
|
+
});
|
|
363
|
+
if (first) count = chunk.count;
|
|
364
|
+
first = false;
|
|
365
|
+
entities.push(...chunk.entities);
|
|
366
|
+
if (chunk.entities.length < chunkLimit || chunkLimit === 0) break;
|
|
367
|
+
}
|
|
368
|
+
return { entities, count };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Bounded `kb_query_entities` paging for ports without the engine method. */
|
|
372
|
+
// implements REQ-kibi-operation-interface-parity
|
|
373
|
+
export function queryEntitiesViaQuery(
|
|
374
|
+
prolog: Pick<PrologPort, "query">,
|
|
375
|
+
input: PrologEntityQueryInput,
|
|
376
|
+
signal?: AbortSignal,
|
|
377
|
+
): Promise<PrologEntityQueryResult> {
|
|
378
|
+
const atom = (value: string | undefined) =>
|
|
379
|
+
value === undefined ? "none" : `'${escapeAtomContent(value)}'`;
|
|
380
|
+
const tags = `[${(input.tags ?? []).map((tag) => `'${escapeAtomContent(tag)}'`).join(",")}]`;
|
|
381
|
+
return queryEntityChunks(
|
|
382
|
+
(chunk) =>
|
|
383
|
+
readRowsAndCount(
|
|
384
|
+
prolog,
|
|
385
|
+
`kb_query_entities(${atom(chunk.type)}, ${atom(chunk.id)}, ${tags}, ${atom(chunk.sourceFile)}, ${chunk.limit}, ${chunk.offset}, Rows, Count)`,
|
|
386
|
+
"Indexed entity query failed",
|
|
387
|
+
signal,
|
|
388
|
+
),
|
|
389
|
+
input,
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** Every entity id, without materializing entity properties. */
|
|
394
|
+
// implements REQ-002
|
|
395
|
+
export async function loadEntityIds(
|
|
396
|
+
prolog: Pick<PrologPort, "query">,
|
|
397
|
+
): Promise<string[]> {
|
|
398
|
+
const result = await prolog.query("kb_entity_ids(Ids)");
|
|
399
|
+
if (!result.success) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
`Entity id enumeration failed: ${result.error ?? "Unknown error"}`,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
return parseAtomList(result.bindings.Ids ?? "[]").map(normalizeEntityId);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Replace projected search candidates with complete entities, one bounded
|
|
409
|
+
* query per entity. Used only for the final result page when a caller asks
|
|
410
|
+
* for full entity bodies.
|
|
411
|
+
*/
|
|
412
|
+
// implements REQ-mcp-search-discovery
|
|
413
|
+
export async function reloadFullEntities<
|
|
414
|
+
TMatch extends { readonly entity: Record<string, unknown> },
|
|
415
|
+
>(
|
|
416
|
+
prolog: Pick<PrologPort, "query">,
|
|
417
|
+
matches: readonly TMatch[],
|
|
418
|
+
): Promise<TMatch[]> {
|
|
419
|
+
const reloaded: TMatch[] = [];
|
|
420
|
+
for (const match of matches) {
|
|
421
|
+
const id = String(match.entity.id ?? "");
|
|
422
|
+
const type = match.entity.type;
|
|
423
|
+
const [full] = id
|
|
424
|
+
? await loadEntities(prolog, {
|
|
425
|
+
id,
|
|
426
|
+
...(typeof type === "string" ? { type } : {}),
|
|
427
|
+
})
|
|
428
|
+
: [];
|
|
429
|
+
reloaded.push(full ? { ...match, entity: full } : match);
|
|
430
|
+
}
|
|
431
|
+
return reloaded;
|
|
432
|
+
}
|
|
433
|
+
|
|
165
434
|
// implements REQ-002
|
|
166
435
|
export function dedupeEntities(
|
|
167
436
|
entities: readonly Record<string, unknown>[],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { compilerFingerprintReason } from "../../commands/sync/cache.js";
|
|
1
2
|
import { EngineClient } from "../../engine.js";
|
|
2
3
|
import {
|
|
3
4
|
type IntentSearchAnalysis,
|
|
@@ -21,6 +22,8 @@ import {
|
|
|
21
22
|
loadEntities,
|
|
22
23
|
loadSearchCandidates,
|
|
23
24
|
paginateResults,
|
|
25
|
+
queryEntitiesViaQuery,
|
|
26
|
+
reloadFullEntities,
|
|
24
27
|
validateEntityType,
|
|
25
28
|
} from "./discovery-entities.js";
|
|
26
29
|
import {
|
|
@@ -60,6 +63,7 @@ export type SearchInput = {
|
|
|
60
63
|
readonly semanticFacets?: IntentSearchFacets;
|
|
61
64
|
readonly sourceLocations?: readonly SourceLocation[];
|
|
62
65
|
readonly minScore?: number;
|
|
66
|
+
readonly fields?: "summary" | "full";
|
|
63
67
|
};
|
|
64
68
|
|
|
65
69
|
export type SearchPayload = {
|
|
@@ -140,51 +144,29 @@ export async function executeQuery(
|
|
|
140
144
|
// implements REQ-kibi-operation-interface-parity
|
|
141
145
|
const { type, id, tags, sourceFile, limit = 100, offset = 0 } = input;
|
|
142
146
|
try {
|
|
147
|
+
validateEntityType(type);
|
|
143
148
|
const prolog = requireProlog(context);
|
|
144
149
|
const signal = context.signal;
|
|
145
|
-
const
|
|
146
|
-
? await prolog.queryEntities(
|
|
147
|
-
{
|
|
148
|
-
...(type !== undefined ? { type } : {}),
|
|
149
|
-
...(id !== undefined ? { id } : {}),
|
|
150
|
-
...(tags !== undefined ? { tags } : {}),
|
|
151
|
-
...(sourceFile !== undefined ? { sourceFile } : {}),
|
|
152
|
-
limit,
|
|
153
|
-
offset,
|
|
154
|
-
},
|
|
155
|
-
signal,
|
|
156
|
-
)
|
|
157
|
-
: null;
|
|
158
|
-
if (indexedPage !== null) {
|
|
159
|
-
const paginated = indexedPage.entities;
|
|
160
|
-
const text =
|
|
161
|
-
indexedPage.count === 0
|
|
162
|
-
? `No entities found${type ? ` of type '${type}'` : ""}.`
|
|
163
|
-
: `Found ${indexedPage.count} entities${type ? ` of type '${type}'` : ""}. Showing ${paginated.length} (offset ${offset}, limit ${limit}): ${paginated
|
|
164
|
-
.map((entity) => {
|
|
165
|
-
const entityId = String(entity.id ?? "").replace(
|
|
166
|
-
/^file:\/\/.*\//,
|
|
167
|
-
"",
|
|
168
|
-
);
|
|
169
|
-
return `${entityId} (${String(entity.title ?? "")}, status=${String(entity.status ?? "")})`;
|
|
170
|
-
})
|
|
171
|
-
.join(", ")}`;
|
|
172
|
-
return {
|
|
173
|
-
content: [{ type: "text", text }],
|
|
174
|
-
structuredContent: { entities: paginated, count: indexedPage.count },
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
const entities = await loadEntities(requireProlog(context), {
|
|
150
|
+
const pageInput = {
|
|
178
151
|
...(type !== undefined ? { type } : {}),
|
|
179
152
|
...(id !== undefined ? { id } : {}),
|
|
180
153
|
...(tags !== undefined ? { tags } : {}),
|
|
181
154
|
...(sourceFile !== undefined ? { sourceFile } : {}),
|
|
182
|
-
|
|
183
|
-
|
|
155
|
+
limit,
|
|
156
|
+
offset,
|
|
157
|
+
};
|
|
158
|
+
// Both paths return one bounded page plus the total count. Ports without
|
|
159
|
+
// the engine method page kb_query_entities through `query` instead of
|
|
160
|
+
// materializing every matching entity (receipt histories make an
|
|
161
|
+
// all-tests answer exceed the bounded engine output).
|
|
162
|
+
const indexedPage = prolog.queryEntities
|
|
163
|
+
? await prolog.queryEntities(pageInput, signal)
|
|
164
|
+
: await queryEntitiesViaQuery(prolog, pageInput, signal);
|
|
165
|
+
const paginated = indexedPage.entities;
|
|
184
166
|
const text =
|
|
185
|
-
|
|
167
|
+
indexedPage.count === 0
|
|
186
168
|
? `No entities found${type ? ` of type '${type}'` : ""}.`
|
|
187
|
-
: `Found ${
|
|
169
|
+
: `Found ${indexedPage.count} entities${type ? ` of type '${type}'` : ""}. Showing ${paginated.length} (offset ${offset}, limit ${limit}): ${paginated
|
|
188
170
|
.map((entity) => {
|
|
189
171
|
const entityId = String(entity.id ?? "").replace(
|
|
190
172
|
/^file:\/\/.*\//,
|
|
@@ -195,7 +177,7 @@ export async function executeQuery(
|
|
|
195
177
|
.join(", ")}`;
|
|
196
178
|
return {
|
|
197
179
|
content: [{ type: "text", text }],
|
|
198
|
-
structuredContent: { entities: paginated, count:
|
|
180
|
+
structuredContent: { entities: paginated, count: indexedPage.count },
|
|
199
181
|
};
|
|
200
182
|
} catch (error) {
|
|
201
183
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -203,6 +185,55 @@ export async function executeQuery(
|
|
|
203
185
|
}
|
|
204
186
|
}
|
|
205
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Identifying metadata a caller needs to decide which hits to open.
|
|
190
|
+
*
|
|
191
|
+
* Search is a discovery step, so returning complete entity bodies for every
|
|
192
|
+
* hit spends a large share of an agent's context before it has chosen
|
|
193
|
+
* anything. Full bodies stay available through `fields: "full"` or a follow-up
|
|
194
|
+
* kb_query for the exact ids.
|
|
195
|
+
*/
|
|
196
|
+
const SUMMARY_ENTITY_FIELDS = [
|
|
197
|
+
"id",
|
|
198
|
+
"type",
|
|
199
|
+
"title",
|
|
200
|
+
"status",
|
|
201
|
+
"priority",
|
|
202
|
+
"tags",
|
|
203
|
+
"source",
|
|
204
|
+
"sourceFile",
|
|
205
|
+
"updated_at",
|
|
206
|
+
] as const;
|
|
207
|
+
|
|
208
|
+
function summarizeMatch<TMatch extends { readonly entity: unknown }>(
|
|
209
|
+
match: TMatch,
|
|
210
|
+
): TMatch {
|
|
211
|
+
const entity = match.entity;
|
|
212
|
+
if (entity === null || typeof entity !== "object" || Array.isArray(entity)) {
|
|
213
|
+
return match;
|
|
214
|
+
}
|
|
215
|
+
const row = entity as Record<string, unknown>;
|
|
216
|
+
const summary: Record<string, unknown> = {};
|
|
217
|
+
for (const field of SUMMARY_ENTITY_FIELDS) {
|
|
218
|
+
if (row[field] !== undefined) summary[field] = row[field];
|
|
219
|
+
}
|
|
220
|
+
return { ...match, entity: summary };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Candidates are projected rows, so `fields: "full"` reloads the final page's
|
|
224
|
+
// complete entities by id (one bounded query each).
|
|
225
|
+
async function projectMatches<
|
|
226
|
+
TMatch extends { readonly entity: Record<string, unknown> },
|
|
227
|
+
>(
|
|
228
|
+
prolog: Pick<PrologPort, "query">,
|
|
229
|
+
matches: readonly TMatch[],
|
|
230
|
+
fields: SearchInput["fields"],
|
|
231
|
+
): Promise<readonly TMatch[]> {
|
|
232
|
+
return fields === "full"
|
|
233
|
+
? reloadFullEntities(prolog, matches)
|
|
234
|
+
: matches.map(summarizeMatch);
|
|
235
|
+
}
|
|
236
|
+
|
|
206
237
|
export async function executeSearch(
|
|
207
238
|
input: SearchInput,
|
|
208
239
|
context: OperationContext,
|
|
@@ -217,6 +248,7 @@ export async function executeSearch(
|
|
|
217
248
|
semanticFacets,
|
|
218
249
|
sourceLocations,
|
|
219
250
|
minScore,
|
|
251
|
+
fields = "summary",
|
|
220
252
|
} = input;
|
|
221
253
|
const trimmedQuery = query.trim();
|
|
222
254
|
if (!trimmedQuery) {
|
|
@@ -258,27 +290,23 @@ export async function executeSearch(
|
|
|
258
290
|
return {
|
|
259
291
|
content: [{ type: "text", text }],
|
|
260
292
|
structuredContent: {
|
|
261
|
-
results: paginated,
|
|
293
|
+
results: await projectMatches(prolog, paginated, fields),
|
|
262
294
|
count: intentResult.matches.length,
|
|
263
295
|
queryAnalysis: intentResult.analysis,
|
|
264
296
|
},
|
|
265
297
|
};
|
|
266
298
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
? [...indexedCandidates]
|
|
279
|
-
: await loadEntities(prolog, {
|
|
280
|
-
...(type !== undefined ? { type } : {}),
|
|
281
|
-
});
|
|
299
|
+
// Candidates are projected rows (no receipt histories or other large
|
|
300
|
+
// structured properties); ports without the engine method run the same
|
|
301
|
+
// bounded Prolog search through `query`.
|
|
302
|
+
const entities = await loadSearchCandidates(
|
|
303
|
+
prolog,
|
|
304
|
+
{
|
|
305
|
+
query: trimmedQuery,
|
|
306
|
+
...(type !== undefined ? { type } : {}),
|
|
307
|
+
},
|
|
308
|
+
context.signal,
|
|
309
|
+
);
|
|
282
310
|
const matches = await rankEntities(
|
|
283
311
|
entities,
|
|
284
312
|
trimmedQuery,
|
|
@@ -296,7 +324,10 @@ export async function executeSearch(
|
|
|
296
324
|
.join(", ")}`;
|
|
297
325
|
return {
|
|
298
326
|
content: [{ type: "text", text }],
|
|
299
|
-
structuredContent: {
|
|
327
|
+
structuredContent: {
|
|
328
|
+
results: await projectMatches(prolog, paginated, fields),
|
|
329
|
+
count: matches.length,
|
|
330
|
+
},
|
|
300
331
|
};
|
|
301
332
|
} catch (error) {
|
|
302
333
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -400,6 +431,7 @@ export async function executeStatus(
|
|
|
400
431
|
const existingReasons = payload.staleReasons ?? [];
|
|
401
432
|
const storeReason = branchStoreReason(store);
|
|
402
433
|
const lockReason = storeLockJournalReason(store.path);
|
|
434
|
+
const compilerReason = compilerFingerprintReason(store.path);
|
|
403
435
|
const engineReason = engineStatus
|
|
404
436
|
? {
|
|
405
437
|
code: engineStatus.errorCode ?? "engine_status_unavailable",
|
|
@@ -418,12 +450,16 @@ export async function executeStatus(
|
|
|
418
450
|
...existingReasons,
|
|
419
451
|
...(storeReason ? [storeReason] : []),
|
|
420
452
|
...(lockReason ? [lockReason] : []),
|
|
453
|
+
...(compilerReason ? [compilerReason] : []),
|
|
421
454
|
...(engineReason ? [engineReason] : []),
|
|
422
455
|
].sort((left, right) =>
|
|
423
456
|
String(left.path ?? "").localeCompare(String(right.path ?? "")),
|
|
424
457
|
);
|
|
425
458
|
const enrichedPayload: StatusPayload = {
|
|
426
459
|
...payload,
|
|
460
|
+
// Source hashes can all match while the compilation itself is lossy, so
|
|
461
|
+
// a compiler contract change alone makes the store stale.
|
|
462
|
+
...(compilerReason ? { syncState: "stale", dirty: true } : {}),
|
|
427
463
|
branchAttachment: attachment,
|
|
428
464
|
branchStore: store,
|
|
429
465
|
...(engineStatus ? { engineStatus } : {}),
|
|
@@ -319,6 +319,58 @@ export function migrationAction(
|
|
|
319
319
|
return actionDefaults(input);
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
/**
|
|
323
|
+
* A predicate-schema-conformance finding whose repair is mechanical (move to
|
|
324
|
+
* the only matching schema namespace, rewrite declared aliases) becomes an
|
|
325
|
+
* automatic action carrying the exact kb_upsert input, so it is reviewable in
|
|
326
|
+
* the plan and replayable through either surface.
|
|
327
|
+
*/
|
|
328
|
+
// implements REQ-kibi-predicate-vocabulary-migration
|
|
329
|
+
function predicateSchemaAlignmentAction(
|
|
330
|
+
diagnostic: Readonly<Record<string, unknown>>,
|
|
331
|
+
): MigrationAction | null {
|
|
332
|
+
if (diagnostic.id !== "rule.predicate-schema-conformance") return null;
|
|
333
|
+
const evidence = isRecord(diagnostic.evidence) ? diagnostic.evidence : null;
|
|
334
|
+
const repair =
|
|
335
|
+
evidence !== null && isRecord(evidence.repair) ? evidence.repair : null;
|
|
336
|
+
if (
|
|
337
|
+
repair === null ||
|
|
338
|
+
repair.type !== "fact" ||
|
|
339
|
+
typeof repair.id !== "string" ||
|
|
340
|
+
!isRecord(repair.properties)
|
|
341
|
+
) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
return migrationAction({
|
|
345
|
+
id: `predicate-schema-alignment-${repair.id}`,
|
|
346
|
+
code: PREDICATE_SCHEMA_ALIGNMENT_CODE,
|
|
347
|
+
category: "semantic",
|
|
348
|
+
safety: "automatic",
|
|
349
|
+
autoApplicable: true,
|
|
350
|
+
invocation: { kind: "operation", name: "kb_upsert", input: repair },
|
|
351
|
+
affectedEntityIds: [repair.id],
|
|
352
|
+
affectedFiles:
|
|
353
|
+
typeof diagnostic.source === "string" ? [diagnostic.source] : [],
|
|
354
|
+
postconditions: [
|
|
355
|
+
{
|
|
356
|
+
rule: "predicate-schema-conformance",
|
|
357
|
+
entityId: repair.id,
|
|
358
|
+
findings: 0,
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
evidence: {
|
|
362
|
+
issue: evidence?.issue,
|
|
363
|
+
namespace: evidence?.namespace,
|
|
364
|
+
alignment: evidence?.alignment,
|
|
365
|
+
rewrites: evidence?.rewrites,
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Migration code for mechanical predicate namespace/alias repairs. */
|
|
371
|
+
export const PREDICATE_SCHEMA_ALIGNMENT_CODE = "predicate_schema_alignment";
|
|
372
|
+
|
|
373
|
+
// implements REQ-agent-guided-migration-orchestration, REQ-kibi-predicate-vocabulary-migration
|
|
322
374
|
export function buildActionsFromCheck(input: {
|
|
323
375
|
violations?: readonly Readonly<Record<string, unknown>>[];
|
|
324
376
|
qualityDiagnostics?: readonly Readonly<Record<string, unknown>>[];
|
|
@@ -358,6 +410,11 @@ export function buildActionsFromCheck(input: {
|
|
|
358
410
|
? diagnostic.suggestion
|
|
359
411
|
: "Review this quality diagnostic.";
|
|
360
412
|
const blocking = diagnostic.blocking === true;
|
|
413
|
+
const predicateRepair = predicateSchemaAlignmentAction(diagnostic);
|
|
414
|
+
if (predicateRepair !== null) {
|
|
415
|
+
actions.push(predicateRepair);
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
361
418
|
actions.push(
|
|
362
419
|
migrationAction({
|
|
363
420
|
id: `diagnostic-${id}-${entityId || "workspace"}-${index + 1}`,
|