kibi-cli 2.21.2 → 2.22.1
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-register-foundation.d.ts.map +1 -1
- package/dist/cli-register-foundation.js +1 -0
- package/dist/commands/check.d.ts +0 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +47 -111
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +108 -6
- package/dist/commands/init-helpers.d.ts +16 -2
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +52 -10
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -1
- package/dist/commands/legacy-storage-migration.d.ts +46 -1
- package/dist/commands/legacy-storage-migration.d.ts.map +1 -1
- package/dist/commands/legacy-storage-migration.js +219 -9
- package/dist/commands/migrate.d.ts +37 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +72 -188
- package/dist/commands/sync/manifest.d.ts.map +1 -1
- package/dist/commands/sync/manifest.js +9 -0
- package/dist/extractors/symbols-coordinator.js +1 -0
- package/dist/intent-search.d.ts.map +1 -1
- package/dist/intent-search.js +28 -6
- package/dist/operations/migration/inventory-rederive.d.ts +40 -1
- package/dist/operations/migration/inventory-rederive.d.ts.map +1 -1
- package/dist/operations/migration/inventory-rederive.js +99 -8
- package/dist/operations/migration/requirement-status.d.ts +25 -0
- package/dist/operations/migration/requirement-status.d.ts.map +1 -0
- package/dist/operations/migration/requirement-status.js +105 -0
- package/dist/operations/migration/schema6.d.ts +25 -0
- package/dist/operations/migration/schema6.d.ts.map +1 -1
- package/dist/operations/migration/schema6.js +265 -7
- package/dist/operations/migration/symbol-legacy-links.d.ts +51 -0
- package/dist/operations/migration/symbol-legacy-links.d.ts.map +1 -0
- package/dist/operations/migration/symbol-legacy-links.js +319 -0
- package/dist/operations/mutation/context-warning.d.ts +12 -2
- package/dist/operations/mutation/context-warning.d.ts.map +1 -1
- package/dist/operations/mutation/context-warning.js +6 -6
- package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
- package/dist/operations/mutation/source-authoring.js +130 -35
- package/dist/operations/mutation/types.d.ts +2 -1
- package/dist/operations/mutation/types.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.d.ts +17 -2
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +28 -11
- package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
- package/dist/operations/mutation/validate-upsert.js +40 -8
- package/dist/operations/mutation/validation.d.ts.map +1 -1
- package/dist/operations/mutation/validation.js +43 -1
- package/dist/operations/planning/apply-plan.d.ts.map +1 -1
- package/dist/operations/planning/apply-plan.js +34 -10
- package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/ingestion-boundary.js +25 -1
- package/dist/operations/semantic-advisor/stored-ledger-drift.d.ts +19 -0
- package/dist/operations/semantic-advisor/stored-ledger-drift.d.ts.map +1 -0
- package/dist/operations/semantic-advisor/stored-ledger-drift.js +78 -0
- package/dist/public/impact/diagnostics.d.ts.map +1 -1
- package/dist/public/impact/diagnostics.js +24 -14
- package/dist/public/operations/catalog.d.ts +3 -3
- package/dist/public/operations/check-executor.d.ts.map +1 -1
- package/dist/public/operations/check-executor.js +3 -0
- package/dist/public/operations/contracts.d.ts.map +1 -1
- package/dist/public/operations/contracts.js +6 -1
- package/dist/public/operations/decision-firmness.d.ts +3 -1
- package/dist/public/operations/decision-firmness.d.ts.map +1 -1
- package/dist/public/operations/decision-firmness.js +13 -3
- package/dist/public/operations/discovery-executors.d.ts +2 -0
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +24 -2
- package/dist/public/operations/migration-plan.d.ts +15 -0
- package/dist/public/operations/migration-plan.d.ts.map +1 -1
- package/dist/public/operations/migration-plan.js +130 -17
- package/dist/public/operations/result-envelope.d.ts.map +1 -1
- package/dist/public/operations/result-envelope.js +27 -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 +2 -1
- package/dist/public/operations/specs/check.d.ts +1 -1
- package/dist/public/operations/specs/planning.d.ts +2 -2
- package/dist/public/operations/specs/planning.js +2 -2
- package/dist/public/operations/specs/semantic.d.ts.map +1 -1
- package/dist/public/operations/specs/semantic.js +26 -1
- package/dist/public/operations/symbol-granularity-check.d.ts +18 -0
- package/dist/public/operations/symbol-granularity-check.d.ts.map +1 -0
- package/dist/public/operations/symbol-granularity-check.js +175 -0
- package/dist/public/skills/kibi-usage/SKILL.md +8 -3
- package/dist/public/symbol-granularity.d.ts +17 -0
- package/dist/public/symbol-granularity.d.ts.map +1 -1
- package/dist/public/symbol-granularity.js +21 -0
- package/dist/traceability/evidence-model.d.ts +7 -3
- package/dist/traceability/evidence-model.d.ts.map +1 -1
- package/dist/traceability/staged-symbols-manifest.js +6 -5
- package/dist/traceability/validate.d.ts +14 -1
- package/dist/traceability/validate.d.ts.map +1 -1
- package/dist/traceability/validate.js +41 -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 +3 -3
- package/src/public/impact/diagnostics.ts +29 -17
- package/src/public/operations/check-executor.ts +7 -0
- package/src/public/operations/contracts.ts +6 -1
- package/src/public/operations/decision-firmness.ts +18 -2
- package/src/public/operations/discovery-executors.ts +28 -2
- package/src/public/operations/migration-plan.ts +147 -20
- package/src/public/operations/result-envelope.ts +34 -1
- package/src/public/operations/specs/check-rules.generated.ts +2 -1
- package/src/public/operations/specs/planning.ts +2 -2
- package/src/public/operations/specs/semantic.ts +37 -2
- package/src/public/operations/symbol-granularity-check.ts +233 -0
- package/src/public/skills/kibi-usage/SKILL.md +8 -3
- package/src/public/symbol-granularity.ts +37 -0
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getNonBehavioralSymbolNames,
|
|
13
13
|
isAllowedGranularityReason,
|
|
14
14
|
isTraceabilityRelationshipType,
|
|
15
|
+
narrowerSymbols,
|
|
15
16
|
} from "../symbol-granularity.js";
|
|
16
17
|
import type {
|
|
17
18
|
QualityDiagnostic,
|
|
@@ -19,16 +20,6 @@ import type {
|
|
|
19
20
|
SymbolGranularityDiagnosticsOptions,
|
|
20
21
|
} from "./types.js";
|
|
21
22
|
|
|
22
|
-
function isNarrowerSymbol(
|
|
23
|
-
manifestSymbolName: string,
|
|
24
|
-
candidate: ExtractedSymbol,
|
|
25
|
-
sourceSymbols: readonly ExtractedSymbol[],
|
|
26
|
-
): boolean {
|
|
27
|
-
if (candidate.name === manifestSymbolName) return false;
|
|
28
|
-
if (candidate.name.startsWith(`${manifestSymbolName}.`)) return true;
|
|
29
|
-
return !sourceSymbols.some((symbol) => symbol.name === manifestSymbolName);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
23
|
function hasTraceabilityRelationship(result: ExtractionResult): boolean {
|
|
33
24
|
return result.relationships.some((relationship) =>
|
|
34
25
|
isTraceabilityRelationshipType(relationship.type),
|
|
@@ -110,6 +101,7 @@ function isChangedBehavioralReviewCandidate(symbol: ExtractedSymbol): boolean {
|
|
|
110
101
|
return symbol.hunkRanges.length > 0;
|
|
111
102
|
}
|
|
112
103
|
|
|
104
|
+
// implements REQ-cli-staged-impact-enforcement, REQ-cli-check
|
|
113
105
|
export function createSymbolGranularityDiagnostics(
|
|
114
106
|
options: SymbolGranularityDiagnosticsOptions,
|
|
115
107
|
): KibiImpactDiagnostic[] {
|
|
@@ -130,15 +122,17 @@ export function createSymbolGranularityDiagnostics(
|
|
|
130
122
|
result.sourceFile,
|
|
131
123
|
options,
|
|
132
124
|
);
|
|
133
|
-
const
|
|
134
|
-
|
|
125
|
+
const narrower = narrowerSymbols(
|
|
126
|
+
result.entity.title,
|
|
127
|
+
candidateSymbols,
|
|
128
|
+
sourceSymbols,
|
|
135
129
|
);
|
|
136
|
-
if (
|
|
130
|
+
if (narrower.length === 0) continue;
|
|
137
131
|
|
|
138
|
-
const behavioralNames = getBehavioralSymbolNames(
|
|
132
|
+
const behavioralNames = getBehavioralSymbolNames(narrower);
|
|
139
133
|
if (behavioralNames.length === 0) continue;
|
|
140
134
|
|
|
141
|
-
const nonBehavioralNames = getNonBehavioralSymbolNames(
|
|
135
|
+
const nonBehavioralNames = getNonBehavioralSymbolNames(narrower);
|
|
142
136
|
const ignoredSymbolsSuggestion =
|
|
143
137
|
nonBehavioralNames.length > 0
|
|
144
138
|
? ` Non-behavioral symbols ignored for this decision: ${nonBehavioralNames.join(
|
|
@@ -146,15 +140,33 @@ export function createSymbolGranularityDiagnostics(
|
|
|
146
140
|
)}.`
|
|
147
141
|
: "";
|
|
148
142
|
|
|
143
|
+
// A title the extractor does not produce (a private class member, a
|
|
144
|
+
// renamed or removed declaration) can never become granular by refresh:
|
|
145
|
+
// say so instead of only calling the link coarse.
|
|
146
|
+
const titleExtracted = sourceSymbols.some(
|
|
147
|
+
(symbol) => symbol.name === result.entity.title,
|
|
148
|
+
);
|
|
149
149
|
diagnostics.push({
|
|
150
150
|
id: "symbol_granularity_violation",
|
|
151
151
|
severity: "error",
|
|
152
152
|
blocking: true,
|
|
153
153
|
category: "symbol",
|
|
154
|
+
entityId: result.entity.id,
|
|
155
|
+
...(result.entity.source ? { source: result.entity.source } : {}),
|
|
154
156
|
files: [result.entity.source, result.sourceFile],
|
|
155
157
|
docs: ["docs/symbol-traceability-taxonomy.md"],
|
|
156
|
-
message:
|
|
157
|
-
|
|
158
|
+
message: titleExtracted
|
|
159
|
+
? `Symbol ${result.entity.id} links ${result.sourceFile} coarsely while granular symbols are available (behavioral only): ${behavioralNames.join(", ")}`
|
|
160
|
+
: `Symbol ${result.entity.id} names '${result.entity.title}', which the source extractor does not produce for ${result.sourceFile} (private class members and removed or renamed declarations are not tracked), so it links ${result.sourceFile} coarsely while granular symbols are available (behavioral only): ${behavioralNames.join(", ")}`,
|
|
161
|
+
suggestion: titleExtracted
|
|
162
|
+
? `Move ownership/coverage/test relationships to the narrow behavioral symbol, add a manifest behavioral anchor, or add granularity_reason with ${ALLOWED_GRANULARITY_REASONS_PROSE} when the coarse symbol is intentional.${ignoredSymbolsSuggestion}`
|
|
163
|
+
: `Re-point this symbol's relationships to the public declaration that owns the behavior (one of: ${behavioralNames.join(", ")}) or remove the symbol; add granularity_reason with ${ALLOWED_GRANULARITY_REASONS_PROSE} only when coarse ownership is intentional.${ignoredSymbolsSuggestion}`,
|
|
164
|
+
evidence: {
|
|
165
|
+
sourceFile: result.sourceFile,
|
|
166
|
+
title: result.entity.title,
|
|
167
|
+
titleExtracted,
|
|
168
|
+
granularSymbols: behavioralNames,
|
|
169
|
+
},
|
|
158
170
|
});
|
|
159
171
|
}
|
|
160
172
|
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
collectSourcePathDanglingViolations,
|
|
58
58
|
} from "./source-path-dangling.js";
|
|
59
59
|
import { collectSourceRelationshipParityViolations } from "./source-relationship-parity.js";
|
|
60
|
+
import { collectSymbolGranularityViolations } from "./symbol-granularity-check.js";
|
|
60
61
|
import type { OperationResult } from "./types.js";
|
|
61
62
|
import { readWorkspaceSnapshot } from "./workspace-snapshot.js";
|
|
62
63
|
|
|
@@ -367,6 +368,11 @@ export async function executeCheck(
|
|
|
367
368
|
const sourcePathFindings = rulesAllowlist.has(SOURCE_PATH_DANGLING_RULE)
|
|
368
369
|
? collectSourcePathDanglingViolations(workspaceRoot)
|
|
369
370
|
: [];
|
|
371
|
+
const symbolGranularityFindings = await collectSymbolGranularityViolations(
|
|
372
|
+
prolog,
|
|
373
|
+
workspaceRoot,
|
|
374
|
+
rulesAllowlist,
|
|
375
|
+
);
|
|
370
376
|
const originReviewFindings = await collectOriginReviewViolations(
|
|
371
377
|
prolog,
|
|
372
378
|
rulesAllowlist,
|
|
@@ -390,6 +396,7 @@ export async function executeCheck(
|
|
|
390
396
|
...queryPlanViolations,
|
|
391
397
|
...sourceRelationshipParityViolations,
|
|
392
398
|
...sourcePathFindings,
|
|
399
|
+
...symbolGranularityFindings,
|
|
393
400
|
...predicateConformanceFindings,
|
|
394
401
|
...originReviewFindings,
|
|
395
402
|
...entityContextFindings,
|
|
@@ -442,7 +442,12 @@ const BASE_DATA_SCHEMAS: Readonly<
|
|
|
442
442
|
}),
|
|
443
443
|
kb_skills_read: objectData({ content: stringValue }, ["content"]),
|
|
444
444
|
kb_query: objectData(
|
|
445
|
-
{
|
|
445
|
+
{
|
|
446
|
+
entities: recordArray,
|
|
447
|
+
count: integerValue,
|
|
448
|
+
factQuery: recordValue,
|
|
449
|
+
branchStore: recordValue,
|
|
450
|
+
},
|
|
446
451
|
["entities", "count"],
|
|
447
452
|
),
|
|
448
453
|
kb_search: objectData(
|
|
@@ -63,6 +63,16 @@ export const DECISION_FIRMNESS_RULES = [
|
|
|
63
63
|
|
|
64
64
|
const DECISION_LANES = ["requirements", "adr"] as const;
|
|
65
65
|
|
|
66
|
+
/** The schema version from which a supersede must explain itself. */
|
|
67
|
+
const SUPERSEDES_REASON_SCHEMA_VERSION = 9;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Told on a KB older than schema 9: the pending schema upgrade acknowledges
|
|
71
|
+
* supersedes recorded before the rule existed, so no edit is required now.
|
|
72
|
+
*/
|
|
73
|
+
const PRE_SCHEMA9_SUPERSEDES_NOTE =
|
|
74
|
+
" Supersedes recorded before schema 9 are acknowledged by the plan's schema-config-upgrade (kibi migrate --yes); add the reason only when you next touch the requirement.";
|
|
75
|
+
|
|
66
76
|
/** Every authored requirement and ADR. */
|
|
67
77
|
// implements REQ-kb-decision-firmness
|
|
68
78
|
export function listDecisionEntities(workspaceRoot: string): AuthoredEntity[] {
|
|
@@ -164,7 +174,11 @@ export function evaluateDecisionFirmness(
|
|
|
164
174
|
edges: ReadonlyMap<string, Set<string>>,
|
|
165
175
|
rules: ReadonlySet<string> = new Set(DECISION_FIRMNESS_RULES),
|
|
166
176
|
acknowledged: ReadonlySet<string> = new Set(),
|
|
177
|
+
options: Readonly<{ schemaVersion?: number | null }> = {},
|
|
167
178
|
): Violation[] {
|
|
179
|
+
const preSchema9 =
|
|
180
|
+
typeof options.schemaVersion === "number" &&
|
|
181
|
+
options.schemaVersion < SUPERSEDES_REASON_SCHEMA_VERSION;
|
|
168
182
|
const findings: Violation[] = [];
|
|
169
183
|
const superseded = supersededIds(edges);
|
|
170
184
|
const byId = new Map(entities.map((entity) => [entity.id, entity]));
|
|
@@ -241,7 +255,7 @@ export function evaluateDecisionFirmness(
|
|
|
241
255
|
rule: SUPERSEDES_REASON_MISSING_RULE,
|
|
242
256
|
entityId: entity.id,
|
|
243
257
|
description: `${entity.id} supersedes ${unacknowledged.join(", ")} without a '## ${WHY_THIS_CHANGED_HEADING}' section of at least ${MIN_WHY_CHANGED_WORDS} words`,
|
|
244
|
-
suggestion: `Add '## ${WHY_THIS_CHANGED_HEADING}' to the body: name the evidence (a failing test, a measured cost, a constraint the old decision missed, a new requirement) and answer the superseded decision's reasoning. A preference is not evidence; propose it instead of superseding
|
|
258
|
+
suggestion: `Add '## ${WHY_THIS_CHANGED_HEADING}' to the body: name the evidence (a failing test, a measured cost, a constraint the old decision missed, a new requirement) and answer the superseded decision's reasoning. A preference is not evidence; propose it instead of superseding.${preSchema9 ? PRE_SCHEMA9_SUPERSEDES_NOTE : ""}`,
|
|
245
259
|
source: entity.path,
|
|
246
260
|
evidence: { type: entity.type, supersedes: unacknowledged },
|
|
247
261
|
});
|
|
@@ -271,10 +285,12 @@ export function collectDecisionFirmnessViolations(
|
|
|
271
285
|
): Violation[] {
|
|
272
286
|
if (!DECISION_FIRMNESS_RULES.some((rule) => rules.has(rule))) return [];
|
|
273
287
|
const entities = listDecisionEntities(workspaceRoot);
|
|
288
|
+
const manifest = readKbManifest(workspaceRoot);
|
|
274
289
|
return evaluateDecisionFirmness(
|
|
275
290
|
entities,
|
|
276
291
|
authoredSupersedes(workspaceRoot, entities),
|
|
277
292
|
rules,
|
|
278
|
-
new Set(
|
|
293
|
+
new Set(manifest?.supersedesReasonAcknowledged ?? []),
|
|
294
|
+
{ schemaVersion: manifest?.schemaVersion ?? null },
|
|
279
295
|
);
|
|
280
296
|
}
|
|
@@ -95,6 +95,8 @@ export type QueryPayload = {
|
|
|
95
95
|
readonly entities: readonly Record<string, unknown>[];
|
|
96
96
|
readonly count: number;
|
|
97
97
|
readonly factQuery?: FactQueryPayload;
|
|
98
|
+
/** Present when an empty answer came from a store never compiled for this branch. */
|
|
99
|
+
readonly branchStore?: SearchBranchStoreNotice;
|
|
98
100
|
};
|
|
99
101
|
|
|
100
102
|
export const FACT_KINDS = [
|
|
@@ -711,9 +713,13 @@ export async function executeQuery(
|
|
|
711
713
|
? entity
|
|
712
714
|
: { ...entity, firmness };
|
|
713
715
|
});
|
|
716
|
+
// An empty page from a never-compiled store says nothing about the
|
|
717
|
+
// authored entities, so the answer names the store state.
|
|
718
|
+
const storeNotice =
|
|
719
|
+
indexedPage.count === 0 ? uncompiledSearchNotice(context) : null;
|
|
714
720
|
const text =
|
|
715
721
|
indexedPage.count === 0
|
|
716
|
-
? `No entities found${type ? ` of type '${type}'` : ""}.`
|
|
722
|
+
? `No entities found${type ? ` of type '${type}'` : ""}.${storeNotice ? ` Note: ${storeNotice.detail} Run kibi sync before relying on query results.` : ""}`
|
|
717
723
|
: `Found ${indexedPage.count} entities${type ? ` of type '${type}'` : ""}. Showing ${paginated.length} (offset ${offset}, limit ${limit}): ${paginated
|
|
718
724
|
.map((entity) => {
|
|
719
725
|
const entityId = String(entity.id ?? "").replace(
|
|
@@ -725,7 +731,11 @@ export async function executeQuery(
|
|
|
725
731
|
.join(", ")}`;
|
|
726
732
|
return {
|
|
727
733
|
content: [{ type: "text", text }],
|
|
728
|
-
structuredContent: {
|
|
734
|
+
structuredContent: {
|
|
735
|
+
entities: paginated,
|
|
736
|
+
count: indexedPage.count,
|
|
737
|
+
...(storeNotice ? { branchStore: storeNotice } : {}),
|
|
738
|
+
},
|
|
729
739
|
};
|
|
730
740
|
} catch (error) {
|
|
731
741
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1111,6 +1121,13 @@ export async function executeStatus(
|
|
|
1111
1121
|
: { proofSnapshotError: snapshotEvidence.error }),
|
|
1112
1122
|
};
|
|
1113
1123
|
const schemaStatus = readMigrationConfigStatus(context.workspaceRoot);
|
|
1124
|
+
// implements REQ-agent-guided-migration-orchestration
|
|
1125
|
+
// A schema upgrade plans the source rewrites kibi migrate performs with
|
|
1126
|
+
// it; the planner loads only when an upgrade is due.
|
|
1127
|
+
const sourcePlanner = schemaStatus.needsMigration
|
|
1128
|
+
? (await import("../../operations/migration/schema6.js"))
|
|
1129
|
+
.buildSchema6MigrationFragment
|
|
1130
|
+
: undefined;
|
|
1114
1131
|
const migrationPlan = buildActionsFromStatus({
|
|
1115
1132
|
workspaceRoot: context.workspaceRoot,
|
|
1116
1133
|
branchAttachment: attachment,
|
|
@@ -1125,6 +1142,15 @@ export async function executeStatus(
|
|
|
1125
1142
|
? snapshotEvidence.snapshot.hash
|
|
1126
1143
|
: null,
|
|
1127
1144
|
configStatus: schemaStatus,
|
|
1145
|
+
...(sourcePlanner !== undefined
|
|
1146
|
+
? {
|
|
1147
|
+
planSchemaUpgradeSources: (currentSchemaVersion: number | null) =>
|
|
1148
|
+
sourcePlanner({
|
|
1149
|
+
workspaceRoot: context.workspaceRoot,
|
|
1150
|
+
currentSchemaVersion,
|
|
1151
|
+
}),
|
|
1152
|
+
}
|
|
1153
|
+
: {}),
|
|
1128
1154
|
});
|
|
1129
1155
|
const listed = context.fs?.glob
|
|
1130
1156
|
? await context.fs.glob(
|
|
@@ -396,6 +396,66 @@ function predicateSchemaAlignmentAction(
|
|
|
396
396
|
/** Migration code for mechanical predicate namespace/alias repairs. */
|
|
397
397
|
export const PREDICATE_SCHEMA_ALIGNMENT_CODE = "predicate_schema_alignment";
|
|
398
398
|
|
|
399
|
+
/** Migration code of the grouped untracked-source parity findings. */
|
|
400
|
+
export const UNTRACKED_KNOWLEDGE_SOURCES_CODE = "untracked_knowledge_sources";
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Every source-relationship-parity finding explained by untracked files
|
|
404
|
+
* (evidence `untrackedSources`) has one cause and one fix: the files Git
|
|
405
|
+
* does not track and no pending-source receipt covers. After a storage move
|
|
406
|
+
* that can be thousands of relationships, so they become one review action
|
|
407
|
+
* naming the files instead of one per relationship.
|
|
408
|
+
*/
|
|
409
|
+
// implements REQ-agent-guided-migration-orchestration, REQ-kibi-source-relationship-parity
|
|
410
|
+
function untrackedSourceActionFromViolations(
|
|
411
|
+
violations: readonly Readonly<Record<string, unknown>>[],
|
|
412
|
+
): { action: MigrationAction; consumed: ReadonlySet<number> } | null {
|
|
413
|
+
const consumed = new Set<number>();
|
|
414
|
+
const files = new Set<string>();
|
|
415
|
+
const entityIds = new Set<string>();
|
|
416
|
+
const relationships: unknown[] = [];
|
|
417
|
+
for (const [index, violation] of violations.entries()) {
|
|
418
|
+
if (violation.rule !== "source-relationship-parity") continue;
|
|
419
|
+
const evidence = isRecord(violation.evidence) ? violation.evidence : {};
|
|
420
|
+
const sources = Array.isArray(evidence.untrackedSources)
|
|
421
|
+
? evidence.untrackedSources.filter(isRecord)
|
|
422
|
+
: [];
|
|
423
|
+
if (sources.length === 0) continue;
|
|
424
|
+
consumed.add(index);
|
|
425
|
+
for (const source of sources) {
|
|
426
|
+
if (typeof source.file === "string") files.add(source.file);
|
|
427
|
+
if (typeof source.entityId === "string") entityIds.add(source.entityId);
|
|
428
|
+
}
|
|
429
|
+
if (evidence.relationship !== undefined)
|
|
430
|
+
relationships.push(evidence.relationship);
|
|
431
|
+
}
|
|
432
|
+
if (consumed.size === 0) return null;
|
|
433
|
+
const listed = [...files].sort();
|
|
434
|
+
return {
|
|
435
|
+
consumed,
|
|
436
|
+
action: migrationAction({
|
|
437
|
+
id: "untracked-knowledge-sources",
|
|
438
|
+
code: UNTRACKED_KNOWLEDGE_SOURCES_CODE,
|
|
439
|
+
category: "relationship",
|
|
440
|
+
safety: "review",
|
|
441
|
+
invocation: {
|
|
442
|
+
kind: "review",
|
|
443
|
+
instruction: `${consumed.size} authored relationship(s) are missing from the compiled KB because ${listed.length} file(s) declaring their endpoints are not tracked by Git and no pending-source receipt covers them: ${listed.join(", ")}. Stage them (git add; staging is enough, Kibi never stages files itself) or rewrite the entities with kb_upsert, then run kibi sync and rerun kibi migrate.`,
|
|
444
|
+
},
|
|
445
|
+
affectedEntityIds: [...entityIds],
|
|
446
|
+
affectedFiles: listed,
|
|
447
|
+
postconditions: [{ rule: "source-relationship-parity", findings: 0 }],
|
|
448
|
+
evidence: {
|
|
449
|
+
relationshipCount: consumed.size,
|
|
450
|
+
files: listed,
|
|
451
|
+
// A sample; the count above is the full number.
|
|
452
|
+
relationships: relationships.slice(0, 20),
|
|
453
|
+
},
|
|
454
|
+
dispositionRequired: true,
|
|
455
|
+
}),
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
399
459
|
// implements REQ-agent-guided-migration-orchestration, REQ-kibi-predicate-vocabulary-migration, REQ-cli-schema-migration
|
|
400
460
|
export function buildActionsFromCheck(input: {
|
|
401
461
|
violations?: readonly Readonly<Record<string, unknown>>[];
|
|
@@ -404,8 +464,11 @@ export function buildActionsFromCheck(input: {
|
|
|
404
464
|
const actions: MigrationAction[] = [];
|
|
405
465
|
const lifecycle = lifecycleActionsFromViolations(input.violations ?? []);
|
|
406
466
|
actions.push(...lifecycle.actions.map(migrationAction));
|
|
467
|
+
const untracked = untrackedSourceActionFromViolations(input.violations ?? []);
|
|
468
|
+
if (untracked !== null) actions.push(untracked.action);
|
|
407
469
|
for (const [index, violation] of (input.violations ?? []).entries()) {
|
|
408
470
|
if (lifecycle.consumed.has(index)) continue;
|
|
471
|
+
if (untracked?.consumed.has(index)) continue;
|
|
409
472
|
const rule = typeof violation.rule === "string" ? violation.rule : "check";
|
|
410
473
|
const entityId =
|
|
411
474
|
typeof violation.entityId === "string" ? violation.entityId : "";
|
|
@@ -553,6 +616,14 @@ export function buildActionsFromCoverage(input: {
|
|
|
553
616
|
return actions;
|
|
554
617
|
}
|
|
555
618
|
|
|
619
|
+
/** Source-rewrite actions a schema upgrade performs, as its planner returns them. */
|
|
620
|
+
export type SchemaUpgradeSourceActions = Readonly<{
|
|
621
|
+
actions: readonly MigrationAction[];
|
|
622
|
+
/** Automatic actions that rewrite authored sources; the upgrade waits on them. */
|
|
623
|
+
sourceRewriteActionIds: readonly string[];
|
|
624
|
+
diagnostics: readonly string[];
|
|
625
|
+
}>;
|
|
626
|
+
|
|
556
627
|
export type MigrationConfigStatus = Readonly<{
|
|
557
628
|
status: "missing" | "invalid" | "older" | "current" | "newer";
|
|
558
629
|
currentVersion: number | null;
|
|
@@ -621,6 +692,18 @@ function readLegacySchemaVersion(
|
|
|
621
692
|
/** Migration action code of the automatic recompile of changed sources. */
|
|
622
693
|
export const FRESHNESS_RESYNC = "freshness_resync";
|
|
623
694
|
|
|
695
|
+
// implements REQ-cli-status-pre-first-sync
|
|
696
|
+
/**
|
|
697
|
+
* Stale reasons one `kibi sync` clears: sources whose content changed since
|
|
698
|
+
* the last compilation, and a store compiled under another compiler contract
|
|
699
|
+
* (the sync discards that cache, re-imports every source and stamps the
|
|
700
|
+
* current contract).
|
|
701
|
+
*/
|
|
702
|
+
const RESYNC_REASON_CODES: ReadonlySet<string> = new Set([
|
|
703
|
+
...MTIME_SOURCE_REASON_CODES,
|
|
704
|
+
"compiler_changed",
|
|
705
|
+
]);
|
|
706
|
+
|
|
624
707
|
export function buildActionsFromStatus(input: {
|
|
625
708
|
workspaceRoot: string;
|
|
626
709
|
branchAttachment?: Readonly<Record<string, unknown>>;
|
|
@@ -631,8 +714,17 @@ export function buildActionsFromStatus(input: {
|
|
|
631
714
|
kbSnapshotId?: string | null;
|
|
632
715
|
workspaceSnapshot?: string | null;
|
|
633
716
|
configStatus?: MigrationConfigStatus;
|
|
717
|
+
/**
|
|
718
|
+
* Plans the authored-source rewrites `kibi migrate --yes` performs on its
|
|
719
|
+
* way to the latest schema (the planner `kibi migrate` itself uses), so
|
|
720
|
+
* the schema upgrade declares every file it touches. Read-only.
|
|
721
|
+
*/
|
|
722
|
+
planSchemaUpgradeSources?: (
|
|
723
|
+
currentSchemaVersion: number | null,
|
|
724
|
+
) => SchemaUpgradeSourceActions;
|
|
634
725
|
}): MigrationPlan {
|
|
635
726
|
const actions: MigrationAction[] = [];
|
|
727
|
+
const diagnostics: string[] = [];
|
|
636
728
|
const attachment = input.branchAttachment;
|
|
637
729
|
const store = input.branchStore;
|
|
638
730
|
const configStatus =
|
|
@@ -686,17 +778,20 @@ export function buildActionsFromStatus(input: {
|
|
|
686
778
|
}
|
|
687
779
|
|
|
688
780
|
const storeState = typeof store?.state === "string" ? store.state : null;
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
781
|
+
// Legacy storage is evaluated whether or not the legacy config survives:
|
|
782
|
+
// legacy copies left beside a migrated .kb/ (moved, identical or stale)
|
|
783
|
+
// are the same finding 'kibi doctor' and 'kibi migrate' report.
|
|
784
|
+
// implements REQ-cli-schema-migration
|
|
785
|
+
const legacyStorage = planLegacyStorageMigration(input.workspaceRoot);
|
|
786
|
+
const legacyStorageMigrationRequired =
|
|
787
|
+
legacyConfigExists(input.workspaceRoot) ||
|
|
788
|
+
legacyStorage.moves.length > 0 ||
|
|
789
|
+
legacyStorage.removals.length > 0 ||
|
|
790
|
+
legacyStorage.blockers.length > 0;
|
|
692
791
|
// A legacy cutover that cannot move its files (a destination already
|
|
693
792
|
// exists, a malformed config) is an operator decision, not an automatic
|
|
694
793
|
// step that fails when applied and takes the compile with it.
|
|
695
|
-
const
|
|
696
|
-
? planLegacyStorageMigration(input.workspaceRoot)
|
|
697
|
-
: null;
|
|
698
|
-
const legacyStorageBlocked =
|
|
699
|
-
legacyStorage !== null && legacyStorage.blockers.length > 0;
|
|
794
|
+
const legacyStorageBlocked = legacyStorage.blockers.length > 0;
|
|
700
795
|
if (storeState === "missing") {
|
|
701
796
|
actions.push(
|
|
702
797
|
migrationAction({
|
|
@@ -735,6 +830,21 @@ export function buildActionsFromStatus(input: {
|
|
|
735
830
|
if (configStatus.needsMigration || legacyStorageMigrationRequired) {
|
|
736
831
|
const operatorOnly =
|
|
737
832
|
configStatus.status === "newer" || legacyStorageBlocked;
|
|
833
|
+
// implements REQ-agent-guided-migration-orchestration, REQ-cli-schema-migration
|
|
834
|
+
// The source rewrites the migrate run performs (legacy-link marks, origin
|
|
835
|
+
// backfill, lifecycle repairs, ...) are planned as their own actions with
|
|
836
|
+
// their own files, and the upgrade waits on them, so approving it means
|
|
837
|
+
// approving them. Knowledge still outside .kb/ is planned after it moves.
|
|
838
|
+
const sources =
|
|
839
|
+
!operatorOnly &&
|
|
840
|
+
!legacyStorageMigrationRequired &&
|
|
841
|
+
input.planSchemaUpgradeSources !== undefined
|
|
842
|
+
? input.planSchemaUpgradeSources(configStatus.currentVersion)
|
|
843
|
+
: null;
|
|
844
|
+
if (sources !== null) {
|
|
845
|
+
actions.push(...sources.actions);
|
|
846
|
+
diagnostics.push(...sources.diagnostics);
|
|
847
|
+
}
|
|
738
848
|
actions.push(
|
|
739
849
|
migrationAction({
|
|
740
850
|
id: "schema-config-upgrade",
|
|
@@ -750,28 +860,44 @@ export function buildActionsFromStatus(input: {
|
|
|
750
860
|
invocation: legacyStorageBlocked
|
|
751
861
|
? {
|
|
752
862
|
kind: "review",
|
|
753
|
-
instruction: `
|
|
863
|
+
instruction: `Legacy knowledge files outside .kb/ cannot be migrated automatically: ${legacyStorage.blockers.join(" ")} Then run 'kibi migrate --yes'.`,
|
|
754
864
|
}
|
|
755
865
|
: { kind: "cli", command_argv: ["kibi", "migrate", "--yes"] },
|
|
756
866
|
affectedFiles: [
|
|
757
867
|
".kb/manifest.json",
|
|
758
868
|
".kb/migrations",
|
|
759
869
|
".kb/config.json",
|
|
760
|
-
|
|
870
|
+
// The migrate run keeps the canonical derived entries ignored.
|
|
871
|
+
".gitignore",
|
|
872
|
+
// Unplanned source rewrites can touch any authored .kb/ file.
|
|
873
|
+
...(sources === null ? [".kb/"] : []),
|
|
874
|
+
...legacyStorage.moves.map((move) => move.from),
|
|
875
|
+
...legacyStorage.removals.map((removal) => removal.path),
|
|
761
876
|
],
|
|
762
877
|
evidence: {
|
|
763
878
|
configStatus,
|
|
764
|
-
...(
|
|
765
|
-
? {
|
|
766
|
-
|
|
879
|
+
...(sources === null
|
|
880
|
+
? {
|
|
881
|
+
sourceRewrites:
|
|
882
|
+
"Not planned as separate actions: kibi migrate --yes also marks coarse symbol links legacy-link, backfills origin and repairs lifecycle findings in authored .kb/ sources. The plan lists them as their own actions once the upgrade can run automatically on the canonical .kb/ layout.",
|
|
883
|
+
}
|
|
884
|
+
: {}),
|
|
885
|
+
...(legacyStorageMigrationRequired
|
|
886
|
+
? {
|
|
767
887
|
legacyStorage: {
|
|
768
888
|
moves: legacyStorage.moves,
|
|
889
|
+
removals: legacyStorage.removals,
|
|
890
|
+
conflicts: legacyStorage.conflicts,
|
|
769
891
|
blockers: legacyStorage.blockers,
|
|
770
892
|
legacyConfig: legacyStorage.legacyConfig,
|
|
771
893
|
},
|
|
772
|
-
}
|
|
894
|
+
}
|
|
895
|
+
: {}),
|
|
773
896
|
},
|
|
774
|
-
dependsOn:
|
|
897
|
+
dependsOn: [
|
|
898
|
+
...(storeState === "missing" ? ["branch-store-ensure"] : []),
|
|
899
|
+
...(sources?.sourceRewriteActionIds ?? []),
|
|
900
|
+
],
|
|
775
901
|
autoApplicable: !operatorOnly,
|
|
776
902
|
}),
|
|
777
903
|
);
|
|
@@ -816,12 +942,12 @@ export function buildActionsFromStatus(input: {
|
|
|
816
942
|
// implements REQ-cli-status-pre-first-sync
|
|
817
943
|
// Sources whose content changed since the last compilation (kb_status has
|
|
818
944
|
// already dropped mtime-only reasons) are fixed by recompiling them: one
|
|
819
|
-
// automatic `kibi sync`, not a review per file.
|
|
820
|
-
//
|
|
945
|
+
// automatic `kibi sync`, not a review per file. A store compiled under
|
|
946
|
+
// another compiler contract is fixed by the same sync. An uncompiled store
|
|
947
|
+
// is compiled by the step above, which also covers these reasons.
|
|
821
948
|
const resyncReasons = (input.staleReasons ?? []).filter(
|
|
822
949
|
(reason) =>
|
|
823
|
-
typeof reason.code === "string" &&
|
|
824
|
-
MTIME_SOURCE_REASON_CODES.has(reason.code),
|
|
950
|
+
typeof reason.code === "string" && RESYNC_REASON_CODES.has(reason.code),
|
|
825
951
|
);
|
|
826
952
|
if (resyncReasons.length > 0 && uncompiled === undefined) {
|
|
827
953
|
actions.push(
|
|
@@ -856,7 +982,7 @@ export function buildActionsFromStatus(input: {
|
|
|
856
982
|
// Planned above as an automatic compile step, not a review.
|
|
857
983
|
if (code === BRANCH_STORE_NOT_COMPILED) continue;
|
|
858
984
|
// Planned above as one automatic resync (or covered by the compile).
|
|
859
|
-
if (
|
|
985
|
+
if (RESYNC_REASON_CODES.has(code)) continue;
|
|
860
986
|
const file = typeof reason.path === "string" ? reason.path : "";
|
|
861
987
|
actions.push(
|
|
862
988
|
migrationAction({
|
|
@@ -940,5 +1066,6 @@ export function buildActionsFromStatus(input: {
|
|
|
940
1066
|
],
|
|
941
1067
|
incompleteDomains,
|
|
942
1068
|
actions,
|
|
1069
|
+
diagnostics,
|
|
943
1070
|
});
|
|
944
1071
|
}
|
|
@@ -17,6 +17,36 @@ function diagnostics(value: unknown): KibiResult["diagnostics"] {
|
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// Discovery operations whose empty answer may come from an uncompiled store.
|
|
21
|
+
const STORE_READ_OPERATIONS = new Set(["kb_search", "kb_query"]);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A search or query answered from a branch store that was never compiled
|
|
25
|
+
* reads an empty KB. The payload already names the store state; the envelope
|
|
26
|
+
* also raises it as a warning, so a caller that reads only `status` and
|
|
27
|
+
* `diagnostics` does not take the empty answer as "nothing governs this".
|
|
28
|
+
*/
|
|
29
|
+
// implements REQ-cli-status-pre-first-sync, REQ-mcp-search-discovery
|
|
30
|
+
function uncompiledStoreDiagnostics(
|
|
31
|
+
operation: string,
|
|
32
|
+
row: Record<string, unknown> | undefined,
|
|
33
|
+
): KibiResult["diagnostics"] {
|
|
34
|
+
const store = row?.branchStore;
|
|
35
|
+
if (
|
|
36
|
+
!STORE_READ_OPERATIONS.has(operation) ||
|
|
37
|
+
!record(store) ||
|
|
38
|
+
store.state !== "not_compiled"
|
|
39
|
+
)
|
|
40
|
+
return [];
|
|
41
|
+
return [
|
|
42
|
+
{
|
|
43
|
+
...(typeof store.code === "string" ? { code: store.code } : {}),
|
|
44
|
+
severity: "warning",
|
|
45
|
+
message: `${typeof store.detail === "string" ? `${store.detail} ` : ""}An empty answer here does not mean no requirement governs this; run \`kibi sync\` and ask again.`,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
|
|
20
50
|
function nextActions(value: unknown): KibiResult["nextActions"] {
|
|
21
51
|
if (!Array.isArray(value)) return [];
|
|
22
52
|
return value.flatMap((entry) => {
|
|
@@ -214,7 +244,10 @@ export function toKibiResult<T>(
|
|
|
214
244
|
...spec.effects.map((effect) => effectStatus(effect, row, outcome)),
|
|
215
245
|
...extraFailedEffects(spec.effects, row),
|
|
216
246
|
],
|
|
217
|
-
diagnostics: options.diagnostics ??
|
|
247
|
+
diagnostics: options.diagnostics ?? [
|
|
248
|
+
...diagnostics(row?.diagnostics),
|
|
249
|
+
...uncompiledStoreDiagnostics(String(spec.name), row),
|
|
250
|
+
],
|
|
218
251
|
nextActions: options.nextActions ?? nextActions(row?.nextActions),
|
|
219
252
|
...(resultError ? { error: resultError } : {}),
|
|
220
253
|
};
|
|
@@ -13,6 +13,7 @@ export const SELECTABLE_RULE_NAMES: readonly string[] = [
|
|
|
13
13
|
"must-priority-coverage",
|
|
14
14
|
"symbol-coverage",
|
|
15
15
|
"symbol-traceability",
|
|
16
|
+
"symbol-granularity",
|
|
16
17
|
"no-dangling-refs",
|
|
17
18
|
"source-relationship-parity",
|
|
18
19
|
"branch-store-not-compiled",
|
|
@@ -66,4 +67,4 @@ export const SELECTABLE_RULE_NAMES: readonly string[] = [
|
|
|
66
67
|
// implements REQ-006
|
|
67
68
|
/** Comma-separated "Allowed:" fragment for the kb_check rules description. */
|
|
68
69
|
export const SELECTABLE_RULES_ALLOWED =
|
|
69
|
-
"must-priority-coverage, symbol-coverage, symbol-traceability, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers";
|
|
70
|
+
"must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers";
|
|
@@ -236,13 +236,13 @@ export const applyPlanSpec = {
|
|
|
236
236
|
type: "object",
|
|
237
237
|
required: ["version", "planHash"],
|
|
238
238
|
description:
|
|
239
|
-
"The complete kibi.bootstrap-plan.v1, kibi.compile-plan.v1, kibi.migration-plan.v2, or hash-bound kibi.entity-deletion-plan.v1 object. Migration plans require approvedActionIds and reject blocked
|
|
239
|
+
"The complete kibi.bootstrap-plan.v1, kibi.compile-plan.v1, kibi.migration-plan.v2, or hash-bound kibi.entity-deletion-plan.v1 object. Migration plans require approvedActionIds and reject blocked actions and review actions Kibi cannot execute.",
|
|
240
240
|
},
|
|
241
241
|
approvedActionIds: {
|
|
242
242
|
type: "array",
|
|
243
243
|
items: { type: "string", minLength: 1 },
|
|
244
244
|
description:
|
|
245
|
-
"Required for migration-plan.v2. Exact
|
|
245
|
+
"Required for migration-plan.v2. Exact action IDs explicitly approved for this application: automatic actions, or review actions with a cli invocation that Kibi executes only when approved by id (for example semantic_inventory_rederive_drop_grounding).",
|
|
246
246
|
},
|
|
247
247
|
recoveryJournalId: {
|
|
248
248
|
type: "string",
|