kibi-cli 2.36.0 → 2.39.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-json-command.d.ts.map +1 -1
- package/dist/cli-json-command.js +4 -0
- 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 +2 -1
- package/dist/cli-register-maintenance.d.ts.map +1 -1
- package/dist/cli-register-maintenance.js +5 -0
- package/dist/cli-register-reporting.d.ts.map +1 -1
- package/dist/cli-register-reporting.js +1 -0
- package/dist/cli-validate.d.ts.map +1 -1
- package/dist/cli-validate.js +21 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -1
- package/dist/commands/aggregated-checks.d.ts.map +1 -1
- package/dist/commands/aggregated-checks.js +89 -16
- package/dist/commands/coverage.d.ts +1 -0
- package/dist/commands/coverage.d.ts.map +1 -1
- package/dist/commands/coverage.js +40 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +70 -35
- package/dist/commands/github-init.d.ts.map +1 -1
- package/dist/commands/github-init.js +4 -2
- package/dist/commands/hook-managers.d.ts +43 -0
- package/dist/commands/hook-managers.d.ts.map +1 -0
- package/dist/commands/hook-managers.js +426 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +40 -5
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +9 -3
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +49 -1
- package/dist/commands/telemetry.d.ts +7 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +23 -0
- package/dist/decision-firmness.d.ts.map +1 -1
- package/dist/decision-firmness.js +11 -14
- package/dist/engine-limits.d.ts.map +1 -1
- package/dist/engine-limits.js +8 -10
- package/dist/entity-body-context.d.ts.map +1 -1
- package/dist/entity-body-context.js +39 -36
- package/dist/extractors/markdown.d.ts +6 -0
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +21 -0
- package/dist/extractors/symbol-coordinates.d.ts.map +1 -1
- package/dist/extractors/symbol-coordinates.js +23 -4
- package/dist/firm-decision-edit.d.ts.map +1 -1
- package/dist/firm-decision-edit.js +1 -3
- package/dist/intent-search.d.ts +5 -1
- package/dist/intent-search.d.ts.map +1 -1
- package/dist/intent-search.js +118 -33
- package/dist/logic/ir.d.ts.map +1 -1
- package/dist/logic/ir.js +82 -108
- package/dist/operations/bootstrap/discovery-evidence.d.ts.map +1 -1
- package/dist/operations/bootstrap/discovery-evidence.js +44 -2
- package/dist/operations/bootstrap/discovery.d.ts.map +1 -1
- package/dist/operations/bootstrap/discovery.js +1 -0
- package/dist/operations/bootstrap/guidance-actions.d.ts.map +1 -1
- package/dist/operations/bootstrap/guidance-actions.js +2 -1
- package/dist/operations/bootstrap/plugin-offers.d.ts +11 -2
- package/dist/operations/bootstrap/plugin-offers.d.ts.map +1 -1
- package/dist/operations/bootstrap/plugin-offers.js +48 -9
- package/dist/operations/proof/claim-bindings.d.ts.map +1 -1
- package/dist/operations/proof/claim-bindings.js +1 -0
- package/dist/operations/proof/code-certificates.d.ts +2 -0
- package/dist/operations/proof/code-certificates.d.ts.map +1 -1
- package/dist/operations/proof/code-certificates.js +34 -14
- package/dist/operations/proof/code-models.d.ts.map +1 -1
- package/dist/operations/proof/code-models.js +19 -5
- package/dist/operations/proof/code-runner.d.ts +16 -0
- package/dist/operations/proof/code-runner.d.ts.map +1 -1
- package/dist/operations/proof/code-runner.js +42 -6
- package/dist/operations/proof/explain.d.ts +63 -0
- package/dist/operations/proof/explain.d.ts.map +1 -1
- package/dist/operations/proof/explain.js +162 -0
- package/dist/operations/semantic-advisor/analyze-prose.d.ts +9 -1
- package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analyze-prose.js +21 -0
- package/dist/operations/semantic-advisor/case-preview.d.ts +24 -0
- package/dist/operations/semantic-advisor/case-preview.d.ts.map +1 -0
- package/dist/operations/semantic-advisor/case-preview.js +216 -0
- package/dist/operations/semantic-advisor/shared.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/shared.js +3 -0
- package/dist/operations/semantic-advisor/types.d.ts +23 -0
- package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
- package/dist/plugins/approved-source-analyzers.generated.d.ts +11 -8
- package/dist/plugins/approved-source-analyzers.generated.d.ts.map +1 -1
- package/dist/plugins/approved-source-analyzers.generated.js +11 -8
- package/dist/plugins/compose-search-hints.d.ts +29 -0
- package/dist/plugins/compose-search-hints.d.ts.map +1 -0
- package/dist/plugins/compose-search-hints.js +94 -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 +11 -0
- package/dist/plugins/registry.d.ts +8 -1
- package/dist/plugins/registry.d.ts.map +1 -1
- package/dist/plugins/registry.js +22 -3
- package/dist/plugins/source-classification.d.ts +2 -2
- package/dist/plugins/source-classification.d.ts.map +1 -1
- package/dist/plugins/source-classification.js +25 -1
- package/dist/prolog/codec.d.ts.map +1 -1
- package/dist/prolog/codec.js +10 -20
- package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -1
- package/dist/public/impact/coverage-depth-quality.js +32 -16
- package/dist/public/impact/full-kb-quality.d.ts +6 -0
- package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
- package/dist/public/impact/full-kb-quality.js +8 -3
- package/dist/public/impact/requirement-quality.d.ts.map +1 -1
- package/dist/public/impact/requirement-quality.js +56 -29
- package/dist/public/operations/catalog.d.ts +44 -4
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/check-executor.d.ts +6 -0
- package/dist/public/operations/check-executor.d.ts.map +1 -1
- package/dist/public/operations/check-executor.js +34 -2
- package/dist/public/operations/discovery-executors.d.ts +5 -1
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +65 -4
- package/dist/public/operations/evidence-floor.d.ts +8 -1
- package/dist/public/operations/evidence-floor.d.ts.map +1 -1
- package/dist/public/operations/evidence-floor.js +3 -4
- package/dist/public/operations/migration-plan.d.ts.map +1 -1
- package/dist/public/operations/migration-plan.js +41 -10
- package/dist/public/operations/node-ports.d.ts.map +1 -1
- package/dist/public/operations/node-ports.js +3 -1
- package/dist/public/operations/repair-plan.d.ts +46 -0
- package/dist/public/operations/repair-plan.d.ts.map +1 -1
- package/dist/public/operations/repair-plan.js +110 -1
- package/dist/public/operations/schema6-check-actions.js +1 -1
- package/dist/public/operations/source-analysis-cache.d.ts +22 -0
- package/dist/public/operations/source-analysis-cache.d.ts.map +1 -0
- package/dist/public/operations/source-analysis-cache.js +157 -0
- 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 +2 -1
- package/dist/public/operations/specs/check.d.ts +1 -1
- package/dist/public/operations/specs/discovery.d.ts +7 -2
- package/dist/public/operations/specs/discovery.d.ts.map +1 -1
- package/dist/public/operations/specs/discovery.js +7 -2
- package/dist/public/operations/specs/mutation-schemas.d.ts +36 -1
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +30 -0
- package/dist/public/operations/specs/upsert.d.ts +36 -1
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/operations/symbol-granularity-check.d.ts +3 -1
- package/dist/public/operations/symbol-granularity-check.d.ts.map +1 -1
- package/dist/public/operations/symbol-granularity-check.js +21 -7
- package/dist/public/proof-protocol.d.ts +6 -1
- package/dist/public/proof-protocol.d.ts.map +1 -1
- package/dist/public/proof-protocol.js +12 -3
- package/dist/public/proof-sources.d.ts +2 -0
- package/dist/public/proof-sources.d.ts.map +1 -1
- package/dist/public/proof-sources.js +32 -6
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +59 -0
- package/dist/public/skills/kibi-bootstrap/SKILL.md +2 -2
- package/dist/public/skills/kibi-commit-review/SKILL.md +6 -2
- package/dist/public/skills/kibi-decisions/SKILL.md +7 -2
- package/dist/public/skills/kibi-decisions/resources/signoff-views.md +3 -1
- package/dist/public/skills/kibi-usage/SKILL.md +50 -3
- package/dist/public/skills/kibi-usage/resources/fact-lanes.md +34 -0
- package/dist/public/skills/kibi-usage/resources/proof.md +13 -5
- package/dist/public/skills/kibi-usage/resources/workflows.md +10 -0
- package/dist/relationships/shards.d.ts +2 -0
- package/dist/relationships/shards.d.ts.map +1 -1
- package/dist/relationships/shards.js +25 -1
- package/dist/schemas/entity.schema.json +98 -1
- package/dist/search-answer.d.ts +3 -2
- package/dist/search-answer.d.ts.map +1 -1
- package/dist/search-answer.js +57 -4
- package/dist/search-hints.d.ts +58 -0
- package/dist/search-hints.d.ts.map +1 -0
- package/dist/search-hints.js +187 -0
- package/dist/search-ranking.d.ts +7 -0
- package/dist/search-ranking.d.ts.map +1 -1
- package/dist/search-ranking.js +54 -56
- package/dist/search-token-cache.d.ts +30 -0
- package/dist/search-token-cache.d.ts.map +1 -0
- package/dist/search-token-cache.js +86 -0
- package/dist/traceability/impact-knowledge.d.ts.map +1 -1
- package/dist/traceability/impact-knowledge.js +2 -0
- package/dist/traceability/temp-kb.d.ts.map +1 -1
- package/dist/traceability/temp-kb.js +11 -1
- package/dist/types/entities.d.ts +12 -0
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/usage-telemetry.d.ts +20 -0
- package/dist/usage-telemetry.d.ts.map +1 -0
- package/dist/usage-telemetry.js +93 -0
- package/dist/utils/branch-resolver.d.ts.map +1 -1
- package/dist/utils/branch-resolver.js +1 -0
- package/dist/utils/rule-registry.generated.d.ts +5 -0
- package/dist/utils/rule-registry.generated.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.js +6 -0
- package/package.json +5 -4
- package/schema/entities.pl +12 -0
- package/src/public/impact/coverage-depth-quality.ts +59 -21
- package/src/public/impact/full-kb-quality.ts +15 -1
- package/src/public/impact/requirement-quality.ts +95 -38
- package/src/public/operations/check-executor.ts +41 -1
- package/src/public/operations/discovery-executors.ts +92 -4
- package/src/public/operations/evidence-floor.ts +15 -3
- package/src/public/operations/migration-plan.ts +41 -9
- package/src/public/operations/node-ports.ts +3 -1
- package/src/public/operations/repair-plan.ts +152 -1
- package/src/public/operations/schema6-check-actions.ts +1 -1
- package/src/public/operations/source-analysis-cache.ts +196 -0
- package/src/public/operations/specs/check-rules.generated.ts +2 -1
- package/src/public/operations/specs/discovery.ts +8 -2
- package/src/public/operations/specs/mutation-schemas.ts +34 -0
- package/src/public/operations/symbol-granularity-check.ts +25 -5
- package/src/public/proof-protocol-types.d.ts +1 -1
- package/src/public/proof-protocol.ts +18 -4
- package/src/public/proof-sources.ts +37 -6
- package/src/public/schemas/entity.ts +65 -0
- package/src/public/skills/kibi-bootstrap/SKILL.md +2 -2
- package/src/public/skills/kibi-commit-review/SKILL.md +6 -2
- package/src/public/skills/kibi-decisions/SKILL.md +7 -2
- package/src/public/skills/kibi-decisions/resources/signoff-views.md +3 -1
- package/src/public/skills/kibi-usage/SKILL.md +50 -3
- package/src/public/skills/kibi-usage/resources/fact-lanes.md +34 -0
- package/src/public/skills/kibi-usage/resources/proof.md +13 -5
- package/src/public/skills/kibi-usage/resources/workflows.md +10 -0
- package/src/schemas/entity.schema.json +98 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type IntentSearchAnalysis,
|
|
11
11
|
type IntentSearchFacets,
|
|
12
12
|
type IntentSearchMatch,
|
|
13
|
+
type MarkdownBodyCache,
|
|
13
14
|
type SourceLocation,
|
|
14
15
|
executeIntentSearch,
|
|
15
16
|
questionPaths,
|
|
@@ -18,6 +19,12 @@ import {
|
|
|
18
19
|
import { classifyActivation } from "../../operations/bootstrap/activation.js";
|
|
19
20
|
import { SwiplResolutionError } from "../../prolog/swipl-resolver.js";
|
|
20
21
|
import { type SearchAnswer, buildSearchAnswer } from "../../search-answer.js";
|
|
22
|
+
import {
|
|
23
|
+
type SearchHintsAnalysis,
|
|
24
|
+
buildSearchHints,
|
|
25
|
+
searchHintsNote,
|
|
26
|
+
searchHintsTrigger,
|
|
27
|
+
} from "../../search-hints.js";
|
|
21
28
|
import { rankEntities } from "../../search-ranking.js";
|
|
22
29
|
import type { SearchMatch } from "../../search-ranking.js";
|
|
23
30
|
import { resolveBranchAttachment } from "../../utils/branch-resolver.js";
|
|
@@ -51,7 +58,11 @@ import {
|
|
|
51
58
|
runOperationJsonQuery,
|
|
52
59
|
toPrologAtom,
|
|
53
60
|
} from "./prolog-json.js";
|
|
54
|
-
import type {
|
|
61
|
+
import type {
|
|
62
|
+
CapabilityRegistry,
|
|
63
|
+
OperationContext,
|
|
64
|
+
PrologPort,
|
|
65
|
+
} from "./runtime-types.js";
|
|
55
66
|
import type { OperationResult } from "./types.js";
|
|
56
67
|
import { readWorkspaceSnapshot } from "./workspace-snapshot.js";
|
|
57
68
|
|
|
@@ -176,6 +187,7 @@ export type SearchInput = {
|
|
|
176
187
|
readonly minScore?: number;
|
|
177
188
|
readonly fields?: "summary" | "full";
|
|
178
189
|
readonly answer?: boolean;
|
|
190
|
+
readonly hints?: boolean;
|
|
179
191
|
};
|
|
180
192
|
|
|
181
193
|
export type SearchPayload = {
|
|
@@ -185,7 +197,9 @@ export type SearchPayload = {
|
|
|
185
197
|
/** Rows in `results`: the page this call returned. */
|
|
186
198
|
readonly returned: number;
|
|
187
199
|
readonly truncated?: boolean;
|
|
188
|
-
readonly queryAnalysis?: IntentSearchAnalysis
|
|
200
|
+
readonly queryAnalysis?: IntentSearchAnalysis & {
|
|
201
|
+
readonly hints?: SearchHintsAnalysis;
|
|
202
|
+
};
|
|
189
203
|
readonly answer?: SearchAnswer;
|
|
190
204
|
/** The Kibi branch whose store was searched, when the runtime attached one. */
|
|
191
205
|
readonly branch?: string | null;
|
|
@@ -912,6 +926,51 @@ function questionPathHint(
|
|
|
912
926
|
: ` The question names ${list}; their words were left out of the topic. Pass them as sourceLocations to also see the requirements linked to those files.`;
|
|
913
927
|
}
|
|
914
928
|
|
|
929
|
+
/**
|
|
930
|
+
* Hints from the workspace's kibi.search-hints.v1 provider. A host without a
|
|
931
|
+
* plugin runtime, or a registry that fails to load, gives no hints rather than
|
|
932
|
+
* failing the search.
|
|
933
|
+
*/
|
|
934
|
+
// implements REQ-kibi-search-hints-host
|
|
935
|
+
async function searchHintsFor(
|
|
936
|
+
context: OperationContext,
|
|
937
|
+
options: Omit<
|
|
938
|
+
Parameters<typeof buildSearchHints>[0],
|
|
939
|
+
"resolution" | "workspaceRoot"
|
|
940
|
+
>,
|
|
941
|
+
): Promise<SearchHintsAnalysis | undefined> {
|
|
942
|
+
if (!context.ensurePlugins) return undefined;
|
|
943
|
+
let resolution: Awaited<ReturnType<CapabilityRegistry["resolveSearchHints"]>>;
|
|
944
|
+
try {
|
|
945
|
+
resolution = await (await context.ensurePlugins()).resolveSearchHints();
|
|
946
|
+
} catch (error) {
|
|
947
|
+
// A configured provider that cannot load is reported, not thrown: the
|
|
948
|
+
// lexical answer stands on its own.
|
|
949
|
+
const trigger = searchHintsTrigger(options.analysis);
|
|
950
|
+
return trigger === null
|
|
951
|
+
? undefined
|
|
952
|
+
: {
|
|
953
|
+
trigger,
|
|
954
|
+
provider: null,
|
|
955
|
+
items: [],
|
|
956
|
+
diagnostics: [
|
|
957
|
+
{
|
|
958
|
+
pluginId: "unresolved",
|
|
959
|
+
packageName: null,
|
|
960
|
+
mode: "replace",
|
|
961
|
+
code: "provider_unavailable",
|
|
962
|
+
message: error instanceof Error ? error.message : String(error),
|
|
963
|
+
},
|
|
964
|
+
],
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
return buildSearchHints({
|
|
968
|
+
...options,
|
|
969
|
+
resolution,
|
|
970
|
+
workspaceRoot: context.workspaceRoot,
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
|
|
915
974
|
export async function executeSearch(
|
|
916
975
|
input: SearchInput,
|
|
917
976
|
context: OperationContext,
|
|
@@ -928,6 +987,7 @@ export async function executeSearch(
|
|
|
928
987
|
minScore,
|
|
929
988
|
fields = "summary",
|
|
930
989
|
answer,
|
|
990
|
+
hints: hintsWanted = true,
|
|
931
991
|
} = input;
|
|
932
992
|
const trimmedQuery = query.trim();
|
|
933
993
|
if (!trimmedQuery) {
|
|
@@ -960,10 +1020,19 @@ export async function executeSearch(
|
|
|
960
1020
|
...(minScore !== undefined ? { minScore } : {}),
|
|
961
1021
|
} as const;
|
|
962
1022
|
validateIntentSearchInput(intentInput);
|
|
1023
|
+
let corpus:
|
|
1024
|
+
| {
|
|
1025
|
+
entities: readonly Record<string, unknown>[];
|
|
1026
|
+
bodies: MarkdownBodyCache;
|
|
1027
|
+
}
|
|
1028
|
+
| undefined;
|
|
963
1029
|
const intentResult = await executeIntentSearch(
|
|
964
1030
|
intentInput,
|
|
965
1031
|
prolog,
|
|
966
1032
|
context.workspaceRoot,
|
|
1033
|
+
(entities, bodies) => {
|
|
1034
|
+
corpus = { entities, bodies };
|
|
1035
|
+
},
|
|
967
1036
|
);
|
|
968
1037
|
const paginated = paginateResults(intentResult.matches, limit, offset);
|
|
969
1038
|
// The answer layer summarizes the whole ranking, so it belongs to the
|
|
@@ -982,6 +1051,22 @@ export async function executeSearch(
|
|
|
982
1051
|
const topUngoverned = answerLayer?.ungoverned.find(
|
|
983
1052
|
(entry) => entry.rank === 1,
|
|
984
1053
|
);
|
|
1054
|
+
// Hints come after the lexical answer and never change it; a strong
|
|
1055
|
+
// answer never reaches a provider.
|
|
1056
|
+
const hints =
|
|
1057
|
+
hintsWanted &&
|
|
1058
|
+
offset === 0 &&
|
|
1059
|
+
corpus !== undefined &&
|
|
1060
|
+
searchHintsTrigger(intentResult.analysis) !== null
|
|
1061
|
+
? await searchHintsFor(context, {
|
|
1062
|
+
query: trimmedQuery,
|
|
1063
|
+
analysis: intentResult.analysis,
|
|
1064
|
+
entities: corpus.entities,
|
|
1065
|
+
bodies: corpus.bodies,
|
|
1066
|
+
governingIds: (answerLayer?.governing ?? []).map((req) => req.id),
|
|
1067
|
+
prolog,
|
|
1068
|
+
})
|
|
1069
|
+
: undefined;
|
|
985
1070
|
const text = `${
|
|
986
1071
|
intentResult.matches.length === 0
|
|
987
1072
|
? `No intent search results for '${trimmedQuery}' (abstained). No entity shares enough of the question's words: ask again in the words and language the KB is written in, or pass sourceLocations.`
|
|
@@ -991,7 +1076,7 @@ export async function executeSearch(
|
|
|
991
1076
|
.join(
|
|
992
1077
|
", ",
|
|
993
1078
|
)}${answerLayer ? `. Governing: ${answerLayer.governing.map((req) => req.id).join(", ") || "none found"}` : ""}${topUngoverned ? ` (the top match ${topUngoverned.id} is linked to no requirement)` : ""}.`
|
|
994
|
-
}${questionPathHint(trimmedQuery, sourceLocations)}${CONTRADICTION_QUESTION.test(trimmedQuery) ? CONTRADICTION_HINT : ""}${storeNote}`;
|
|
1079
|
+
}${searchHintsNote(hints)}${questionPathHint(trimmedQuery, sourceLocations)}${CONTRADICTION_QUESTION.test(trimmedQuery) ? CONTRADICTION_HINT : ""}${storeNote}`;
|
|
995
1080
|
return {
|
|
996
1081
|
content: [{ type: "text", text }],
|
|
997
1082
|
structuredContent: {
|
|
@@ -999,7 +1084,10 @@ export async function executeSearch(
|
|
|
999
1084
|
count: intentResult.matches.length,
|
|
1000
1085
|
returned: paginated.length,
|
|
1001
1086
|
truncated: offset + paginated.length < intentResult.matches.length,
|
|
1002
|
-
queryAnalysis:
|
|
1087
|
+
queryAnalysis:
|
|
1088
|
+
hints !== undefined
|
|
1089
|
+
? { ...intentResult.analysis, hints }
|
|
1090
|
+
: intentResult.analysis,
|
|
1003
1091
|
...(answerLayer ? { answer: answerLayer } : {}),
|
|
1004
1092
|
...branchFields,
|
|
1005
1093
|
},
|
|
@@ -60,6 +60,13 @@ export type EvidenceFloorOptions = Readonly<{
|
|
|
60
60
|
* `kibi prove --all`.
|
|
61
61
|
*/
|
|
62
62
|
freshEvidence?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* The instant and workspace snapshot the whole check judges receipts at,
|
|
65
|
+
* so the floor reads the same requirement proofs the coverage evidence
|
|
66
|
+
* read instead of computing them again.
|
|
67
|
+
*/
|
|
68
|
+
checkedAt?: string;
|
|
69
|
+
snapshotEvidence?: Awaited<ReturnType<typeof readWorkspaceSnapshot>>;
|
|
63
70
|
}>;
|
|
64
71
|
|
|
65
72
|
function floorSource(row: EvidenceFloorRow): string {
|
|
@@ -145,6 +152,7 @@ export function evidenceFloorViolations(
|
|
|
145
152
|
export async function readEvidenceFloorRows(
|
|
146
153
|
context: OperationContext,
|
|
147
154
|
snapshotEvidence?: Awaited<ReturnType<typeof readWorkspaceSnapshot>>,
|
|
155
|
+
checkedAt: string = context.clock().toISOString(),
|
|
148
156
|
): Promise<readonly EvidenceFloorRow[]> {
|
|
149
157
|
const prolog = context.prolog ?? (await context.ensureProlog?.());
|
|
150
158
|
if (prolog === undefined) return [];
|
|
@@ -152,7 +160,6 @@ export async function readEvidenceFloorRows(
|
|
|
152
160
|
const snapshot = evidence.available ? evidence.snapshot.hash : "unknown";
|
|
153
161
|
const { perContractTestBindings } = await import("./specs/reporting.js");
|
|
154
162
|
const bindings = await perContractTestBindings({ ...context, prolog });
|
|
155
|
-
const checkedAt = context.clock().toISOString();
|
|
156
163
|
const goal = `discovery:evidence_floor_json(${bindings === null ? "strict_snapshot, _{}" : `per_contract, ${bindings}`}, ${toPrologAtom(snapshot)}, ${toPrologAtom(checkedAt)}, ${PROOF_RECEIPT_MAX_AGE_SECONDS}, JsonString)`;
|
|
157
164
|
const payload = await runOperationJsonQuery<EvidenceFloorPayload>(
|
|
158
165
|
prolog,
|
|
@@ -182,8 +189,13 @@ export async function collectEvidenceFloorViolations(
|
|
|
182
189
|
schemaVersion < EVIDENCE_FLOOR_SCHEMA_VERSION
|
|
183
190
|
)
|
|
184
191
|
return [];
|
|
185
|
-
const snapshotEvidence =
|
|
186
|
-
|
|
192
|
+
const snapshotEvidence =
|
|
193
|
+
options.snapshotEvidence ?? (await readWorkspaceSnapshot(context));
|
|
194
|
+
const rows = await readEvidenceFloorRows(
|
|
195
|
+
context,
|
|
196
|
+
snapshotEvidence,
|
|
197
|
+
options.checkedAt,
|
|
198
|
+
);
|
|
187
199
|
const violations = evidenceFloorViolations(rows, options);
|
|
188
200
|
if (options.freshEvidence === true) {
|
|
189
201
|
const clean =
|
|
@@ -169,20 +169,46 @@ function dependencyOrder(
|
|
|
169
169
|
new Set(action.dependsOn.filter((dependency) => byId.has(dependency))),
|
|
170
170
|
]),
|
|
171
171
|
);
|
|
172
|
+
// Dependents of each action in action order, so an emitted action frees
|
|
173
|
+
// its dependents in the order a scan over every action would.
|
|
174
|
+
const dependents = new Map<string, string[]>();
|
|
175
|
+
for (const action of actions) {
|
|
176
|
+
for (const dependency of remaining.get(action.id) as Set<string>) {
|
|
177
|
+
const list = dependents.get(dependency);
|
|
178
|
+
if (list === undefined) dependents.set(dependency, [action.id]);
|
|
179
|
+
else list.push(action.id);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
172
182
|
const ordered: MigrationAction[] = [];
|
|
173
183
|
const ready = actions
|
|
174
184
|
.filter((action) => (remaining.get(action.id)?.size ?? 0) === 0)
|
|
175
185
|
.sort(actionSort)
|
|
176
186
|
.map((action) => action.id);
|
|
177
187
|
const queued = new Set(ready);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
188
|
+
const compareReady = (left: string, right: string): number =>
|
|
189
|
+
compareReadyActionIds(left, right, byId);
|
|
190
|
+
// `ready` stays sorted; a freed action is inserted after its equals, where
|
|
191
|
+
// a stable sort of the pushed list would have put it.
|
|
192
|
+
const enqueue = (id: string, head: number): void => {
|
|
193
|
+
let low = head;
|
|
194
|
+
let high = ready.length;
|
|
195
|
+
while (low < high) {
|
|
196
|
+
const middle = (low + high) >>> 1;
|
|
197
|
+
if (compareReady(ready[middle] as string, id) <= 0) low = middle + 1;
|
|
198
|
+
else high = middle;
|
|
199
|
+
}
|
|
200
|
+
ready.splice(low, 0, id);
|
|
201
|
+
};
|
|
202
|
+
let head = 0;
|
|
203
|
+
while (head < ready.length) {
|
|
204
|
+
const id = ready[head] as string;
|
|
205
|
+
head += 1;
|
|
181
206
|
queued.delete(id);
|
|
182
|
-
|
|
183
|
-
|
|
207
|
+
// Only ids of `actions` are ever queued or listed as dependents.
|
|
208
|
+
const action = byId.get(id) as MigrationAction;
|
|
184
209
|
ordered.push(action);
|
|
185
|
-
for (const
|
|
210
|
+
for (const candidateId of dependents.get(id) ?? []) {
|
|
211
|
+
const dependencies = remaining.get(candidateId) as Set<string>;
|
|
186
212
|
if (
|
|
187
213
|
!dependencies.delete(id) ||
|
|
188
214
|
dependencies.size !== 0 ||
|
|
@@ -190,9 +216,8 @@ function dependencyOrder(
|
|
|
190
216
|
) {
|
|
191
217
|
continue;
|
|
192
218
|
}
|
|
193
|
-
ready.push(candidateId);
|
|
194
219
|
queued.add(candidateId);
|
|
195
|
-
|
|
220
|
+
enqueue(candidateId, head);
|
|
196
221
|
}
|
|
197
222
|
}
|
|
198
223
|
// Preserve cyclic/missing-dependency actions in canonical order; validation will block them.
|
|
@@ -228,7 +253,14 @@ function summarize(actions: readonly MigrationAction[]) {
|
|
|
228
253
|
|
|
229
254
|
// implements REQ-agent-guided-migration-orchestration
|
|
230
255
|
export function buildMigrationPlan(input: PlanInput = {}): MigrationPlan {
|
|
231
|
-
|
|
256
|
+
// An id names one action, as in a merged plan: a later definition of the
|
|
257
|
+
// same id replaces the earlier one instead of being ordered twice.
|
|
258
|
+
const uniqueActions = [
|
|
259
|
+
...new Map(
|
|
260
|
+
(input.actions ?? []).map((action) => [action.id, action]),
|
|
261
|
+
).values(),
|
|
262
|
+
];
|
|
263
|
+
const actions = dependencyOrder(uniqueActions.sort(actionSort));
|
|
232
264
|
const evaluatedDomains = uniqueSorted(input.evaluatedDomains);
|
|
233
265
|
const incompleteDomains = uniqueSorted(input.incompleteDomains);
|
|
234
266
|
const expected = {
|
|
@@ -118,11 +118,13 @@ async function workspaceSnapshot(workspaceRoot: string) {
|
|
|
118
118
|
);
|
|
119
119
|
const digest = createHash("sha256");
|
|
120
120
|
digest.update("kibi.workspace-snapshot.v2\0");
|
|
121
|
+
// Synchronous reads: one awaited read per file paid a file-handle round
|
|
122
|
+
// trip each, about eight times the time of reading the same files directly.
|
|
121
123
|
for (const { raw, key: relativePath } of paths) {
|
|
122
124
|
digest.update(relativePath);
|
|
123
125
|
digest.update("\0");
|
|
124
126
|
try {
|
|
125
|
-
const content =
|
|
127
|
+
const content = fsSync.readFileSync(path.join(workspaceRoot, raw));
|
|
126
128
|
digest.update(snapshotFileContent(relativePath, content));
|
|
127
129
|
} catch (error) {
|
|
128
130
|
const code =
|
|
@@ -110,6 +110,40 @@ const REPAIR_PHASES = {
|
|
|
110
110
|
validationRules: ["must-priority-coverage", "no-dangling-refs"],
|
|
111
111
|
writePolicy: "sequential_upsert",
|
|
112
112
|
},
|
|
113
|
+
dimension_grounding: {
|
|
114
|
+
order: 64,
|
|
115
|
+
objective:
|
|
116
|
+
"Ground each quantified claim (each, every, all, or a cardinality bound) on what varies: an integer count or bound, a set of members, a rule over a variable, or a state machine. If the quantifier ranges over nothing, record a dimension_waiver on the grounding fact for a person to approve, and record their approval in the same pull request.",
|
|
117
|
+
workflowSteps: [
|
|
118
|
+
"kb_model_requirement",
|
|
119
|
+
"kb_validate_upsert",
|
|
120
|
+
"kb_upsert",
|
|
121
|
+
"kb_check",
|
|
122
|
+
],
|
|
123
|
+
validationRules: ["no-dangling-refs"],
|
|
124
|
+
writePolicy: "sequential_upsert",
|
|
125
|
+
},
|
|
126
|
+
case_scenarios: {
|
|
127
|
+
order: 66,
|
|
128
|
+
objective:
|
|
129
|
+
"Cover each uncovered rejection case: add a scenario whose assumes fact states the case's assumption (a value past the bound, a value outside the set), expects rejection or error, is linked with specified_by and is verified by a passing end-to-end test. Each repair lists caseSkeletons, upsert-ready draft scenarios and their assumed facts: fill in the placeholders, verify the scenario by a test, then set its status to active (a draft never covers a case). If a person accepts the cases as covered another way, record a case_waiver on the grounding fact for them to approve, and record their approval in the same pull request.",
|
|
130
|
+
workflowSteps: [
|
|
131
|
+
"kb_coverage",
|
|
132
|
+
"kb_validate_upsert",
|
|
133
|
+
"kb_upsert",
|
|
134
|
+
"kb_check",
|
|
135
|
+
],
|
|
136
|
+
validationRules: ["scenario-outcome-mismatch", "no-dangling-refs"],
|
|
137
|
+
writePolicy: "sequential_upsert",
|
|
138
|
+
},
|
|
139
|
+
negative_scenarios: {
|
|
140
|
+
order: 65,
|
|
141
|
+
objective:
|
|
142
|
+
"Add a scenario that expects rejection or error and assumes a value the requirement forbids (see suggestedNegatives), link it with specified_by and verify it with a passing end-to-end test.",
|
|
143
|
+
workflowSteps: ["kb_query", "kb_validate_upsert", "kb_upsert", "kb_check"],
|
|
144
|
+
validationRules: ["scenario-outcome-mismatch", "no-dangling-refs"],
|
|
145
|
+
writePolicy: "sequential_upsert",
|
|
146
|
+
},
|
|
113
147
|
scenario_tests: {
|
|
114
148
|
order: 70,
|
|
115
149
|
objective:
|
|
@@ -206,6 +240,9 @@ const GAP_PHASE: Readonly<Record<string, RepairPhase>> = {
|
|
|
206
240
|
missing_scenario: "scenario_endpoints",
|
|
207
241
|
infeasible_scenario: "scenario_endpoints",
|
|
208
242
|
unknown_scenario_feasibility: "scenario_endpoints",
|
|
243
|
+
dimension_unrepresented: "dimension_grounding",
|
|
244
|
+
happy_path_only: "negative_scenarios",
|
|
245
|
+
uncovered_case: "case_scenarios",
|
|
209
246
|
missing_scenario_test: "scenario_tests",
|
|
210
247
|
proof_snapshot_unavailable: "snapshot_runtime",
|
|
211
248
|
missing_passing_e2e: "proof_evidence",
|
|
@@ -235,6 +272,9 @@ const GAP_STAGE: Readonly<Record<string, string>> = {
|
|
|
235
272
|
missing_scenario: "scenarios",
|
|
236
273
|
infeasible_scenario: "scenarios",
|
|
237
274
|
unknown_scenario_feasibility: "scenarios",
|
|
275
|
+
dimension_unrepresented: "dimension",
|
|
276
|
+
happy_path_only: "negative_path",
|
|
277
|
+
uncovered_case: "cases",
|
|
238
278
|
missing_scenario_test: "scenario_tests",
|
|
239
279
|
missing_passing_e2e: "passing_e2e",
|
|
240
280
|
missing_proof_receipt: "passing_e2e",
|
|
@@ -255,6 +295,9 @@ const PROOF_STAGE_KEY: Readonly<Record<string, string>> = {
|
|
|
255
295
|
contradictions: "contradictions",
|
|
256
296
|
scenarios: "scenarios",
|
|
257
297
|
scenario_tests: "scenarioTests",
|
|
298
|
+
negative_path: "negativePath",
|
|
299
|
+
dimension: "dimension",
|
|
300
|
+
cases: "cases",
|
|
258
301
|
passing_e2e: "passingE2e",
|
|
259
302
|
executable_symbols: "executableSymbols",
|
|
260
303
|
production_symbols: "productionSymbols",
|
|
@@ -268,6 +311,34 @@ export type RepairPlanRepair = {
|
|
|
268
311
|
readonly proofStage: string;
|
|
269
312
|
readonly action: string;
|
|
270
313
|
readonly evidence: Readonly<Record<string, unknown>>;
|
|
314
|
+
/** Draft scenarios for an uncovered_case repair, one per uncovered case. */
|
|
315
|
+
readonly caseSkeletons?: readonly CaseSkeleton[];
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
type SkeletonEntity = {
|
|
319
|
+
readonly type: "fact" | "scenario";
|
|
320
|
+
readonly id: string;
|
|
321
|
+
readonly properties: Readonly<Record<string, unknown>>;
|
|
322
|
+
readonly relationships: readonly {
|
|
323
|
+
readonly type: string;
|
|
324
|
+
readonly from: string;
|
|
325
|
+
readonly to: string;
|
|
326
|
+
}[];
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// implements REQ-kibi-case-coverage
|
|
330
|
+
/**
|
|
331
|
+
* An upsert-ready draft scenario for one uncovered case: the fact it assumes
|
|
332
|
+
* (the case's value class) and the scenario that expects the predicted
|
|
333
|
+
* outcome. Drafts never count as coverage, so writing one changes no gap
|
|
334
|
+
* until it is verified and made active. placeholder is true when the value
|
|
335
|
+
* must be chosen by the author (a value outside a set).
|
|
336
|
+
*/
|
|
337
|
+
export type CaseSkeleton = {
|
|
338
|
+
readonly caseId: string;
|
|
339
|
+
readonly constraint: string;
|
|
340
|
+
readonly placeholder: boolean;
|
|
341
|
+
readonly entities: readonly [SkeletonEntity, SkeletonEntity];
|
|
271
342
|
};
|
|
272
343
|
|
|
273
344
|
export type RepairPlanBatch = {
|
|
@@ -379,6 +450,82 @@ function stageEvidence(
|
|
|
379
450
|
return isRecord(stable) ? stable : {};
|
|
380
451
|
}
|
|
381
452
|
|
|
453
|
+
const VALUE_FIELD: Readonly<Record<string, string>> = {
|
|
454
|
+
int: "value_int",
|
|
455
|
+
number: "value_number",
|
|
456
|
+
set: "value_set",
|
|
457
|
+
string: "value_string",
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
function caseSkeleton(
|
|
461
|
+
requirementId: string,
|
|
462
|
+
row: Readonly<Record<string, unknown>>,
|
|
463
|
+
): CaseSkeleton | undefined {
|
|
464
|
+
const caseId = typeof row.caseId === "string" ? row.caseId : undefined;
|
|
465
|
+
const assumption = isRecord(row.assumption) ? row.assumption : undefined;
|
|
466
|
+
const valueType =
|
|
467
|
+
typeof assumption?.value_type === "string" ? assumption.value_type : "";
|
|
468
|
+
const valueField = VALUE_FIELD[valueType];
|
|
469
|
+
if (caseId === undefined || assumption === undefined || !valueField)
|
|
470
|
+
return undefined;
|
|
471
|
+
const constraint = typeof row.constraint === "string" ? row.constraint : "";
|
|
472
|
+
const property =
|
|
473
|
+
typeof assumption.property_key === "string" ? assumption.property_key : "";
|
|
474
|
+
const expects = row.predicted === "rejection" ? "rejection" : "success";
|
|
475
|
+
const placeholder = JSON.stringify(assumption.value).includes("<");
|
|
476
|
+
const suffix = caseId.replace(/^CASE-/, "");
|
|
477
|
+
const factId = `FACT-CASE-${suffix}`;
|
|
478
|
+
const scenarioId = `SCEN-CASE-${suffix}`;
|
|
479
|
+
return {
|
|
480
|
+
caseId,
|
|
481
|
+
constraint,
|
|
482
|
+
placeholder,
|
|
483
|
+
entities: [
|
|
484
|
+
{
|
|
485
|
+
type: "fact",
|
|
486
|
+
id: factId,
|
|
487
|
+
properties: {
|
|
488
|
+
title: `${property} ${constraint}`,
|
|
489
|
+
status: "active",
|
|
490
|
+
fact_kind: "property_value",
|
|
491
|
+
subject_key: assumption.subject_key,
|
|
492
|
+
property_key: property,
|
|
493
|
+
operator: assumption.operator,
|
|
494
|
+
value_type: valueType,
|
|
495
|
+
[valueField]: assumption.value,
|
|
496
|
+
},
|
|
497
|
+
relationships: [],
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
type: "scenario",
|
|
501
|
+
id: scenarioId,
|
|
502
|
+
properties: {
|
|
503
|
+
title: `${property} ${constraint} is ${expects === "rejection" ? "refused" : "accepted"}`,
|
|
504
|
+
status: "draft",
|
|
505
|
+
expects,
|
|
506
|
+
},
|
|
507
|
+
relationships: [
|
|
508
|
+
{ type: "assumes", from: scenarioId, to: factId },
|
|
509
|
+
{ type: "specified_by", from: requirementId, to: scenarioId },
|
|
510
|
+
],
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function caseSkeletons(
|
|
517
|
+
requirementId: string,
|
|
518
|
+
evidence: Readonly<Record<string, unknown>>,
|
|
519
|
+
): CaseSkeleton[] {
|
|
520
|
+
const rows = Array.isArray(evidence.uncoveredCases)
|
|
521
|
+
? evidence.uncoveredCases.filter(isRecord)
|
|
522
|
+
: [];
|
|
523
|
+
return rows.flatMap((row) => {
|
|
524
|
+
const skeleton = caseSkeleton(requirementId, row);
|
|
525
|
+
return skeleton === undefined ? [] : [skeleton];
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
382
529
|
function batchId(requirementId: string, phase: RepairPhase): string {
|
|
383
530
|
const order = String(REPAIR_PHASES[phase].order).padStart(3, "0");
|
|
384
531
|
const safeRequirementId = requirementId.replace(/[^A-Za-z0-9_-]/g, "-");
|
|
@@ -408,6 +555,7 @@ function buildRequirementBatches(
|
|
|
408
555
|
typeof sourceRepair?.stage === "string"
|
|
409
556
|
? sourceRepair.stage
|
|
410
557
|
: (GAP_STAGE[gap] ?? "manual_review");
|
|
558
|
+
const evidence = stageEvidence(row, proofStage);
|
|
411
559
|
const repair: RepairPlanRepair = {
|
|
412
560
|
requirementId,
|
|
413
561
|
gap,
|
|
@@ -417,7 +565,10 @@ function buildRequirementBatches(
|
|
|
417
565
|
),
|
|
418
566
|
proofStage,
|
|
419
567
|
action: actionForRepair(sourceRepair, phase),
|
|
420
|
-
evidence
|
|
568
|
+
evidence,
|
|
569
|
+
...(gap === "uncovered_case"
|
|
570
|
+
? { caseSkeletons: caseSkeletons(requirementId, evidence) }
|
|
571
|
+
: {}),
|
|
421
572
|
};
|
|
422
573
|
const phaseRepairs = byPhase.get(phase) ?? [];
|
|
423
574
|
phaseRepairs.push(repair);
|
|
@@ -728,7 +728,7 @@ export function schema6ReviewActionsFromCheck(
|
|
|
728
728
|
safety: "review",
|
|
729
729
|
invocation: {
|
|
730
730
|
kind: "review",
|
|
731
|
-
instruction: `${total} agent-authored requirement(s) have no human approval${total > listed.length ? `; these ${listed.length} come first` : ""}: ${listed.join(", ")}. Have a person read each one; when it states the intended behavior, kb_upsert it with origin.approved_by set to the reviewer (keep kind: agent and the rest of origin). Rerun kb_check for the next page.`,
|
|
731
|
+
instruction: `${total} agent-authored requirement(s) have no human approval${total > listed.length ? `; these ${listed.length} come first` : ""}: ${listed.join(", ")}. Have a person read each one; when it states the intended behavior, kb_upsert it with origin.approved_by set to the reviewer (keep kind: agent and the rest of origin), in the same change and pull request as the requirement, not a separate one. Rerun kb_check for the next page.`,
|
|
732
732
|
},
|
|
733
733
|
affectedEntityIds: listed,
|
|
734
734
|
affectedFiles: [...new Set(agentRequirementFiles)].sort(),
|