kibi-cli 2.25.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.
Files changed (86) hide show
  1. package/dist/cli-errors.d.ts +14 -0
  2. package/dist/cli-errors.d.ts.map +1 -1
  3. package/dist/cli-errors.js +31 -0
  4. package/dist/commands/check.d.ts.map +1 -1
  5. package/dist/commands/check.js +28 -0
  6. package/dist/commands/migrate.d.ts +2 -0
  7. package/dist/commands/migrate.d.ts.map +1 -1
  8. package/dist/commands/migrate.js +115 -4
  9. package/dist/commands/sync/cache.d.ts +30 -0
  10. package/dist/commands/sync/cache.d.ts.map +1 -1
  11. package/dist/commands/sync/cache.js +61 -0
  12. package/dist/commands/sync/extraction.d.ts +6 -0
  13. package/dist/commands/sync/extraction.d.ts.map +1 -1
  14. package/dist/commands/sync/extraction.js +4 -1
  15. package/dist/commands/sync.d.ts.map +1 -1
  16. package/dist/commands/sync.js +27 -1
  17. package/dist/operations/bootstrap/claim-restatement.d.ts +37 -0
  18. package/dist/operations/bootstrap/claim-restatement.d.ts.map +1 -0
  19. package/dist/operations/bootstrap/claim-restatement.js +83 -0
  20. package/dist/operations/bootstrap/generate.d.ts.map +1 -1
  21. package/dist/operations/bootstrap/generate.js +23 -12
  22. package/dist/operations/bootstrap/intent-claims.d.ts +5 -2
  23. package/dist/operations/bootstrap/intent-claims.d.ts.map +1 -1
  24. package/dist/operations/bootstrap/intent-claims.js +44 -4
  25. package/dist/operations/migration/requirement-status.d.ts +41 -2
  26. package/dist/operations/migration/requirement-status.d.ts.map +1 -1
  27. package/dist/operations/migration/requirement-status.js +75 -7
  28. package/dist/operations/migration/schema6.d.ts +9 -5
  29. package/dist/operations/migration/schema6.d.ts.map +1 -1
  30. package/dist/operations/migration/schema6.js +82 -13
  31. package/dist/operations/migration/symbol-coordinates-refresh.d.ts +36 -0
  32. package/dist/operations/migration/symbol-coordinates-refresh.d.ts.map +1 -0
  33. package/dist/operations/migration/symbol-coordinates-refresh.js +99 -0
  34. package/dist/operations/mutation/source-authoring.d.ts +9 -0
  35. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  36. package/dist/operations/mutation/source-authoring.js +20 -0
  37. package/dist/operations/mutation/stored-entity.d.ts +5 -0
  38. package/dist/operations/mutation/stored-entity.d.ts.map +1 -1
  39. package/dist/operations/mutation/uncompiled-source.d.ts +43 -0
  40. package/dist/operations/mutation/uncompiled-source.d.ts.map +1 -0
  41. package/dist/operations/mutation/uncompiled-source.js +115 -0
  42. package/dist/operations/mutation/upsert.d.ts +6 -0
  43. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  44. package/dist/operations/mutation/upsert.js +32 -5
  45. package/dist/public/impact/analyzer.d.ts.map +1 -1
  46. package/dist/public/impact/analyzer.js +13 -3
  47. package/dist/public/impact/diagnostics.js +1 -1
  48. package/dist/public/impact/unowned-files.d.ts +31 -0
  49. package/dist/public/impact/unowned-files.d.ts.map +1 -0
  50. package/dist/public/impact/unowned-files.js +171 -0
  51. package/dist/public/operations/catalog.d.ts +1 -1
  52. package/dist/public/operations/discovery-entities.d.ts +11 -0
  53. package/dist/public/operations/discovery-entities.d.ts.map +1 -1
  54. package/dist/public/operations/discovery-entities.js +28 -0
  55. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  56. package/dist/public/operations/discovery-executors.js +13 -1
  57. package/dist/public/operations/result-envelope.d.ts +2 -0
  58. package/dist/public/operations/result-envelope.d.ts.map +1 -1
  59. package/dist/public/operations/result-envelope.js +1 -0
  60. package/dist/public/operations/source-relationship-parity.d.ts.map +1 -1
  61. package/dist/public/operations/source-relationship-parity.js +22 -7
  62. package/dist/public/operations/specs/check.d.ts +1 -1
  63. package/dist/public/operations/specs/check.js +1 -1
  64. package/dist/public/skills/kibi-bootstrap/SKILL.md +2 -2
  65. package/dist/public/skills/kibi-usage/SKILL.md +11 -5
  66. package/dist/traceability/git-change-snapshot.d.ts.map +1 -1
  67. package/dist/traceability/git-change-snapshot.js +44 -3
  68. package/dist/traceability/staged-diagnostics.d.ts +1 -1
  69. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  70. package/dist/traceability/staged-migration.d.ts +13 -0
  71. package/dist/traceability/staged-migration.d.ts.map +1 -0
  72. package/dist/traceability/staged-migration.js +59 -0
  73. package/dist/traceability/staged-symbols-manifest.d.ts +11 -0
  74. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  75. package/dist/traceability/staged-symbols-manifest.js +50 -16
  76. package/package.json +2 -2
  77. package/src/public/impact/analyzer.ts +21 -7
  78. package/src/public/impact/diagnostics.ts +1 -1
  79. package/src/public/impact/unowned-files.ts +229 -0
  80. package/src/public/operations/discovery-entities.ts +36 -0
  81. package/src/public/operations/discovery-executors.ts +17 -1
  82. package/src/public/operations/result-envelope.ts +3 -0
  83. package/src/public/operations/source-relationship-parity.ts +22 -7
  84. package/src/public/operations/specs/check.ts +1 -1
  85. package/src/public/skills/kibi-bootstrap/SKILL.md +2 -2
  86. package/src/public/skills/kibi-usage/SKILL.md +11 -5
