phantom-pr 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 (159) hide show
  1. package/LICENSE.md +0 -0
  2. package/README.md +143 -0
  3. package/dist/adapters/git.d.ts +28 -0
  4. package/dist/adapters/git.js +112 -0
  5. package/dist/adapters/git.js.map +1 -0
  6. package/dist/adapters/github.d.ts +71 -0
  7. package/dist/adapters/github.js +194 -0
  8. package/dist/adapters/github.js.map +1 -0
  9. package/dist/cli.d.ts +47 -0
  10. package/dist/cli.js +201 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands/context.d.ts +2 -0
  13. package/dist/commands/context.js +275 -0
  14. package/dist/commands/context.js.map +1 -0
  15. package/dist/commands/full.d.ts +13 -0
  16. package/dist/commands/full.js +590 -0
  17. package/dist/commands/full.js.map +1 -0
  18. package/dist/commands/gen_test.d.ts +2 -0
  19. package/dist/commands/gen_test.js +94 -0
  20. package/dist/commands/gen_test.js.map +1 -0
  21. package/dist/commands/index.d.ts +2 -0
  22. package/dist/commands/index.js +62 -0
  23. package/dist/commands/index.js.map +1 -0
  24. package/dist/commands/plan.d.ts +2 -0
  25. package/dist/commands/plan.js +107 -0
  26. package/dist/commands/plan.js.map +1 -0
  27. package/dist/commands/pr.d.ts +9 -0
  28. package/dist/commands/pr.js +400 -0
  29. package/dist/commands/pr.js.map +1 -0
  30. package/dist/commands/pr_list.d.ts +2 -0
  31. package/dist/commands/pr_list.js +158 -0
  32. package/dist/commands/pr_list.js.map +1 -0
  33. package/dist/commands/status.d.ts +6 -0
  34. package/dist/commands/status.js +132 -0
  35. package/dist/commands/status.js.map +1 -0
  36. package/dist/commands/test.d.ts +2 -0
  37. package/dist/commands/test.js +143 -0
  38. package/dist/commands/test.js.map +1 -0
  39. package/dist/commands/testability.d.ts +10 -0
  40. package/dist/commands/testability.js +406 -0
  41. package/dist/commands/testability.js.map +1 -0
  42. package/dist/commands/tests.d.ts +9 -0
  43. package/dist/commands/tests.js +801 -0
  44. package/dist/commands/tests.js.map +1 -0
  45. package/dist/core/code/exports.d.ts +5 -0
  46. package/dist/core/code/exports.js +68 -0
  47. package/dist/core/code/exports.js.map +1 -0
  48. package/dist/core/config/forkPolicy.d.ts +25 -0
  49. package/dist/core/config/forkPolicy.js +35 -0
  50. package/dist/core/config/forkPolicy.js.map +1 -0
  51. package/dist/core/config/load.d.ts +13 -0
  52. package/dist/core/config/load.js +35 -0
  53. package/dist/core/config/load.js.map +1 -0
  54. package/dist/core/config/types.d.ts +87 -0
  55. package/dist/core/config/types.js +2 -0
  56. package/dist/core/config/types.js.map +1 -0
  57. package/dist/core/config/validate.d.ts +4 -0
  58. package/dist/core/config/validate.js +246 -0
  59. package/dist/core/config/validate.js.map +1 -0
  60. package/dist/core/context/packer.d.ts +31 -0
  61. package/dist/core/context/packer.js +345 -0
  62. package/dist/core/context/packer.js.map +1 -0
  63. package/dist/core/context/types.d.ts +41 -0
  64. package/dist/core/context/types.js +2 -0
  65. package/dist/core/context/types.js.map +1 -0
  66. package/dist/core/converge/loop.d.ts +13 -0
  67. package/dist/core/converge/loop.js +35 -0
  68. package/dist/core/converge/loop.js.map +1 -0
  69. package/dist/core/converge/types.d.ts +15 -0
  70. package/dist/core/converge/types.js +2 -0
  71. package/dist/core/converge/types.js.map +1 -0
  72. package/dist/core/generator/llm/diffApply.d.ts +26 -0
  73. package/dist/core/generator/llm/diffApply.js +276 -0
  74. package/dist/core/generator/llm/diffApply.js.map +1 -0
  75. package/dist/core/generator/llm/qualityGate.d.ts +34 -0
  76. package/dist/core/generator/llm/qualityGate.js +324 -0
  77. package/dist/core/generator/llm/qualityGate.js.map +1 -0
  78. package/dist/core/generator/llmGenerator.d.ts +34 -0
  79. package/dist/core/generator/llmGenerator.js +245 -0
  80. package/dist/core/generator/llmGenerator.js.map +1 -0
  81. package/dist/core/generator/quality.d.ts +17 -0
  82. package/dist/core/generator/quality.js +31 -0
  83. package/dist/core/generator/quality.js.map +1 -0
  84. package/dist/core/generator/registry.d.ts +26 -0
  85. package/dist/core/generator/registry.js +29 -0
  86. package/dist/core/generator/registry.js.map +1 -0
  87. package/dist/core/generator/smokeGenerator.d.ts +11 -0
  88. package/dist/core/generator/smokeGenerator.js +27 -0
  89. package/dist/core/generator/smokeGenerator.js.map +1 -0
  90. package/dist/core/generator/types.d.ts +48 -0
  91. package/dist/core/generator/types.js +2 -0
  92. package/dist/core/generator/types.js.map +1 -0
  93. package/dist/core/index/indexer.d.ts +29 -0
  94. package/dist/core/index/indexer.js +167 -0
  95. package/dist/core/index/indexer.js.map +1 -0
  96. package/dist/core/jest/parser.d.ts +17 -0
  97. package/dist/core/jest/parser.js +90 -0
  98. package/dist/core/jest/parser.js.map +1 -0
  99. package/dist/core/llm/provider.d.ts +55 -0
  100. package/dist/core/llm/provider.js +105 -0
  101. package/dist/core/llm/provider.js.map +1 -0
  102. package/dist/core/logger.d.ts +19 -0
  103. package/dist/core/logger.js +44 -0
  104. package/dist/core/logger.js.map +1 -0
  105. package/dist/core/plan/planner.d.ts +16 -0
  106. package/dist/core/plan/planner.js +91 -0
  107. package/dist/core/plan/planner.js.map +1 -0
  108. package/dist/core/process/exec.d.ts +22 -0
  109. package/dist/core/process/exec.js +83 -0
  110. package/dist/core/process/exec.js.map +1 -0
  111. package/dist/core/redact.d.ts +6 -0
  112. package/dist/core/redact.js +49 -0
  113. package/dist/core/redact.js.map +1 -0
  114. package/dist/core/repo/boundary.d.ts +10 -0
  115. package/dist/core/repo/boundary.js +58 -0
  116. package/dist/core/repo/boundary.js.map +1 -0
  117. package/dist/core/stableJson.d.ts +1 -0
  118. package/dist/core/stableJson.js +32 -0
  119. package/dist/core/stableJson.js.map +1 -0
  120. package/dist/core/state/policy.d.ts +20 -0
  121. package/dist/core/state/policy.js +51 -0
  122. package/dist/core/state/policy.js.map +1 -0
  123. package/dist/core/state/state.d.ts +67 -0
  124. package/dist/core/state/state.js +142 -0
  125. package/dist/core/state/state.js.map +1 -0
  126. package/dist/core/state/storage.d.ts +9 -0
  127. package/dist/core/state/storage.js +25 -0
  128. package/dist/core/state/storage.js.map +1 -0
  129. package/dist/core/targets/resolve.d.ts +28 -0
  130. package/dist/core/targets/resolve.js +96 -0
  131. package/dist/core/targets/resolve.js.map +1 -0
  132. package/dist/core/testGenerator/conventions.d.ts +7 -0
  133. package/dist/core/testGenerator/conventions.js +29 -0
  134. package/dist/core/testGenerator/conventions.js.map +1 -0
  135. package/dist/core/testGenerator/generate.d.ts +35 -0
  136. package/dist/core/testGenerator/generate.js +127 -0
  137. package/dist/core/testGenerator/generate.js.map +1 -0
  138. package/dist/core/testRunner/hints.d.ts +12 -0
  139. package/dist/core/testRunner/hints.js +133 -0
  140. package/dist/core/testRunner/hints.js.map +1 -0
  141. package/dist/core/testRunner/infer.d.ts +24 -0
  142. package/dist/core/testRunner/infer.js +65 -0
  143. package/dist/core/testRunner/infer.js.map +1 -0
  144. package/dist/core/testRunner/resolve.d.ts +12 -0
  145. package/dist/core/testRunner/resolve.js +31 -0
  146. package/dist/core/testRunner/resolve.js.map +1 -0
  147. package/dist/core/testRunner/runner.d.ts +24 -0
  148. package/dist/core/testRunner/runner.js +145 -0
  149. package/dist/core/testRunner/runner.js.map +1 -0
  150. package/dist/core/testability/heuristics.d.ts +7 -0
  151. package/dist/core/testability/heuristics.js +35 -0
  152. package/dist/core/testability/heuristics.js.map +1 -0
  153. package/dist/core/tests/fixers.d.ts +14 -0
  154. package/dist/core/tests/fixers.js +59 -0
  155. package/dist/core/tests/fixers.js.map +1 -0
  156. package/dist/core/tests/types.d.ts +98 -0
  157. package/dist/core/tests/types.js +2 -0
  158. package/dist/core/tests/types.js.map +1 -0
  159. package/package.json +55 -0
