@orangepro/orangepro-mcp 0.1.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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +328 -0
  3. package/dist/local/agentWorkflow.js +81 -0
  4. package/dist/local/aiGraph/links.js +635 -0
  5. package/dist/local/analyze/analyzer.js +2129 -0
  6. package/dist/local/analyze/behaviorContracts.js +169 -0
  7. package/dist/local/analyze/boilerplate.js +42 -0
  8. package/dist/local/analyze/callGraph.js +458 -0
  9. package/dist/local/analyze/classify.js +219 -0
  10. package/dist/local/analyze/clustering.js +357 -0
  11. package/dist/local/analyze/confirm.js +2422 -0
  12. package/dist/local/analyze/coverage.js +518 -0
  13. package/dist/local/analyze/coverageArtifacts.js +607 -0
  14. package/dist/local/analyze/frameworks.js +115 -0
  15. package/dist/local/analyze/linkage/conventions.js +160 -0
  16. package/dist/local/analyze/parseCache.js +164 -0
  17. package/dist/local/analyze/selfAssert.js +53 -0
  18. package/dist/local/analyze/symbols.js +430 -0
  19. package/dist/local/analyze/testLayer.js +135 -0
  20. package/dist/local/analyze/treeSitter/engine.js +1253 -0
  21. package/dist/local/analyze/treeSitter/languages.js +101 -0
  22. package/dist/local/autoProve.js +620 -0
  23. package/dist/local/cli.js +1468 -0
  24. package/dist/local/cliArgs.js +112 -0
  25. package/dist/local/corpusScope.js +162 -0
  26. package/dist/local/enrich/csv.js +348 -0
  27. package/dist/local/enrich/index.js +43 -0
  28. package/dist/local/enrich/markdown.js +193 -0
  29. package/dist/local/explain/explain.js +91 -0
  30. package/dist/local/exportCli.js +26 -0
  31. package/dist/local/flows/flowWalker.js +215 -0
  32. package/dist/local/flows/llmFlowDiscovery.js +567 -0
  33. package/dist/local/freshness/changed.js +280 -0
  34. package/dist/local/freshness/manifest.js +35 -0
  35. package/dist/local/freshness/status.js +30 -0
  36. package/dist/local/gaps/gaps.js +114 -0
  37. package/dist/local/generate/buckets.js +73 -0
  38. package/dist/local/generate/compareJudge.js +124 -0
  39. package/dist/local/generate/compareReport.js +538 -0
  40. package/dist/local/generate/compareScore.js +105 -0
  41. package/dist/local/generate/deriveImports.js +91 -0
  42. package/dist/local/generate/generator.js +2586 -0
  43. package/dist/local/generate/prompt.js +144 -0
  44. package/dist/local/generate/promptV5.js +438 -0
  45. package/dist/local/generate/providers.js +400 -0
  46. package/dist/local/generate/runHints.js +304 -0
  47. package/dist/local/graph/citations.js +73 -0
  48. package/dist/local/graph/confirmable.js +72 -0
  49. package/dist/local/graph/factories.js +210 -0
  50. package/dist/local/graph/ontology.js +18 -0
  51. package/dist/local/interactive.js +53 -0
  52. package/dist/local/jobs/jobStore.js +80 -0
  53. package/dist/local/jobs/notify.js +29 -0
  54. package/dist/local/jobs/runner.js +75 -0
  55. package/dist/local/ledger.js +117 -0
  56. package/dist/local/localConfig.js +112 -0
  57. package/dist/local/mcp.js +548 -0
  58. package/dist/local/operations.js +1749 -0
  59. package/dist/local/pack/coverageReport.js +192 -0
  60. package/dist/local/pack/exporter.js +195 -0
  61. package/dist/local/pack/schema.js +128 -0
  62. package/dist/local/pack/summary.js +127 -0
  63. package/dist/local/pack/validate.js +25 -0
  64. package/dist/local/proofRunnability.js +366 -0
  65. package/dist/local/recipe/dbSqljs.js +255 -0
  66. package/dist/local/reprove/paths.js +13 -0
  67. package/dist/local/reprove/scoped.js +136 -0
  68. package/dist/local/resolve/barrelWalker.js +178 -0
  69. package/dist/local/resolve/exportIndex.js +270 -0
  70. package/dist/local/resolve/importGraph.js +347 -0
  71. package/dist/local/resolve/resolver.js +122 -0
  72. package/dist/local/resolve/resolverCache.js +117 -0
  73. package/dist/local/rtm.js +413 -0
  74. package/dist/local/score/coverage.js +99 -0
  75. package/dist/local/score/doctor.js +67 -0
  76. package/dist/local/score/risk.js +362 -0
  77. package/dist/local/score/score.js +182 -0
  78. package/dist/local/types.js +1 -0
  79. package/dist/local/util/hash.js +16 -0
  80. package/dist/local/util/ids.js +16 -0
  81. package/dist/local/util/progress.js +8 -0
  82. package/dist/local/util/redact.js +39 -0
  83. package/dist/local/util/time.js +1 -0
  84. package/dist/local/util/walk.js +174 -0
  85. package/dist/local/viz/behaviorReportData.js +367 -0
  86. package/dist/local/viz/behaviorReportHtml.js +664 -0
  87. package/dist/local/viz/d3.bundle.js +3 -0
  88. package/dist/local/viz/html.js +1152 -0
  89. package/dist/local/viz/payload.js +525 -0
  90. package/dist/local/workspace.js +99 -0
  91. package/docs/agent-workflow.md +167 -0
  92. package/docs/agents/claude-code.md +43 -0
  93. package/docs/agents/codex.md +52 -0
  94. package/docs/agents/cursor.md +39 -0
  95. package/docs/agents/opencode.md +43 -0
  96. package/docs/agents/vscode.md +34 -0
  97. package/docs/local-proof-kit.md +269 -0
  98. package/package.json +92 -0
  99. package/scripts/spikes/dynamic-proof-jest-reporter.cjs +66 -0
  100. package/scripts/spikes/dynamic-proof-mocha-reporter.cjs +105 -0
  101. package/scripts/spikes/dynamic-proof-spike.mjs +2335 -0
  102. package/scripts/spikes/dynamic-proof-vitest-reporter.mjs +81 -0
  103. package/scripts/spikes/failure-summary.mjs +29 -0
