@documentation-robotics/cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +10 -14
  2. package/dist/analyzers/active-analyzer.js +62 -0
  3. package/dist/analyzers/active-analyzer.js.map +7 -0
  4. package/dist/analyzers/cards/build.js +21 -1
  5. package/dist/analyzers/cards/build.js.map +2 -2
  6. package/dist/analyzers/cards/extract.js +138 -0
  7. package/dist/analyzers/cards/extract.js.map +7 -0
  8. package/dist/analyzers/cards/routes.js +96 -0
  9. package/dist/analyzers/cards/routes.js.map +7 -0
  10. package/dist/analyzers/cbm-analyzer.js +76 -4
  11. package/dist/analyzers/cbm-analyzer.js.map +2 -2
  12. package/dist/analyzers/cbm-responses.js +12 -6
  13. package/dist/analyzers/cbm-responses.js.map +2 -2
  14. package/dist/analyzers/code-changes.js +159 -0
  15. package/dist/analyzers/code-changes.js.map +7 -0
  16. package/dist/analyzers/edge-relationships.js +6 -2
  17. package/dist/analyzers/edge-relationships.js.map +2 -2
  18. package/dist/analyzers/extraction-plan.js +541 -0
  19. package/dist/analyzers/extraction-plan.js.map +7 -0
  20. package/dist/analyzers/index-job.js +229 -0
  21. package/dist/analyzers/index-job.js.map +7 -0
  22. package/dist/cli.js +12 -10
  23. package/dist/cli.js.map +2 -2
  24. package/dist/coding-agents/drbot-mcp.js +120 -18
  25. package/dist/coding-agents/drbot-mcp.js.map +2 -2
  26. package/dist/commands/add.js +4 -2
  27. package/dist/commands/add.js.map +2 -2
  28. package/dist/commands/analyzer.js +35 -35
  29. package/dist/commands/analyzer.js.map +2 -2
  30. package/dist/commands/changeset-verify.js +16 -8
  31. package/dist/commands/changeset-verify.js.map +2 -2
  32. package/dist/commands/changeset.js +41 -11
  33. package/dist/commands/changeset.js.map +2 -2
  34. package/dist/commands/init.js +9 -26
  35. package/dist/commands/init.js.map +2 -2
  36. package/dist/commands/mcp.js +35 -31
  37. package/dist/commands/mcp.js.map +3 -3
  38. package/dist/commands/relationship.js +2 -12
  39. package/dist/commands/relationship.js.map +2 -2
  40. package/dist/commands/reports.js +3 -3
  41. package/dist/commands/reports.js.map +2 -2
  42. package/dist/commands/update.js +14 -13
  43. package/dist/commands/update.js.map +2 -2
  44. package/dist/commands/upgrade.js +28 -120
  45. package/dist/commands/upgrade.js.map +2 -2
  46. package/dist/commands/validate.js +19 -3
  47. package/dist/commands/validate.js.map +2 -2
  48. package/dist/commands/version.js +1 -1
  49. package/dist/commands/visualize.js +30 -12
  50. package/dist/commands/visualize.js.map +2 -2
  51. package/dist/core/annotation-store.js +15 -5
  52. package/dist/core/annotation-store.js.map +2 -2
  53. package/dist/core/catalog-migration.js +2 -1
  54. package/dist/core/catalog-migration.js.map +2 -2
  55. package/dist/core/changeset-base-index.js +7 -0
  56. package/dist/core/changeset-base-index.js.map +2 -2
  57. package/dist/core/changeset-brief-compact.js +256 -0
  58. package/dist/core/changeset-brief-compact.js.map +7 -0
  59. package/dist/core/changeset-verify.js +356 -26
  60. package/dist/core/changeset-verify.js.map +3 -3
  61. package/dist/core/extraction-observability.js +161 -0
  62. package/dist/core/extraction-observability.js.map +7 -0
  63. package/dist/core/migration-registry.js +13 -1
  64. package/dist/core/migration-registry.js.map +2 -2
  65. package/dist/core/project-init.js +53 -0
  66. package/dist/core/project-init.js.map +7 -0
  67. package/dist/core/relationship-staging.js +94 -1
  68. package/dist/core/relationship-staging.js.map +2 -2
  69. package/dist/core/staged-changeset-storage.js +83 -3
  70. package/dist/core/staged-changeset-storage.js.map +2 -2
  71. package/dist/core/staging-area.js +85 -8
  72. package/dist/core/staging-area.js.map +2 -2
  73. package/dist/core/upgrade-plan.js +117 -0
  74. package/dist/core/upgrade-plan.js.map +7 -0
  75. package/dist/generated/relationship-index.js +124 -15
  76. package/dist/generated/relationship-index.js.map +2 -2
  77. package/dist/integrations/claude_code/.claude-plugin/plugin.json +24 -0
  78. package/dist/integrations/claude_code/agents/dr-architect.md +19 -5
  79. package/dist/integrations/claude_code/agents/dr-extractor.md +7 -1
  80. package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
  81. package/dist/integrations/claude_code/commands/dr-map.md +7 -1
  82. package/dist/integrations/claude_code/commands/dr-plan.md +42 -16
  83. package/dist/integrations/claude_code/docs/MCP_SERVER.md +28 -31
  84. package/dist/integrations/claude_code/docs/README.md +20 -1
  85. package/dist/integrations/claude_code/plugin-agents/dr-advisor.md +255 -0
  86. package/dist/integrations/claude_code/plugin-agents/dr-architect.md +306 -0
  87. package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +3 -0
  88. package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +90 -31
  89. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +5 -0
  90. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +5 -0
  91. package/dist/integrations/github_copilot/agents/dr-architect.md +2 -0
  92. package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +3 -0
  93. package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +88 -30
  94. package/dist/mcp/changeset-only.js +14 -3
  95. package/dist/mcp/changeset-only.js.map +2 -2
  96. package/dist/mcp/project-root.js +85 -0
  97. package/dist/mcp/project-root.js.map +7 -0
  98. package/dist/mcp/server-instructions.js +27 -0
  99. package/dist/mcp/server-instructions.js.map +7 -0
  100. package/dist/mcp/strict-schema.js +52 -0
  101. package/dist/mcp/strict-schema.js.map +7 -0
  102. package/dist/mcp/tool-registry.js +39 -5
  103. package/dist/mcp/tool-registry.js.map +2 -2
  104. package/dist/mcp/tools/changeset-analysis-tools.js +25 -5
  105. package/dist/mcp/tools/changeset-analysis-tools.js.map +3 -3
  106. package/dist/mcp/tools/changeset-commit.js +224 -0
  107. package/dist/mcp/tools/changeset-commit.js.map +7 -0
  108. package/dist/mcp/tools/changeset-editing.js +133 -57
  109. package/dist/mcp/tools/changeset-editing.js.map +3 -3
  110. package/dist/mcp/tools/changeset-lifecycle.js +1 -1
  111. package/dist/mcp/tools/changeset-lifecycle.js.map +2 -2
  112. package/dist/mcp/tools/changeset-verify.js +12 -6
  113. package/dist/mcp/tools/changeset-verify.js.map +2 -2
  114. package/dist/mcp/tools/codebase-extract.js +836 -0
  115. package/dist/mcp/tools/codebase-extract.js.map +7 -0
  116. package/dist/mcp/tools/codebase-index.js +125 -0
  117. package/dist/mcp/tools/codebase-index.js.map +7 -0
  118. package/dist/mcp/tools/codebase-shared.js +265 -0
  119. package/dist/mcp/tools/codebase-shared.js.map +7 -0
  120. package/dist/mcp/tools/codebase-verify.js +48 -0
  121. package/dist/mcp/tools/codebase-verify.js.map +7 -0
  122. package/dist/mcp/tools/index.js +15 -0
  123. package/dist/mcp/tools/index.js.map +2 -2
  124. package/dist/mcp/tools/model-add.js +189 -73
  125. package/dist/mcp/tools/model-add.js.map +2 -2
  126. package/dist/mcp/tools/model-update.js +16 -12
  127. package/dist/mcp/tools/model-update.js.map +2 -2
  128. package/dist/mcp/tools/model-validate.js +46 -4
  129. package/dist/mcp/tools/model-validate.js.map +2 -2
  130. package/dist/mcp/tools/project-status-collectors.js +381 -0
  131. package/dist/mcp/tools/project-status-collectors.js.map +7 -0
  132. package/dist/mcp/tools/project-status.js +237 -0
  133. package/dist/mcp/tools/project-status.js.map +7 -0
  134. package/dist/mcp/tools/project-tools.js +505 -0
  135. package/dist/mcp/tools/project-tools.js.map +7 -0
  136. package/dist/mcp/tools/relationship-tools.js +89 -20
  137. package/dist/mcp/tools/relationship-tools.js.map +2 -2
  138. package/dist/mcp/tools/review-open.js +210 -0
  139. package/dist/mcp/tools/review-open.js.map +7 -0
  140. package/dist/mcp/tools/shared.js +10 -2
  141. package/dist/mcp/tools/shared.js.map +2 -2
  142. package/dist/reports/model-report-orchestrator.js +31 -2
  143. package/dist/reports/model-report-orchestrator.js.map +2 -2
  144. package/dist/schemas/bundled/analyzers/cbm.json +22 -10
  145. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  146. package/dist/schemas/bundled/api.json +1 -1
  147. package/dist/schemas/bundled/apm.json +1 -1
  148. package/dist/schemas/bundled/application.json +1 -1
  149. package/dist/schemas/bundled/base.json +1 -1
  150. package/dist/schemas/bundled/business.json +1 -1
  151. package/dist/schemas/bundled/data-model.json +1 -1
  152. package/dist/schemas/bundled/data-store.json +1 -1
  153. package/dist/schemas/bundled/manifest.json +3 -3
  154. package/dist/schemas/bundled/motivation.json +1 -1
  155. package/dist/schemas/bundled/navigation.json +1 -1
  156. package/dist/schemas/bundled/product.json +1 -1
  157. package/dist/schemas/bundled/security.json +1 -1
  158. package/dist/schemas/bundled/technology.json +1 -1
  159. package/dist/schemas/bundled/testing.json +145 -1
  160. package/dist/schemas/bundled/ux.json +1 -1
  161. package/dist/server/server.js +508 -237
  162. package/dist/server/server.js.map +3 -3
  163. package/dist/server/viewer-registry.js +264 -0
  164. package/dist/server/viewer-registry.js.map +7 -0
  165. package/dist/telemetry/index.js +1 -1
  166. package/dist/utils/id-generator.js +5 -2
  167. package/dist/utils/id-generator.js.map +2 -2
  168. package/dist/utils/source-reference.js +58 -27
  169. package/dist/utils/source-reference.js.map +2 -2
  170. package/dist/utils/spec-version.js +2 -2
  171. package/dist/utils/spec-version.js.map +1 -1
  172. package/dist/validators/model-warnings.js +28 -0
  173. package/dist/validators/model-warnings.js.map +7 -0
  174. package/dist/viewer/README.md +6 -6
  175. package/dist/viewer/assets/main-CUOQGGRA.css +1 -0
  176. package/dist/viewer/assets/main-DvH3gYrI.js +84 -0
  177. package/dist/viewer/index.html +2 -2
  178. package/dist/viewer/manifest.json +10 -10
  179. package/dist/viewer/version.json +1 -1
  180. package/package.json +1 -1
  181. package/dist/viewer/assets/main-CxzcuU_o.js +0 -84
  182. package/dist/viewer/assets/main-j-7ULYpm.css +0 -1