@@ -21,16 +21,18 @@ import { FIRMNESS_FROM_ORIGIN_SCHEMA_VERSION } from "../../public/operations/dec
21
21
  import { migrationAction, } from "../../public/operations/migration-plan.js";
22
22
  import { CLOSE_SUPERSEDED_REQUIREMENTS_ACTION_ID, CLOSE_SUPERSEDED_REQUIREMENTS_CODE, MIGRATION_SYNC_ACTION_ID, MIGRATION_SYNC_CODE, SOURCE_PATH_REWRITE_ACTION_ID, SOURCE_PATH_REWRITE_CODE, closeSupersededRequirementsActionInput, exceptionUnapprovedActionInput, migrationSyncActionInput, sourcePathReviewActionInput, sourcePathRewriteActionInput, supersessionCycleReviewActionInput, } from "../../public/operations/schema6-check-actions.js";
23
23
  import { readAllShards } from "../../relationships/shards.js";
24
+ import { KB_PATHS } from "../../utils/kb-paths.js";
24
25
  import { CONTEXT_MISSING_TAG_CODE, SEMANTIC_TEXT_PIN_CODE, applyContextMissingTags, applySemanticTextPins, countByType, planContextMissingTags, planSemanticTextPins, } from "./entity-body-context.js";
25
26
  import { FIRM_SUPERSEDE_ACKNOWLEDGE_CODE, applyFirmSupersedeAcknowledgement, planFirmSupersedeAcknowledgement, } from "./firm-supersede.js";
26
27
  import { applyInventoryRederivation, applyInventoryRederivationDroppingGrounding, planInventoryRederivation, } from "./inventory-rederive.js";
27
28
  import { listLaneMarkdownFiles } from "./kb-sources.js";
28
29
  import { applyOriginBackfill, planOriginBackfill } from "./origin-backfill.js";
29
30
  import { POLARITY_VALUE_BACKFILL_CODE, applyPolarityValueBackfill, planPolarityValueBackfill, } from "./polarity-values.js";
30
- import { applyRequirementStatusMapping, planRequirementStatusMappings, } from "./requirement-status.js";
31
+ import { DELIVERED_REQUIREMENT_TAG, applyDeliveredStatusMappings, applyRequirementStatusMapping, planRequirementStatusMappings, } from "./requirement-status.js";
31
32
  import { applySourcePathRepairs, findSourceRepairs, plannedSourcePathRepairs, } from "./source-paths.js";
32
33
  import { applySupersededClosures, planSupersededClosures, } from "./superseded-closure.js";
33
34
  import { SUPERSEDES_REASON_ACKNOWLEDGE_CODE, applySupersedesReasonAcknowledgement, planSupersedesReasonAcknowledgement, } from "./supersedes-reason.js";
35
+ import { coordinateRefreshFailureMessage, readGeneratedManifestBytes, refreshCoordinatesAfterMigration, } from "./symbol-coordinates-refresh.js";
34
36
  import { SYMBOL_LEGACY_LINK_MARK_ACTION_ID, SYMBOL_LEGACY_LINK_MARK_CODE, applySymbolLegacyLinks, planSymbolLegacyLinks, } from "./symbol-legacy-links.js";