@@ -0,0 +1,169 @@
1
+ import { slugify } from "../util/ids.js";
2
+ const HTTP_METHODS = new Set(["get", "post", "put", "delete", "patch", "options", "head", "all"]);
3
+ // Known limits for this v1 metadata extractor:
4
+ // - only literal route strings are captured;
5
+ // - custom/composed decorators are not expanded;
6
+ // - computed router methods/paths are ignored.
7
+ // That is intentional while Endpoint nodes are informational only and excluded
8
+ // from the coverage denominator.
9
+ const NEST_METHOD_DECORATOR = /@(Get|Post|Put|Delete|Patch|Options|Head|All)\s*\(\s*(?:(["'`])([^"'`]*)\2)?\s*\)(?:\s|\/\*[\s\S]*?\*\/|\/\/[^\n]*\n)*?(?:public\s+|private\s+|protected\s+|async\s+)*([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/g;
10
+ const NEST_CONTROLLER_DECORATOR = /@Controller\s*\(\s*(?:(["'`])([^"'`]*)\1)?\s*\)\s*(?:export\s+)?class\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
11
+ const EXPRESS_ROUTER_CALL = /\b(?:router|app)\s*\.\s*(get|post|put|delete|patch|options|head|all)\s*\(\s*(["'`])([^"'`]*)\2\s*,\s*([A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)?|\([^)]*\)\s*=>|async\s+\([^)]*\)\s*=>|function\s+[A-Za-z_$][A-Za-z0-9_$]*)/gi;
12
+ const EXPRESS_ROUTE_CHAIN = /\b(?:router|app)\s*\.\s*route\s*\(\s*(["'`])([^"'`]*)\1\s*\)((?:\s*\.\s*(?:get|post|put|delete|patch|options|head|all)\s*\([^)]*\))+)/gi;
13
+ const CHAINED_METHOD_CALL = /\.\s*(get|post|put|delete|patch|options|head|all)\s*\(\s*([A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)?|\([^)]*\)\s*=>|async\s+\([^)]*\)\s*=>|function\s+[A-Za-z_$][A-Za-z0-9_$]*)/gi;
14
+ const FASTIFY_CALL = /\bfastify\s*\.\s*(get|post|put|delete|patch|options|head|all)\s*\(\s*(["'`])([^"'`]*)\2\s*,\s*(?:\{[^)]*\}\s*,\s*)?([A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)?|\([^)]*\)\s*=>|async\s+\([^)]*\)\s*=>|function\s+[A-Za-z_$][A-Za-z0-9_$]*)/gi;
15
+ const FILE_ROUTE_EXPORT = /\bexport\s+(?:(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|ALL)\s*\(|const\s+(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|ALL)\s*=)/g;
16
+ export function extractBehaviorContracts(content, file) {
17
+ return dedupeContracts([
18
+ ...extractFileRouteContracts(content, file),
19
+ ...extractNestContracts(content, file),
20
+ ...extractRouterContracts(content, file, EXPRESS_ROUTER_CALL, "express"),
21
+ ...extractExpressRouteChains(content, file),
22
+ ...extractRouterContracts(content, file, FASTIFY_CALL, "fastify")
23
+ ]);
24
+ }
25
+ function extractFileRouteContracts(content, file) {
26
+ const path = fileRoutePath(file);
27
+ if (!path)
28
+ return [];
29
+ const contracts = [];
30
+ for (const match of content.matchAll(FILE_ROUTE_EXPORT)) {
31
+ const method = httpMethod(match[1] ?? match[2]);
32
+ contracts.push(makeContract({
33
+ file,
34
+ framework: "file_route",
35
+ method,
36
+ path,
37
+ handler: method
38
+ }));
39
+ }
40
+ return contracts;
41
+ }
42
+ function extractNestContracts(content, file) {
43
+ const controllers = [...content.matchAll(NEST_CONTROLLER_DECORATOR)].map((match) => ({
44
+ index: match.index ?? 0,
45
+ path: normalizeRoutePath(match[2] ?? ""),
46
+ name: match[3]
47
+ }));
48
+ if (controllers.length === 0)
49
+ return [];
50
+ const contracts = [];
51
+ for (const match of content.matchAll(NEST_METHOD_DECORATOR)) {
52
+ const index = match.index ?? 0;
53
+ const controller = nearestController(controllers, index);
54
+ const method = httpMethod(match[1]);
55
+ const routePath = joinRoutePaths(controller?.path ?? "", match[3] ?? "");
56
+ const handler = match[4];
57
+ contracts.push(makeContract({
58
+ file,
59
+ framework: "nestjs",
60
+ method,
61
+ path: routePath,
62
+ handler,
63
+ controller: controller?.name
64
+ }));
65
+ }
66
+ return contracts;
67
+ }
68
+ function nearestController(controllers, index) {
69
+ let current;
70
+ for (const controller of controllers) {
71
+ if (controller.index <= index)
72
+ current = controller;
73
+ else
74
+ break;
75
+ }
76
+ return current;
77
+ }
78
+ function extractRouterContracts(content, file, pattern, framework) {
79
+ const contracts = [];
80
+ for (const match of content.matchAll(pattern)) {
81
+ const handler = handlerName(match[4]);
82
+ contracts.push(makeContract({
83
+ file,
84
+ framework,
85
+ method: httpMethod(match[1]),
86
+ path: normalizeRoutePath(match[3] ?? ""),
87
+ handler
88
+ }));
89
+ }
90
+ return contracts;
91
+ }
92
+ function extractExpressRouteChains(content, file) {
93
+ const contracts = [];
94
+ for (const match of content.matchAll(EXPRESS_ROUTE_CHAIN)) {
95
+ const path = normalizeRoutePath(match[2] ?? "");
96
+ const chain = match[3] ?? "";
97
+ for (const methodMatch of chain.matchAll(CHAINED_METHOD_CALL)) {
98
+ contracts.push(makeContract({
99
+ file,
100
+ framework: "express",
101
+ method: httpMethod(methodMatch[1]),
102
+ path,
103
+ handler: handlerName(methodMatch[2])
104
+ }));
105
+ }
106
+ }
107
+ return contracts;
108
+ }
109
+ function makeContract(input) {
110
+ const title = `${input.method} ${input.path}`;
111
+ return {
112
+ id: `endpoint:${slugify(`${input.method}-${input.path}-${input.file}-${input.handler ?? ""}`)}`,
113
+ title,
114
+ kind: "http_endpoint",
115
+ framework: input.framework,
116
+ method: input.method,
117
+ path: input.path,
118
+ file: input.file,
119
+ handler: input.handler,
120
+ controller: input.controller,
121
+ source: "framework"
122
+ };
123
+ }
124
+ function httpMethod(value) {
125
+ const lower = value.toLowerCase();
126
+ return HTTP_METHODS.has(lower) ? lower.toUpperCase() : value.toUpperCase();
127
+ }
128
+ function normalizeRoutePath(value) {
129
+ const trimmed = value.trim().replace(/^\/+|\/+$/g, "");
130
+ return trimmed ? `/${trimmed}` : "/";
131
+ }
132
+ function fileRoutePath(file) {
133
+ const normalized = file.replace(/\\/g, "/");
134
+ if (!/(^|\/)route\.[cm]?[jt]sx?$/.test(normalized))
135
+ return null;
136
+ const parts = normalized.split("/");
137
+ const apiIndex = parts.findIndex((part) => part === "api" || part === "apis");
138
+ if (apiIndex === -1)
139
+ return null;
140
+ const routeParts = parts.slice(apiIndex + 1, -1).map((part) => part
141
+ .replace(/^\[\[\.\.\.(.+)\]\]$/, ":$1*")
142
+ .replace(/^\[\.\.\.(.+)\]$/, ":$1*")
143
+ .replace(/^\[(.+)\]$/, ":$1"));
144
+ return normalizeRoutePath(routeParts.join("/"));
145
+ }
146
+ function joinRoutePaths(prefix, route) {
147
+ const parts = [prefix, route].map((part) => part.trim().replace(/^\/+|\/+$/g, "")).filter(Boolean);
148
+ return parts.length ? `/${parts.join("/")}` : "/";
149
+ }
150
+ function handlerName(value) {
151
+ if (!value)
152
+ return undefined;
153
+ const trimmed = value.trim();
154
+ if (trimmed.startsWith("async") || trimmed.startsWith("(") || trimmed.startsWith("function"))
155
+ return undefined;
156
+ return trimmed;
157
+ }
158
+ function dedupeContracts(contracts) {
159
+ const seen = new Set();
160
+ const output = [];
161
+ for (const contract of contracts) {
162
+ const key = `${contract.framework}:${contract.method}:${contract.path}:${contract.file}:${contract.handler ?? ""}`;
163
+ if (seen.has(key))
164
+ continue;
165
+ seen.add(key);
166
+ output.push(contract);
167
+ }
168
+ return output;
169
+ }
@@ -0,0 +1,42 @@
1
+ // Trivial-accessor / object-protocol exclusion from the BEHAVIOR denominator.
2
+ //
3
+ // Boilerplate accessors (Java getId/setName, toString/equals/hashCode, Python
4
+ // __repr__/__str__) have no meaningful behavior to test. Counting them as
5
+ // untested behaviors under-claims coverage on entity-heavy repos (Spring
6
+ // Petclinic was ~33% getters/setters). They stay in the graph as CodeSymbol
7
+ // nodes for completeness, but are marked denominator_eligible:false with a
8
+ // reason, and surfaced as `excluded_boilerplate` so the report can disclose:
9
+ // "107 symbols found, 35 boilerplate accessors excluded, 72 behaviors counted."
10
+ //
11
+ // Name-based and deliberately CONSERVATIVE — language-aware so it never strips
12
+ // real logic. Body-aware refinement is a documented follow-up.
13
+ export const BOILERPLATE_REASON = "Trivial accessor/object method — excluded from behavior denominator.";
14
+ /**
15
+ * True iff `name` is a trivial accessor/object-protocol member for `language`.
16
+ * Only ever applied to function/method symbols (classes are never boilerplate).
17
+ *
18
+ * - Java/Kotlin: getX/setX/isX ONLY when the body is AST-proven trivial
19
+ * (trivialAccessor); toString/equals/hashCode by name (object plumbing).
20
+ * - Python: low-signal dunders __repr__ and __str__ ONLY. snake_case accessors
21
+ * like get_x / is_x are NOT excluded — those routinely hit DB/API/business logic.
22
+ * - Go and everything else: nothing (e.g. `main` may be real runtime wiring).
23
+ */
24
+ export function isBoilerplateSymbol(name, language, symbolKind, trivialAccessor) {
25
+ if (symbolKind !== "method" && symbolKind !== "function")
26
+ return false;
27
+ switch (language) {
28
+ case "java":
29
+ case "kotlin":
30
+ // A getX/setX/isX JavaBean accessor is excluded ONLY when the AST proved
31
+ // its body is trivial — so `getOwner()` that calls a repository, or any
32
+ // get-named method with real logic, stays a counted behavior.
33
+ if (/^(get|set|is)[A-Z]/.test(name))
34
+ return trivialAccessor === true;
35
+ // Object-protocol plumbing, excluded by name (not business behavior).
36
+ return name === "toString" || name === "equals" || name === "hashCode";
37
+ case "python":
38
+ return name === "__repr__" || name === "__str__";
39
+ default:
40
+ return false;
41
+ }
42
+ }
@@ -0,0 +1,458 @@
1
+ // AST-derived call extraction for TS/JS (Layer-1 call graph, PR 1).
2
+ //
3
+ // Pure parse step: walk a file's AST and record each call as a (caller, callee)
4
+ // pair, where the caller is the nearest enclosing NAMED symbol scope. Resolution
5
+ // of the callee to a concrete CodeSymbol — and the strict "both endpoints are
6
+ // emitted symbols" rule — happens in the analyzer, which holds the cross-file
7
+ // symbol index and the import-binding map.
8
+ //
9
+ // SCOPE-AWARE (no guessing): a free `bar()` or static `C.bar()` is dropped when
10
+ // `bar`/`C` is shadowed by a parameter, local variable, local function/class,
11
+ // catch binding, or destructuring binding in the enclosing lexical scope — the
12
+ // call then targets the LOCAL, not an emitted symbol. We over-approximate the
13
+ // shadow set (every binding anywhere in the enclosing function chain, excluding
14
+ // nested function scopes), which can only UNDER-emit; hard CALLS cannot guess.
15
+ //
16
+ // Callee forms captured (everything else dropped):
17
+ // - `bar()` → free identifier (same-file or import-resolved later)
18
+ // - `this.bar()` → a member of the caller's own class (never shadowed)
19
+ // - `C.bar()` → a static member, qualifier is the class identifier
20
+ // - `this.dep.bar()` → an injected field whose type is explicit in the class
21
+ import ts from "typescript";
22
+ export const MEDUSA_INTERNAL_SERVICE_TYPE = "__medusa_internal_service__";
23
+ const MAX_CALLS_PER_FILE = 2000;
24
+ function isFunctionLike(n) {
25
+ return (ts.isFunctionDeclaration(n) ||
26
+ ts.isFunctionExpression(n) ||
27
+ ts.isArrowFunction(n) ||
28
+ ts.isMethodDeclaration(n) ||
29
+ ts.isConstructorDeclaration(n) ||
30
+ ts.isGetAccessorDeclaration(n) ||
31
+ ts.isSetAccessorDeclaration(n));
32
+ }
33
+ /** Collect identifier names introduced by a binding name (incl. destructuring). */
34
+ function collectBindingName(name, out) {
35
+ if (ts.isIdentifier(name)) {
36
+ out.add(name.text);
37
+ }
38
+ else {
39
+ for (const el of name.elements) {
40
+ if (ts.isBindingElement(el))
41
+ collectBindingName(el.name, out);
42
+ }
43
+ }
44
+ }
45
+ /**
46
+ * All names bound lexically inside `fn`: its own name, parameters (destructuring-
47
+ * aware), and every declaration at any block depth within the body — EXCLUDING
48
+ * nested function scopes (which own their bindings). Over-approximated on
49
+ * purpose (block/TDZ nuance ignored): a superset only ever drops more edges.
50
+ */
51
+ function scopeBindings(fn) {
52
+ const out = new Set();
53
+ if ("name" in fn && fn.name && ts.isIdentifier(fn.name))
54
+ out.add(fn.name.text);
55
+ for (const p of fn.parameters)
56
+ collectBindingName(p.name, out);
57
+ if (fn.body)
58
+ collectDecls(fn.body, out);
59
+ return out;
60
+ }
61
+ /** Collect declared bindings under `node`, NOT descending into nested function scopes. */
62
+ function collectDecls(node, out) {
63
+ ts.forEachChild(node, (child) => {
64
+ if (isFunctionLike(child) || ts.isClassExpression(child)) {
65
+ if ((ts.isFunctionDeclaration(child) || ts.isClassDeclaration(child)) && child.name)
66
+ out.add(child.name.text);
67
+ return; // separate scope — its locals do not shadow here
68
+ }
69
+ if (ts.isClassDeclaration(child)) {
70
+ if (child.name)
71
+ out.add(child.name.text);
72
+ return;
73
+ }
74
+ if (ts.isVariableDeclaration(child) || ts.isBindingElement(child))
75
+ collectBindingName(child.name, out);
76
+ if (ts.isCatchClause(child) && child.variableDeclaration)
77
+ collectBindingName(child.variableDeclaration.name, out);
78
+ collectDecls(child, out);
79
+ });
80
+ }
81
+ function simpleTypeName(type) {
82
+ if (!type || !ts.isTypeReferenceNode(type) || !ts.isIdentifier(type.typeName) || (type.typeArguments?.length ?? 0) > 0)
83
+ return null;
84
+ return type.typeName.text;
85
+ }
86
+ function medusaInternalServiceTypeName(type) {
87
+ if (!type || !ts.isTypeReferenceNode(type))
88
+ return null;
89
+ const name = type.typeName;
90
+ if (ts.isIdentifier(name) && name.text === "IMedusaInternalService")
91
+ return MEDUSA_INTERNAL_SERVICE_TYPE;
92
+ if (ts.isQualifiedName(name) && name.right.text === "IMedusaInternalService")
93
+ return MEDUSA_INTERNAL_SERVICE_TYPE;
94
+ return null;
95
+ }
96
+ function injectableTypeName(type) {
97
+ return simpleTypeName(type) ?? medusaInternalServiceTypeName(type);
98
+ }
99
+ function hasParameterPropertyModifier(param) {
100
+ return Boolean(param.modifiers?.some((m) => m.kind === ts.SyntaxKind.PublicKeyword ||
101
+ m.kind === ts.SyntaxKind.PrivateKeyword ||
102
+ m.kind === ts.SyntaxKind.ProtectedKeyword ||
103
+ m.kind === ts.SyntaxKind.ReadonlyKeyword));
104
+ }
105
+ function typeLiteralPropertyTypes(type) {
106
+ const out = new Map();
107
+ if (!type || !ts.isTypeLiteralNode(type))
108
+ return out;
109
+ for (const member of type.members) {
110
+ if (!ts.isPropertySignature(member) || !member.type)
111
+ continue;
112
+ const prop = member.name && ts.isIdentifier(member.name) ? member.name.text : null;
113
+ const propType = injectableTypeName(member.type);
114
+ if (prop && propType)
115
+ out.set(prop, propType);
116
+ }
117
+ return out;
118
+ }
119
+ function collectInterfacePropertyTypes(sf) {
120
+ const out = new Map();
121
+ ts.forEachChild(sf, (node) => {
122
+ if (ts.isTypeAliasDeclaration(node) && ts.isTypeLiteralNode(node.type)) {
123
+ out.set(node.name.text, typeLiteralPropertyTypes(node.type));
124
+ return;
125
+ }
126
+ if (!ts.isInterfaceDeclaration(node))
127
+ return;
128
+ const props = new Map();
129
+ for (const member of node.members) {
130
+ if (!ts.isPropertySignature(member) || !member.type || !ts.isIdentifier(member.name))
131
+ continue;
132
+ const propType = simpleTypeName(member.type);
133
+ if (propType)
134
+ props.set(member.name.text, propType);
135
+ }
136
+ out.set(node.name.text, props);
137
+ });
138
+ return out;
139
+ }
140
+ function objectBindingPropertyTypes(type, interfaces) {
141
+ if (type && ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) && (type.typeArguments?.length ?? 0) === 0) {
142
+ return interfaces.get(type.typeName.text) ?? new Map();
143
+ }
144
+ return typeLiteralPropertyTypes(type);
145
+ }
146
+ function collectConstructorParamTypes(ctor, interfaces) {
147
+ const out = new Map();
148
+ for (const p of ctor.parameters) {
149
+ const directType = injectableTypeName(p.type);
150
+ if (ts.isIdentifier(p.name) && directType) {
151
+ out.set(p.name.text, directType);
152
+ continue;
153
+ }
154
+ if (!ts.isObjectBindingPattern(p.name))
155
+ continue;
156
+ const propTypes = objectBindingPropertyTypes(p.type, interfaces);
157
+ for (const el of p.name.elements) {
158
+ if (!ts.isBindingElement(el) || !ts.isIdentifier(el.name))
159
+ continue;
160
+ const propName = el.propertyName && ts.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
161
+ const propType = propTypes.get(propName);
162
+ if (propType)
163
+ out.set(el.name.text, propType);
164
+ }
165
+ }
166
+ return out;
167
+ }
168
+ function collectInjectedFields(cls, interfaces) {
169
+ const out = new Map();
170
+ for (const member of cls.members) {
171
+ if (ts.isPropertyDeclaration(member) && member.name && ts.isIdentifier(member.name)) {
172
+ const typeName = injectableTypeName(member.type);
173
+ if (typeName)
174
+ out.set(member.name.text, typeName);
175
+ continue;
176
+ }
177
+ if (!ts.isConstructorDeclaration(member))
178
+ continue;
179
+ const paramTypes = collectConstructorParamTypes(member, interfaces);
180
+ for (const p of member.parameters) {
181
+ if (!ts.isIdentifier(p.name) || !hasParameterPropertyModifier(p))
182
+ continue;
183
+ const typeName = injectableTypeName(p.type);
184
+ if (typeName)
185
+ out.set(p.name.text, typeName);
186
+ }
187
+ if (!member.body)
188
+ continue;
189
+ for (const stmt of member.body.statements) {
190
+ if (!ts.isExpressionStatement(stmt) || !ts.isBinaryExpression(stmt.expression))
191
+ continue;
192
+ const assignment = stmt.expression;
193
+ if (assignment.operatorToken.kind !== ts.SyntaxKind.EqualsToken)
194
+ continue;
195
+ const left = assignment.left;
196
+ const right = assignment.right;
197
+ if (!ts.isPropertyAccessExpression(left) || left.expression.kind !== ts.SyntaxKind.ThisKeyword || !ts.isIdentifier(left.name))
198
+ continue;
199
+ if (!ts.isIdentifier(right))
200
+ continue;
201
+ const typeName = paramTypes.get(right.text);
202
+ if (typeName)
203
+ out.set(left.name.text, typeName);
204
+ }
205
+ }
206
+ return out;
207
+ }
208
+ function collectInjectedFieldsByClass(sf) {
209
+ const out = new Map();
210
+ const interfaces = collectInterfacePropertyTypes(sf);
211
+ ts.forEachChild(sf, (node) => {
212
+ if (!ts.isClassDeclaration(node) || !node.name)
213
+ return;
214
+ const fields = collectInjectedFields(node, interfaces);
215
+ if (fields.size > 0)
216
+ out.set(node.name.text, fields);
217
+ });
218
+ return out;
219
+ }
220
+ export const MEDUSA_GENERATED_METHOD_BASES = ["retrieve", "list", "listAndCount", "delete", "softDelete", "restore", "create", "update"];
221
+ function upperCaseFirst(value) {
222
+ return value ? `${value[0].toUpperCase()}${value.slice(1)}` : value;
223
+ }
224
+ function pluralizeModelKey(value) {
225
+ if (/[sxz]$/i.test(value) || /(?:ch|sh)$/i.test(value))
226
+ return `${value}es`;
227
+ if (/[^aeiou]y$/i.test(value))
228
+ return `${value.slice(0, -1)}ies`;
229
+ return `${value}s`;
230
+ }
231
+ export function medusaGeneratedMethodNames(modelKey) {
232
+ return MEDUSA_GENERATED_METHOD_BASES.map((base) => medusaGeneratedMethodName(modelKey, base));
233
+ }
234
+ export function medusaGeneratedMethodName(modelKey, base) {
235
+ const singular = upperCaseFirst(modelKey);
236
+ const plural = upperCaseFirst(pluralizeModelKey(modelKey));
237
+ return `${base}${base === "retrieve" ? singular : plural}`;
238
+ }
239
+ function modelKeysFromObjectLiteral(expr) {
240
+ const keys = [];
241
+ for (const prop of expr.properties) {
242
+ if (!ts.isPropertyAssignment(prop) && !ts.isShorthandPropertyAssignment(prop))
243
+ return null;
244
+ if (ts.isPropertyAssignment(prop)) {
245
+ if (prop.name && ts.isIdentifier(prop.name))
246
+ keys.push(prop.name.text);
247
+ else if (prop.name && ts.isStringLiteral(prop.name) && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(prop.name.text))
248
+ keys.push(prop.name.text);
249
+ else
250
+ return null;
251
+ }
252
+ else {
253
+ keys.push(prop.name.text);
254
+ }
255
+ }
256
+ return keys;
257
+ }
258
+ function constObjectModelMaps(sf) {
259
+ const out = new Map();
260
+ ts.forEachChild(sf, (node) => {
261
+ if (!ts.isVariableStatement(node))
262
+ return;
263
+ for (const decl of node.declarationList.declarations) {
264
+ if (!ts.isIdentifier(decl.name) || !decl.initializer || !ts.isObjectLiteralExpression(decl.initializer))
265
+ continue;
266
+ const keys = modelKeysFromObjectLiteral(decl.initializer);
267
+ if (keys)
268
+ out.set(decl.name.text, keys);
269
+ }
270
+ });
271
+ return out;
272
+ }
273
+ function medusaServiceCall(expr) {
274
+ if (!ts.isCallExpression(expr))
275
+ return null;
276
+ const callee = expr.expression;
277
+ if (ts.isIdentifier(callee) && callee.text === "MedusaService")
278
+ return expr;
279
+ if (ts.isPropertyAccessExpression(callee) &&
280
+ callee.name.text === "MedusaService" &&
281
+ ts.isIdentifier(callee.expression) &&
282
+ callee.expression.text === "ModulesSdkUtils") {
283
+ return expr;
284
+ }
285
+ return null;
286
+ }
287
+ function modelKeysFromExpr(expr, constModels) {
288
+ if (ts.isObjectLiteralExpression(expr))
289
+ return modelKeysFromObjectLiteral(expr);
290
+ if (ts.isIdentifier(expr))
291
+ return constModels.get(expr.text) ?? null;
292
+ return null;
293
+ }
294
+ export function extractMedusaGeneratedServices(content, tsx) {
295
+ const sf = ts.createSourceFile(tsx ? "generated.tsx" : "generated.ts", content, ts.ScriptTarget.Latest, true, tsx ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
296
+ const constModels = constObjectModelMaps(sf);
297
+ const out = [];
298
+ ts.forEachChild(sf, (node) => {
299
+ if (!ts.isClassDeclaration(node) || !node.name || !node.heritageClauses)
300
+ return;
301
+ for (const clause of node.heritageClauses) {
302
+ if (clause.token !== ts.SyntaxKind.ExtendsKeyword)
303
+ continue;
304
+ for (const type of clause.types) {
305
+ const call = medusaServiceCall(type.expression);
306
+ const arg = call?.arguments[0];
307
+ if (!arg)
308
+ continue;
309
+ const modelKeys = modelKeysFromExpr(arg, constModels);
310
+ if (!modelKeys || modelKeys.length === 0)
311
+ continue;
312
+ const methods = [...new Set(modelKeys.flatMap(medusaGeneratedMethodNames))];
313
+ out.push({ className: node.name.text, modelKeys, methods });
314
+ }
315
+ }
316
+ });
317
+ return out;
318
+ }
319
+ /**
320
+ * Extract raw (caller, callee) call pairs from a TS/JS file. `tsx` selects the
321
+ * grammar (TSX for .tsx/.jsx). Caller attribution: a call is attributed to the
322
+ * nearest enclosing function declaration, class method, or const binding —
323
+ * anonymous closures (callbacks, `useEffect(() => …)`) inherit their enclosing
324
+ * named scope. Calls at module scope (no enclosing named symbol) are dropped.
325
+ */
326
+ export function extractCalls(content, tsx) {
327
+ const sf = ts.createSourceFile(tsx ? "calls.tsx" : "calls.ts", content, ts.ScriptTarget.Latest,
328
+ /*setParentNodes*/ true, tsx ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
329
+ const out = [];
330
+ let capped = false;
331
+ const injectedFieldsByClass = collectInjectedFieldsByClass(sf);
332
+ // `thisExact` = `this` currently refers to the lexical class instance, so a
333
+ // `this.member()` resolves exactly to that class's member. True only inside a
334
+ // class method body and arrow callbacks nested within it; a non-arrow
335
+ // `function () {}` (or a nested function declaration) REBINDS `this`, so a
336
+ // `this.member()` there is NOT exact and is dropped.
337
+ const recordCall = (node, caller, shadowed, thisExact) => {
338
+ if (out.length >= MAX_CALLS_PER_FILE) {
339
+ capped = true;
340
+ return;
341
+ }
342
+ const e = node.expression;
343
+ if (ts.isIdentifier(e)) {
344
+ if (shadowed.has(e.text))
345
+ return; // a local binding shadows the callee
346
+ out.push({ caller, callee: e.text, via: "free" });
347
+ }
348
+ else if (ts.isPropertyAccessExpression(e) && ts.isIdentifier(e.name)) {
349
+ if (e.expression.kind === ts.SyntaxKind.ThisKeyword) {
350
+ if (!thisExact)
351
+ return; // `this` was rebound by a non-arrow function — not exact
352
+ out.push({ caller, callee: e.name.text, via: "this" });
353
+ }
354
+ else if (ts.isPropertyAccessExpression(e.expression) &&
355
+ e.expression.expression.kind === ts.SyntaxKind.ThisKeyword &&
356
+ ts.isIdentifier(e.expression.name)) {
357
+ if (!thisExact)
358
+ return; // `this` was rebound by a non-arrow function — not exact
359
+ const cls = caller.includes(".") ? caller.slice(0, caller.indexOf(".")) : "";
360
+ const field = e.expression.name.text;
361
+ const injectedType = cls ? injectedFieldsByClass.get(cls)?.get(field) : undefined;
362
+ if (injectedType)
363
+ out.push({ caller, callee: e.name.text, via: "injected", qualifier: field, injectedType });
364
+ }
365
+ else if (ts.isIdentifier(e.expression)) {
366
+ if (shadowed.has(e.expression.text))
367
+ return; // a local shadows the qualifier object
368
+ out.push({ caller, callee: e.name.text, via: "static", qualifier: e.expression.text });
369
+ }
370
+ }
371
+ };
372
+ const extend = (parent, fn) => {
373
+ const locals = scopeBindings(fn);
374
+ if (locals.size === 0)
375
+ return parent;
376
+ return new Set([...parent, ...locals]);
377
+ };
378
+ // `topLevel` = we are at the file's MODULE scope, where a named function/class/
379
+ // const declaration is an emittable symbol the analyzer can match by name. It
380
+ // is FALSE inside any function/method/closure body AND inside a `namespace`/
381
+ // `module` block (whose members are NOT emitted as top-level symbols, so a
382
+ // namespace-nested `inner` must not impersonate a top-level exported `inner`).
383
+ // A nested named declaration does not establish a caller; its calls are dropped
384
+ // (under-emit; hard CALLS cannot guess). Anonymous closures still inherit the
385
+ // enclosing caller — they have no name to impersonate.
386
+ const walk = (node, caller, shadowed, thisExact, topLevel) => {
387
+ if (capped)
388
+ return;
389
+ if (ts.isModuleDeclaration(node)) {
390
+ // namespace/module N { … } — a non-emitting boundary. Drop its calls.
391
+ if (node.body)
392
+ ts.forEachChild(node.body, (x) => walk(x, null, shadowed, false, false));
393
+ return;
394
+ }
395
+ if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
396
+ if (!topLevel || ts.isClassExpression(node))
397
+ return; // nested/namespace/expression class — drop
398
+ const cls = node.name?.text;
399
+ for (const m of node.members) {
400
+ if (ts.isMethodDeclaration(m) && m.name && ts.isIdentifier(m.name) && m.body) {
401
+ const c = cls ? `${cls}.${m.name.text}` : null;
402
+ // A method body has class `this` (exact); it is no longer top-level.
403
+ if (c)
404
+ ts.forEachChild(m.body, (x) => walk(x, c, extend(shadowed, m), true, false));
405
+ }
406
+ }
407
+ return;
408
+ }
409
+ if (ts.isFunctionDeclaration(node) && node.name) {
410
+ if (node.body) {
411
+ const sh = extend(shadowed, node);
412
+ // Nested/namespace function declaration → drop (caller=null); rebinds `this`.
413
+ const next = topLevel ? node.name.text : null;
414
+ ts.forEachChild(node.body, (x) => walk(x, next, sh, false, false));
415
+ }
416
+ return;
417
+ }
418
+ if (ts.isVariableStatement(node)) {
419
+ if (!topLevel)
420
+ return; // nested/namespace const/let → drop (would impersonate an emitted symbol)
421
+ for (const d of node.declarationList.declarations) {
422
+ if (ts.isIdentifier(d.name) && d.initializer) {
423
+ const callerName = d.name.text;
424
+ const init = d.initializer;
425
+ if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) {
426
+ walk(init, callerName, shadowed, thisExact, false); // arrow/fn handler decides `this`
427
+ }
428
+ else {
429
+ // wrapper call (forwardRef(...), memo(...)): don't record it, descend for inner closures.
430
+ ts.forEachChild(init, (x) => walk(x, callerName, shadowed, thisExact, false));
431
+ }
432
+ }
433
+ }
434
+ return;
435
+ }
436
+ if (ts.isArrowFunction(node)) {
437
+ // Arrow: keeps the enclosing caller AND lexical `this`. Walk the body NODE
438
+ // directly so an EXPRESSION body (`() => render()`) is not skipped.
439
+ const sh = extend(shadowed, node);
440
+ if (node.body)
441
+ walk(node.body, caller, sh, thisExact, false);
442
+ return;
443
+ }
444
+ if (ts.isFunctionExpression(node)) {
445
+ // `function () {}` rebinds `this` — clear thisExact for its body.
446
+ const sh = extend(shadowed, node);
447
+ if (node.body)
448
+ walk(node.body, caller, sh, false, false);
449
+ return;
450
+ }
451
+ if (caller && ts.isCallExpression(node))
452
+ recordCall(node, caller, shadowed, thisExact);
453
+ ts.forEachChild(node, (c) => walk(c, caller, shadowed, thisExact, topLevel));
454
+ };
455
+ const EMPTY = new Set();
456
+ ts.forEachChild(sf, (c) => walk(c, null, EMPTY, false, true));
457
+ return out;
458
+ }