kibi-cli 2.24.0 → 2.26.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-errors.d.ts +14 -0
- package/dist/cli-errors.d.ts.map +1 -1
- package/dist/cli-errors.js +31 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +28 -0
- package/dist/commands/migrate.d.ts +2 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +115 -4
- package/dist/commands/sync/cache.d.ts +30 -0
- package/dist/commands/sync/cache.d.ts.map +1 -1
- package/dist/commands/sync/cache.js +61 -0
- package/dist/commands/sync/extraction.d.ts +6 -0
- package/dist/commands/sync/extraction.d.ts.map +1 -1
- package/dist/commands/sync/extraction.js +4 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +27 -1
- package/dist/firm-decision-edit.d.ts +30 -0
- package/dist/firm-decision-edit.d.ts.map +1 -0
- package/dist/firm-decision-edit.js +90 -0
- package/dist/operations/bootstrap/claim-restatement.d.ts +37 -0
- package/dist/operations/bootstrap/claim-restatement.d.ts.map +1 -0
- package/dist/operations/bootstrap/claim-restatement.js +83 -0
- package/dist/operations/bootstrap/generate.d.ts.map +1 -1
- package/dist/operations/bootstrap/generate.js +23 -12
- package/dist/operations/bootstrap/intent-claims.d.ts +5 -2
- package/dist/operations/bootstrap/intent-claims.d.ts.map +1 -1
- package/dist/operations/bootstrap/intent-claims.js +44 -4
- package/dist/operations/migration/requirement-status.d.ts +41 -2
- package/dist/operations/migration/requirement-status.d.ts.map +1 -1
- package/dist/operations/migration/requirement-status.js +75 -7
- package/dist/operations/migration/schema6.d.ts +9 -5
- package/dist/operations/migration/schema6.d.ts.map +1 -1
- package/dist/operations/migration/schema6.js +82 -13
- package/dist/operations/migration/symbol-coordinates-refresh.d.ts +36 -0
- package/dist/operations/migration/symbol-coordinates-refresh.d.ts.map +1 -0
- package/dist/operations/migration/symbol-coordinates-refresh.js +99 -0
- package/dist/operations/mutation/firm-decision-guard.d.ts +10 -0
- package/dist/operations/mutation/firm-decision-guard.d.ts.map +1 -0
- package/dist/operations/mutation/firm-decision-guard.js +71 -0
- package/dist/operations/mutation/source-authoring.d.ts +9 -0
- package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
- package/dist/operations/mutation/source-authoring.js +20 -0
- package/dist/operations/mutation/stored-entity.d.ts +5 -0
- package/dist/operations/mutation/stored-entity.d.ts.map +1 -1
- package/dist/operations/mutation/uncompiled-source.d.ts +43 -0
- package/dist/operations/mutation/uncompiled-source.d.ts.map +1 -0
- package/dist/operations/mutation/uncompiled-source.js +115 -0
- 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 +34 -5
- package/dist/public/impact/analyzer.d.ts.map +1 -1
- package/dist/public/impact/analyzer.js +13 -3
- package/dist/public/impact/diagnostics.js +1 -1
- package/dist/public/impact/unowned-files.d.ts +31 -0
- package/dist/public/impact/unowned-files.d.ts.map +1 -0
- package/dist/public/impact/unowned-files.js +171 -0
- package/dist/public/operations/catalog.d.ts +3 -3
- package/dist/public/operations/decision-firmness.d.ts +11 -1
- package/dist/public/operations/decision-firmness.d.ts.map +1 -1
- package/dist/public/operations/decision-firmness.js +78 -5
- package/dist/public/operations/discovery-entities.d.ts +11 -0
- package/dist/public/operations/discovery-entities.d.ts.map +1 -1
- package/dist/public/operations/discovery-entities.js +28 -0
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +13 -1
- package/dist/public/operations/result-envelope.d.ts +2 -0
- package/dist/public/operations/result-envelope.d.ts.map +1 -1
- package/dist/public/operations/result-envelope.js +1 -0
- package/dist/public/operations/source-relationship-parity.d.ts.map +1 -1
- package/dist/public/operations/source-relationship-parity.js +22 -7
- 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 +2 -2
- package/dist/public/operations/specs/check.js +1 -1
- package/dist/public/operations/specs/mutation-schemas.d.ts +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +1 -1
- package/dist/public/operations/specs/upsert.d.ts +1 -1
- package/dist/public/skills/kibi-bootstrap/SKILL.md +2 -2
- package/dist/public/skills/kibi-usage/SKILL.md +18 -7
- package/dist/traceability/git-change-snapshot.d.ts.map +1 -1
- package/dist/traceability/git-change-snapshot.js +44 -3
- package/dist/traceability/staged-diagnostics.d.ts +1 -1
- package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
- package/dist/traceability/staged-migration.d.ts +13 -0
- package/dist/traceability/staged-migration.d.ts.map +1 -0
- package/dist/traceability/staged-migration.js +59 -0
- package/dist/traceability/staged-symbols-manifest.d.ts +11 -0
- package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
- package/dist/traceability/staged-symbols-manifest.js +50 -16
- 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 +2 -2
- package/src/public/impact/analyzer.ts +21 -7
- package/src/public/impact/diagnostics.ts +1 -1
- package/src/public/impact/unowned-files.ts +229 -0
- package/src/public/operations/decision-firmness.ts +110 -13
- package/src/public/operations/discovery-entities.ts +36 -0
- package/src/public/operations/discovery-executors.ts +17 -1
- package/src/public/operations/result-envelope.ts +3 -0
- package/src/public/operations/source-relationship-parity.ts +22 -7
- package/src/public/operations/specs/check-rules.generated.ts +2 -1
- package/src/public/operations/specs/check.ts +1 -1
- package/src/public/operations/specs/mutation-schemas.ts +1 -1
- package/src/public/skills/kibi-bootstrap/SKILL.md +2 -2
- package/src/public/skills/kibi-usage/SKILL.md +18 -7
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { sourceExtractionRemediation } from "../../commands/sync/cache.js";
|
|
20
|
+
import { extractFromMarkdownString } from "../../extractors/markdown.js";
|
|
21
|
+
import { authoredFrontmatter, newEntitySourceTarget, renderSourceDocument, } from "./source-authoring.js";
|
|
22
|
+
function errorText(error) {
|
|
23
|
+
return error instanceof Error ? error.message : String(error);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The authored document an upsert of `input` would merge into although the
|
|
27
|
+
* store does not hold the entity: a file left out of the last sync (one sync
|
|
28
|
+
* could not extract, or one written since). Null when the writer would
|
|
29
|
+
* create a new file, writes a symbol manifest, or the file declares another
|
|
30
|
+
* entity. A file whose front matter does not parse is refused here: the
|
|
31
|
+
* writer could not merge into it either.
|
|
32
|
+
*/
|
|
33
|
+
// implements REQ-kibi-uncompiled-source-visibility
|
|
34
|
+
export async function readUncompiledAuthoredSource(context, input) {
|
|
35
|
+
if (context.fs === undefined || context.sourceFirst === false)
|
|
36
|
+
return null;
|
|
37
|
+
if (input.type === "symbol")
|
|
38
|
+
return null;
|
|
39
|
+
const absolute = newEntitySourceTarget(context, input);
|
|
40
|
+
if (absolute === undefined || !/\.mdx?$/i.test(absolute))
|
|
41
|
+
return null;
|
|
42
|
+
let content;
|
|
43
|
+
try {
|
|
44
|
+
content = await context.fs.readFile(absolute);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const relative = path
|
|
50
|
+
.relative(context.workspaceRoot, absolute)
|
|
51
|
+
.replaceAll(path.sep, "/");
|
|
52
|
+
try {
|
|
53
|
+
const { entity } = extractFromMarkdownString(content, absolute);
|
|
54
|
+
if (entity.id !== input.id)
|
|
55
|
+
return null;
|
|
56
|
+
return {
|
|
57
|
+
path: relative,
|
|
58
|
+
absolute,
|
|
59
|
+
content,
|
|
60
|
+
stored: entity,
|
|
61
|
+
extractionError: null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const extractionError = errorText(error);
|
|
66
|
+
let frontmatter;
|
|
67
|
+
try {
|
|
68
|
+
frontmatter = authoredFrontmatter(content);
|
|
69
|
+
}
|
|
70
|
+
catch (parseError) {
|
|
71
|
+
throw new Error(`Upsert of ${input.id} refused: ${relative} exists but kibi sync cannot compile it (${extractionError}), and its front matter does not parse (${errorText(parseError)}), so the write cannot merge into it. Fix the front matter of ${relative}, run kibi sync, then retry.`);
|
|
72
|
+
}
|
|
73
|
+
if (frontmatter.id !== undefined && frontmatter.id !== input.id)
|
|
74
|
+
return null;
|
|
75
|
+
if (frontmatter.type !== undefined && frontmatter.type !== input.type)
|
|
76
|
+
return null;
|
|
77
|
+
return {
|
|
78
|
+
path: relative,
|
|
79
|
+
absolute,
|
|
80
|
+
content,
|
|
81
|
+
stored: { ...frontmatter, id: input.id, type: input.type },
|
|
82
|
+
extractionError,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The store lookup for an upsert whose entity exists only in its authored
|
|
88
|
+
* file: the file's own state is what the write changes, so the stored
|
|
89
|
+
* ledger, origin and firmness checks read it.
|
|
90
|
+
*/
|
|
91
|
+
// implements REQ-kibi-uncompiled-source-visibility
|
|
92
|
+
export function authoredEntityLookup(source) {
|
|
93
|
+
return {
|
|
94
|
+
exists: async () => true,
|
|
95
|
+
entity: async () => source.stored,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Render the document the writer will leave and extract it as kibi sync
|
|
100
|
+
* will. A write that leaves the file uncompilable is refused, naming the
|
|
101
|
+
* file and the error, because the entity would stay invisible to every
|
|
102
|
+
* store-backed tool. Returns the relationships the written file declares.
|
|
103
|
+
*/
|
|
104
|
+
// implements REQ-kibi-uncompiled-source-visibility
|
|
105
|
+
export function assertAuthoredWriteCompiles(source, input, entity) {
|
|
106
|
+
let after;
|
|
107
|
+
try {
|
|
108
|
+
after = renderSourceDocument(input, entity, source.content, source.path);
|
|
109
|
+
return extractFromMarkdownString(after, source.absolute).relationships.map((relationship) => ({ ...relationship }));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
const message = errorText(error);
|
|
113
|
+
throw new Error(`Upsert of ${input.id} refused: ${input.id} exists in ${source.path} but is not compiled${source.extractionError === null ? "" : ` (${source.extractionError})`}, and this write would leave the file uncompilable: ${message}. ${sourceExtractionRemediation(source.path, message)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { OperationResult } from "../../public/operations/types.js";
|
|
|
3
3
|
import type { SemanticAdvisorReceipt } from "../semantic-advisor/types.js";
|
|
4
4
|
import { type StoredEntityLookup } from "./stored-entity.js";
|
|
5
5
|
import type { RelationshipInput, StagedUpsertState, UpsertInput, UpsertPayload, ValidatedUpsert } from "./types.js";
|
|
6
|
+
import { type UncompiledAuthoredSource } from "./uncompiled-source.js";
|
|
6
7
|
export declare function assertRelationshipShardContained(workspaceRoot: string, shardPath: string): void;
|
|
7
8
|
export declare function validateAppendOnlyProofReceipts(entity: Readonly<Record<string, unknown>>, context: OperationContext, staged?: StagedUpsertState): Promise<void>;
|
|
8
9
|
export declare function effectiveRelationships(input: UpsertInput, entity: Readonly<Record<string, unknown>>, relationships: readonly RelationshipInput[], context: OperationContext, staged?: StagedUpsertState, lookup?: StoredEntityLookup): Promise<readonly RelationshipInput[]>;
|
|
@@ -32,6 +33,11 @@ export declare function analyzeUpsertSemantics(payload: UpsertInput & {
|
|
|
32
33
|
}): UpsertSemanticAnalysis;
|
|
33
34
|
export type UpsertValidation = Readonly<{
|
|
34
35
|
readonly validated: ValidatedUpsert;
|
|
36
|
+
/**
|
|
37
|
+
* The authored file the write merges into although the store does not
|
|
38
|
+
* hold the entity; absent for an ordinary create or update.
|
|
39
|
+
*/
|
|
40
|
+
readonly uncompiledSource?: UncompiledAuthoredSource;
|
|
35
41
|
/** The upsert's relationships merged with the entity's existing ones. */
|
|
36
42
|
readonly relationships: readonly RelationshipInput[];
|
|
37
43
|
readonly semantic: UpsertSemanticAnalysis;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAexE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@ import { analyzeSemanticAdvisorInput } from "../semantic-advisor/analyze-prose.j
|
|
|
15
15
|
import { assertLogicalGroundingClaimKeys, assertSemanticInventoryBoundary, } from "../semantic-advisor/ingestion-boundary.js";
|
|
16
16
|
import { entityContextWarnings } from "./context-warning.js";
|
|
17
17
|
import { buildUpsertCommitGoal, formatUpsertError } from "./contradictions.js";
|
|
18
|
+
import { assertFirmDecisionUnchanged } from "./firm-decision-guard.js";
|
|
18
19
|
import { resolveUpsertOrigin } from "./origin.js";
|
|
19
20
|
import { planRecoveryNotes, settlePendingPlanApplyJournals, withPlanRecoveryNotes, } from "./plan-apply-journal.js";
|
|
20
21
|
import { assertPredicateArgumentVocabulary } from "./predicate-vocabulary-guard.js";
|
|
@@ -26,6 +27,7 @@ import { storedEntityLookup, } from "./stored-entity.js";
|
|
|
26
27
|
import { acquireSymbolCompilerLock, releaseSymbolCompilerLock, } from "./symbol-compiler-lock.js";
|
|
27
28
|
import { validateSymbolGranularity } from "./symbol-granularity.js";
|
|
28
29
|
import { refreshSymbolCoordinatesForManifest } from "./symbol-refresh.js";
|
|
30
|
+
import { assertAuthoredWriteCompiles, authoredEntityLookup, readUncompiledAuthoredSource, } from "./uncompiled-source.js";
|
|
29
31
|
import { validateUpsertInput } from "./validation.js";
|
|
30
32
|
import { scenarioCoverageWarnings, symbolCoverageWarnings, } from "./warnings.js";
|
|
31
33
|
import { acquireWorkspaceMutationLock, releaseWorkspaceMutationLock, } from "./workspace-mutation-lock.js";
|
|
@@ -146,12 +148,14 @@ export async function effectiveRelationships(input, entity, relationships, conte
|
|
|
146
148
|
// Relationships an earlier plan step adds from this entity will exist when
|
|
147
149
|
// this upsert runs.
|
|
148
150
|
const plannedOwn = (staged?.relationships ?? []).filter((relationship) => relationship.from === input.id);
|
|
149
|
-
const
|
|
151
|
+
const resolved = lookup ?? storedEntityLookup(prolog, input.id, input.type);
|
|
152
|
+
const exists = await resolved.exists();
|
|
150
153
|
if (!exists && plannedOwn.length === 0)
|
|
151
154
|
return relationships;
|
|
152
155
|
try {
|
|
153
156
|
const current = exists
|
|
154
|
-
? (
|
|
157
|
+
? (resolved.relationships ??
|
|
158
|
+
(await existingRelationships(prolog, String(entity.id))))
|
|
155
159
|
// An upsert owns only relationships whose source is the upserted
|
|
156
160
|
// entity. Incoming relationships must not be copied into its
|
|
157
161
|
// validation or canonical source projection.
|
|
@@ -230,15 +234,32 @@ export async function validateUpsertForCommit(payload, context, options = {}) {
|
|
|
230
234
|
// The payload must be valid on its own before the store is read.
|
|
231
235
|
const checked = validateUpsertInput(payload, context.clock());
|
|
232
236
|
validateRelationshipSources(payload.id, checked.relationships);
|
|
237
|
+
// Symbol granularity reads only the payload and its source file, and stored
|
|
238
|
+
// state never changes a symbol payload, so it runs before the store is read.
|
|
239
|
+
await validateSymbolGranularity(checked.entity, checked.relationships, context);
|
|
233
240
|
// One read of the entity this upsert replaces, shared by the ledger merge
|
|
234
241
|
// and the stored origin below.
|
|
235
|
-
|
|
242
|
+
let lookup = storedEntityLookup(prolog, payload.id, payload.type);
|
|
243
|
+
// implements REQ-kibi-uncompiled-source-visibility
|
|
244
|
+
// An entity missing from the store whose authored file exists (one sync
|
|
245
|
+
// could not extract, or one written since the last sync) is an update of
|
|
246
|
+
// that file, not a create: the writer merges into it. Judge the write
|
|
247
|
+
// against the file's own state so every stored-state check applies.
|
|
248
|
+
const uncompiledSource = staged?.entities.has(payload.id) === true || (await lookup.exists())
|
|
249
|
+
? null
|
|
250
|
+
: await readUncompiledAuthoredSource(context, payload);
|
|
251
|
+
if (uncompiledSource !== null)
|
|
252
|
+
lookup = authoredEntityLookup(uncompiledSource);
|
|
236
253
|
const input = await withStoredRequirementSemantics(payload, prolog, staged, lookup);
|
|
237
254
|
const validated = input === payload ? checked : validateUpsertInput(input, context.clock());
|
|
255
|
+
if (uncompiledSource !== null)
|
|
256
|
+
lookup = {
|
|
257
|
+
...lookup,
|
|
258
|
+
relationships: assertAuthoredWriteCompiles(uncompiledSource, input, validated.entity),
|
|
259
|
+
};
|
|
238
260
|
if (options.allowReceiptsPrune !== true) {
|
|
239
261
|
await validateAppendOnlyProofReceipts(validated.entity, context, staged);
|
|
240
262
|
}
|
|
241
|
-
await validateSymbolGranularity(validated.entity, validated.relationships, context);
|
|
242
263
|
const relationships = await effectiveRelationships(input, validated.entity, validated.relationships, context, staged, lookup);
|
|
243
264
|
await validateStrictLanePairing(prolog, validated.relationships, staged);
|
|
244
265
|
await validateLiveRelationshipTargets(prolog, validated.entity, validated.relationships, staged);
|
|
@@ -247,6 +268,7 @@ export async function validateUpsertForCommit(payload, context, options = {}) {
|
|
|
247
268
|
await assertStoredRequirementBoundary(input, relationships, prolog, staged, lookup);
|
|
248
269
|
await assertLogicalGroundingClaimKeys(prolog, { ...input, relationships }, relationships, staged);
|
|
249
270
|
await assertPredicateArgumentVocabulary(prolog, validated.entity, staged);
|
|
271
|
+
await assertFirmDecisionUnchanged(payload, context.workspaceRoot, lookup);
|
|
250
272
|
// The origin the write records is added last: none of the checks above
|
|
251
273
|
// reads it, and a rejected payload never pays for the stored-origin read.
|
|
252
274
|
const recorded = await resolveUpsertOrigin(input, prolog, context.clock(), staged, lookup);
|
|
@@ -257,6 +279,7 @@ export async function validateUpsertForCommit(payload, context, options = {}) {
|
|
|
257
279
|
: validateUpsertInput(decided, context.clock()),
|
|
258
280
|
relationships,
|
|
259
281
|
semantic,
|
|
282
|
+
...(uncompiledSource !== null ? { uncompiledSource } : {}),
|
|
260
283
|
};
|
|
261
284
|
}
|
|
262
285
|
/**
|
|
@@ -368,7 +391,7 @@ export async function executeUpsert(input, context, options = {}) {
|
|
|
368
391
|
// upserts cannot lose updates.
|
|
369
392
|
compilerLock = await acquireSymbolCompilerLock(context.workspaceRoot);
|
|
370
393
|
}
|
|
371
|
-
const { validated, relationships, semantic } = await validateUpsertForCommit(input, context, {
|
|
394
|
+
const { validated, relationships, semantic, uncompiledSource } = await validateUpsertForCommit(input, context, {
|
|
372
395
|
allowReceiptsPrune: options.allowReceiptsPrune === true,
|
|
373
396
|
});
|
|
374
397
|
relationshipCount = validated.relationships.length;
|
|
@@ -602,6 +625,12 @@ export async function executeUpsert(input, context, options = {}) {
|
|
|
602
625
|
relationships_created: validated.relationships.length,
|
|
603
626
|
warnings: [
|
|
604
627
|
...planRecoveryWarnings,
|
|
628
|
+
// implements REQ-kibi-uncompiled-source-visibility
|
|
629
|
+
...(uncompiledSource !== undefined
|
|
630
|
+
? [
|
|
631
|
+
`${input.id} was not in the compiled KB; this write updated its authored file ${uncompiledSource.path}${uncompiledSource.extractionError === null ? "" : `, which kibi sync could not compile before (${uncompiledSource.extractionError})`}. Run kibi sync to recompile it.`,
|
|
632
|
+
]
|
|
633
|
+
: []),
|
|
605
634
|
...semantic.warnings,
|
|
606
635
|
...coverage,
|
|
607
636
|
...idStyleWarnings,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../../src/public/impact/analyzer.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../../src/public/impact/analyzer.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAGpB,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CAmKlC"}
|
|
@@ -11,6 +11,7 @@ import { normalizeSourceFile } from "./source-changes.js";
|
|
|
11
11
|
import { collectSourceChanges, uniqueSorted } from "./source-changes.js";
|
|
12
12
|
import { buildNextActions, collectLinkedEntities, formatExtractedSymbols, } from "./summaries.js";
|
|
13
13
|
import { createSymbolQualityDiagnostics } from "./symbol-quality.js";
|
|
14
|
+
import { createUnownedFileDiagnostics } from "./unowned-files.js";
|
|
14
15
|
export async function analyzeChangedFileImpact(options) {
|
|
15
16
|
const workspaceRoot = path.resolve(options.workspaceRoot);
|
|
16
17
|
const snapshot = options.staged
|
|
@@ -77,9 +78,20 @@ export async function analyzeChangedFileImpact(options) {
|
|
|
77
78
|
const activeSourceFiles = new Set(sourceFiles);
|
|
78
79
|
const activeManifestResults = manifestResults.filter((result) => result.sourceFile !== undefined &&
|
|
79
80
|
activeSourceFiles.has(result.sourceFile));
|
|
81
|
+
const linkedSourceFiles = new Set(manifestResults
|
|
82
|
+
.filter((result) => result.relationships.some((relationship) => isTraceabilityRelationshipType(relationship.type)))
|
|
83
|
+
.map((result) => result.sourceFile)
|
|
84
|
+
.filter((file) => file !== undefined));
|
|
80
85
|
const impactDiagnostics = options.includeImpactDiagnostics === false
|
|
81
86
|
? []
|
|
82
87
|
: [
|
|
88
|
+
// Named files first: a file without links is the answer a scoped
|
|
89
|
+
// check most easily misreads as "no rule applies".
|
|
90
|
+
...createUnownedFileDiagnostics({
|
|
91
|
+
workspaceRoot,
|
|
92
|
+
requestedFiles: [...filter],
|
|
93
|
+
linkedSourceFiles,
|
|
94
|
+
}),
|
|
83
95
|
...createSymbolGranularityDiagnostics({
|
|
84
96
|
manifestResults: activeManifestResults,
|
|
85
97
|
symbolsByFile,
|
|
@@ -93,9 +105,7 @@ export async function analyzeChangedFileImpact(options) {
|
|
|
93
105
|
}),
|
|
94
106
|
...createSemanticReviewDiagnostics({
|
|
95
107
|
symbolsByFile,
|
|
96
|
-
linkedSourceFiles: new Set(
|
|
97
|
-
.filter((result) => result.relationships.some((relationship) => isTraceabilityRelationshipType(relationship.type)))
|
|
98
|
-
.map((result) => result.sourceFile)),
|
|
108
|
+
linkedSourceFiles: new Set([...linkedSourceFiles].filter((file) => activeSourceFiles.has(file))),
|
|
99
109
|
}),
|
|
100
110
|
];
|
|
101
111
|
const maxDiagnostics = options.maxDiagnostics;
|
|
@@ -136,7 +136,7 @@ export function createSemanticReviewDiagnostics(options) {
|
|
|
136
136
|
? `Changed behavioral symbol ${symbol.name} is in a file no requirement owns, so no recorded intent covers the behavior it has after this change`
|
|
137
137
|
: `Changed behavioral symbol ${symbol.name} needs semantic review against linked Kibi coverage: ${linkedTargets}`,
|
|
138
138
|
suggestion: unlinked
|
|
139
|
-
? `If the change adds or alters behavior, record it
|
|
139
|
+
? `If the change adds or alters behavior, record it as part of this change; that is part of finishing it, not a separate request, so it needs no permission: kb_search({query:"<the behavior>", sourceLocations:[{path:"${file}"}]}) for a requirement that already describes it and link ${symbol.name} to it; if none does, kb_upsert a requirement with a scenario and link the symbol with implements. If behavior is unchanged (a refactor, renaming), say so in the final report instead.`
|
|
140
140
|
: `Use kb_search and kb_query for ${file}, then verify that ${linkedTargets} actually describe and test the changed behavior or UI copy. Update Kibi links or requirements/tests while the edit context is fresh.`,
|
|
141
141
|
});
|
|
142
142
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { KibiImpactDiagnostic } from "../../traceability/staged-diagnostics.js";
|
|
2
|
+
/**
|
|
3
|
+
* The topic words of a workspace path, without layout, language and role
|
|
4
|
+
* words (`src/app/pages/coach-dashboard/coach-dashboard.html` -> `coach`,
|
|
5
|
+
* `dashboard`).
|
|
6
|
+
*/
|
|
7
|
+
export declare function pathTopicWords(relativePath: string): string[];
|
|
8
|
+
export type RequirementCandidate = {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly title: string;
|
|
11
|
+
readonly status?: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Current requirements ranked by how many of the path's topic words their
|
|
15
|
+
* title shares, then by how often the title uses them, then by id; only
|
|
16
|
+
* those sharing at least one word.
|
|
17
|
+
*/
|
|
18
|
+
export declare function rankRequirementCandidates(relativePath: string, requirements: readonly RequirementCandidate[], limit?: number): RequirementCandidate[];
|
|
19
|
+
export type UnownedFileDiagnosticsOptions = {
|
|
20
|
+
readonly workspaceRoot: string;
|
|
21
|
+
/** Workspace-relative files the check was asked about. */
|
|
22
|
+
readonly requestedFiles: readonly string[];
|
|
23
|
+
/** Files where some manifest symbol carries a traceability link. */
|
|
24
|
+
readonly linkedSourceFiles: ReadonlySet<string>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* One `source_file_unowned` note per requested file without a linked symbol,
|
|
28
|
+
* naming the current requirements whose titles share its path words.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createUnownedFileDiagnostics(options: UnownedFileDiagnosticsOptions): KibiImpactDiagnostic[];
|
|
31
|
+
//# sourceMappingURL=unowned-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unowned-files.d.ts","sourceRoot":"","sources":["../../../src/public/impact/unowned-files.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAqDrF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CAS7D;AAGD,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAGF;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,SAAS,oBAAoB,EAAE,EAC7C,KAAK,GAAE,MAAuB,GAC7B,oBAAoB,EAAE,CAyBxB;AA+DD,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,0DAA0D;IAC1D,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACjD,CAAC;AAGF;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,6BAA6B,GACrC,oBAAoB,EAAE,CAqCxB"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { closeSync, openSync, readSync, readdirSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* A file `kb_check` was asked about that no symbol links to a requirement.
|
|
5
|
+
* No link is not no rule, so the check says so and names the current
|
|
6
|
+
* requirements whose titles share the file's path words: candidates to
|
|
7
|
+
* confirm, never owners. Kibi's hooks name the same candidates before an edit
|
|
8
|
+
* (kibi-agent-core `requirement-candidates`), with the same word rules.
|
|
9
|
+
*/
|
|
10
|
+
const REQUIREMENTS_DIR = path.join(".kb", "requirements");
|
|
11
|
+
const HEAD_BYTES = 4096;
|
|
12
|
+
const MAX_CANDIDATES = 3;
|
|
13
|
+
const MAX_TITLE = 100;
|
|
14
|
+
/** Path words that name a layout, a language or a role, not a behavior. */
|
|
15
|
+
const LAYOUT_WORDS = new Set(("src app apps lib libs source page pages component components view views " +
|
|
16
|
+
"screen screens package packages main index shared common core util utils " +
|
|
17
|
+
"helper helpers public static asset assets web client server frontend " +
|
|
18
|
+
"backend module modules feature features internal service services test " +
|
|
19
|
+
"tests spec specs dist build html htm tsx jsx mjs cjs vue svelte css scss " +
|
|
20
|
+
"less json yaml yml java swift php").split(" "));
|
|
21
|
+
/** Words of a title or path that carry no topic. */
|
|
22
|
+
const FILLER_WORDS = new Set(("the and for with must should shall may can are its their that this " +
|
|
23
|
+
"from into when each every all not req auto").split(" "));
|
|
24
|
+
const RETIRED_STATUS = /supersed|deprecat|reject|obsolete|retired|archived|removed/i;
|
|
25
|
+
/** Lowercase word stems of an identifier, path segment or title. */
|
|
26
|
+
function words(text) {
|
|
27
|
+
return text
|
|
28
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.split(/[^a-z0-9]+/)
|
|
31
|
+
.filter((word) => word.length >= 3 && !/\d/.test(word))
|
|
32
|
+
.map((word) => word.length > 4 && word.endsWith("s") && !word.endsWith("ss")
|
|
33
|
+
? word.slice(0, -1)
|
|
34
|
+
: word);
|
|
35
|
+
}
|
|
36
|
+
// implements REQ-kb-unowned-file-candidates
|
|
37
|
+
/**
|
|
38
|
+
* The topic words of a workspace path, without layout, language and role
|
|
39
|
+
* words (`src/app/pages/coach-dashboard/coach-dashboard.html` -> `coach`,
|
|
40
|
+
* `dashboard`).
|
|
41
|
+
*/
|
|
42
|
+
export function pathTopicWords(relativePath) {
|
|
43
|
+
const segments = relativePath.split(/[\\/]/).filter(Boolean);
|
|
44
|
+
const last = segments.pop() ?? "";
|
|
45
|
+
const dot = last.indexOf(".");
|
|
46
|
+
const parts = dot >= 0 ? [last.slice(0, dot), last.slice(dot + 1)] : [last];
|
|
47
|
+
const found = [...segments, ...parts]
|
|
48
|
+
.flatMap(words)
|
|
49
|
+
.filter((word) => !LAYOUT_WORDS.has(word) && !FILLER_WORDS.has(word));
|
|
50
|
+
return [...new Set(found)];
|
|
51
|
+
}
|
|
52
|
+
// implements REQ-kb-unowned-file-candidates
|
|
53
|
+
/**
|
|
54
|
+
* Current requirements ranked by how many of the path's topic words their
|
|
55
|
+
* title shares, then by how often the title uses them, then by id; only
|
|
56
|
+
* those sharing at least one word.
|
|
57
|
+
*/
|
|
58
|
+
export function rankRequirementCandidates(relativePath, requirements, limit = MAX_CANDIDATES) {
|
|
59
|
+
const topic = new Set(pathTopicWords(relativePath));
|
|
60
|
+
if (topic.size === 0)
|
|
61
|
+
return [];
|
|
62
|
+
const scored = [];
|
|
63
|
+
for (const requirement of requirements) {
|
|
64
|
+
if (requirement.status !== undefined &&
|
|
65
|
+
RETIRED_STATUS.test(requirement.status))
|
|
66
|
+
continue;
|
|
67
|
+
const matched = words(requirement.title).filter((word) => topic.has(word));
|
|
68
|
+
const shared = new Set(matched).size;
|
|
69
|
+
if (shared > 0)
|
|
70
|
+
scored.push({ requirement, shared, uses: matched.length });
|
|
71
|
+
}
|
|
72
|
+
scored.sort((a, b) => b.shared - a.shared ||
|
|
73
|
+
b.uses - a.uses ||
|
|
74
|
+
a.requirement.id.localeCompare(b.requirement.id));
|
|
75
|
+
return scored.slice(0, limit).map((entry) => entry.requirement);
|
|
76
|
+
}
|
|
77
|
+
function unquote(value) {
|
|
78
|
+
const trimmed = value.trim();
|
|
79
|
+
return /^(["']).*\1$/.test(trimmed) ? trimmed.slice(1, -1) : trimmed;
|
|
80
|
+
}
|
|
81
|
+
function readRequirementHead(file) {
|
|
82
|
+
let head;
|
|
83
|
+
try {
|
|
84
|
+
const descriptor = openSync(file, "r");
|
|
85
|
+
try {
|
|
86
|
+
const buffer = Buffer.alloc(HEAD_BYTES);
|
|
87
|
+
const bytes = readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
88
|
+
head = buffer.subarray(0, bytes).toString("utf8");
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
closeSync(descriptor);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
const lines = head.split(/\r?\n/);
|
|
98
|
+
if (lines[0]?.trim() !== "---")
|
|
99
|
+
return undefined;
|
|
100
|
+
const fields = {};
|
|
101
|
+
for (const line of lines.slice(1)) {
|
|
102
|
+
if (line.trim() === "---")
|
|
103
|
+
break;
|
|
104
|
+
const match = /^(id|title|status):\s*(.+)$/.exec(line);
|
|
105
|
+
if (match?.[1] && match[2])
|
|
106
|
+
fields[match[1]] = unquote(match[2]);
|
|
107
|
+
}
|
|
108
|
+
if (!fields.id || !fields.title)
|
|
109
|
+
return undefined;
|
|
110
|
+
return {
|
|
111
|
+
id: fields.id,
|
|
112
|
+
title: fields.title,
|
|
113
|
+
...(fields.status ? { status: fields.status } : {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function readRequirementHeads(workspaceRoot) {
|
|
117
|
+
const directory = path.join(workspaceRoot, REQUIREMENTS_DIR);
|
|
118
|
+
let names;
|
|
119
|
+
try {
|
|
120
|
+
names = readdirSync(directory).filter((name) => name.endsWith(".md"));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
return names
|
|
126
|
+
.map((name) => readRequirementHead(path.join(directory, name)))
|
|
127
|
+
.filter((requirement) => requirement !== undefined);
|
|
128
|
+
}
|
|
129
|
+
function truncate(text, limit) {
|
|
130
|
+
return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
|
|
131
|
+
}
|
|
132
|
+
/** Files the unowned note does not apply to: the KB itself and prose. */
|
|
133
|
+
function isKnowledgeOrProse(file) {
|
|
134
|
+
return file.startsWith(".kb/") || /\.mdx?$/i.test(file);
|
|
135
|
+
}
|
|
136
|
+
// implements REQ-kb-unowned-file-candidates
|
|
137
|
+
/**
|
|
138
|
+
* One `source_file_unowned` note per requested file without a linked symbol,
|
|
139
|
+
* naming the current requirements whose titles share its path words.
|
|
140
|
+
*/
|
|
141
|
+
export function createUnownedFileDiagnostics(options) {
|
|
142
|
+
const files = [...new Set(options.requestedFiles)].filter((file) => !options.linkedSourceFiles.has(file) && !isKnowledgeOrProse(file));
|
|
143
|
+
if (files.length === 0)
|
|
144
|
+
return [];
|
|
145
|
+
const requirements = readRequirementHeads(options.workspaceRoot);
|
|
146
|
+
return files.map((file) => {
|
|
147
|
+
const candidates = rankRequirementCandidates(file, requirements);
|
|
148
|
+
const named = candidates
|
|
149
|
+
.map((candidate) => `${candidate.id} (${truncate(candidate.title, MAX_TITLE)})`)
|
|
150
|
+
.join("; ");
|
|
151
|
+
const lead = `No symbol in ${file} is linked to a requirement, so this check finds no owner for it; that does not mean no recorded rule covers the behavior it implements.`;
|
|
152
|
+
const record = "If none does and the change adds behavior, record a requirement with a scenario and link it with implements as part of this change; if behavior is unchanged, say so in the final report.";
|
|
153
|
+
return {
|
|
154
|
+
id: "source_file_unowned",
|
|
155
|
+
severity: "info",
|
|
156
|
+
blocking: false,
|
|
157
|
+
category: "requirement",
|
|
158
|
+
files: [file],
|
|
159
|
+
docs: ["docs/symbol-traceability-taxonomy.md"],
|
|
160
|
+
message: candidates.length > 0
|
|
161
|
+
? `${lead} Requirements whose titles share its path words: ${named}.`
|
|
162
|
+
: lead,
|
|
163
|
+
suggestion: candidates.length > 0
|
|
164
|
+
? `kb_query each candidate to see whether it describes the changed behavior; when one does, link the symbol that implements it (for markup, the component that renders it) with implements and name the requirement in the final report. ${record}`
|
|
165
|
+
: `kb_search({query:"<the behavior>", sourceLocations:[{path:${JSON.stringify(file)}}]}) for a requirement that already describes it. ${record}`,
|
|
166
|
+
evidence: {
|
|
167
|
+
candidates: candidates.map(({ id, title }) => ({ id, title })),
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
}
|
|
@@ -1125,7 +1125,7 @@ export declare const OPERATION_CATALOG: readonly [{
|
|
|
1125
1125
|
readonly firmness: {
|
|
1126
1126
|
readonly type: "string";
|
|
1127
1127
|
readonly enum: readonly ["firm", "default", "exploratory"];
|
|
1128
|
-
readonly description: "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words);
|
|
1128
|
+
readonly description: "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words); its title, meaning or firmness changes only with new human words (a new origin.ref) or by supersede. default = delegated or agent choice; exploratory = flagged guess. Absent = default if an agent wrote it, else firm.";
|
|
1129
1129
|
};
|
|
1130
1130
|
readonly rationale: {
|
|
1131
1131
|
readonly type: "string";
|
|
@@ -1839,7 +1839,7 @@ export declare const OPERATION_CATALOG: readonly [{
|
|
|
1839
1839
|
readonly type: "string";
|
|
1840
1840
|
readonly enum: readonly string[];
|
|
1841
1841
|
};
|
|
1842
|
-
readonly description: "Optional rule subset. Allowed: 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. If omitted, server runs canonical and advisory rules plus the full-KB qualityDiagnostics audit scan, including usage telemetry acceptance when evidence exists; migration rules run only when explicitly selected. Advisory and migration findings are non-blocking qualityDiagnostics. If supplied, server preserves scoped validation and skips the full-KB advisory scan.";
|
|
1842
|
+
readonly description: "Optional rule subset. Allowed: 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, decision-firm-edited, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers. If omitted, server runs canonical and advisory rules plus the full-KB qualityDiagnostics audit scan, including usage telemetry acceptance when evidence exists; migration rules run only when explicitly selected. Advisory and migration findings are non-blocking qualityDiagnostics. If supplied, server preserves scoped validation and skips the full-KB advisory scan.";
|
|
1843
1843
|
};
|
|
1844
1844
|
readonly sourceFiles: {
|
|
1845
1845
|
readonly type: "array";
|
|
@@ -1858,7 +1858,7 @@ export declare const OPERATION_CATALOG: readonly [{
|
|
|
1858
1858
|
};
|
|
1859
1859
|
readonly includeImpactDiagnostics: {
|
|
1860
1860
|
readonly type: "boolean";
|
|
1861
|
-
readonly description: "When true, include changed-file impact diagnostics such as symbol_granularity_violation and
|
|
1861
|
+
readonly description: "When true, include changed-file impact diagnostics such as symbol_granularity_violation, symbol_semantic_review_needed and source_file_unowned in structured output.";
|
|
1862
1862
|
};
|
|
1863
1863
|
readonly maxDiagnostics: {
|
|
1864
1864
|
readonly type: "integer";
|
|
@@ -4,11 +4,13 @@ import { type AuthoredEntity } from "./entity-context.js";
|
|
|
4
4
|
export declare const DECISION_FIRM_UNREFERENCED_RULE = "decision-firm-unreferenced";
|
|
5
5
|
/** Only a firm decision may supersede a firm decision (blocking). */
|
|
6
6
|
export declare const DECISION_FIRM_SUPERSEDED_RULE = "decision-firm-superseded";
|
|
7
|
+
/** A firm decision's claim must not change in place without new human words (blocking). */
|
|
8
|
+
export declare const DECISION_FIRM_EDITED_RULE = "decision-firm-edited";
|
|
7
9
|
/** A supersede must say why under "Why this changed" (blocking). */
|
|
8
10
|
export declare const SUPERSEDES_REASON_MISSING_RULE = "supersedes-reason-missing";
|
|
9
11
|
/** How many legacy supersedes the schema 9 migration acknowledged (info). */
|
|
10
12
|
export declare const SUPERSEDES_REASON_ACKNOWLEDGED_RULE = "supersedes-reason-acknowledged";
|
|
11
|
-
export declare const DECISION_FIRMNESS_RULES: readonly ["decision-firm-unreferenced", "decision-firm-superseded", "supersedes-reason-missing", "supersedes-reason-acknowledged"];
|
|
13
|
+
export declare const DECISION_FIRMNESS_RULES: readonly ["decision-firm-unreferenced", "decision-firm-superseded", "decision-firm-edited", "supersedes-reason-missing", "supersedes-reason-acknowledged"];
|
|
12
14
|
/**
|
|
13
15
|
* The schema version from which a decision without `firmness` reads its
|
|
14
16
|
* firmness from its origin, so a person's or imported rule is firm.
|
|
@@ -45,6 +47,14 @@ export declare function evaluateDecisionFirmness(entities: readonly AuthoredEnti
|
|
|
45
47
|
/** Edges the schema 10 migration recorded (`FROM -> TO`). */
|
|
46
48
|
firmSupersedeAcknowledged?: ReadonlySet<string>;
|
|
47
49
|
}>): Violation[];
|
|
50
|
+
/** Reads a workspace-relative file as committed at HEAD; null when absent. */
|
|
51
|
+
export type CommittedFileReader = (relativePath: string) => string | null;
|
|
52
|
+
/**
|
|
53
|
+
* Current firm requirements and ADRs whose claim changed since HEAD without
|
|
54
|
+
* citing new human words: an in-place edit that bypassed kb_upsert's guard.
|
|
55
|
+
* A decision new since HEAD, or one that was not firm there, is not judged.
|
|
56
|
+
*/
|
|
57
|
+
export declare function evaluateFirmDecisionEdits(entities: readonly AuthoredEntity[], edges: ReadonlyMap<string, Set<string>>, changed: ReadonlySet<string>, readCommitted: CommittedFileReader): Violation[];
|
|
48
58
|
/** Read the authored decisions once and evaluate the selected rules. */
|
|
49
59
|
export declare function collectDecisionFirmnessViolations(rules: ReadonlySet<string>, workspaceRoot: string): Violation[];
|
|
50
60
|
//# sourceMappingURL=decision-firmness.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision-firmness.d.ts","sourceRoot":"","sources":["../../../src/public/operations/decision-firmness.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"decision-firmness.d.ts","sourceRoot":"","sources":["../../../src/public/operations/decision-firmness.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,qBAAqB,CAAC;AAE7B,wEAAwE;AAExE,eAAO,MAAM,+BAA+B,+BAA+B,CAAC;AAC5E,qEAAqE;AAErE,eAAO,MAAM,6BAA6B,6BAA6B,CAAC;AACxE,2FAA2F;AAE3F,eAAO,MAAM,yBAAyB,yBAAyB,CAAC;AAChE,oEAAoE;AAEpE,eAAO,MAAM,8BAA8B,8BAA8B,CAAC;AAC1E,6EAA6E;AAE7E,eAAO,MAAM,mCAAmC,mCACd,CAAC;AAGnC,eAAO,MAAM,uBAAuB,4JAM1B,CAAC;AAOX;;;GAGG;AAEH,eAAO,MAAM,mCAAmC,KAAK,CAAC;AAiBtD,0CAA0C;AAE1C,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,cAAc,EAAE,CAS5E;AAED;;;;;GAKG;AAEH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,SAAS,cAAc,EAAE,GAClC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CA+B1B;AASD;;;;;GAKG;AAEH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GACtC,cAAc,EAAE,CAQlB;AAED,2EAA2E;AAE3E,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GACtC,MAAM,EAAE,CAIV;AAED;;;;;GAKG;AAEH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GACtC,MAAM,EAAE,CAyBV;AAED,uEAAuE;AAEvE,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EACvC,KAAK,GAAE,WAAW,CAAC,MAAM,CAAoC,EAC7D,YAAY,GAAE,WAAW,CAAC,MAAM,CAAa,EAC7C,OAAO,GAAE,QAAQ,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,6DAA6D;IAC7D,yBAAyB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACjD,CAAM,GACN,SAAS,EAAE,CA+Gb;AAED,8EAA8E;AAE9E,MAAM,MAAM,mBAAmB,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAkC1E;;;;GAIG;AAEH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EACvC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAC5B,aAAa,EAAE,mBAAmB,GACjC,SAAS,EAAE,CAwBb;AAED,wEAAwE;AAExE,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,EAC1B,aAAa,EAAE,MAAM,GACpB,SAAS,EAAE,CA+Bb"}
|