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
|
@@ -0,0 +1,319 @@
|
|
|
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 { createHash } from "node:crypto";
|
|
19
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { isDeepStrictEqual } from "node:util";
|
|
22
|
+
import { load as parseYAML } from "js-yaml";
|
|
23
|
+
import { createTsMorphSourceAnalysisProvider } from "../../extractors/symbols-ts.js";
|
|
24
|
+
import { coarseLinkBehavioralNames, isTraceabilityRelationshipType, } from "../../public/symbol-granularity.js";
|
|
25
|
+
import { extractSymbolsFromStagedFile, } from "../../traceability/symbol-extract.js";
|
|
26
|
+
import { KB_PATHS } from "../../utils/kb-paths.js";
|
|
27
|
+
import { writeFileAtomically } from "./kb-sources.js";
|
|
28
|
+
/**
|
|
29
|
+
* Mark the coarse traceability links of a symbols manifest
|
|
30
|
+
* `granularity_reason: legacy-link`.
|
|
31
|
+
*
|
|
32
|
+
* A link is coarse by the staged granularity gate's own rule
|
|
33
|
+
* (`coarseLinkBehavioralNames`): the source declares behavioral symbols
|
|
34
|
+
* narrower than the manifest symbol, so `kibi check --staged` would block the
|
|
35
|
+
* first commit that touches it. The pass is idempotent and independent of
|
|
36
|
+
* the schema version: it marks whatever the current rule calls coarse and
|
|
37
|
+
* has no reason yet, and a second run finds nothing.
|
|
38
|
+
*/
|
|
39
|
+
/** Automatic migration action code of the legacy-link pass. */
|
|
40
|
+
// implements REQ-cli-schema-migration
|
|
41
|
+
export const SYMBOL_LEGACY_LINK_MARK_CODE = "symbol_legacy_link_mark";
|
|
42
|
+
// implements REQ-cli-schema-migration
|
|
43
|
+
export const SYMBOL_LEGACY_LINK_MARK_ACTION_ID = "symbol-legacy-link-mark";
|
|
44
|
+
function isRecord(value) {
|
|
45
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
46
|
+
}
|
|
47
|
+
function linkType(link) {
|
|
48
|
+
// A bare string link compiles as `implements`; a typed link keeps its type.
|
|
49
|
+
if (typeof link === "string")
|
|
50
|
+
return "implements";
|
|
51
|
+
return isRecord(link) ? link.type : undefined;
|
|
52
|
+
}
|
|
53
|
+
/** True when the record compiles at least one traceability relationship. */
|
|
54
|
+
function hasTraceabilityRelationship(record) {
|
|
55
|
+
const links = Array.isArray(record.links) ? record.links : [];
|
|
56
|
+
const relationships = Array.isArray(record.relationships)
|
|
57
|
+
? record.relationships
|
|
58
|
+
: [];
|
|
59
|
+
return [
|
|
60
|
+
...links.map(linkType),
|
|
61
|
+
...relationships.map((relationship) => isRecord(relationship) ? relationship.type : undefined),
|
|
62
|
+
].some(isTraceabilityRelationshipType);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The analyzer the staged gate uses (the built-in ts-morph provider), built
|
|
66
|
+
* once per pass: a provider per file would rebuild the TypeScript project for
|
|
67
|
+
* every source the manifest names.
|
|
68
|
+
*/
|
|
69
|
+
function sharedAnalyzer() {
|
|
70
|
+
let provider = null;
|
|
71
|
+
return (filePath, content) => {
|
|
72
|
+
provider ??= createTsMorphSourceAnalysisProvider();
|
|
73
|
+
return provider.supportsFile(filePath)
|
|
74
|
+
? provider.analyzeText(filePath, content)
|
|
75
|
+
: null;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function sourceSymbols(workspaceRoot, sourceFile, analyzeText) {
|
|
79
|
+
const absolutePath = path.isAbsolute(sourceFile)
|
|
80
|
+
? sourceFile
|
|
81
|
+
: path.join(workspaceRoot, sourceFile);
|
|
82
|
+
if (!existsSync(absolutePath))
|
|
83
|
+
return [];
|
|
84
|
+
try {
|
|
85
|
+
return extractSymbolsFromStagedFile({
|
|
86
|
+
path: sourceFile,
|
|
87
|
+
status: "M",
|
|
88
|
+
hunkRanges: [{ start: 1, end: Number.MAX_SAFE_INTEGER }],
|
|
89
|
+
content: readFileSync(absolutePath, "utf8"),
|
|
90
|
+
}, undefined, { analyzeText });
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function escapeRegExp(value) {
|
|
97
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* True when `title` is an exported function, variable (not a class
|
|
101
|
+
* expression), interface, type or enum declaration of the source file
|
|
102
|
+
* and no class of that name exists. The extractor then reports `title`
|
|
103
|
+
* itself and no `title.*` member, so nothing is narrower and the link is not
|
|
104
|
+
* coarse: the full analysis reaches the same answer, only much slower, and a
|
|
105
|
+
* manifest names hundreds of such functions.
|
|
106
|
+
*/
|
|
107
|
+
function declaresMemberlessSymbol(workspaceRoot, sourceFile, title, cache) {
|
|
108
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(title))
|
|
109
|
+
return false;
|
|
110
|
+
let content = cache.get(sourceFile);
|
|
111
|
+
if (content === undefined) {
|
|
112
|
+
try {
|
|
113
|
+
content = readFileSync(path.resolve(workspaceRoot, sourceFile), "utf8");
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
content = null;
|
|
117
|
+
}
|
|
118
|
+
cache.set(sourceFile, content);
|
|
119
|
+
}
|
|
120
|
+
if (content === null)
|
|
121
|
+
return false;
|
|
122
|
+
const name = escapeRegExp(title);
|
|
123
|
+
if (new RegExp(`\\bclass\\s+${name}\\b`).test(content))
|
|
124
|
+
return false;
|
|
125
|
+
return [
|
|
126
|
+
`^export\\s+(?:default\\s+)?(?:async\\s+)?function\\s*\\*?\\s*${name}\\s*[(<]`,
|
|
127
|
+
`^export\\s+(?:declare\\s+)?(?:interface|type|enum|const\\s+enum)\\s+${name}\\b`,
|
|
128
|
+
// A variable, unless its initializer is a class expression (members).
|
|
129
|
+
`^export\\s+(?:const|let|var)\\s+${name}\\s*(?::[^=\\n]*)?=\\s*(?!class\\b)\\S`,
|
|
130
|
+
].some((pattern) => new RegExp(pattern, "m").test(content));
|
|
131
|
+
}
|
|
132
|
+
function digest(content) {
|
|
133
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The coarse links of `manifestPath` (default `.kb/symbols.yaml`) that carry
|
|
137
|
+
* no granularity_reason yet. Read-only.
|
|
138
|
+
*/
|
|
139
|
+
// implements REQ-cli-schema-migration, REQ-symbol-behavioral-anchors
|
|
140
|
+
export function planSymbolLegacyLinks(workspaceRoot, manifestPath = KB_PATHS.symbolsManifest) {
|
|
141
|
+
const absolute = path.resolve(workspaceRoot, manifestPath);
|
|
142
|
+
let content;
|
|
143
|
+
try {
|
|
144
|
+
content = readFileSync(absolute, "utf8");
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return { manifestPath, manifestHash: null, targets: [] };
|
|
148
|
+
}
|
|
149
|
+
const empty = { manifestPath, manifestHash: digest(content), targets: [] };
|
|
150
|
+
let parsed;
|
|
151
|
+
try {
|
|
152
|
+
parsed = parseYAML(content);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// A malformed manifest fails sync with its own diagnostic.
|
|
156
|
+
return empty;
|
|
157
|
+
}
|
|
158
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.symbols))
|
|
159
|
+
return empty;
|
|
160
|
+
const targets = [];
|
|
161
|
+
const symbolsByFile = new Map();
|
|
162
|
+
const analyzeText = sharedAnalyzer();
|
|
163
|
+
const sourceText = new Map();
|
|
164
|
+
for (const record of parsed.symbols) {
|
|
165
|
+
if (!isRecord(record))
|
|
166
|
+
continue;
|
|
167
|
+
if (typeof record.id !== "string" ||
|
|
168
|
+
typeof record.title !== "string" ||
|
|
169
|
+
typeof record.sourceFile !== "string")
|
|
170
|
+
continue;
|
|
171
|
+
if (record.granularity_reason !== undefined)
|
|
172
|
+
continue;
|
|
173
|
+
if (!hasTraceabilityRelationship(record))
|
|
174
|
+
continue;
|
|
175
|
+
if (declaresMemberlessSymbol(workspaceRoot, record.sourceFile, record.title, sourceText))
|
|
176
|
+
continue;
|
|
177
|
+
let symbols = symbolsByFile.get(record.sourceFile);
|
|
178
|
+
if (symbols === undefined) {
|
|
179
|
+
symbols = sourceSymbols(workspaceRoot, record.sourceFile, analyzeText);
|
|
180
|
+
symbolsByFile.set(record.sourceFile, symbols);
|
|
181
|
+
}
|
|
182
|
+
const granularSymbols = coarseLinkBehavioralNames(record.title, symbols);
|
|
183
|
+
if (granularSymbols.length === 0)
|
|
184
|
+
continue;
|
|
185
|
+
targets.push({
|
|
186
|
+
id: record.id,
|
|
187
|
+
title: record.title,
|
|
188
|
+
sourceFile: record.sourceFile,
|
|
189
|
+
granularSymbols,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return { manifestPath, manifestHash: digest(content), targets };
|
|
193
|
+
}
|
|
194
|
+
function unquoted(value) {
|
|
195
|
+
const trimmed = value.trim();
|
|
196
|
+
if (trimmed.length >= 2 &&
|
|
197
|
+
(trimmed.startsWith('"') || trimmed.startsWith("'")) &&
|
|
198
|
+
trimmed.endsWith(trimmed[0] ?? "")) {
|
|
199
|
+
return trimmed.slice(1, -1);
|
|
200
|
+
}
|
|
201
|
+
return trimmed;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Insert `granularity_reason: legacy-link` into each listed symbol entry,
|
|
205
|
+
* leaving every other byte of the manifest as it was.
|
|
206
|
+
*/
|
|
207
|
+
function withLegacyReasons(content, symbolIds) {
|
|
208
|
+
const lines = content.split("\n");
|
|
209
|
+
const output = [];
|
|
210
|
+
let activeId = null;
|
|
211
|
+
let activeHasReason = false;
|
|
212
|
+
let activePropertyIndent = "";
|
|
213
|
+
const flushLegacyReason = () => {
|
|
214
|
+
if (activeId !== null &&
|
|
215
|
+
symbolIds.has(activeId) &&
|
|
216
|
+
!activeHasReason &&
|
|
217
|
+
activePropertyIndent.length > 0) {
|
|
218
|
+
output.push(`${activePropertyIndent}granularity_reason: legacy-link`);
|
|
219
|
+
activeHasReason = true;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
for (const line of lines) {
|
|
223
|
+
const idMatch = line.match(/^(\s*)-\s+id:\s+(.+?)\s*$/);
|
|
224
|
+
if (idMatch) {
|
|
225
|
+
flushLegacyReason();
|
|
226
|
+
activeId = unquoted(idMatch[2] ?? "");
|
|
227
|
+
activeHasReason = false;
|
|
228
|
+
activePropertyIndent = `${idMatch[1] ?? ""} `;
|
|
229
|
+
}
|
|
230
|
+
if (activeId !== null && /^\s+granularity_reason:\s+/.test(line)) {
|
|
231
|
+
activeHasReason = true;
|
|
232
|
+
}
|
|
233
|
+
if (activeId !== null &&
|
|
234
|
+
symbolIds.has(activeId) &&
|
|
235
|
+
!activeHasReason &&
|
|
236
|
+
/^\s+status:\s+/.test(line)) {
|
|
237
|
+
const indent = line.match(/^\s*/)?.[0] ?? "";
|
|
238
|
+
output.push(`${indent}granularity_reason: legacy-link`);
|
|
239
|
+
activeHasReason = true;
|
|
240
|
+
}
|
|
241
|
+
output.push(line);
|
|
242
|
+
}
|
|
243
|
+
flushLegacyReason();
|
|
244
|
+
return output.join("\n");
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Mark the coarse links found by `planSymbolLegacyLinks`. With
|
|
248
|
+
* `expectedHash` (a migration plan's evidence), the manifest must still hold
|
|
249
|
+
* the bytes the plan was read from; a manifest whose planned links are all
|
|
250
|
+
* marked already is left alone, so the pass is idempotent. Returns the ids
|
|
251
|
+
* marked, and the planned ones that could not be marked in place (an entry
|
|
252
|
+
* whose `id` is not its first key, for example).
|
|
253
|
+
*/
|
|
254
|
+
// implements REQ-cli-schema-migration, REQ-symbol-behavioral-anchors
|
|
255
|
+
export function applySymbolLegacyLinks(workspaceRoot, options = {}) {
|
|
256
|
+
const plan = planSymbolLegacyLinks(workspaceRoot, options.manifestPath ?? KB_PATHS.symbolsManifest);
|
|
257
|
+
if (plan.targets.length === 0)
|
|
258
|
+
return { marked: [], unmarked: [] };
|
|
259
|
+
if (options.expectedHash !== undefined &&
|
|
260
|
+
plan.manifestHash !== options.expectedHash) {
|
|
261
|
+
throw new Error(`${plan.manifestPath} changed since planning; rerun kibi migrate and approve the new plan.`);
|
|
262
|
+
}
|
|
263
|
+
const ids = new Set(plan.targets
|
|
264
|
+
.map((target) => target.id)
|
|
265
|
+
.filter((id) => options.expectedIds === undefined || options.expectedIds.includes(id)));
|
|
266
|
+
if (ids.size === 0)
|
|
267
|
+
return { marked: [], unmarked: [] };
|
|
268
|
+
const absolute = path.resolve(workspaceRoot, plan.manifestPath);
|
|
269
|
+
const content = readFileSync(absolute, "utf8");
|
|
270
|
+
const next = withLegacyReasons(content, ids);
|
|
271
|
+
const marked = markedIds(content, next, ids);
|
|
272
|
+
if (marked === null)
|
|
273
|
+
throw new Error(`${plan.manifestPath}: legacy-link reasons could not be added without changing other symbol fields; add granularity_reason: legacy-link by hand.`);
|
|
274
|
+
if (marked.length > 0)
|
|
275
|
+
writeFileAtomically(absolute, next);
|
|
276
|
+
return {
|
|
277
|
+
marked,
|
|
278
|
+
unmarked: [...ids].filter((id) => !marked.includes(id)).sort(),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The ids whose only change between `before` and `after` is a new
|
|
283
|
+
* `granularity_reason: legacy-link`, or null when anything else changed.
|
|
284
|
+
*/
|
|
285
|
+
function markedIds(before, after, ids) {
|
|
286
|
+
try {
|
|
287
|
+
const previous = parseYAML(before);
|
|
288
|
+
const next = parseYAML(after);
|
|
289
|
+
if (!Array.isArray(previous.symbols) ||
|
|
290
|
+
!Array.isArray(next.symbols) ||
|
|
291
|
+
previous.symbols.length !== next.symbols.length)
|
|
292
|
+
return null;
|
|
293
|
+
const marked = [];
|
|
294
|
+
for (const [index, entry] of next.symbols.entries()) {
|
|
295
|
+
const old = previous.symbols[index];
|
|
296
|
+
if (!isRecord(entry) || !isRecord(old)) {
|
|
297
|
+
if (!isDeepStrictEqual(entry, old))
|
|
298
|
+
return null;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const { granularity_reason: reason, ...rest } = entry;
|
|
302
|
+
const { granularity_reason: oldReason, ...oldRest } = old;
|
|
303
|
+
if (!isDeepStrictEqual(rest, oldRest))
|
|
304
|
+
return null;
|
|
305
|
+
if (reason === oldReason)
|
|
306
|
+
continue;
|
|
307
|
+
if (reason !== "legacy-link" ||
|
|
308
|
+
oldReason !== undefined ||
|
|
309
|
+
typeof entry.id !== "string" ||
|
|
310
|
+
!ids.has(entry.id))
|
|
311
|
+
return null;
|
|
312
|
+
marked.push(entry.id);
|
|
313
|
+
}
|
|
314
|
+
return marked.sort();
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import type { UpsertInput } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Where the upsert's authored document lives. `writtenPath` is the file a
|
|
4
|
+
* real write just published; `targetPath` is the file a write would update
|
|
5
|
+
* (a dry run resolves it the same way the write does, because the preview
|
|
6
|
+
* entity's `source` is the runtime marker rather than the authored file).
|
|
7
|
+
*/
|
|
8
|
+
export interface UpsertDocumentPaths {
|
|
9
|
+
readonly writtenPath?: string | undefined;
|
|
10
|
+
readonly targetPath?: string | undefined;
|
|
11
|
+
}
|
|
2
12
|
/**
|
|
3
13
|
* The body the authored document has, or will have, after this upsert: the
|
|
4
14
|
* written file when there is one, the supplied body, the preserved body of
|
|
5
15
|
* the entity's existing document, or the default a new requirement gets.
|
|
6
16
|
*/
|
|
7
|
-
export declare function upsertDocumentBody(input: UpsertInput, entity: Readonly<Record<string, unknown>>, workspaceRoot: string,
|
|
17
|
+
export declare function upsertDocumentBody(input: UpsertInput, entity: Readonly<Record<string, unknown>>, workspaceRoot: string, paths?: UpsertDocumentPaths): string;
|
|
8
18
|
/**
|
|
9
19
|
* kb_upsert warning for a written entity whose body lacks context. The same
|
|
10
20
|
* finding blocks `kb_check` as entity-context-missing, so agents see it first.
|
|
11
21
|
*/
|
|
12
|
-
export declare function entityContextWarnings(input: UpsertInput, entity: Readonly<Record<string, unknown>>, workspaceRoot: string,
|
|
22
|
+
export declare function entityContextWarnings(input: UpsertInput, entity: Readonly<Record<string, unknown>>, workspaceRoot: string, paths?: UpsertDocumentPaths): readonly string[];
|
|
13
23
|
//# sourceMappingURL=context-warning.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-warning.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/context-warning.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAgB9C;;;;GAIG;AAEH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,aAAa,EAAE,MAAM,EACrB,
|
|
1
|
+
{"version":3,"file":"context-warning.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/context-warning.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAgB9C;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED;;;;GAIG;AAEH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,aAAa,EAAE,MAAM,EACrB,KAAK,GAAE,mBAAwB,GAC9B,MAAM,CAYR;AAED;;;GAGG;AAEH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,aAAa,EAAE,MAAM,EACrB,KAAK,GAAE,mBAAwB,GAC9B,SAAS,MAAM,EAAE,CA2BnB"}
|
|
@@ -20,15 +20,15 @@ function readBody(workspaceRoot, source) {
|
|
|
20
20
|
* the entity's existing document, or the default a new requirement gets.
|
|
21
21
|
*/
|
|
22
22
|
// implements REQ-kb-entity-body-context
|
|
23
|
-
export function upsertDocumentBody(input, entity, workspaceRoot,
|
|
24
|
-
if (writtenPath !== undefined) {
|
|
25
|
-
const written = readBody(workspaceRoot, writtenPath);
|
|
23
|
+
export function upsertDocumentBody(input, entity, workspaceRoot, paths = {}) {
|
|
24
|
+
if (paths.writtenPath !== undefined) {
|
|
25
|
+
const written = readBody(workspaceRoot, paths.writtenPath);
|
|
26
26
|
if (written !== undefined)
|
|
27
27
|
return written;
|
|
28
28
|
}
|
|
29
29
|
if (input.document?.body !== undefined)
|
|
30
30
|
return input.document.body;
|
|
31
|
-
const existing = readBody(workspaceRoot, input.document?.path ?? entity.source);
|
|
31
|
+
const existing = readBody(workspaceRoot, input.document?.path ?? paths.targetPath ?? entity.source);
|
|
32
32
|
if (existing !== undefined)
|
|
33
33
|
return existing;
|
|
34
34
|
return input.type === "req" ? `${String(entity.semantic_text ?? "")}\n` : "";
|
|
@@ -38,8 +38,8 @@ export function upsertDocumentBody(input, entity, workspaceRoot, writtenPath) {
|
|
|
38
38
|
* finding blocks `kb_check` as entity-context-missing, so agents see it first.
|
|
39
39
|
*/
|
|
40
40
|
// implements REQ-kb-entity-body-context
|
|
41
|
-
export function entityContextWarnings(input, entity, workspaceRoot,
|
|
42
|
-
const body = upsertDocumentBody(input, entity, workspaceRoot,
|
|
41
|
+
export function entityContextWarnings(input, entity, workspaceRoot, paths = {}) {
|
|
42
|
+
const body = upsertDocumentBody(input, entity, workspaceRoot, paths);
|
|
43
43
|
const semanticText = typeof entity.semantic_text === "string" && entity.semantic_text.trim()
|
|
44
44
|
? entity.semantic_text
|
|
45
45
|
: input.type === "req"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-authoring.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/source-authoring.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"source-authoring.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/source-authoring.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import fs from "node:fs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { isDeepStrictEqual } from "node:util";
|
|
7
7
|
import { dump as dumpYaml, load as loadYaml } from "js-yaml";
|
|
8
|
-
import { parseDocument, stringify } from "yaml";
|
|
8
|
+
import { isCollection, isMap, isNode, isScalar, parseDocument, stringify, } from "yaml";
|
|
9
9
|
import { OperationError } from "../../cli-errors.js";
|
|
10
10
|
import { requirementSemanticText } from "../../extractors/markdown.js";
|
|
11
11
|
import { canonicalFilesystemPath } from "../../utils/canonical-path.js";
|
|
@@ -169,22 +169,32 @@ export function normalizeAuthoredSourcePath(workspaceRoot, source) {
|
|
|
169
169
|
.relative(path.resolve(workspaceRoot), resolveContainedSourcePath(workspaceRoot, relative))
|
|
170
170
|
.replaceAll(path.sep, "/");
|
|
171
171
|
}
|
|
172
|
-
|
|
172
|
+
/** The raw front matter block of a Markdown document, split at its fences. */
|
|
173
|
+
function frontmatterBlock(content) {
|
|
173
174
|
if (!content.startsWith("---"))
|
|
174
|
-
return
|
|
175
|
+
return undefined;
|
|
175
176
|
const firstEnd = content.indexOf("\n", 3);
|
|
176
177
|
if (firstEnd < 0)
|
|
177
|
-
return
|
|
178
|
+
return undefined;
|
|
178
179
|
const marker = content.indexOf("\n---", firstEnd);
|
|
179
180
|
if (marker < 0)
|
|
181
|
+
return undefined;
|
|
182
|
+
return {
|
|
183
|
+
opening: content.slice(0, firstEnd + 1),
|
|
184
|
+
raw: content.slice(firstEnd + 1, marker),
|
|
185
|
+
body: content.slice(marker + "\n---".length),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function bodyAndFrontmatter(content) {
|
|
189
|
+
const block = frontmatterBlock(content);
|
|
190
|
+
if (block === undefined)
|
|
180
191
|
return { frontmatter: {}, body: content };
|
|
181
|
-
const
|
|
182
|
-
const parsed = loadYaml(raw);
|
|
192
|
+
const parsed = loadYaml(block.raw);
|
|
183
193
|
return {
|
|
184
194
|
frontmatter: parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
|
|
185
195
|
? parsed
|
|
186
196
|
: {},
|
|
187
|
-
body:
|
|
197
|
+
body: block.body,
|
|
188
198
|
};
|
|
189
199
|
}
|
|
190
200
|
/**
|
|
@@ -238,6 +248,105 @@ function sourcePath(context, input, entity, existing) {
|
|
|
238
248
|
}
|
|
239
249
|
return resolveContainedSourcePath(context.workspaceRoot, relative);
|
|
240
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Front matter keys an upsert never writes: runtime provenance/timestamps
|
|
253
|
+
* belong to the compiled entity, not to the authored source artifact, and
|
|
254
|
+
* relationships live in `links`/shards. Keeping them out also makes the
|
|
255
|
+
* source diff deterministic across CLI and MCP transports.
|
|
256
|
+
*/
|
|
257
|
+
const UNAUTHORED_FRONTMATTER_KEYS = new Set([
|
|
258
|
+
"id",
|
|
259
|
+
"type",
|
|
260
|
+
"source",
|
|
261
|
+
"links",
|
|
262
|
+
"relationships",
|
|
263
|
+
"created_at",
|
|
264
|
+
"updated_at",
|
|
265
|
+
]);
|
|
266
|
+
/** The front matter values an upsert of `entity` sets, in write order. */
|
|
267
|
+
function entityFrontmatterUpdates(entity, existing, body) {
|
|
268
|
+
const updates = Object.fromEntries(Object.entries(entity).filter(([key, value]) => value !== undefined && !UNAUTHORED_FRONTMATTER_KEYS.has(key)));
|
|
269
|
+
updates.id = entity.id;
|
|
270
|
+
if (updates.title === undefined && existing.title === undefined) {
|
|
271
|
+
updates.title = entity.id;
|
|
272
|
+
}
|
|
273
|
+
updates.type = entity.type;
|
|
274
|
+
// The checked meaning is always stated, never derived from a body that now
|
|
275
|
+
// also carries context sections.
|
|
276
|
+
const semanticText = updates.semantic_text ?? existing.semantic_text;
|
|
277
|
+
if (entity.type === "req" &&
|
|
278
|
+
(typeof semanticText !== "string" || semanticText.trim() === "")) {
|
|
279
|
+
const derived = requirementSemanticText(body);
|
|
280
|
+
if (derived !== "")
|
|
281
|
+
updates.semantic_text = derived;
|
|
282
|
+
}
|
|
283
|
+
return updates;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Apply `updates` to an existing front matter block without re-serializing
|
|
287
|
+
* it: only keys whose value actually changes are rewritten (or appended), so
|
|
288
|
+
* every untouched key keeps its authored bytes (quoting, timestamps, flow
|
|
289
|
+
* lists, indentation, comments). Returns undefined when the block is not a
|
|
290
|
+
* plain block mapping that can be patched in place.
|
|
291
|
+
*/
|
|
292
|
+
// implements REQ-core-persistence
|
|
293
|
+
function patchFrontmatter(raw, loaded, updates) {
|
|
294
|
+
const doc = parseDocument(raw);
|
|
295
|
+
if (doc.errors.length > 0)
|
|
296
|
+
return undefined;
|
|
297
|
+
const contents = doc.contents;
|
|
298
|
+
if (contents === null && raw.trim() !== "")
|
|
299
|
+
return undefined;
|
|
300
|
+
if (contents !== null && (!isMap(contents) || contents.flow)) {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
const pairs = contents === null ? [] : contents.items;
|
|
304
|
+
const current = (doc.toJS() ?? {});
|
|
305
|
+
const edits = [];
|
|
306
|
+
const appended = [];
|
|
307
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
308
|
+
const pair = pairs.find((candidate) => isScalar(candidate.key) && String(candidate.key.value) === key);
|
|
309
|
+
if (pair !== undefined &&
|
|
310
|
+
(isDeepStrictEqual(current[key], value) ||
|
|
311
|
+
isDeepStrictEqual(loaded[key], value))) {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const valueNode = pair?.value;
|
|
315
|
+
const flow = isCollection(valueNode) && valueNode.flow === true;
|
|
316
|
+
const text = dumpYaml({ [key]: value }, {
|
|
317
|
+
noRefs: true,
|
|
318
|
+
lineWidth: -1,
|
|
319
|
+
sortKeys: false,
|
|
320
|
+
...(flow ? { flowLevel: 1 } : {}),
|
|
321
|
+
}).replace(/\n$/, "");
|
|
322
|
+
if (pair === undefined) {
|
|
323
|
+
appended.push(text);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const keyRange = isScalar(pair.key) ? pair.key.range : undefined;
|
|
327
|
+
if (!keyRange)
|
|
328
|
+
return undefined;
|
|
329
|
+
const start = keyRange[0];
|
|
330
|
+
let end = isNode(valueNode) && valueNode.range ? valueNode.range[1] : keyRange[2];
|
|
331
|
+
// `key:` with no value: replace through the end of the key's line.
|
|
332
|
+
if (end <= keyRange[1]) {
|
|
333
|
+
const lineEnd = raw.indexOf("\n", keyRange[1]);
|
|
334
|
+
end = lineEnd < 0 ? raw.length : lineEnd;
|
|
335
|
+
}
|
|
336
|
+
while (end > start && /[\r\n]/.test(raw[end - 1] ?? ""))
|
|
337
|
+
end -= 1;
|
|
338
|
+
edits.push({ start, end, text });
|
|
339
|
+
}
|
|
340
|
+
let patched = raw;
|
|
341
|
+
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
342
|
+
patched = `${patched.slice(0, edit.start)}${edit.text}${patched.slice(edit.end)}`;
|
|
343
|
+
}
|
|
344
|
+
if (appended.length > 0) {
|
|
345
|
+
const separator = patched === "" || patched.endsWith("\n") ? "" : "\n";
|
|
346
|
+
patched = `${patched}${separator}${appended.join("\n")}`;
|
|
347
|
+
}
|
|
348
|
+
return patched;
|
|
349
|
+
}
|
|
241
350
|
function renderEntityDocument(input, entity, existingContent) {
|
|
242
351
|
const existing = existingContent
|
|
243
352
|
? bodyAndFrontmatter(existingContent)
|
|
@@ -249,35 +358,21 @@ function renderEntityDocument(input, entity, existingContent) {
|
|
|
249
358
|
: input.type === "req"
|
|
250
359
|
? `${String(entity.semantic_text ?? "").trim()}\n`
|
|
251
360
|
: "\n";
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
"created_at",
|
|
265
|
-
"updated_at",
|
|
266
|
-
].includes(key))),
|
|
267
|
-
};
|
|
268
|
-
frontmatter.id = entity.id;
|
|
269
|
-
frontmatter.title ??= entity.id;
|
|
270
|
-
frontmatter.type = entity.type;
|
|
271
|
-
// The checked meaning is always stated, never derived from a body that now
|
|
272
|
-
// also carries context sections.
|
|
273
|
-
if (entity.type === "req" &&
|
|
274
|
-
(typeof frontmatter.semantic_text !== "string" ||
|
|
275
|
-
frontmatter.semantic_text.trim() === "")) {
|
|
276
|
-
const derived = requirementSemanticText(body);
|
|
277
|
-
if (derived !== "")
|
|
278
|
-
frontmatter.semantic_text = derived;
|
|
361
|
+
const updates = entityFrontmatterUpdates(entity, existing.frontmatter, body);
|
|
362
|
+
const bodySuffix = `${body.startsWith("\n") ? "" : "\n"}${body}`;
|
|
363
|
+
// An existing document keeps its authored front matter bytes; only the
|
|
364
|
+
// keys this upsert changes are rewritten.
|
|
365
|
+
const block = existingContent !== undefined
|
|
366
|
+
? frontmatterBlock(existingContent)
|
|
367
|
+
: undefined;
|
|
368
|
+
if (block !== undefined) {
|
|
369
|
+
const patched = patchFrontmatter(block.raw, existing.frontmatter, updates);
|
|
370
|
+
if (patched !== undefined) {
|
|
371
|
+
return `${block.opening}${patched}\n---${bodySuffix}`;
|
|
372
|
+
}
|
|
279
373
|
}
|
|
280
|
-
|
|
374
|
+
const frontmatter = { ...existing.frontmatter, ...updates };
|
|
375
|
+
return `---\n${dumpYaml(frontmatter, { noRefs: true, lineWidth: -1, sortKeys: false })}---${bodySuffix}`;
|
|
281
376
|
}
|
|
282
377
|
/** Generated coordinate fields belong exclusively to `.kb/symbol-coordinates.yaml`. */
|
|
283
378
|
// implements REQ-generated-coordinate-persistence
|
|
@@ -52,7 +52,8 @@ export type UpsertPayload = {
|
|
|
52
52
|
readonly updated: number;
|
|
53
53
|
readonly relationships_created: number;
|
|
54
54
|
readonly warnings: readonly string[];
|
|
55
|
-
|
|
55
|
+
/** The requirement semantic advisor receipt; absent for other entity types. */
|
|
56
|
+
readonly semanticAdvisor?: SemanticAdvisorReceipt;
|
|
56
57
|
/**
|
|
57
58
|
* For a fact that carries claim_text: the stored claim_key, derived from
|
|
58
59
|
* claim_text when the payload did not supply it, so the writer learns the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAGrD,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAGlE,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,iDAAiD;QACjD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACvB,yEAAyE;QACzE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACpD,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAGF,wDAAwD;AACxD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACvD,CAAC;AAGF;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1E,0DAA0D;IAC1D,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACtD,CAAC;AAGF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACtD,CAAC;AAGF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,eAAe,EAAE,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/types.ts"],"names":[],"mappings":"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"}
|