@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,607 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { reportProgress } from "../util/progress.js";
5
+ const DEFAULT_TIMEOUT_MS = 120_000;
6
+ const DEFAULT_BUDGET_MS = 600_000;
7
+ const GENERATED_COVERAGE_DIR = ".orangepro/coverage";
8
+ const MAVEN_JACOCO_REPORT_GOAL = "org.jacoco:jacoco-maven-plugin:0.8.12:report";
9
+ const IGNORE_DIRS = new Set([".git", "node_modules", "vendor", "target", "dist", "build", ".orangepro", ".venv", "venv", ".tox", "__pycache__"]);
10
+ const KNOWN_ARTIFACTS = [
11
+ { path: "coverage.out", language: "go", format: "go-coverprofile", ingestible: true },
12
+ { path: "cover.out", language: "go", format: "go-coverprofile", ingestible: true },
13
+ { path: "coverprofile.out", language: "go", format: "go-coverprofile", ingestible: true },
14
+ { path: "go.coverprofile", language: "go", format: "go-coverprofile", ingestible: true },
15
+ { path: "coverage/go.coverprofile", language: "go", format: "go-coverprofile", ingestible: true },
16
+ { path: "coverage/coverage.out", language: "go", format: "go-coverprofile", ingestible: true },
17
+ { path: "coverage/lcov.info", language: "tsjs", format: "lcov", ingestible: true },
18
+ { path: "lcov.info", language: "tsjs", format: "lcov", ingestible: true },
19
+ { path: "coverage/cobertura-coverage.xml", language: "tsjs", format: "cobertura", ingestible: false },
20
+ { path: "coverage/coverage-final.json", language: "tsjs", format: "unknown", ingestible: false },
21
+ { path: "coverage.xml", language: "python", format: "coverage-py", ingestible: true },
22
+ { path: ".coverage", language: "python", format: "coverage-py", ingestible: false },
23
+ { path: "target/site/jacoco/jacoco.xml", language: "java", format: "jacoco", ingestible: true },
24
+ { path: "build/reports/jacoco/test/jacocoTestReport.xml", language: "java", format: "jacoco", ingestible: true },
25
+ { path: "coverage.cobertura.xml", language: "csharp", format: "cobertura", ingestible: false },
26
+ { path: "coverage/.resultset.json", language: "ruby", format: "simplecov", ingestible: false },
27
+ { path: "clover.xml", language: "php", format: "clover", ingestible: false }
28
+ ];
29
+ export function prepareRuntimeCoverage(root, opts = {}) {
30
+ const absRoot = path.resolve(root);
31
+ const timeoutMs = opts.timeoutMs ?? timeoutMsFromEnv() ?? DEFAULT_TIMEOUT_MS;
32
+ const budgetMs = opts.budgetMs ?? budgetMsFromEnv() ?? DEFAULT_BUDGET_MS;
33
+ const coverageDir = path.join(absRoot, GENERATED_COVERAGE_DIR);
34
+ const warnings = [];
35
+ const generated = [];
36
+ if (opts.generate) {
37
+ const deadline = Date.now() + budgetMs;
38
+ let budgetSkipped = false;
39
+ // Returns true once the aggregate budget is spent; warns once and stops the remaining modules.
40
+ const overBudget = () => {
41
+ if (Date.now() < deadline)
42
+ return false;
43
+ if (!budgetSkipped) {
44
+ budgetSkipped = true;
45
+ warnings.push(`Coverage generation exceeded its ${Math.round(budgetMs / 1000)}s budget, so OrangePro skipped the remaining modules. Raise it with ORANGEPRO_COVERAGE_BUDGET_MS.`);
46
+ }
47
+ return true;
48
+ };
49
+ mkdirSync(coverageDir, { recursive: true });
50
+ const goModules = findGoModules(absRoot);
51
+ for (const mod of goModules) {
52
+ if (overBudget())
53
+ break;
54
+ const relModule = path.relative(absRoot, mod.dir).replace(/\\/g, "/");
55
+ const label = slug(relModule || "root");
56
+ const artifactRel = `${GENERATED_COVERAGE_DIR}/go-${label}.coverprofile`;
57
+ const artifactAbs = path.join(absRoot, artifactRel);
58
+ const args = ["test", "./...", `-coverprofile=${artifactAbs}`];
59
+ reportProgress(`coverage: Go ${relModule || "."} — go ${args.map(shellToken).join(" ")}`);
60
+ const res = (opts.runner ?? defaultRunner)(mod.dir, "go", args, timeoutMs);
61
+ const artifactWritten = fileHasBytes(artifactAbs);
62
+ const ok = res.status === 0 && artifactWritten;
63
+ generated.push({
64
+ language: "go",
65
+ module_dir: relModule || ".",
66
+ command: `go ${args.map(shellToken).join(" ")}`,
67
+ ...(artifactWritten ? { artifact_path: artifactRel } : {}),
68
+ ok,
69
+ ...(!ok && artifactWritten ? { partial: true } : {}),
70
+ ...(!ok ? { reason: summarizeFailure(res) } : {})
71
+ });
72
+ reportProgress(`${ok ? "coverage: ok" : "coverage: failed"} Go ${relModule || "."}`);
73
+ }
74
+ for (const task of findTsJsCoverageTasks(absRoot)) {
75
+ if (overBudget())
76
+ break;
77
+ reportProgress(`coverage: TS/JS ${task.relModule || "."} — ${renderCommand(task.command, task.args)}`);
78
+ const res = (opts.runner ?? defaultRunner)(task.dir, task.command, task.args, timeoutMs);
79
+ const artifactWritten = fileHasBytes(task.artifactAbs);
80
+ const ok = res.status === 0 && artifactWritten;
81
+ generated.push({
82
+ language: "tsjs",
83
+ module_dir: task.relModule || ".",
84
+ command: renderCommand(task.command, task.args),
85
+ ...(artifactWritten ? { artifact_path: task.artifactRel } : {}),
86
+ ok,
87
+ ...(!ok && artifactWritten ? { partial: true } : {}),
88
+ ...(!ok ? { reason: summarizeFailure(res) } : {})
89
+ });
90
+ reportProgress(`${ok ? "coverage: ok" : "coverage: failed"} TS/JS ${task.relModule || "."}`);
91
+ }
92
+ for (const task of findPythonCoverageTasks(absRoot)) {
93
+ if (overBudget())
94
+ break;
95
+ reportProgress(`coverage: Python ${task.relModule || "."} — ${renderCommand(task.command, task.args)}`);
96
+ const res = (opts.runner ?? defaultRunner)(task.dir, task.command, task.args, timeoutMs);
97
+ const artifactWritten = fileHasBytes(task.artifactAbs);
98
+ const ok = res.status === 0 && artifactWritten;
99
+ generated.push({
100
+ language: "python",
101
+ module_dir: task.relModule || ".",
102
+ command: renderCommand(task.command, task.args),
103
+ ...(artifactWritten ? { artifact_path: task.artifactRel } : {}),
104
+ ok,
105
+ ...(!ok && artifactWritten ? { partial: true } : {}),
106
+ ...(!ok ? { reason: summarizeFailure(res) } : {})
107
+ });
108
+ reportProgress(`${ok ? "coverage: ok" : "coverage: failed"} Python ${task.relModule || "."}`);
109
+ }
110
+ for (const task of findJavaCoverageTasks(absRoot)) {
111
+ if (overBudget())
112
+ break;
113
+ reportProgress(`coverage: Java ${task.relModule || "."} — ${renderCommand(task.command, task.args)}`);
114
+ const res = (opts.runner ?? defaultRunner)(task.dir, task.command, task.args, timeoutMs);
115
+ const artifactWritten = fileHasBytes(task.artifactAbs);
116
+ const ok = res.status === 0 && artifactWritten;
117
+ generated.push({
118
+ language: "java",
119
+ module_dir: task.relModule || ".",
120
+ command: renderCommand(task.command, task.args),
121
+ ...(artifactWritten ? { artifact_path: task.artifactRel } : {}),
122
+ ok,
123
+ ...(!ok && artifactWritten ? { partial: true } : {}),
124
+ ...(!ok ? { reason: summarizeFailure(res) } : {})
125
+ });
126
+ reportProgress(`${ok ? "coverage: ok" : "coverage: failed"} Java ${task.relModule || "."}`);
127
+ }
128
+ if (generated.length === 0)
129
+ warnings.push("No local coverage generators found, so OrangePro did not generate runtime coverage.");
130
+ }
131
+ const artifacts = detectCoverageArtifacts(absRoot);
132
+ const suggested_commands = suggestCoverageCommands(absRoot);
133
+ return {
134
+ root: absRoot,
135
+ coverage_dir: path.relative(absRoot, coverageDir).replace(/\\/g, "/") || GENERATED_COVERAGE_DIR,
136
+ artifacts,
137
+ generated,
138
+ suggested_commands,
139
+ warnings
140
+ };
141
+ }
142
+ export function detectCoverageArtifacts(root) {
143
+ const absRoot = path.resolve(root);
144
+ const out = new Map();
145
+ const add = (info) => {
146
+ const abs = path.join(absRoot, info.path);
147
+ if (!existsSync(abs))
148
+ return;
149
+ try {
150
+ if (!statSync(abs).isFile())
151
+ return;
152
+ }
153
+ catch {
154
+ return;
155
+ }
156
+ out.set(info.path, info);
157
+ };
158
+ for (const known of KNOWN_ARTIFACTS)
159
+ add({ ...known, source: "existing" });
160
+ for (const rel of findExistingGoCoverprofiles(absRoot))
161
+ add({ path: rel, language: "go", format: "go-coverprofile", ingestible: true, source: "existing" });
162
+ for (const info of findExistingRuntimeCoverageReports(absRoot))
163
+ add({ ...info, source: "existing" });
164
+ for (const info of generatedCoverageArtifacts(absRoot))
165
+ add({ ...info, source: "generated" });
166
+ return [...out.values()].sort((a, b) => a.path.localeCompare(b.path));
167
+ }
168
+ export function suggestCoverageCommands(root) {
169
+ const absRoot = path.resolve(root);
170
+ const out = [];
171
+ for (const mod of findGoModules(absRoot)) {
172
+ const relModule = path.relative(absRoot, mod.dir).replace(/\\/g, "/") || ".";
173
+ const label = slug(relModule === "." ? "root" : relModule);
174
+ const artifactPath = `${GENERATED_COVERAGE_DIR}/go-${label}.coverprofile`;
175
+ const artifactFromModule = path.relative(mod.dir, path.join(absRoot, artifactPath)).replace(/\\/g, "/");
176
+ out.push({
177
+ language: "go",
178
+ cwd: relModule,
179
+ command: `go test ./... -coverprofile=${artifactFromModule}`,
180
+ artifact_path: artifactPath,
181
+ reason: "Go has built-in coverage; no external service or key is required."
182
+ });
183
+ }
184
+ const tsTasks = findTsJsCoverageTasks(absRoot);
185
+ if (tsTasks.length > 0) {
186
+ for (const task of tsTasks) {
187
+ out.push({
188
+ language: "tsjs",
189
+ cwd: task.relModule || ".",
190
+ command: renderCommand(task.command, task.args),
191
+ artifact_path: task.artifactRel,
192
+ reason: "Repo package script already emits lcov; OrangePro ingests it locally when the artifact exists."
193
+ });
194
+ }
195
+ }
196
+ else if (existsSync(path.join(absRoot, "package.json"))) {
197
+ out.push({
198
+ language: "tsjs",
199
+ cwd: ".",
200
+ command: "npm test -- --coverage",
201
+ artifact_path: "coverage/lcov.info",
202
+ reason: "Use the repo's existing JS/TS runner to emit lcov; OrangePro ingests lcov locally when the artifact exists."
203
+ });
204
+ }
205
+ for (const task of findPythonCoverageTasks(absRoot)) {
206
+ const artifactFromModule = path.relative(task.dir, task.artifactAbs).replace(/\\/g, "/");
207
+ out.push({
208
+ language: "python",
209
+ cwd: task.relModule || ".",
210
+ command: `python3 -m pytest --cov=. --cov-report=xml:${artifactFromModule}`,
211
+ artifact_path: task.artifactRel,
212
+ reason: "coverage.py/pytest-cov can emit XML locally; no external service or key is required."
213
+ });
214
+ }
215
+ for (const task of findJavaCoverageTasks(absRoot)) {
216
+ out.push({
217
+ language: "java",
218
+ cwd: task.relModule || ".",
219
+ command: renderCommand(task.command, task.args),
220
+ artifact_path: task.artifactRel,
221
+ reason: task.buildTool === "maven"
222
+ ? "JaCoCo runs locally through Maven; no SonarQube key is required."
223
+ : "JaCoCo runs locally through Gradle; no SonarQube key is required."
224
+ });
225
+ }
226
+ return out;
227
+ }
228
+ function findGoModules(root) {
229
+ const out = [];
230
+ const visit = (dir) => {
231
+ let entries;
232
+ try {
233
+ entries = readdirSync(dir, { withFileTypes: true });
234
+ }
235
+ catch {
236
+ return;
237
+ }
238
+ if (entries.some((e) => e.isFile() && e.name === "go.mod"))
239
+ out.push({ dir });
240
+ for (const entry of entries) {
241
+ if (!entry.isDirectory() || IGNORE_DIRS.has(entry.name))
242
+ continue;
243
+ visit(path.join(dir, entry.name));
244
+ }
245
+ };
246
+ visit(root);
247
+ return out.sort((a, b) => a.dir.localeCompare(b.dir));
248
+ }
249
+ function findTsJsCoverageTasks(root) {
250
+ const out = [];
251
+ for (const pkg of findPackageJsons(root)) {
252
+ const task = coverageScriptForPackage(pkg.dir, pkg.json);
253
+ if (!task)
254
+ continue;
255
+ const relModule = path.relative(root, pkg.dir).replace(/\\/g, "/");
256
+ const artifactRel = path.join(relModule, "coverage", "lcov.info").replace(/\\/g, "/").replace(/^\/+/, "");
257
+ out.push({
258
+ dir: pkg.dir,
259
+ relModule,
260
+ command: task.command,
261
+ args: task.args,
262
+ artifactRel,
263
+ artifactAbs: path.join(root, artifactRel)
264
+ });
265
+ }
266
+ return out.sort((a, b) => a.relModule.localeCompare(b.relModule));
267
+ }
268
+ function findJavaCoverageTasks(root) {
269
+ return [...findMavenCoverageTasks(root), ...findGradleCoverageTasks(root)].sort((a, b) => a.relModule.localeCompare(b.relModule));
270
+ }
271
+ function findMavenCoverageTasks(root) {
272
+ const out = [];
273
+ const visit = (dir, rel) => {
274
+ let entries;
275
+ try {
276
+ entries = readdirSync(dir, { withFileTypes: true });
277
+ }
278
+ catch {
279
+ return;
280
+ }
281
+ if (entries.some((e) => e.isFile() && e.name === "pom.xml")) {
282
+ const relModule = rel.replace(/\\/g, "/");
283
+ const artifactRel = path.join(relModule, "target", "site", "jacoco", "jacoco.xml").replace(/\\/g, "/").replace(/^\/+/, "");
284
+ out.push({
285
+ dir,
286
+ relModule,
287
+ command: existsSync(path.join(dir, "mvnw")) ? "./mvnw" : "mvn",
288
+ args: ["test", MAVEN_JACOCO_REPORT_GOAL],
289
+ artifactRel,
290
+ artifactAbs: path.join(root, artifactRel),
291
+ buildTool: "maven"
292
+ });
293
+ return;
294
+ }
295
+ for (const entry of entries) {
296
+ if (!entry.isDirectory() || IGNORE_DIRS.has(entry.name))
297
+ continue;
298
+ const childRel = rel ? `${rel}/${entry.name}` : entry.name;
299
+ visit(path.join(dir, entry.name), childRel);
300
+ }
301
+ };
302
+ visit(root, "");
303
+ return out;
304
+ }
305
+ function findGradleCoverageTasks(root) {
306
+ const out = [];
307
+ const visit = (dir, rel) => {
308
+ let entries;
309
+ try {
310
+ entries = readdirSync(dir, { withFileTypes: true });
311
+ }
312
+ catch {
313
+ return;
314
+ }
315
+ if (entries.some((e) => e.isFile() && (e.name === "build.gradle" || e.name === "build.gradle.kts"))) {
316
+ const relModule = rel.replace(/\\/g, "/");
317
+ const artifactRel = path.join(relModule, "build", "reports", "jacoco", "test", "jacocoTestReport.xml")
318
+ .replace(/\\/g, "/")
319
+ .replace(/^\/+/, "");
320
+ out.push({
321
+ dir,
322
+ relModule,
323
+ command: existsSync(path.join(dir, "gradlew")) ? "./gradlew" : "gradle",
324
+ args: ["test", "jacocoTestReport"],
325
+ artifactRel,
326
+ artifactAbs: path.join(root, artifactRel),
327
+ buildTool: "gradle"
328
+ });
329
+ return;
330
+ }
331
+ for (const entry of entries) {
332
+ if (!entry.isDirectory() || IGNORE_DIRS.has(entry.name))
333
+ continue;
334
+ const childRel = rel ? `${rel}/${entry.name}` : entry.name;
335
+ visit(path.join(dir, entry.name), childRel);
336
+ }
337
+ };
338
+ visit(root, "");
339
+ return out;
340
+ }
341
+ function findPythonCoverageTasks(root) {
342
+ const out = [];
343
+ const hasMarker = (entries) => entries.some((e) => e.isFile() && (e.name === "pyproject.toml" || e.name === "pytest.ini" || e.name === "setup.cfg" || e.name === "tox.ini"));
344
+ const add = (dir, rel) => {
345
+ const relModule = rel.replace(/\\/g, "/");
346
+ const label = slug(relModule || "root");
347
+ const artifactRel = `${GENERATED_COVERAGE_DIR}/python-${label}.coverage.xml`;
348
+ const artifactAbs = path.join(root, artifactRel);
349
+ out.push({
350
+ dir,
351
+ relModule,
352
+ command: "python3",
353
+ args: ["-m", "pytest", "--cov=.", `--cov-report=xml:${artifactAbs}`],
354
+ artifactRel,
355
+ artifactAbs
356
+ });
357
+ };
358
+ const visit = (dir, rel) => {
359
+ let entries;
360
+ try {
361
+ entries = readdirSync(dir, { withFileTypes: true });
362
+ }
363
+ catch {
364
+ return;
365
+ }
366
+ if (hasMarker(entries))
367
+ add(dir, rel);
368
+ for (const entry of entries) {
369
+ if (!entry.isDirectory() || IGNORE_DIRS.has(entry.name) || entry.name === "coverage")
370
+ continue;
371
+ const childRel = rel ? `${rel}/${entry.name}` : entry.name;
372
+ visit(path.join(dir, entry.name), childRel);
373
+ }
374
+ };
375
+ try {
376
+ const rootEntries = readdirSync(root, { withFileTypes: true });
377
+ if (hasMarker(rootEntries))
378
+ add(root, "");
379
+ else
380
+ visit(root, "");
381
+ }
382
+ catch {
383
+ return [];
384
+ }
385
+ return out.sort((a, b) => a.relModule.localeCompare(b.relModule));
386
+ }
387
+ function findPackageJsons(root) {
388
+ const out = [];
389
+ const visit = (dir, rel) => {
390
+ let entries;
391
+ try {
392
+ entries = readdirSync(dir, { withFileTypes: true });
393
+ }
394
+ catch {
395
+ return;
396
+ }
397
+ if (rel && isIgnoredPackageDir(rel))
398
+ return;
399
+ const pkg = entries.find((e) => e.isFile() && e.name === "package.json");
400
+ if (pkg) {
401
+ try {
402
+ out.push({ dir, json: JSON.parse(readFileSync(path.join(dir, "package.json"), "utf8")) });
403
+ }
404
+ catch {
405
+ /* ignore unreadable package.json */
406
+ }
407
+ }
408
+ for (const entry of entries) {
409
+ if (!entry.isDirectory() || IGNORE_DIRS.has(entry.name) || entry.name === "coverage")
410
+ continue;
411
+ const childRel = rel ? `${rel}/${entry.name}` : entry.name;
412
+ if (isIgnoredPackageDir(childRel))
413
+ continue;
414
+ visit(path.join(dir, entry.name), childRel);
415
+ }
416
+ };
417
+ visit(root, "");
418
+ return out;
419
+ }
420
+ function isIgnoredPackageDir(rel) {
421
+ return /(^|\/)(\.github|e2e-tests|node_modules|vendor|dist|build|target|\.orangepro)(\/|$)/.test(rel);
422
+ }
423
+ function coverageScriptForPackage(dir, pkg) {
424
+ if (pkg.workspaces !== undefined)
425
+ return null;
426
+ const scripts = pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : {};
427
+ const candidates = Object.entries(scripts)
428
+ .filter(([, value]) => typeof value === "string")
429
+ .map(([name, value]) => ({ name, value: value, score: coverageScriptScore(name, value) }))
430
+ .filter((x) => x.score > 0)
431
+ .sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
432
+ const best = candidates[0];
433
+ if (!best)
434
+ return null;
435
+ const manager = packageManagerFor(dir, pkg);
436
+ if (manager === "yarn")
437
+ return { command: "yarn", args: [best.name] };
438
+ if (manager === "pnpm")
439
+ return { command: "pnpm", args: ["run", best.name] };
440
+ return { command: "npm", args: ["run", best.name] };
441
+ }
442
+ function coverageScriptScore(name, command) {
443
+ const text = `${name} ${command}`.toLowerCase();
444
+ if (/\b(watch|e2e|cypress|playwright)\b/.test(text))
445
+ return 0;
446
+ if (!/\bcoverage\b/.test(text) && !/--coverage\b/.test(text))
447
+ return 0;
448
+ let score = 0;
449
+ if (/\b--coverage\b/.test(text) || /\bcoverage\b/.test(name.toLowerCase()))
450
+ score += 100;
451
+ if (/^test[:-]?ci$/.test(name.toLowerCase()))
452
+ score += 50;
453
+ if (/^test/.test(name.toLowerCase()))
454
+ score += 20;
455
+ if (/\b(jest|vitest)\b/.test(command.toLowerCase()))
456
+ score += 10;
457
+ return score;
458
+ }
459
+ function packageManagerFor(dir, pkg) {
460
+ const declared = typeof pkg.packageManager === "string" ? pkg.packageManager.toLowerCase() : "";
461
+ if (declared.startsWith("pnpm"))
462
+ return "pnpm";
463
+ if (declared.startsWith("yarn"))
464
+ return "yarn";
465
+ if (existsSync(path.join(dir, "pnpm-lock.yaml")))
466
+ return "pnpm";
467
+ if (existsSync(path.join(dir, "yarn.lock")))
468
+ return "yarn";
469
+ return "npm";
470
+ }
471
+ function generatedCoverageArtifacts(root) {
472
+ const dir = path.join(root, GENERATED_COVERAGE_DIR);
473
+ let entries;
474
+ try {
475
+ entries = readdirSync(dir, { withFileTypes: true });
476
+ }
477
+ catch {
478
+ return [];
479
+ }
480
+ return entries
481
+ .filter((e) => e.isFile())
482
+ .flatMap((e) => {
483
+ const rel = `${GENERATED_COVERAGE_DIR}/${e.name}`;
484
+ if (/\.(coverprofile|out|cov)$/i.test(e.name)) {
485
+ return [{ path: rel, language: "go", format: "go-coverprofile", ingestible: true }];
486
+ }
487
+ if (/\.coverage\.xml$/i.test(e.name)) {
488
+ return [{ path: rel, language: "python", format: "coverage-py", ingestible: true }];
489
+ }
490
+ return [];
491
+ });
492
+ }
493
+ function fileHasBytes(abs) {
494
+ try {
495
+ return existsSync(abs) && statSync(abs).size > 0;
496
+ }
497
+ catch {
498
+ return false;
499
+ }
500
+ }
501
+ function findExistingGoCoverprofiles(root) {
502
+ const out = [];
503
+ const visit = (dir, rel) => {
504
+ let entries;
505
+ try {
506
+ entries = readdirSync(dir, { withFileTypes: true });
507
+ }
508
+ catch {
509
+ return;
510
+ }
511
+ for (const entry of entries) {
512
+ const childRel = rel ? `${rel}/${entry.name}` : entry.name;
513
+ const childAbs = path.join(dir, entry.name);
514
+ if (entry.isDirectory()) {
515
+ if (IGNORE_DIRS.has(entry.name))
516
+ continue;
517
+ visit(childAbs, childRel);
518
+ }
519
+ else if (entry.isFile() && isGoCoverprofileName(childRel)) {
520
+ out.push(childRel);
521
+ }
522
+ }
523
+ };
524
+ visit(root, "");
525
+ return out;
526
+ }
527
+ function findExistingRuntimeCoverageReports(root) {
528
+ const out = new Map();
529
+ const visit = (dir, rel) => {
530
+ let entries;
531
+ try {
532
+ entries = readdirSync(dir, { withFileTypes: true });
533
+ }
534
+ catch {
535
+ return;
536
+ }
537
+ for (const entry of entries) {
538
+ const childRel = rel ? `${rel}/${entry.name}` : entry.name;
539
+ const childAbs = path.join(dir, entry.name);
540
+ if (entry.isDirectory()) {
541
+ if (IGNORE_DIRS.has(entry.name))
542
+ continue;
543
+ visit(childAbs, childRel);
544
+ }
545
+ else if (entry.isFile()) {
546
+ const lower = childRel.toLowerCase();
547
+ if (lower.endsWith("/lcov.info") || lower === "lcov.info") {
548
+ out.set(childRel, { path: childRel, language: "tsjs", format: "lcov", ingestible: true });
549
+ }
550
+ else if (lower.endsWith("/coverage.xml") || lower === "coverage.xml") {
551
+ out.set(childRel, { path: childRel, language: "python", format: "coverage-py", ingestible: true });
552
+ }
553
+ else if (lower.endsWith("/jacoco.xml") || lower.endsWith("/jacocotestreport.xml")) {
554
+ out.set(childRel, { path: childRel, language: "java", format: "jacoco", ingestible: true });
555
+ }
556
+ }
557
+ }
558
+ };
559
+ visit(root, "");
560
+ return [...out.values()];
561
+ }
562
+ function isGoCoverprofileName(rel) {
563
+ const base = path.posix.basename(rel);
564
+ return /^(coverage|cover)(profile)?\.(out|cov)$/i.test(base) || /\.coverprofile$/i.test(base);
565
+ }
566
+ function defaultRunner(cwd, command, args, timeoutMs) {
567
+ const res = spawnSync(command, args, { cwd, encoding: "utf8", timeout: timeoutMs, maxBuffer: 1_000_000 });
568
+ return {
569
+ status: res.status,
570
+ stdout: res.stdout ?? "",
571
+ stderr: res.stderr ?? "",
572
+ ...(res.error ? { error: res.error } : {})
573
+ };
574
+ }
575
+ function summarizeFailure(res) {
576
+ if (res.error)
577
+ return res.error.message;
578
+ const text = `${res.stderr}\n${res.stdout}`.trim();
579
+ return text ? text.slice(0, 500) : `command exited with status ${res.status ?? "unknown"}`;
580
+ }
581
+ function timeoutMsFromEnv() {
582
+ const raw = process.env.ORANGEPRO_COVERAGE_TIMEOUT_MS;
583
+ if (!raw)
584
+ return undefined;
585
+ const n = Number(raw);
586
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : undefined;
587
+ }
588
+ function budgetMsFromEnv() {
589
+ const raw = process.env.ORANGEPRO_COVERAGE_BUDGET_MS;
590
+ if (!raw)
591
+ return undefined;
592
+ const n = Number(raw);
593
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : undefined;
594
+ }
595
+ function slug(s) {
596
+ return s
597
+ .toLowerCase()
598
+ .replace(/[^a-z0-9]+/g, "-")
599
+ .replace(/^-+|-+$/g, "")
600
+ .slice(0, 80) || "root";
601
+ }
602
+ function shellToken(s) {
603
+ return /^[A-Za-z0-9_./:=+-]+$/.test(s) ? s : JSON.stringify(s);
604
+ }
605
+ function renderCommand(command, args) {
606
+ return [command, ...args].map(shellToken).join(" ");
607
+ }