35
37
  /** The KB schema generation that introduced entity `origin`. */
36
38
  // implements REQ-kibi-schema6-migration, REQ-cli-schema-migration
@@ -56,6 +58,11 @@ export const SEMANTIC_INVENTORY_REDERIVE_DROP_GROUNDING_CODE = "semantic_invento
56
58
  /** Review, applied only when approved by id: map a legacy requirement status. */
57
59
  // implements REQ-cli-schema-migration
58
60
  export const REQ_STATUS_VOCABULARY_MAP_CODE = "req_status_vocabulary_map";
61
+ /** Automatic: map delivery statuses to `closed` plus the tag `implemented`. */
62
+ // implements REQ-cli-schema-migration
63
+ export const REQ_STATUS_DELIVERED_MAP_CODE = "req_status_delivered_map";
64
+ // implements REQ-cli-schema-migration
65
+ export const REQ_STATUS_DELIVERED_MAP_ACTION_ID = "req-status-delivered-map";
59
66
  /** Automatic: stamp `origin: {kind: migration}` on authored entities. */
60
67
  // implements REQ-kibi-schema6-migration, REQ-cli-schema-migration
61
68
  export const ENTITY_ORIGIN_BACKFILL_CODE = "entity_origin_backfill";
@@ -210,16 +217,52 @@ function plannedDrops(action) {
210
217
  : []);
211
218
  }
212
219
  /**
213
- * One action per requirement whose status is outside the vocabulary: a
214
- * review (the target status is a semantic choice) carrying the suggested
215
- * mapping and the inventory drift the requirement shows once mapped. With a
216
- * suggestion it is executable when approved by id; the re-derivation of the
217
- * latent drift depends on it.
220
+ * The automatic action for requirements whose status is delivery vocabulary
221
+ * (`implemented`, `done`, `completed`): sync cannot compile them, and their
222
+ * one lossless reading is `closed` plus the tag `implemented`.
223
+ */
224
+ function deliveredStatusAction(findings) {
225
+ return migrationAction({
226
+ id: REQ_STATUS_DELIVERED_MAP_ACTION_ID,
227
+ code: REQ_STATUS_DELIVERED_MAP_CODE,
228
+ category: "schema",
229
+ safety: "automatic",
230
+ autoApplicable: true,
231
+ invocation: { kind: "cli", command_argv: ["kibi", "migrate", "--yes"] },
232
+ affectedEntityIds: findings.map((finding) => finding.requirementId),
233
+ affectedFiles: findings.map((finding) => finding.path),
234
+ postconditions: [{ rule: "req-status-vocabulary", findings: 0 }],
235
+ evidence: {
236
+ reason: "Delivery statuses are outside the entity schema, so kibi sync cannot compile these requirements and every store-backed tool misses them. Each becomes status closed with the tag implemented, which Kibi reads as a delivered requirement that still owes coverage; only the status line and the tags change.",
237
+ count: findings.length,
238
+ mappings: findings.map((finding) => ({
239
+ requirementId: finding.requirementId,
240
+ path: finding.path,
241
+ from: finding.status,
242
+ to: finding.automatic?.status ?? "closed",
243
+ tag: finding.automatic?.tag ?? DELIVERED_REQUIREMENT_TAG,
244
+ })),
245
+ },
246
+ });
247
+ }
248
+ /**
249
+ * Requirements whose status is outside the vocabulary. Delivery vocabulary
250
+ * gets one automatic action (see deliveredStatusAction). Every other status
251
+ * gets one review action (the target status is a semantic choice) carrying
252
+ * the suggested mapping and the inventory drift the requirement shows once
253
+ * mapped. With a suggestion it is executable when approved by id; the
254
+ * re-derivation of the latent drift depends on it.
218
255
  */
219
256
  // implements REQ-cli-schema-migration, REQ-kibi-proposition-complete-ingestion