@@ -0,0 +1,28 @@
1
+ export type PlanTargetLike = {
2
+ id: string;
3
+ paths: string[];
4
+ reasons?: string[];
5
+ estimatedRisk?: string;
6
+ };
7
+ export type PlanLike = {
8
+ schemaVersion?: number;
9
+ targets?: PlanTargetLike[];
10
+ };
11
+ export type TargetSuggestion = {
12
+ id: string;
13
+ rootPath: string;
14
+ paths: string[];
15
+ };
16
+ export type ResolveTargetResult = {
17
+ found: true;
18
+ target: PlanTargetLike;
19
+ } | {
20
+ found: false;
21
+ suggestions: TargetSuggestion[];
22
+ };
23
+ export declare function resolveTargetIdOrPath(opts: {
24
+ root: string;
25
+ plan: PlanLike;
26
+ targetInput: string;
27
+ maxSuggestions?: number;
28
+ }): ResolveTargetResult;
@@ -0,0 +1,96 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ function toPosix(p) {
4
+ return p.split(path.sep).join('/');
5
+ }
6
+ function normalizeRel(p) {
7
+ let x = toPosix(p).trim();
8
+ if (x.startsWith('./'))
9
+ x = x.slice(2);
10
+ x = path.posix.normalize(x);
11
+ if (x === '.')
12
+ x = '';
13
+ return x;
14
+ }
15
+ function commonPrefixSegments(a, b) {
16
+ const as = normalizeRel(a).split('/').filter(Boolean);
17
+ const bs = normalizeRel(b).split('/').filter(Boolean);
18
+ const n = Math.min(as.length, bs.length);
19
+ let i = 0;
20
+ for (; i < n; i++) {
21
+ if (as[i] !== bs[i])
22
+ break;
23
+ }
24
+ return i;
25
+ }
26
+ function rootPathForTarget(t) {
27
+ const first = t.paths[0] ?? '';
28
+ const dir = path.posix.dirname(first);
29
+ return dir === '.' ? '' : dir;
30
+ }
31
+ function scoreTargetByPath(inputPath, t) {
32
+ let best = 0;
33
+ for (const p of t.paths) {
34
+ best = Math.max(best, commonPrefixSegments(inputPath, p));
35
+ }
36
+ // Also consider rootPath prefixing.
37
+ best = Math.max(best, commonPrefixSegments(inputPath, rootPathForTarget(t)));
38
+ return best;
39
+ }
40
+ function toSuggestion(t) {
41
+ const paths = [...new Set(t.paths)].sort((a, b) => a.localeCompare(b));
42
+ return { id: t.id, rootPath: rootPathForTarget(t), paths };
43
+ }
44
+ export function resolveTargetIdOrPath(opts) {
45
+ const maxSuggestions = Math.max(1, Math.min(5, opts.maxSuggestions ?? 5));
46
+ const targets = Array.isArray(opts.plan.targets) ? opts.plan.targets : [];
47
+ const input = opts.targetInput.trim();
48
+ const inputNorm = normalizeRel(input);
49
+ // 1) Exact id match.
50
+ for (const t of targets) {
51
+ if (t && typeof t.id === 'string' && t.id === input)
52
+ return { found: true, target: t };
53
+ }
54
+ // Determine whether input is an existing path in the repo.
55
+ const abs = path.resolve(opts.root, input);
56
+ const pathExists = inputNorm !== '' && fs.existsSync(abs);
57
+ if (pathExists) {
58
+ // 2) Existing path: prefer target whose rootPath is the longest prefix of provided path.
59
+ const candidates = [];
60
+ for (const t of targets) {
61
+ if (!t || typeof t.id !== 'string')
62
+ continue;
63
+ const root = rootPathForTarget(t);
64
+ const rootNorm = normalizeRel(root);
65
+ if (!rootNorm)
66
+ continue;
67
+ const prefix = rootNorm.endsWith('/') ? rootNorm : `${rootNorm}/`;
68
+ if (inputNorm === rootNorm || inputNorm.startsWith(prefix))
69
+ candidates.push({ t, root: rootNorm });
70
+ }
71
+ candidates.sort((a, b) => {
72
+ if (b.root.length !== a.root.length)
73
+ return b.root.length - a.root.length;
74
+ return a.t.id.localeCompare(b.t.id);
75
+ });
76
+ const first = candidates[0];
77
+ if (first)
78
+ return { found: true, target: first.t };
79
+ }
80
+ // 3) Closest match by prefix segments across target paths (deterministic).
81
+ const scored = targets
82
+ .filter((t) => !!t && typeof t.id === 'string' && Array.isArray(t.paths))
83
+ .map((t) => ({ t, score: scoreTargetByPath(inputNorm, t) }))
84
+ .sort((a, b) => {
85
+ if (b.score !== a.score)
86
+ return b.score - a.score;
87
+ return a.t.id.localeCompare(b.t.id);
88
+ });
89
+ const best = scored[0];
90
+ if (best && best.score > 0)
91
+ return { found: true, target: best.t };
92
+ // Suggestions: top N by score, then id. If all scores are 0, just first N by id.
93
+ const suggestions = scored.slice(0, maxSuggestions).map((s) => toSuggestion(s.t));
94
+ return { found: false, suggestions };
95
+ }
96
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/core/targets/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export type TestConvention = 'colocated_test_ts' | 'dir_tests';
2
+ export declare function detectTestConvention(indexPaths: readonly string[]): TestConvention;
3
+ export declare function computeTestPath(opts: {
4
+ convention: TestConvention;
5
+ targetPath: string;
6
+ testExt: 'ts' | 'js';
7
+ }): string;
@@ -0,0 +1,29 @@
1
+ function hasAny(paths, pred) {
2
+ for (const p of paths)
3
+ if (pred(p))
4
+ return true;
5
+ return false;
6
+ }
7
+ export function detectTestConvention(indexPaths) {
8
+ // Prefer an existing convention in the repo; otherwise default to colocated for simplicity.
9
+ const hasDirTests = hasAny(indexPaths, (p) => p.includes('/__tests__/') && (p.endsWith('.test.ts') || p.endsWith('.test.js')));
10
+ if (hasDirTests)
11
+ return 'dir_tests';
12
+ const hasColocated = hasAny(indexPaths, (p) => p.startsWith('src/') && (p.endsWith('.test.ts') || p.endsWith('.test.js')));
13
+ if (hasColocated)
14
+ return 'colocated_test_ts';
15
+ return 'colocated_test_ts';
16
+ }
17
+ export function computeTestPath(opts) {
18
+ const target = opts.targetPath;
19
+ const dot = target.lastIndexOf('.');
20
+ const base = dot === -1 ? target : target.slice(0, dot);
21
+ const fileName = base.split('/').pop() ?? base;
22
+ const dir = base.includes('/') ? base.split('/').slice(0, -1).join('/') : '';
23
+ if (opts.convention === 'dir_tests') {
24
+ const testsDir = dir ? `${dir}/__tests__` : '__tests__';
25
+ return `${testsDir}/${fileName}.test.${opts.testExt}`;
26
+ }
27
+ return `${base}.test.${opts.testExt}`;
28
+ }
29
+ //# sourceMappingURL=conventions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conventions.js","sourceRoot":"","sources":["../../../src/core/testGenerator/conventions.ts"],"names":[],"mappings":"AAEA,SAAS,MAAM,CAAC,KAAwB,EAAE,IAA4B;IACpE,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,IAAI,IAAI,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;IAChD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,UAA6B;IAChE,4FAA4F;IAC5F,MAAM,WAAW,GAAG,MAAM,CACxB,UAAU,EACV,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CACvF,CAAC;IACF,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IAEpC,MAAM,YAAY,GAAG,MAAM,CACzB,UAAU,EACV,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAClF,CAAC;IACF,IAAI,YAAY;QAAE,OAAO,mBAAmB,CAAC;IAE7C,OAAO,mBAAmB,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAI/B;IACC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;IAC/B,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAE7E,IAAI,IAAI,CAAC,UAAU,KAAK,WAAW,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;QACxD,OAAO,GAAG,QAAQ,IAAI,QAAQ,SAAS,IAAI,CAAC,OAAO,EAAE,CAAC;IACxD,CAAC;IAED,OAAO,GAAG,IAAI,SAAS,IAAI,CAAC,OAAO,EAAE,CAAC;AACxC,CAAC"}
@@ -0,0 +1,35 @@
1
+ export type GenerateTestResult = {
2
+ created: true;
3
+ testPath: string;
4
+ reason: string;
5
+ generatedFiles: string[];
6
+ targetId: string;
7
+ targetPath: string;
8
+ assumptionsUsed: string[];
9
+ warnings: string[];
10
+ } | {
11
+ created: false;
12
+ testPath: string | null;
13
+ reason: string;
14
+ generatedFiles: string[];
15
+ targetId: string;
16
+ targetPath: string;
17
+ assumptionsUsed: string[];
18
+ warnings: string[];
19
+ };
20
+ type IndexFile = {
21
+ path: string;
22
+ };
23
+ export declare function previewGeneratedTestFiles(opts: {
24
+ targetPath: string;
25
+ indexFiles: IndexFile[];
26
+ }): {
27
+ testPath: string;
28
+ wouldCreate: boolean;
29
+ };
30
+ export declare function generateJestTest(opts: {
31
+ root: string;
32
+ targetPath: string;
33
+ indexFiles: IndexFile[];
34
+ }): GenerateTestResult;
35
+ export {};
@@ -0,0 +1,127 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { computeTestPath, detectTestConvention } from './conventions.js';
5
+ import { extractNamedExportsFromJsLike } from '../code/exports.js';
6
+ function toPosix(p) {
7
+ return p.split(path.sep).join('/');
8
+ }
9
+ function isJsLike(targetPosix) {
10
+ const ext = path.posix.extname(targetPosix).toLowerCase();
11
+ return ext === '.js' || ext === '.jsx' || ext === '.cjs' || ext === '.mjs';
12
+ }
13
+ export function previewGeneratedTestFiles(opts) {
14
+ const targetPosix = toPosix(opts.targetPath);
15
+ const indexPaths = opts.indexFiles.map((f) => f.path);
16
+ const convention = detectTestConvention(indexPaths);
17
+ const testExt = isJsLike(targetPosix) ? 'js' : 'ts';
18
+ const testPath = computeTestPath({ convention, targetPath: targetPosix, testExt });
19
+ const wouldCreate = !indexPaths.includes(testPath);
20
+ return { testPath, wouldCreate };
21
+ }
22
+ function uniqSorted(xs) {
23
+ return [...new Set(xs)].sort((a, b) => a.localeCompare(b));
24
+ }
25
+ function stableIdForTargetPath(targetPosix) {
26
+ const h = crypto.createHash('sha1').update(targetPosix).digest('hex').slice(0, 8);
27
+ return `tg_${h}`;
28
+ }
29
+ function detectModuleSystem(rootAbs, targetPosix) {
30
+ const ext = path.posix.extname(targetPosix).toLowerCase();
31
+ if (ext === '.cjs')
32
+ return 'cjs';
33
+ if (ext === '.mjs')
34
+ return 'esm';
35
+ try {
36
+ const pkgPath = path.join(rootAbs, 'package.json');
37
+ if (!fs.existsSync(pkgPath))
38
+ return 'cjs';
39
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
40
+ return pkg.type === 'module' ? 'esm' : 'cjs';
41
+ }
42
+ catch {
43
+ return 'cjs';
44
+ }
45
+ }
46
+ function renderTest(opts) {
47
+ const lines = [];
48
+ lines.push('// Generated by test-agent. Safe smoke test; edit as needed.');
49
+ lines.push('');
50
+ lines.push(`describe('${opts.targetPath}', () => {`);
51
+ if (opts.moduleSystem === 'cjs') {
52
+ lines.push(` it('loads and has expected exports', () => {`);
53
+ lines.push(` // eslint-disable-next-line @typescript-eslint/no-var-requires`);
54
+ lines.push(` const mod = require('${opts.importPath}');`);
55
+ }
56
+ else {
57
+ lines.push(` it('loads and has expected exports', async () => {`);
58
+ lines.push(` const mod = await import('${opts.importPath}');`);
59
+ }
60
+ lines.push(` expect(mod).toBeDefined();`);
61
+ if (opts.moduleSystem === 'esm' && opts.exports.hasDefault)
62
+ lines.push(` expect(mod.default).toBeDefined();`);
63
+ for (const name of opts.exports.named)
64
+ lines.push(` expect(mod.${name}).toBeDefined();`);
65
+ lines.push(' });');
66
+ lines.push('});');
67
+ lines.push('');
68
+ return lines.join('\n');
69
+ }
70
+ function relativeImport(fromTestPath, toTargetPath) {
71
+ const fromDir = path.posix.dirname(fromTestPath);
72
+ const rel = path.posix.relative(fromDir, toTargetPath);
73
+ const noExt = rel.replace(/\.[^.]+$/, '');
74
+ return noExt.startsWith('.') ? noExt : `./${noExt}`;
75
+ }
76
+ export function generateJestTest(opts) {
77
+ const rootAbs = path.resolve(opts.root);
78
+ const targetPosix = toPosix(opts.targetPath);
79
+ const targetId = stableIdForTargetPath(targetPosix);
80
+ const warnings = [];
81
+ const assumptionsUsed = [];
82
+ const indexPaths = opts.indexFiles.map((f) => f.path);
83
+ const convention = detectTestConvention(indexPaths);
84
+ assumptionsUsed.push(`testConvention=${convention}`);
85
+ const targetExt = path.posix.extname(targetPosix).toLowerCase();
86
+ const testExt = isJsLike(targetPosix) ? 'js' : 'ts';
87
+ assumptionsUsed.push(`testExt=${testExt}`);
88
+ const testPath = computeTestPath({ convention, targetPath: targetPosix, testExt });
89
+ // Idempotency: if any likely test file already exists, do not create a new one.
90
+ const likelyExisting = indexPaths.includes(testPath);
91
+ const testAbs = path.join(rootAbs, ...testPath.split('/'));
92
+ if (likelyExisting || fs.existsSync(testAbs)) {
93
+ return {
94
+ created: false,
95
+ testPath,
96
+ reason: 'Test file already exists; skipping',
97
+ generatedFiles: [],
98
+ targetId,
99
+ targetPath: targetPosix,
100
+ assumptionsUsed,
101
+ warnings,
102
+ };
103
+ }
104
+ const targetAbs = path.join(rootAbs, ...targetPosix.split('/'));
105
+ const source = fs.readFileSync(targetAbs, 'utf8');
106
+ const exp = extractNamedExportsFromJsLike(source);
107
+ if (exp.named.length === 0 && !exp.hasDefault)
108
+ warnings.push('No obvious exports found; generating load-only test');
109
+ const moduleSystem = detectModuleSystem(rootAbs, targetPosix);
110
+ assumptionsUsed.push(`moduleSystem=${moduleSystem}`);
111
+ assumptionsUsed.push(moduleSystem === 'cjs' ? 'importStyle=require' : 'importStyle=dynamic_import');
112
+ const importPath = relativeImport(testPath, targetPosix);
113
+ const content = renderTest({ targetPath: targetPosix, importPath, exports: exp, moduleSystem });
114
+ fs.mkdirSync(path.dirname(testAbs), { recursive: true });
115
+ fs.writeFileSync(testAbs, content, 'utf8');
116
+ return {
117
+ created: true,
118
+ testPath,
119
+ reason: 'Generated smoke test',
120
+ generatedFiles: [testPath],
121
+ targetId,
122
+ targetPath: targetPosix,
123
+ assumptionsUsed,
124
+ warnings,
125
+ };
126
+ }
127
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/core/testGenerator/generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export type RunnerKind = 'jest' | 'vitest' | 'pytest' | 'unknown';
2
+ export type RunnerHints = {
3
+ failingFiles: string[];
4
+ failingTests: string[];
5
+ };
6
+ export declare function extractRunnerHints(opts: {
7
+ command: string;
8
+ rawOutput: string;
9
+ }): {
10
+ runner: RunnerKind;
11
+ hints: RunnerHints;
12
+ };
@@ -0,0 +1,133 @@
1
+ function uniqSorted(values) {
2
+ const out = [...new Set(values.map((s) => s.trim()).filter((s) => s !== ''))];
3
+ out.sort((a, b) => a.localeCompare(b));
4
+ return out;
5
+ }
6
+ function detectRunner(command, output) {
7
+ const c = command.toLowerCase();
8
+ const o = output.toLowerCase();
9
+ if (c.includes('pytest') || o.includes('pytest') || o.includes('collected ') || o.includes('== fail'))
10
+ return 'pytest';
11
+ // Vitest: detect by command, explicit banner, or common UI glyphs in Vitest output.
12
+ // Keep this best-effort (false positives are acceptable; hints remain best-effort).
13
+ if (c.includes('vitest') || o.includes('vitest'))
14
+ return 'vitest';
15
+ if (output.includes('✓') || output.includes('×') || output.includes('❯')) {
16
+ // Heuristic: Vitest uses these glyphs heavily; combined with FAIL lines it’s a good signal.
17
+ if (o.includes('fail'))
18
+ return 'vitest';
19
+ }
20
+ if (c.includes('jest') || o.includes('jest') || o.includes('test suites:'))
21
+ return 'jest';
22
+ return 'unknown';
23
+ }
24
+ function extractFileLikePathsFromFailLines(output) {
25
+ // Best-effort: collect file-like paths pointing to tests, but only from failing lines.
26
+ // This avoids polluting hints with passing test files (common in Vitest output).
27
+ const out = [];
28
+ const re = /([A-Za-z]:)?[^\s'"]*(?:__tests__|\.test\.)[^\s'"]*/g;
29
+ for (const line of output.split('\n')) {
30
+ const l = line.trim();
31
+ if (!l.startsWith('FAIL '))
32
+ continue;
33
+ let m;
34
+ while ((m = re.exec(l))) {
35
+ const v = m[0] ?? '';
36
+ if (v.includes('__tests__') || v.includes('.test.'))
37
+ out.push(v);
38
+ if (out.length >= 200)
39
+ break;
40
+ }
41
+ if (out.length >= 200)
42
+ break;
43
+ }
44
+ return out;
45
+ }
46
+ function extractJestVitestHints(output) {
47
+ const failingFiles = [];
48
+ const failingTests = [];
49
+ for (const line of output.split('\n')) {
50
+ const l = line.trim();
51
+ // Jest and Vitest both commonly print: "FAIL path/to/file.test.ts"
52
+ if (l.startsWith('FAIL ')) {
53
+ const rest = l.slice('FAIL '.length).trim();
54
+ if (rest)
55
+ failingFiles.push(rest.split(/\s+/)[0] ?? rest);
56
+ }
57
+ // Jest: "● suite › test"
58
+ if (l.startsWith('● ')) {
59
+ const rest = l.slice(2).trim();
60
+ if (rest)
61
+ failingTests.push(rest);
62
+ }
63
+ // Vitest often: "FAIL file.test.ts > suite > test"
64
+ if (l.startsWith('FAIL ') && l.includes('>')) {
65
+ const parts = l.split('>').map((x) => x.trim()).filter(Boolean);
66
+ if (parts.length >= 2)
67
+ failingTests.push(parts.slice(1).join(' > '));
68
+ }
69
+ }
70
+ failingFiles.push(...extractFileLikePathsFromFailLines(output));
71
+ return { failingFiles: uniqSorted(failingFiles), failingTests: uniqSorted(failingTests) };
72
+ }
73
+ function extractPytestHints(output) {
74
+ const failingFiles = [];
75
+ const failingTests = [];
76
+ for (const line of output.split('\n')) {
77
+ const l = line.trim();
78
+ // Pytest: "FAILED path/to/test_file.py::TestClass::test_x - ..."
79
+ if (l.startsWith('FAILED ')) {
80
+ const rest = l.slice('FAILED '.length).trim();
81
+ const nodeid = rest.split(/\s+-\s+|\s+/)[0] ?? rest;
82
+ if (nodeid)
83
+ failingTests.push(nodeid);
84
+ const file = nodeid.split('::')[0];
85
+ if (file && file.endsWith('.py'))
86
+ failingFiles.push(file);
87
+ }
88
+ }
89
+ // Also accept nodeids anywhere in the output.
90
+ const nodeidRe = /([A-Za-z]:)?[^\s'"]+\.py(?:::[^\s'"]+)+/g;
91
+ let m;
92
+ while ((m = nodeidRe.exec(output))) {
93
+ const nodeid = m[0] ?? '';
94
+ failingTests.push(nodeid);
95
+ const file = nodeid.split('::')[0];
96
+ if (file && file.endsWith('.py'))
97
+ failingFiles.push(file);
98
+ if (failingTests.length >= 200)
99
+ break;
100
+ }
101
+ // Traceback-ish file references like "path/to/test_x.py:12: AssertionError"
102
+ const fileLineRe = /([A-Za-z]:)?[^\s'"]+\.py:\d+:/g;
103
+ while ((m = fileLineRe.exec(output))) {
104
+ const hit = m[0] ?? '';
105
+ const file = hit.split(':')[0] ?? '';
106
+ if (file && file.endsWith('.py'))
107
+ failingFiles.push(file);
108
+ if (failingFiles.length >= 200)
109
+ break;
110
+ }
111
+ return { failingFiles: uniqSorted(failingFiles), failingTests: uniqSorted(failingTests) };
112
+ }
113
+ export function extractRunnerHints(opts) {
114
+ const runner = detectRunner(opts.command, opts.rawOutput);
115
+ try {
116
+ if (runner === 'pytest')
117
+ return { runner, hints: extractPytestHints(opts.rawOutput) };
118
+ if (runner === 'jest' || runner === 'vitest')
119
+ return { runner, hints: extractJestVitestHints(opts.rawOutput) };
120
+ // unknown: try both extractors but keep runner=unknown.
121
+ const a = extractJestVitestHints(opts.rawOutput);
122
+ const b = extractPytestHints(opts.rawOutput);
123
+ const hints = {
124
+ failingFiles: uniqSorted([...a.failingFiles, ...b.failingFiles]),
125
+ failingTests: uniqSorted([...a.failingTests, ...b.failingTests]),
126
+ };
127
+ return { runner: 'unknown', hints };
128
+ }
129
+ catch {
130
+ return { runner, hints: { failingFiles: [], failingTests: [] } };
131
+ }
132
+ }
133
+ //# sourceMappingURL=hints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hints.js","sourceRoot":"","sources":["../../../src/core/testRunner/hints.ts"],"names":[],"mappings":"AAOA,SAAS,UAAU,CAAC,MAAgB;IAClC,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9E,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,YAAY,CAAC,OAAe,EAAE,MAAc;IACnD,MAAM,CAAC,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAChC,MAAM,CAAC,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IAC/B,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,QAAQ,CAAC;IACvH,oFAAoF;IACpF,oFAAoF;IACpF,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAClE,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACzE,4FAA4F;QAC5F,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,QAAQ,CAAC;IAC1C,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC;QAAE,OAAO,MAAM,CAAC;IAC1F,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,iCAAiC,CAAC,MAAc;IACvD,uFAAuF;IACvF,iFAAiF;IACjF,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,EAAE,GAAG,qDAAqD,CAAC;IACjE,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,SAAS;QACrC,IAAI,CAAyB,CAAC;QAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACxB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACrB,IAAI,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACjE,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG;gBAAE,MAAM;QAC/B,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG;YAAE,MAAM;IAC/B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,sBAAsB,CAAC,MAAc;IAC5C,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,YAAY,GAAa,EAAE,CAAC;IAElC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACtB,oEAAoE;QACpE,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;QAC5D,CAAC;QACD,yBAAyB;QACzB,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC/B,IAAI,IAAI;gBAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;QACD,oDAAoD;QACpD,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAChE,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;gBAAE,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IAED,YAAY,CAAC,IAAI,CAAC,GAAG,iCAAiC,CAAC,MAAM,CAAC,CAAC,CAAC;IAEhE,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,YAAY,CAAC,EAAE,YAAY,EAAE,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;AAC5F,CAAC;AAED,SAAS,kBAAkB,CAAC,MAAc;IACxC,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,YAAY,GAAa,EAAE,CAAC;IAElC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACtB,iEAAiE;QACjE,IAAI,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;YACpD,IAAI,MAAM;gBAAE,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACnC,IAAI,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,8CAA8C;IAC9C,MAAM,QAAQ,GAAG,0CAA0C,CAAC;IAC5D,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1B,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1D,IAAI,YAAY,CAAC,MAAM,IAAI,GAAG;YAAE,MAAM;IACxC,CAAC;IAED,4EAA4E;IAC5E,MAAM,UAAU,GAAG,gCAAgC,CAAC;IACpD,OAAO,CAAC,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,IAAI,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1D,IAAI,YAAY,CAAC,MAAM,IAAI,GAAG;YAAE,MAAM;IACxC,CAAC;IAED,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,YAAY,CAAC,EAAE,YAAY,EAAE,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;AAC5F,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAA4C;IAC7E,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1D,IAAI,CAAC;QACH,IAAI,MAAM,KAAK,QAAQ;YAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QACtF,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,QAAQ;YAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/G,wDAAwD;QACxD,MAAM,CAAC,GAAG,sBAAsB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,CAAC,GAAG,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAgB;YACzB,YAAY,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;YAChE,YAAY,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;SACjE,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,EAAE,CAAC;IACnE,CAAC;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ export type InferredRunner = 'jest' | 'vitest' | 'pytest' | 'unknown';
2
+ export type InferredSource = 'config' | 'policy' | 'package_json' | 'inferred' | 'none';
3
+ export type InferInput = {
4
+ rootAbs: string;
5
+ packageJson: {
6
+ scripts?: Record<string, string>;
7
+ } | null;
8
+ pythonBoundary: {
9
+ boundaryFileRel: string | null;
10
+ } | null;
11
+ config: {
12
+ testCommand: string | null;
13
+ };
14
+ policy: {
15
+ workingTestCommand: string | null;
16
+ };
17
+ };
18
+ export type InferResult = {
19
+ runner: InferredRunner;
20
+ command: string | null;
21
+ source: InferredSource;
22
+ reason: string;
23
+ };
24
+ export declare function inferTestCommandAndRunner(input: InferInput): InferResult;
@@ -0,0 +1,65 @@
1
+ function runnerFromCommand(command) {
2
+ const c = command.toLowerCase();
3
+ if (c.includes('pytest'))
4
+ return 'pytest';
5
+ if (c.includes('vitest'))
6
+ return 'vitest';
7
+ if (c.includes('jest'))
8
+ return 'jest';
9
+ return 'unknown';
10
+ }
11
+ function runnerFromScriptText(script) {
12
+ const s = script.toLowerCase();
13
+ if (s.includes('vitest'))
14
+ return 'vitest';
15
+ if (s.includes('jest'))
16
+ return 'jest';
17
+ return 'unknown';
18
+ }
19
+ const SCRIPT_ORDER = ['test:unit', 'test', 'unit', 'test-ci'];
20
+ function firstMatchingScript(scripts) {
21
+ if (!scripts)
22
+ return null;
23
+ for (const name of SCRIPT_ORDER) {
24
+ const v = scripts[name];
25
+ if (typeof v === 'string' && v.trim() !== '')
26
+ return { name, value: v };
27
+ }
28
+ return null;
29
+ }
30
+ export function inferTestCommandAndRunner(input) {
31
+ const fromConfig = input.config.testCommand?.trim() ?? '';
32
+ if (fromConfig) {
33
+ return {
34
+ runner: runnerFromCommand(fromConfig),
35
+ command: fromConfig,
36
+ source: 'config',
37
+ reason: 'config.test.command',
38
+ };
39
+ }
40
+ const fromPolicy = input.policy.workingTestCommand?.trim() ?? '';
41
+ if (fromPolicy) {
42
+ return {
43
+ runner: runnerFromCommand(fromPolicy),
44
+ command: fromPolicy,
45
+ source: 'policy',
46
+ reason: 'policy.workingTestCommand',
47
+ };
48
+ }
49
+ const hit = firstMatchingScript(input.packageJson?.scripts);
50
+ if (hit) {
51
+ const runner = runnerFromScriptText(hit.value);
52
+ return {
53
+ runner,
54
+ command: `npm run ${hit.name}`,
55
+ source: 'package_json',
56
+ reason: `package.json scripts.${hit.name}`,
57
+ };
58
+ }
59
+ if (input.pythonBoundary) {
60
+ const marker = input.pythonBoundary.boundaryFileRel ? ` (${input.pythonBoundary.boundaryFileRel})` : '';
61
+ return { runner: 'pytest', command: 'python -m pytest -q', source: 'inferred', reason: `python boundary${marker}` };
62
+ }
63
+ return { runner: 'unknown', command: null, source: 'none', reason: 'no_match' };
64
+ }
65
+ //# sourceMappingURL=infer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infer.js","sourceRoot":"","sources":["../../../src/core/testRunner/infer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export type ResolvedTestCommand = {
2
+ command: string;
3
+ source: 'config' | 'policy' | 'package_json' | 'inferred';
4
+ reason: string;
5
+ };
6
+ export declare function resolveTestCommand(opts: {
7
+ root: string;
8
+ configTestCommand: string | null;
9
+ policyWorkingTestCommand: string | null;
10
+ boundaryKind?: 'js' | 'py' | 'unknown';
11
+ boundaryFileRel?: string | null;
12
+ }): ResolvedTestCommand | null;
@@ -0,0 +1,31 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { inferTestCommandAndRunner } from './infer.js';
4
+ function readPackageJson(root) {
5
+ const p = path.join(root, 'package.json');
6
+ if (!fs.existsSync(p))
7
+ return null;
8
+ try {
9
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
15
+ export function resolveTestCommand(opts) {
16
+ const pkg = readPackageJson(opts.root);
17
+ const inferred = inferTestCommandAndRunner({
18
+ rootAbs: opts.root,
19
+ packageJson: pkg,
20
+ pythonBoundary: opts.boundaryKind === 'py' ? { boundaryFileRel: opts.boundaryFileRel ?? null } : null,
21
+ config: { testCommand: opts.configTestCommand },
22
+ policy: { workingTestCommand: opts.policyWorkingTestCommand },
23
+ });
24
+ if (!inferred.command)
25
+ return null;
26
+ if (inferred.source === 'none')
27
+ return null;
28
+ // resolveTestCommand historically only returns a command when we have one; keep that contract.
29
+ return { command: inferred.command, source: inferred.source === 'package_json' ? 'package_json' : inferred.source, reason: inferred.reason };
30
+ }
31
+ //# sourceMappingURL=resolve.js.map