@@ -0,0 +1,541 @@
1
+ import path from "node:path";
2
+ import { toKebabCase } from "../utils/id-generator.js";
3
+ import { changedInRange } from "./code-changes.js";
4
+ import { dedupeRelationships, relationshipFor } from "./edge-relationships.js";
5
+ import { FILE_JOINED_EDGES, parseRouteName, shortQualifiedName, symbolSpellings } from "./cards/extract.js";
6
+ const REFERENCE_EDGES = /* @__PURE__ */ new Set(["USAGE", "IMPORTS", "CALLS"]);
7
+ function refersToClass(refs, cls) {
8
+ if (!refs)
9
+ return !1;
10
+ const simple = cls.slice(cls.lastIndexOf(".") + 1);
11
+ return refs.has(cls) || refs.has(`${cls}.${simple}`) || refs.has(`${cls}.constructor`) || refs.has(`${cls}.__init__`);
12
+ }
13
+ function decisionKey(file, symbol) {
14
+ return `${file ?? ""}#${routeKey(symbol) ?? symbol}`;
15
+ }
16
+ function routePathOf(s, route) {
17
+ return s.routePath ?? route.path;
18
+ }
19
+ function sourceSymbolOf(s, project) {
20
+ const { card } = s, route = parseRouteName(card.name);
21
+ if (route)
22
+ return `${route.method} ${routePathOf(s, route)}`;
23
+ if (card.label === "Module")
24
+ return shortQualifiedName(s.qualifiedName, project);
25
+ if (card.label === "Method" && card.parent_class) {
26
+ const parent = card.parent_class.split(".").pop();
27
+ if (parent)
28
+ return `${parent}.${card.name}`;
29
+ }
30
+ return card.name;
31
+ }
32
+ function baseNameOf(s, project) {
33
+ const route = parseRouteName(s.card.name);
34
+ return route ? `${route.method} ${routePathOf(s, route).replace(/\{\}/g, "{param}")}` : s.card.label === "Module" ? (s.card.file_path || s.card.name).replace(/\.[^./]+$/, "") : sourceSymbolOf(s, project);
35
+ }
36
+ function modelAddName(name) {
37
+ const root = /^\s*([A-Z]+)\s+\/\s*$/.exec(name), out = root ? `${root[1]} root` : /[/{}]/.test(name) ? name.replace(/[./{}]+/g, " ").replace(/\s+/g, " ").trim() : name.replace(/([\w$)])\.(?=[A-Za-z_$])/g, "$1 ");
38
+ return out !== name ? out : void 0;
39
+ }
40
+ function extractedElementId(layer, type, name) {
41
+ const root = /^\s*([A-Z]+)\s+\/\s*$/.exec(name);
42
+ return `${layer}.${type}.${toKebabCase(root ? `${root[1]} root` : name.replace(/[./{}]+/g, " "))}`;
43
+ }
44
+ function describeDocstring(doc) {
45
+ if (!doc)
46
+ return;
47
+ const text = doc.replace(/^\s*\/\*\*?/, "").replace(/\*\/\s*$/, "").split(`
48
+ `).map((l) => l.replace(/^\s*(\*|\/\/\/?|#)\s?/, "").trim()).join(`
49
+ `).replace(/^["']{3}|["']{3}$/g, "").trim().split(/\n\s*\n/)[0].replace(/\s+/g, " ").trim();
50
+ if (!(!text || text.startsWith("@")))
51
+ return text.length > 280 ? `${text.slice(0, 277)}...` : text;
52
+ }
53
+ function deriveAttributes(layer, type, s) {
54
+ const key = `${layer}.${type}`, route = parseRouteName(s.card.name);
55
+ switch (key) {
56
+ case "api.operation": {
57
+ if (!route)
58
+ return { facts: {}, defaults: {} };
59
+ const p = routePathOf(s, route), segment = p.split("/").find((x) => x && !x.startsWith("{") && !x.startsWith(":")) ?? "default", words = p.replace(/[/{}:]+/g, " ").trim();
60
+ return {
61
+ facts: { http_method: route.method, http_path: p },
62
+ defaults: {
63
+ operationId: toKebabCase(`${route.method} ${words || "root"}`),
64
+ summary: `${route.method} ${p}`,
65
+ tags: segment
66
+ }
67
+ };
68
+ }
69
+ case "testing.testcasesketch":
70
+ return { facts: {}, defaults: { status: "automated" } };
71
+ case "application.applicationinterface":
72
+ return { facts: {}, defaults: { protocol: s.facts.decorator_names.includes("RestController") ? "REST" : "HTTP" } };
73
+ case "application.applicationcomponent":
74
+ return { facts: {}, defaults: { type: "internal" } };
75
+ case "application.applicationservice":
76
+ return { facts: {}, defaults: { serviceType: "synchronous" } };
77
+ case "data-model.objectschema":
78
+ return { facts: {}, defaults: { type: "object" } };
79
+ case "data-store.accesspattern": {
80
+ const entity = /Repository\s*<\s*([\w.]+)/.exec(s.card.source ?? s.card.signature ?? "")?.[1];
81
+ return { facts: {}, defaults: { patternType: "CUSTOM", ...entity ? { targetCollection: entity.split(".").pop() } : {} } };
82
+ }
83
+ default:
84
+ return { facts: {}, defaults: {} };
85
+ }
86
+ }
87
+ function codeLocations(e) {
88
+ return (e.sourceReference?.locations ?? []).filter((l) => typeof l.file == "string" && l.file).map((l) => ({ file: l.file.replace(/\\/g, "/").replace(/^\.\//, ""), ...l.symbol ? { symbol: l.symbol } : {} }));
89
+ }
90
+ function normPath(p) {
91
+ return p.replace(/\{[^}]*\}|:[A-Za-z_]\w*/g, "{}").replace(/\/+$/, "") || "/";
92
+ }
93
+ function indexExisting(graph) {
94
+ const bySymbol = /* @__PURE__ */ new Map(), byRouteSymbol = /* @__PURE__ */ new Map(), byRouteSymbolAnyFile = /* @__PURE__ */ new Map(), byRoute = /* @__PURE__ */ new Map(), byId = /* @__PURE__ */ new Map(), live = [], push = (m, k, e) => (m.get(k) ?? m.set(k, []).get(k)).push(e);
95
+ for (const e of graph.elements.values()) {
96
+ if (byId.set(e.id, e), e.status === "deleted")
97
+ continue;
98
+ live.push(e);
99
+ const locations = codeLocations(e);
100
+ for (const loc of locations) {
101
+ if (!loc.symbol)
102
+ continue;
103
+ push(bySymbol, `${loc.file}#${loc.symbol}`, e);
104
+ const route = routeKey(loc.symbol);
105
+ route && (push(byRouteSymbol, `${loc.file}#${route}`, e), push(byRouteSymbolAnyFile, route, e));
106
+ }
107
+ if (e.layer === "api" && e.type === "operation" && !locations.length) {
108
+ const method = e.attributes.http_method, p = e.attributes.http_path;
109
+ typeof method == "string" && typeof p == "string" && push(byRoute, `${method.toUpperCase()} ${normPath(p)}`, e);
110
+ }
111
+ }
112
+ return { bySymbol, byRouteSymbol, byRouteSymbolAnyFile, byRoute, byId, live };
113
+ }
114
+ function findBySymbol(index, file, spellings) {
115
+ for (const s of spellings)
116
+ if (file) {
117
+ const hit = index.bySymbol.get(`${file}#${s}`);
118
+ if (hit?.length)
119
+ return hit[0];
120
+ }
121
+ }
122
+ function hasSymbol(spellings, symbol) {
123
+ return !!spellings && spellings.has(symbol);
124
+ }
125
+ const LABEL_ORDER = ["Route", "Class", "Interface", "Type", "Enum", "Trait", "Struct", "Function", "Method", "Module"], KIND_ORDER = ["needs-attributes", "ambiguous", "narrowed", "no-rule", "unhandled-route"];
126
+ function inLayers(layer, layers) {
127
+ return !layers || layers.has(layer);
128
+ }
129
+ function sortCards(cards) {
130
+ const rank = (l) => {
131
+ const i = LABEL_ORDER.indexOf(l);
132
+ return i < 0 ? LABEL_ORDER.length : i;
133
+ };
134
+ return cards.sort((a, b) => (a.changedInRange ? 0 : 1) - (b.changedInRange ? 0 : 1) || KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind) || rank(a.label) - rank(b.label) || (a.file ?? "").localeCompare(b.file ?? "") || a.qualifiedName.localeCompare(b.qualifiedName));
135
+ }
136
+ function cardOf(s, project, kind, reason, extra = {}, hunks, renamedTo) {
137
+ const { card } = s, doc = describeDocstring(card.docstring), name = modelAddName(baseNameOf(s, project)), ranges = hunks && card.file_path ? hunks.get(card.file_path) : void 0, changed = card.label === "Module" ? !!hunks && !!card.file_path && (ranges === "all" || !!renamedTo?.has(card.file_path)) : changedInRange(ranges, card.start_line, card.end_line);
138
+ return {
139
+ cardId: card.id,
140
+ kind,
141
+ label: card.label,
142
+ symbol: sourceSymbolOf(s, project),
143
+ ...changed ? { changedInRange: !0 } : {},
144
+ ...name ? { name } : {},
145
+ file: card.file_path,
146
+ lines: card.start_line !== null ? `${card.start_line}-${card.end_line ?? card.start_line}` : null,
147
+ qualifiedName: card.qualified_name,
148
+ ...card.signature ? { signature: card.signature.slice(0, 200) } : {},
149
+ ...doc ? { docstring: doc } : {},
150
+ ...s.facts.decorator_names.length ? { decorators: s.facts.decorator_names } : {},
151
+ candidates: [],
152
+ reason,
153
+ ...extra
154
+ };
155
+ }
156
+ function planElements(extraction, existing, options) {
157
+ const { project, config } = extraction, layers = options.layers?.length ? new Set(options.layers) : null, index = indexExisting(existing), taken = new Set(index.byId.keys()), plan = {
158
+ adds: [],
159
+ updates: [],
160
+ unchanged: 0,
161
+ modelled: 0,
162
+ rejected: 0,
163
+ skipped: 0,
164
+ outsideLayers: 0,
165
+ retyped: [],
166
+ undecided: [],
167
+ candidateDeletes: [],
168
+ elementOf: /* @__PURE__ */ new Map(),
169
+ descriptions: {}
170
+ }, matched = /* @__PURE__ */ new Set(), hunks = options.changes?.hunks, renamedTo = renameTargets(options.changes), cardFor = (s, kind, reason, extra = {}) => cardOf(s, project, kind, reason, extra, hunks, renamedTo), notElements = options.notElements ?? /* @__PURE__ */ new Set(), symbols = [...extraction.symbols].sort((a, b) => (a.card.file_path ?? "").localeCompare(b.card.file_path ?? "") || a.qualifiedName.localeCompare(b.qualifiedName)), claimed = /* @__PURE__ */ new Set(), matchOf = /* @__PURE__ */ new Map(), ambiguousWith = /* @__PURE__ */ new Map(), pick = (candidates, prefer) => {
171
+ if (!candidates?.length)
172
+ return;
173
+ const free = candidates.filter((e) => !claimed.has(e.id));
174
+ return free.length ? (prefer && free.find(prefer)) ?? free[0] : "claimed";
175
+ }, tryLookups = (s, lookups) => {
176
+ for (const lookup of lookups) {
177
+ const hit = lookup();
178
+ if (hit === "claimed") {
179
+ const owner = lookupOwner(s);
180
+ owner && ambiguousWith.set(s, owner);
181
+ continue;
182
+ }
183
+ if (hit) {
184
+ claimed.add(hit.id), matchOf.set(s, hit), ambiguousWith.delete(s);
185
+ return;
186
+ }
187
+ }
188
+ }, lookupOwner = (s) => {
189
+ const file = s.card.file_path, route = parseRouteName(s.card.name);
190
+ return (route ? [file ? index.byRouteSymbol.get(`${file}#${routeKey(s.card.name)}`) : index.byRouteSymbolAnyFile.get(routeKey(s.card.name)), index.byRoute.get(`${route.method} ${normPath(route.path)}`)] : [file ? index.bySymbol.get(`${file}#${s.card.name}`) : void 0]).find((k) => k?.length)?.[0]?.id;
191
+ }, strongSpellings = (s) => {
192
+ const symbol = sourceSymbolOf(s, project);
193
+ return [symbol, ...symbolSpellings(s.qualifiedName, s.card.name, project)].filter((sp, i, all) => all.indexOf(sp) === i && (sp === symbol || sp !== s.card.name));
194
+ };
195
+ for (const s of symbols) {
196
+ const file = s.card.file_path;
197
+ file && tryLookups(s, strongSpellings(s).map((sp) => () => pick(index.bySymbol.get(`${file}#${sp}`))));
198
+ }
199
+ for (const s of symbols) {
200
+ if (matchOf.has(s))
201
+ continue;
202
+ const { card, tier0 } = s, file = card.file_path, route = parseRouteName(card.name), lookups = [];
203
+ if (route) {
204
+ const key = routeKey(card.name), exact = (e) => e.attributes.http_path === route.path;
205
+ lookups.push(() => pick(file ? index.byRouteSymbol.get(`${file}#${key}`) : index.byRouteSymbolAnyFile.get(key))), lookups.push(() => pick(index.byRoute.get(`${route.method} ${normPath(route.path)}`), exact));
206
+ } else file && sourceSymbolOf(s, project) !== card.name && lookups.push(() => pick(index.bySymbol.get(`${file}#${card.name}`)));
207
+ if (tier0?.kind === "decide") {
208
+ const decided = tier0;
209
+ lookups.push(() => {
210
+ const byId = index.byId.get(extractedElementId(decided.layer, decided.type, baseNameOf(s, project)));
211
+ if (!(!byId || byId.status === "deleted" || byId.layer !== decided.layer || byId.type !== decided.type || codeLocations(byId).length))
212
+ return pick([byId]);
213
+ });
214
+ }
215
+ tryLookups(s, lookups);
216
+ }
217
+ for (const s of symbols) {
218
+ const { card, tier0 } = s, file = card.file_path, symbol = sourceSymbolOf(s, project), decided = tier0?.kind === "decide" ? tier0 : null, spellings = [symbol, ...symbolSpellings(s.qualifiedName, card.name, project)], match = matchOf.get(s);
219
+ if (!match && notElements.has(decisionKey(file, symbol))) {
220
+ plan.skipped++;
221
+ continue;
222
+ }
223
+ const ambiguous = !match && tier0?.kind !== "reject" ? ambiguousWith.get(s) : void 0;
224
+ if (ambiguous) {
225
+ if (decided && !inLayers(decided.layer, layers)) {
226
+ plan.outsideLayers++;
227
+ continue;
228
+ }
229
+ plan.undecided.push(cardFor(s, "ambiguous", `Its name also matches ${ambiguous}, which another symbol already matched. If it is a separate element, stage it under its own symbol; if it is that one, add this location to it.`, {
230
+ candidates: decided ? [`${decided.layer}.${decided.type}`] : tier0?.kind === "narrow" ? tier0.candidates.map((c) => `${c.layer}.${c.type ?? "*"}`) : [],
231
+ ...tier0 && tier0.kind !== "reject" ? { rule: tier0.rule } : {}
232
+ }));
233
+ continue;
234
+ }
235
+ if (match) {
236
+ if (matched.add(match.id), plan.elementOf.set(s.qualifiedName, match.id), !decided) {
237
+ plan.modelled++;
238
+ continue;
239
+ }
240
+ if (match.layer !== decided.layer || match.type !== decided.type) {
241
+ plan.retyped.push({ id: match.id, currentType: `${match.layer}.${match.type}`, decidedType: `${decided.layer}.${decided.type}`, rule: decided.rule, file, symbol });
242
+ continue;
243
+ }
244
+ if (!inLayers(decided.layer, layers)) {
245
+ plan.outsideLayers++;
246
+ continue;
247
+ }
248
+ const fields = changedFields(match, s, symbol, spellings, decided.layer, decided.type, options.extractedDescriptions ?? {});
249
+ typeof fields.description == "string" && (plan.descriptions[match.id] = fields.description), Object.keys(fields).length ? plan.updates.push({ id: match.id, layer: match.layer, fields, symbol: s }) : plan.unchanged++;
250
+ continue;
251
+ }
252
+ if (s.unhandled) {
253
+ if (layers && !layers.has("api")) {
254
+ plan.outsideLayers++;
255
+ continue;
256
+ }
257
+ plan.undecided.push(cardFor(s, "unhandled-route", "Nothing in this code handles this route (no handler, no file of its own): it is most likely a URL that tests or client code call. Model it as an api.operation only if this codebase serves it; otherwise reject it.", { candidates: ["api.operation"] }));
258
+ continue;
259
+ }
260
+ if (!tier0 || tier0.kind === "narrow") {
261
+ const candidates = tier0 ? tier0.candidates.map((c) => `${c.layer}.${c.type ?? "*"}`) : [], hint = config.labelDefaults[card.label];
262
+ if (!(layers ? tier0 ? tier0.candidates.some((c) => layers.has(c.layer)) : !!hint && layers.has(hint.split(".")[0]) : !0)) {
263
+ plan.outsideLayers++;
264
+ continue;
265
+ }
266
+ plan.undecided.push(tier0 ? cardFor(s, "narrowed", `Tier 0 rule ${tier0.rule} narrows it to these types; choosing needs judgment (Tier 1 is not wired).`, {
267
+ candidates: candidates.filter((c) => !layers || layers.has(c.split(".")[0])),
268
+ rule: tier0.rule
269
+ }) : cardFor(s, "no-rule", `No Tier-0 rule covers this ${card.label}: decide whether it is a model element, and which.`, {
270
+ ...hint ? { hint } : {}
271
+ }));
272
+ continue;
273
+ }
274
+ if (tier0.kind === "reject") {
275
+ plan.rejected++;
276
+ continue;
277
+ }
278
+ if (!inLayers(tier0.layer, layers)) {
279
+ plan.outsideLayers++;
280
+ continue;
281
+ }
282
+ const { facts, defaults } = deriveAttributes(tier0.layer, tier0.type, s), base = baseNameOf(s, project), name = uniqueName(base, s, tier0.layer, tier0.type, taken), id = extractedElementId(tier0.layer, tier0.type, name);
283
+ taken.add(id);
284
+ const description = describeDocstring(card.docstring);
285
+ description && (plan.descriptions[id] = description), plan.adds.push({
286
+ id,
287
+ layer: tier0.layer,
288
+ type: tier0.type,
289
+ name,
290
+ ...description ? { description } : {},
291
+ attributes: { ...defaults, ...facts },
292
+ ...file ? { sourceReference: { provenance: "extracted", locations: [{ file, symbol }] } } : {},
293
+ symbol: s,
294
+ rule: tier0.rule
295
+ }), plan.elementOf.set(s.qualifiedName, id);
296
+ }
297
+ return plan.candidateDeletes = candidateDeletes(extraction, index, matched, plan.adds, options, layers), plan.undecided = sortCards(plan.undecided), plan;
298
+ }
299
+ function renameTargets(changes) {
300
+ return new Set([...changes?.deleted.values() ?? []].filter((to) => !!to));
301
+ }
302
+ function needsAttributesCard(add, project, errors, hunks) {
303
+ return cardOf(add.symbol, project, "needs-attributes", `Tier 0 decided ${add.layer}.${add.type} (${add.rule}), but its attributes don't validate: ${errors.map((e) => e.replace(/^Element '[^']*':\s*/, "")).join("; ")}. Stage it with valid attributes (model_add with proposed.name gives it proposed.id), or reject it if it is not an element.`, {
304
+ candidates: [`${add.layer}.${add.type}`],
305
+ rule: add.rule,
306
+ proposed: { layer: add.layer, type: add.type, name: modelAddName(add.name) ?? add.name, id: add.id, attributes: add.attributes }
307
+ }, hunks);
308
+ }
309
+ function lineSpan(card) {
310
+ const m = /^(\d+)-(\d+)$/.exec(card.lines ?? "");
311
+ return m ? [Number(m[1]), Number(m[2])] : null;
312
+ }
313
+ function attributeChangedLines(cards, hunks) {
314
+ if (!hunks)
315
+ return cards;
316
+ const flagged = cards.filter((c) => c.changedInRange && c.file);
317
+ return cards.map((card) => {
318
+ if (!card.changedInRange || !card.file || card.label === "Module")
319
+ return card;
320
+ const ranges = hunks.get(card.file), own = lineSpan(card);
321
+ if (!ranges || ranges === "all" || !own)
322
+ return card;
323
+ const members = flagged.filter((m) => m !== card && m.file === card.file && m.qualifiedName.startsWith(card.qualifiedName + ".")).map(lineSpan).filter((span) => span !== null);
324
+ if (!members.length)
325
+ return card;
326
+ const inMember = (line) => members.some(([a, b]) => line >= a && line <= b);
327
+ if (ranges.some(([a, b, deletion]) => {
328
+ if (deletion)
329
+ return a >= own[0] && a < own[1] && !members.some(([m0, m1]) => a >= m0 && a < m1);
330
+ for (let line = Math.max(a, own[0]); line <= Math.min(b, own[1]); line++)
331
+ if (!inMember(line))
332
+ return !0;
333
+ return !1;
334
+ }))
335
+ return card;
336
+ const { changedInRange: _changed, ...rest } = card;
337
+ return rest;
338
+ });
339
+ }
340
+ function sortUndecided(cards) {
341
+ return sortCards(cards);
342
+ }
343
+ function uniqueName(base, s, layer, type, taken) {
344
+ const file = s.card.file_path ?? "", stem = path.posix.basename(file).replace(/\.[^.]+$/, ""), dir = path.posix.dirname(file), options = [base, `${stem} ${base}`, `${dir}/${stem} ${base}`, s.card.qualified_name];
345
+ for (const name of options)
346
+ if (!taken.has(extractedElementId(layer, type, name)))
347
+ return name;
348
+ for (let i = 2; ; i++) {
349
+ const name = `${s.card.qualified_name} ${i}`;
350
+ if (!taken.has(extractedElementId(layer, type, name)))
351
+ return name;
352
+ }
353
+ }
354
+ function changedFields(match, s, symbol, spellings, layer, type, extractedDescriptions) {
355
+ const fields = {}, file = s.card.file_path, locations = codeLocations(match), route = routeKey(s.card.name), names = (l) => !!l.symbol && (spellings.includes(l.symbol) || !!route && routeKey(l.symbol) === route);
356
+ if (file && !locations.some((l) => l.file === file && names(l))) {
357
+ const ref = match.sourceReference;
358
+ fields.source_reference = {
359
+ provenance: ref?.provenance ?? "extracted",
360
+ locations: [...ref?.locations ?? [], { file, symbol }],
361
+ ...ref?.repository ? { repository: ref.repository } : {}
362
+ };
363
+ }
364
+ const { facts } = deriveAttributes(layer, type, s);
365
+ for (const [k, v] of Object.entries(facts))
366
+ sameFact(k, match.attributes[k], v) || (fields[`attributes.${k}`] = v);
367
+ const description = describeDocstring(s.card.docstring), current = match.description ?? "";
368
+ return description && current !== description && (!current || current === extractedDescriptions[match.id]) && (fields.description = description), fields;
369
+ }
370
+ function sameFact(key, current, fromCode) {
371
+ if (typeof current == "string" && typeof fromCode == "string") {
372
+ if (key === "http_path")
373
+ return normPath(current) === normPath(fromCode);
374
+ if (key === "http_method")
375
+ return current.toUpperCase() === fromCode.toUpperCase();
376
+ }
377
+ return current === fromCode;
378
+ }
379
+ function candidateDeletes(extraction, index, matched, adds, options, layers) {
380
+ const routes = new Set(extraction.routes.map(routeKey).filter((k) => !!k)), fileSymbols = new Map(Object.entries(extraction.fileSymbols).map(([f, s]) => [f, new Set(s)])), addAt = /* @__PURE__ */ new Map();
381
+ for (const a of adds)
382
+ for (const l of a.sourceReference?.locations ?? [])
383
+ addAt.set(`${l.file}#${l.symbol ?? ""}`, a.id);
384
+ const out = [];
385
+ for (const e of index.live) {
386
+ if (matched.has(e.id) || !inLayers(e.layer, layers))
387
+ continue;
388
+ const locations = codeLocations(e);
389
+ if (!locations.length)
390
+ continue;
391
+ let gone = null, present = !1;
392
+ for (const loc of locations) {
393
+ const verdict = judgeLocation(loc, options, fileSymbols, routes, addAt);
394
+ verdict ? gone ??= verdict : present = !0;
395
+ }
396
+ gone && !present && out.push({ id: e.id, layer: e.layer, type: e.type, ...gone });
397
+ }
398
+ return out.sort((a, b) => a.id.localeCompare(b.id));
399
+ }
400
+ function judgeLocation(loc, options, fileSymbols, routes, addAt) {
401
+ const base = { file: loc.file, ...loc.symbol ? { symbol: loc.symbol } : {} }, changes = options.changes;
402
+ if (changes) {
403
+ if (changes.deleted.has(loc.file)) {
404
+ const to = changes.deleted.get(loc.file);
405
+ if (to) {
406
+ const replacedBy = loc.symbol ? addAt.get(`${to}#${loc.symbol}`) : void 0;
407
+ return { ...base, evidence: "file-renamed", detail: `${loc.file} was renamed to ${to} in the range`, ...replacedBy ? { replacedBy } : {} };
408
+ }
409
+ return { ...base, evidence: "file-deleted", detail: `${loc.file} was deleted in the range` };
410
+ }
411
+ if (!changes.changed.has(loc.file))
412
+ return null;
413
+ } else if (!options.fileExists(loc.file))
414
+ return { ...base, evidence: "file-missing", detail: `${loc.file} does not exist in the codebase` };
415
+ if (!loc.symbol)
416
+ return null;
417
+ const route = routeKey(loc.symbol);
418
+ if (route)
419
+ return routes.has(route) ? null : { ...base, evidence: "symbol-missing", detail: `the code has no route ${loc.symbol}` };
420
+ if (!/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(loc.symbol))
421
+ return null;
422
+ const symbols = fileSymbols.get(loc.file);
423
+ if (hasSymbol(symbols, loc.symbol))
424
+ return null;
425
+ const last = loc.symbol.split(".").pop().replace(/\$/g, "\\$"), names = (text2) => new RegExp(`(^|[^\\w$])${last}($|[^\\w$])`).test(text2), text = options.fileText?.(loc.file) ?? null;
426
+ if (text === null) {
427
+ if (!symbols)
428
+ return null;
429
+ } else if (names(text))
430
+ return null;
431
+ if (changes?.fileAtBase) {
432
+ const oldPath = [...changes.deleted].find(([, to]) => to === loc.file)?.[0], before = changes.fileAtBase(oldPath ?? loc.file);
433
+ return before === void 0 ? { ...base, evidence: "symbol-missing", detail: `${loc.file} has no ${loc.symbol} (not among its symbols, and the name is not in the file); its text at the start of the range couldn't be read` } : before === null || !names(before) ? {
434
+ ...base,
435
+ evidence: "reference-never-matched",
436
+ detail: `The model's reference to ${loc.symbol} matches nothing in ${loc.file}, and ${before === null ? "the file didn't exist" : "the file didn't have it either"} at the start of the range: the range didn't remove it, the reference was wrong before it`
437
+ } : { ...base, evidence: "symbol-missing", detail: `${loc.file} had ${loc.symbol} at the start of the range and doesn't now (not among its symbols, and the name is not in the file)` };
438
+ }
439
+ return { ...base, evidence: "symbol-missing", detail: `${loc.file} has no ${loc.symbol} (not among its symbols, and the name is not in the file)` };
440
+ }
441
+ function routeKey(symbol) {
442
+ const parsed = parseRouteName(symbol);
443
+ if (parsed)
444
+ return `${parsed.method} ${normPath(parsed.path)}`;
445
+ const m = /^([A-Z]+) (\/\S*)$/.exec(symbol);
446
+ return m ? `${m[1]} ${normPath(m[2])}` : null;
447
+ }
448
+ function planRelationships(extraction, existing, elementOf, elements, hasTriple, layers) {
449
+ const { project } = extraction, index = indexExisting(existing), inScope = new Set(extraction.symbols.map((s) => s.qualifiedName)), moduleOf = /* @__PURE__ */ new Map();
450
+ for (const s of extraction.symbols) {
451
+ const id = elementOf.get(s.qualifiedName);
452
+ s.card.label === "Module" && s.card.file_path && id && moduleOf.set(s.card.file_path, id);
453
+ }
454
+ const moduleIds = new Set(moduleOf.values()), layerSet = layers?.length ? new Set(layers) : null, mappings = new Map(extraction.config.edgeMappings.map((m) => [m.cbm_edge, m])), referencedIn = /* @__PURE__ */ new Map();
455
+ for (const e of extraction.edges) {
456
+ if (!REFERENCE_EDGES.has(e.type) || !e.from.file)
457
+ continue;
458
+ let refs = referencedIn.get(e.from.file);
459
+ refs || referencedIn.set(e.from.file, refs = /* @__PURE__ */ new Set()), refs.add(e.to.qualifiedName);
460
+ }
461
+ const direct = (qualifiedName, name, file) => {
462
+ const own = elementOf.get(qualifiedName);
463
+ if (own)
464
+ return own;
465
+ if (inScope.has(qualifiedName))
466
+ return;
467
+ const route = parseRouteName(name) ?? parseRouteName(qualifiedName);
468
+ if (route) {
469
+ const key = `${route.method} ${normPath(route.path)}`;
470
+ return (index.byRouteSymbolAnyFile.get(key)?.[0] ?? index.byRoute.get(key)?.[0])?.id;
471
+ }
472
+ return findBySymbol(index, file, symbolSpellings(qualifiedName, name, project))?.id;
473
+ }, resolve = (end, fileJoined) => {
474
+ const own = direct(end.qualifiedName, end.name, end.file);
475
+ if (own)
476
+ return { id: own };
477
+ if (fileJoined)
478
+ return end.file && moduleOf.has(end.file) ? { id: moduleOf.get(end.file) } : void 0;
479
+ if (parseRouteName(end.name))
480
+ return;
481
+ const cut = end.qualifiedName.lastIndexOf("."), parent = cut > 0 ? end.qualifiedName.slice(0, cut) : "";
482
+ if (!parent || parent === project)
483
+ return;
484
+ const id = direct(parent, parent.slice(parent.lastIndexOf(".") + 1), end.file);
485
+ return id ? { id, rolledUp: parent } : void 0;
486
+ }, found = [];
487
+ for (const edge of extraction.edges) {
488
+ const m = mappings.get(edge.type);
489
+ if (!m)
490
+ continue;
491
+ const fileJoined = FILE_JOINED_EDGES.has(edge.type), ra = resolve(edge.from, fileJoined), rb = resolve(edge.to, fileJoined);
492
+ if (!ra || !rb || ra.rolledUp && edge.to.qualifiedName.startsWith(ra.rolledUp + ".") || rb.rolledUp && edge.from.qualifiedName.startsWith(rb.rolledUp + "."))
493
+ continue;
494
+ const a = ra.id, b = rb.id;
495
+ if (a === b)
496
+ continue;
497
+ const ea = elements.get(a), eb = elements.get(b);
498
+ if (!ea || !eb || ea.layer === "testing" && rb.rolledUp && !moduleIds.has(b) && !(edge.from.file && refersToClass(referencedIn.get(edge.from.file), rb.rolledUp)))
499
+ continue;
500
+ const rel = relationshipFor(m, `${ea.layer}.${ea.type}`, `${eb.layer}.${eb.type}`, hasTriple);
501
+ if (!rel)
502
+ continue;
503
+ const [sourceId, destinationId] = rel.inverted ? [b, a] : [a, b];
504
+ found.push({ sourceId, destinationId, relationship: rel, edge: edge.type });
505
+ }
506
+ const relationships = [];
507
+ let alreadyPresent = 0;
508
+ for (const r of dedupeRelationships(found)) {
509
+ const source = elements.get(r.sourceId), target = elements.get(r.destinationId);
510
+ if (!(layerSet && !layerSet.has(source.layer) && !layerSet.has(target.layer))) {
511
+ if (existing.getRelationship(r.sourceId, r.relationship.predicate, r.destinationId)) {
512
+ alreadyPresent++;
513
+ continue;
514
+ }
515
+ relationships.push({
516
+ source: r.sourceId,
517
+ predicate: r.relationship.predicate,
518
+ target: r.destinationId,
519
+ sourceLayer: source.layer,
520
+ targetLayer: target.layer,
521
+ edge: r.edge
522
+ });
523
+ }
524
+ }
525
+ return relationships.sort((x, y) => x.source.localeCompare(y.source) || x.predicate.localeCompare(y.predicate) || x.target.localeCompare(y.target)), { relationships, alreadyPresent };
526
+ }
527
+ export {
528
+ attributeChangedLines,
529
+ decisionKey,
530
+ deriveAttributes,
531
+ describeDocstring,
532
+ extractedElementId,
533
+ modelAddName,
534
+ needsAttributesCard,
535
+ planElements,
536
+ planRelationships,
537
+ sameFact,
538
+ sortUndecided,
539
+ sourceSymbolOf
540
+ };
541
+ //# sourceMappingURL=extraction-plan.js.map