220
257
  export function requirementStatusActions(workspaceRoot) {
221
258
  const actions = [];
222
- for (const finding of planRequirementStatusMappings(workspaceRoot)) {
259
+ const findings = planRequirementStatusMappings(workspaceRoot);
260
+ const delivered = findings.filter((finding) => finding.automatic !== null);
261
+ if (delivered.length > 0)
262
+ actions.push(deliveredStatusAction(delivered));
263
+ for (const finding of findings) {
264
+ if (finding.automatic !== null)
265
+ continue;
223
266
  const id = `req-status-vocabulary-map-${finding.requirementId}`;
224
267
  const drift = finding.latentDrift;
225
268
  const target = finding.suggestedStatus;
@@ -232,7 +275,9 @@ export function requirementStatusActions(workspaceRoot) {
232
275
  invocation: target === null
233
276
  ? {
234
277
  kind: "review",
235
- instruction: `${finding.requirementId} (${finding.path}) has status ${finding.status}, which is not a requirement status, so it is skipped by the proof ladder and its semantic inventory is never validated. Set status to open, in_progress or closed (legacy: active, approved) in the file, or use proof_exempt to park it, then rerun kibi migrate.`,
278
+ instruction: finding.extractionError === null
279
+ ? `${finding.requirementId} (${finding.path}) has status ${finding.status}, which is not a requirement status, so it is skipped by the proof ladder and its semantic inventory is never validated. Set status to open, in_progress or closed (legacy: active, approved) in the file, or use proof_exempt to park it, then rerun kibi migrate.`
280
+ : `${finding.requirementId} (${finding.path}) has status ${finding.status}, which is not a status Kibi knows, so kibi sync cannot compile it (${finding.extractionError}) and kb_query, kb_coverage and kb_upsert do not see it. Set status to open, in_progress or closed in the file (closed with the tag implemented for delivered work), then run kibi sync and rerun kibi migrate.`,
236
281
  }
237
282
  : { kind: "cli", command_argv: approvalArgv([id]) },
238
283
  affectedEntityIds: [finding.requirementId],
@@ -241,9 +286,14 @@ export function requirementStatusActions(workspaceRoot) {
241
286
  dispositionRequired: true,
242
287
  allowedDispositions: ["fixed", "deferred"],
243
288
  evidence: {
244
- reason: `Status ${finding.status} is ADR or lifecycle vocabulary, not a requirement status: the requirement is skipped by the proof ladder and kibi sync never validates its semantic inventory.`,
289
+ reason: finding.extractionError === null
290
+ ? `Status ${finding.status} is ADR or lifecycle vocabulary, not a requirement status: the requirement is skipped by the proof ladder and kibi sync never validates its semantic inventory.`
291
+ : `Status ${finding.status} is outside the entity schema, so kibi sync cannot compile the requirement at all.`,
245
292
  status: finding.status,
246
293
  suggestedStatus: target,
294
+ ...(finding.extractionError === null
295
+ ? {}
296
+ : { extractionError: finding.extractionError }),
247
297
  ...(target === null
248
298
  ? {}
249
299
  : {
@@ -515,9 +565,13 @@ export function buildSchema6MigrationFragment(input) {
515
565
  if (!plan.safe && plan.rederivableByDroppingGrounding)
516
566
  actions.push(dropGroundingAction(plan));
517
567
  }
518
- // Requirements with a status outside the vocabulary: mapping is a review,
519
- // and their latent drift is re-derived only after an approved mapping.
520
- actions.push(...requirementStatusActions(input.workspaceRoot));
568
+ // Requirements with a status outside the vocabulary: delivery statuses are
569
+ // mapped automatically; any other mapping is a review, and its latent drift
570
+ // is re-derived only after an approved mapping.
571
+ const statusActions = requirementStatusActions(input.workspaceRoot);
572
+ actions.push(...statusActions);
573
+ if (statusActions.some((action) => action.id === REQ_STATUS_DELIVERED_MAP_ACTION_ID))
574
+ sourceRewriteActionIds.push(REQ_STATUS_DELIVERED_MAP_ACTION_ID);
521
575
  if (drift.some((plan) => !plan.safe)) {
522
576
  diagnostics.push(`${drift.filter((plan) => !plan.safe).length} requirement inventory(ies) need a manual fix before kibi sync succeeds; see the semantic_inventory_review actions.`);
523
577
  }
@@ -637,7 +691,9 @@ export function symbolLegacyLinkAction(workspaceRoot) {
637
691
  autoApplicable: true,
638
692
  invocation: { kind: "cli", command_argv: ["kibi", "migrate", "--yes"] },
639
693
  affectedEntityIds: plan.targets.map((target) => target.id),
640
- affectedFiles: [plan.manifestPath],
694
+ // A mark changes the record's coordinate identity, so the action also
695
+ // regenerates the coordinate artifact.
696
+ affectedFiles: [plan.manifestPath, KB_PATHS.symbolCoordinates],
641
697
  postconditions: [{ rule: "symbol_granularity_violation", findings: 0 }],
642
698
  evidence: {
643
699
  reason: "These symbols link a source file coarsely while it declares narrower behavioral symbols, so the staged check (kibi check --staged) blocks the next commit that touches them. Each gets granularity_reason: legacy-link (accepted legacy granularity); no link moves and no other field changes.",
@@ -725,6 +781,12 @@ export async function applySchema6MigrationAction(action, context) {
725
781
  applyRequirementStatusMapping(context.workspaceRoot, mapping.path, mapping.from, mapping.to);
726
782
  return;
727
783
  }
784
+ case REQ_STATUS_DELIVERED_MAP_CODE: {
785
+ const result = applyDeliveredStatusMappings(context.workspaceRoot, new Set(action.affectedEntityIds));
786
+ if (result.skipped.length > 0)
787
+ throw new Error(`Could not map the status of ${result.skipped.map((skip) => `${skip.path} (${skip.reason})`).join(", ")}; set status: closed and add the tag implemented by hand.`);
788
+ return;
789
+ }
728
790
  case SEMANTIC_INVENTORY_REDERIVE_DROP_GROUNDING_CODE: {
729
791
  const requirementId = action.affectedEntityIds[0];
730
792
  const contractHash = action.evidence.contractHash;
@@ -737,6 +799,7 @@ export async function applySchema6MigrationAction(action, context) {
737
799
  applyOriginBackfill(context.workspaceRoot, context.clock().toISOString());
738
800
  return;
739
801
  case SYMBOL_LEGACY_LINK_MARK_CODE: {
802
+ const before = readGeneratedManifestBytes(context.workspaceRoot);
740
803
  const result = applySymbolLegacyLinks(context.workspaceRoot, {
741
804
  ...(typeof action.evidence.manifestHash === "string"
742
805
  ? { expectedHash: action.evidence.manifestHash }
@@ -745,6 +808,11 @@ export async function applySchema6MigrationAction(action, context) {
745
808
  });
746
809
  if (result.unmarked.length > 0)
747
810
  throw new Error(`Could not mark ${result.unmarked.join(", ")} as legacy-link in place; add granularity_reason: legacy-link to them by hand.`);
811
+ // A legacy-link mark changes the record's coordinate identity; keep
812
+ // the generated artifact current so the next commit carries no drift.
813
+ const refresh = await refreshCoordinatesAfterMigration(context.workspaceRoot, before);
814
+ if (refresh.outcome === "failed")
815
+ console.warn(`Warning: ${coordinateRefreshFailureMessage(refresh.reason)}`);
748
816
  return;
749
817
  }
750
818
  case CLOSE_SUPERSEDED_REQUIREMENTS_CODE: {
@@ -791,6 +859,7 @@ export const SCHEMA6_AUTOMATIC_CODES = new Set([
791
859
  SUPERSEDES_REASON_ACKNOWLEDGE_CODE,
792
860
  FIRM_SUPERSEDE_ACKNOWLEDGE_CODE,
793
861
  SEMANTIC_INVENTORY_REDERIVE_CODE,
862
+ REQ_STATUS_DELIVERED_MAP_CODE,
794
863
  ENTITY_ORIGIN_BACKFILL_CODE,
795
864
  CLOSE_SUPERSEDED_REQUIREMENTS_CODE,
796
865
  SOURCE_PATH_REWRITE_CODE,
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Bytes of the symbols manifest and its generated coordinate artifact at
3
+ * their canonical paths; null for a file that does not exist.
4
+ */
5
+ export type GeneratedManifestBytes = Readonly<{
6
+ symbols: string | null;
7
+ coordinates: string | null;
8
+ }>;
9
+ /** Snapshot taken before a migration writes, to tell what it rewrote. */
10
+ export declare function readGeneratedManifestBytes(workspaceRoot: string): GeneratedManifestBytes;
11
+ /** What the post-migration coordinate refresh did. */
12
+ export type MigrationCoordinateRefresh = {
13
+ outcome: "skipped";
14
+ } | {
15
+ outcome: "refreshed";
16
+ } | {
17
+ outcome: "failed";
18
+ reason: string;
19
+ };
20
+ /**
21
+ * Refresh generated symbol coordinates the way
22
+ * `kibi sync --refresh-symbol-coordinates` does, after a migration rewrote or
23
+ * moved the symbols manifest. The coordinate artifact binds each record by an
24
+ * identity hash that includes `granularity_reason`, so a legacy-link mark
25
+ * leaves the committed coordinates stale; without this refresh the pre-commit
26
+ * generated-manifest gate rejects the first commit after a migration.
27
+ *
28
+ * Skipped when `before` shows the run left both files untouched, or when the
29
+ * manifest has no symbols. A refresh failure is returned, not thrown: the
30
+ * migration itself is complete, and the caller tells the user to refresh by
31
+ * hand before committing.
32
+ */
33
+ export declare function refreshCoordinatesAfterMigration(workspaceRoot: string, before?: GeneratedManifestBytes): Promise<MigrationCoordinateRefresh>;
34
+ /** The line a migration prints when its coordinate refresh failed. */
35
+ export declare function coordinateRefreshFailureMessage(reason: string): string;
36
+ //# sourceMappingURL=symbol-coordinates-refresh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbol-coordinates-refresh.d.ts","sourceRoot":"","sources":["../../../src/operations/migration/symbol-coordinates-refresh.ts"],"names":[],"mappings":"AA0BA;;;GAGG;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC,CAAC;AAMH,yEAAyE;AAEzE,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,MAAM,GACpB,sBAAsB,CAMxB;AAYD,sDAAsD;AAEtD,MAAM,MAAM,0BAA0B,GAClC;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,GACtB;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,GACxB;IAAE,OAAO,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1C;;;;;;;;;;;;GAYG;AAEH,wBAAsB,gCAAgC,CACpD,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,sBAAsB,GAC9B,OAAO,CAAC,0BAA0B,CAAC,CA0CrC;AAED,sEAAsE;AAEtE,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtE"}
@@ -0,0 +1,99 @@
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 { load as parseYAML } from "js-yaml";
22
+ import { resolveSymbolsManifestPaths } from "../../utils/manifest-paths.js";
23
+ import { republishPendingSourceReceipt } from "../mutation/source-authoring.js";
24
+ import { withSymbolCompilerLock } from "../mutation/symbol-compiler-lock.js";
25
+ function readOrNull(filePath) {
26
+ return existsSync(filePath) ? readFileSync(filePath, "utf8") : null;
27
+ }
28
+ /** Snapshot taken before a migration writes, to tell what it rewrote. */
29
+ // implements REQ-cli-migration-first-commit
30
+ export function readGeneratedManifestBytes(workspaceRoot) {
31
+ const paths = resolveSymbolsManifestPaths(workspaceRoot);
32
+ return {
33
+ symbols: readOrNull(paths.symbolsPath),
34
+ coordinates: readOrNull(paths.coordinatesPath),
35
+ };
36
+ }
37
+ function hasSymbols(content) {
38
+ try {
39
+ const parsed = parseYAML(content);
40
+ return Array.isArray(parsed?.symbols) && parsed.symbols.length > 0;
41
+ }
42
+ catch {
43
+ // An unreadable manifest is reported by the refresh itself.
44
+ return true;
45
+ }
46
+ }
47
+ /**
48
+ * Refresh generated symbol coordinates the way
49
+ * `kibi sync --refresh-symbol-coordinates` does, after a migration rewrote or
50
+ * moved the symbols manifest. The coordinate artifact binds each record by an
51
+ * identity hash that includes `granularity_reason`, so a legacy-link mark
52
+ * leaves the committed coordinates stale; without this refresh the pre-commit
53
+ * generated-manifest gate rejects the first commit after a migration.
54
+ *
55
+ * Skipped when `before` shows the run left both files untouched, or when the
56
+ * manifest has no symbols. A refresh failure is returned, not thrown: the
57
+ * migration itself is complete, and the caller tells the user to refresh by
58
+ * hand before committing.
59
+ */
60
+ // implements REQ-cli-migration-first-commit
61
+ export async function refreshCoordinatesAfterMigration(workspaceRoot, before) {
62
+ const after = readGeneratedManifestBytes(workspaceRoot);
63
+ if (after.symbols === null || !hasSymbols(after.symbols))
64
+ return { outcome: "skipped" };
65
+ if (before !== undefined &&
66
+ before.symbols === after.symbols &&
67
+ before.coordinates === after.coordinates)
68
+ return { outcome: "skipped" };
69
+ const { symbolsPath, coordinatesPath } = resolveSymbolsManifestPaths(workspaceRoot);
70
+ try {
71
+ const { refreshManifestCoordinates } = await import("../../commands/sync/manifest.js");
72
+ await withSymbolCompilerLock(workspaceRoot, () => refreshManifestCoordinates(symbolsPath, workspaceRoot, {
73
+ refreshSymbolCoordinates: true,
74
+ }));
75
+ // Files the migration moved are still untracked and pinned by a
76
+ // pending-source receipt; rebind each to its refreshed bytes so the next
77
+ // sync does not refuse it as hash drift. A file without a receipt is
78
+ // left alone.
79
+ for (const filePath of [symbolsPath, coordinatesPath]) {
80
+ if (!existsSync(filePath))
81
+ continue;
82
+ republishPendingSourceReceipt(workspaceRoot, path.relative(workspaceRoot, filePath).split(path.sep).join("/"), createHash("sha256")
83
+ .update(readFileSync(filePath, "utf8"), "utf8")
84
+ .digest("hex"));
85
+ }
86
+ return { outcome: "refreshed" };
87
+ }
88
+ catch (error) {
89
+ return {
90
+ outcome: "failed",
91
+ reason: error instanceof Error ? error.message : String(error),
92
+ };
93
+ }
94
+ }
95
+ /** The line a migration prints when its coordinate refresh failed. */
96
+ // implements REQ-cli-migration-first-commit
97
+ export function coordinateRefreshFailureMessage(reason) {
98
+ return `Could not refresh symbol coordinates after the migration (${reason}). Run 'kibi sync --refresh-symbol-coordinates' and stage .kb/symbols.yaml and .kb/symbol-coordinates.yaml before the first commit, or the pre-commit hook reports generated manifest drift.`;
99
+ }
@@ -43,6 +43,15 @@ export declare function normalizeAuthoredSourcePath(workspaceRoot: string, sourc
43
43
  */
44
44
  export declare function configuredSourceTarget(workspaceRoot: string, type: string): string | undefined;
45
45
  export declare function hasConfiguredSourceTarget(workspaceRoot: string, type: string): boolean;
46
+ /**
47
+ * The absolute authored document an upsert of an entity the store does not
48
+ * hold would write: the same resolution writeSourceForUpsert uses, so a check
49
+ * on that file judges the file the writer merges into. Undefined when the
50
+ * writer has no target (it reports DOCUMENT_PATH_REQUIRED itself).
51
+ */
52
+ export declare function newEntitySourceTarget(context: OperationContext, input: UpsertInput): string | undefined;
53
+ /** The front matter of an authored document; throws on YAML it cannot parse. */
54
+ export declare function authoredFrontmatter(content: string): Record<string, unknown>;
46
55
  /** Generated coordinate fields belong exclusively to `.kb/symbol-coordinates.yaml`. */
47
56
  export declare const GENERATED_SYMBOL_FIELDS: readonly ["sourceLine", "sourceColumn", "sourceEndLine", "sourceEndColumn", "coordinatesGeneratedAt"];
48
57
  export declare function manifestRelationships(item: unknown): Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"source-authoring.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/source-authoring.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"source-authoring.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/source-authoring.ts"],"names":[],"mappings":"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"}
@@ -248,6 +248,26 @@ function sourcePath(context, input, entity, existing) {
248
248
  }
249
249
  return resolveContainedSourcePath(context.workspaceRoot, relative);
250
250
  }
251
+ /**
252
+ * The absolute authored document an upsert of an entity the store does not
253
+ * hold would write: the same resolution writeSourceForUpsert uses, so a check
254
+ * on that file judges the file the writer merges into. Undefined when the
255
+ * writer has no target (it reports DOCUMENT_PATH_REQUIRED itself).
256
+ */
257
+ // implements REQ-kibi-uncompiled-source-visibility
258
+ export function newEntitySourceTarget(context, input) {
259
+ try {
260
+ return sourcePath(context, input, { id: input.id }, undefined);
261
+ }
262
+ catch {
263
+ return undefined;
264
+ }
265
+ }
266
+ /** The front matter of an authored document; throws on YAML it cannot parse. */
267
+ // implements REQ-kibi-uncompiled-source-visibility
268
+ export function authoredFrontmatter(content) {
269
+ return bodyAndFrontmatter(content).frontmatter;
270
+ }
251
271
  /**
252
272
  * Front matter keys an upsert never writes: runtime provenance/timestamps
253
273
  * belong to the compiled entity, not to the authored source artifact, and
@@ -9,6 +9,11 @@ export type StoredEntityLookup = {
9
9
  readonly exists: () => Promise<boolean>;
10
10
  /** The stored entity, loaded only when one exists. */
11
11
  readonly entity: () => Promise<StoredEntity>;
12
+ /**
13
+ * The entity's own relationships when they come from its authored file
14
+ * rather than the store (an entity the store does not hold yet).
15
+ */
16
+ readonly relationships?: readonly Readonly<Record<string, unknown>>[];
12
17
  };
13
18
  /**
14
19
  * One read of the entity an upsert replaces, shared by every step of the
@@ -1 +1 @@
1
- {"version":3,"file":"stored-entity.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/stored-entity.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;AAEhF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;CAC9C,CAAC;AAGF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EACjC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,GACX,kBAAkB,CAsBpB"}
1
+ {"version":3,"file":"stored-entity.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/stored-entity.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;AAGhF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7C;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;CACvE,CAAC;AAGF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EACjC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,GACX,kBAAkB,CAsBpB"}
@@ -0,0 +1,43 @@
1
+ import type { OperationContext } from "../../public/operations/runtime-types.js";
2
+ import type { StoredEntityLookup } from "./stored-entity.js";
3
+ import type { RelationshipInput, UpsertInput } from "./types.js";
4
+ /**
5
+ * An authored document for an entity the compiled store does not hold: the
6
+ * file the source-first writer would merge an upsert into.
7
+ */
8
+ export type UncompiledAuthoredSource = Readonly<{
9
+ /** Workspace-relative path. */
10
+ path: string;
11
+ absolute: string;
12
+ content: string;
13
+ /**
14
+ * The entity as its file states it: the extracted entity when the file
15
+ * compiles, its front matter when it does not.
16
+ */
17
+ stored: Readonly<Record<string, unknown>>;
18
+ /** Why kibi sync cannot compile the file as authored; null when it can. */
19
+ extractionError: string | null;
20
+ }>;
21
+ /**
22
+ * The authored document an upsert of `input` would merge into although the
23
+ * store does not hold the entity: a file left out of the last sync (one sync
24
+ * could not extract, or one written since). Null when the writer would
25
+ * create a new file, writes a symbol manifest, or the file declares another
26
+ * entity. A file whose front matter does not parse is refused here: the
27
+ * writer could not merge into it either.
28
+ */
29
+ export declare function readUncompiledAuthoredSource(context: OperationContext, input: UpsertInput): Promise<UncompiledAuthoredSource | null>;
30
+ /**
31
+ * The store lookup for an upsert whose entity exists only in its authored
32
+ * file: the file's own state is what the write changes, so the stored
33
+ * ledger, origin and firmness checks read it.
34
+ */
35
+ export declare function authoredEntityLookup(source: UncompiledAuthoredSource): StoredEntityLookup;
36
+ /**
37
+ * Render the document the writer will leave and extract it as kibi sync
38
+ * will. A write that leaves the file uncompilable is refused, naming the
39
+ * file and the error, because the entity would stay invisible to every
40
+ * store-backed tool. Returns the relationships the written file declares.
41
+ */
42
+ export declare function assertAuthoredWriteCompiles(source: UncompiledAuthoredSource, input: UpsertInput, entity: Readonly<Record<string, unknown>>): readonly RelationshipInput[];
43
+ //# sourceMappingURL=uncompiled-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uncompiled-source.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/uncompiled-source.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAMjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEjE;;;GAGG;AAEH,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1C,2EAA2E;IAC3E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC,CAAC;AAMH;;;;;;;GAOG;AAEH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CA8C1C;AAED;;;;GAIG;AAEH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,wBAAwB,GAC/B,kBAAkB,CAKpB;AAED;;;;;GAKG;AAEH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,wBAAwB,EAChC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxC,SAAS,iBAAiB,EAAE,CAa9B"}
@@ -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;AA6B3E,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AAsEpB,wBAAgB,gCAAgC,CAC9C,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,GAChB,IAAI,CAkBN;AAwCD,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,aAAa,EAAE,SAAS,iBAAiB,EAAE,EAC3C,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CA8BvC;AAmCD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CACrC,CAAC,CAAC;AAGH,uFAAuF;AACvF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC,CAAC;AAGH;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,WAAW,GAAG;IACrB,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACtD,GACA,sBAAsB,CASxB;AAGD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;CAC3C,CAAC,CAAC;AAGH;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAkF3B;AAgDD,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C;;;;;;OAMG;IAEH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACxC,CAAC,CAAC;AAyBH,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CA2hBzC"}
1
+ {"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"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"}