@planu/cli 4.3.9 → 4.3.11
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.
- package/CHANGELOG.md +8 -0
- package/dist/cli/commands/status.js +79 -46
- package/dist/engine/pr-description-generator.js +5 -2
- package/dist/engine/spec-generator/api-key-resolver.d.ts +4 -0
- package/dist/engine/spec-generator/api-key-resolver.js +31 -0
- package/dist/engine/spec-generator/index.d.ts +3 -0
- package/dist/engine/spec-generator/index.js +3 -0
- package/dist/engine/spec-generator/opus-generator.d.ts +12 -0
- package/dist/engine/spec-generator/opus-generator.js +97 -0
- package/dist/engine/spec-generator/quality-validator.d.ts +5 -0
- package/dist/engine/spec-generator/quality-validator.js +22 -0
- package/dist/engine/spec-migrator/planu-canonical-policy.js +9 -1
- package/dist/engine/spec-migrator/strict-planu-cleanup.js +11 -2
- package/dist/engine/validator/checklist.js +1 -1
- package/dist/resources/process.js +1 -1
- package/dist/resources/templates.js +1 -1
- package/dist/tools/create-spec.js +5 -2
- package/dist/types/cli.d.ts +17 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/types/spec-format.d.ts +2 -2
- package/dist/types/spec-generator.d.ts +38 -0
- package/package.json +11 -10
- package/dist/engine/context-intelligence/index.d.ts +0 -7
- package/dist/engine/context-intelligence/index.js +0 -6
- package/dist/engine/legacy/ast-analyzer.d.ts +0 -21
- package/dist/engine/legacy/ast-analyzer.js +0 -113
- package/dist/engine/legacy/characterization-generator.d.ts +0 -6
- package/dist/engine/legacy/characterization-generator.js +0 -146
- package/dist/engine/legacy/hyrum-scanner.d.ts +0 -6
- package/dist/engine/legacy/hyrum-scanner.js +0 -137
- package/dist/engine/legacy/safety-net-orchestrator.d.ts +0 -7
- package/dist/engine/legacy/safety-net-orchestrator.js +0 -114
- package/dist/engine/legacy/seam-finder.d.ts +0 -6
- package/dist/engine/legacy/seam-finder.js +0 -139
- package/dist/tools/legacy/characterize-legacy-code.d.ts +0 -9
- package/dist/tools/legacy/characterize-legacy-code.js +0 -63
- package/dist/tools/legacy/detect-hyrum-risks.d.ts +0 -8
- package/dist/tools/legacy/detect-hyrum-risks.js +0 -47
- package/dist/tools/legacy/refactor-with-safety-net.d.ts +0 -10
- package/dist/tools/legacy/refactor-with-safety-net.js +0 -55
- package/dist/tools/legacy/seams-detector.d.ts +0 -8
- package/dist/tools/legacy/seams-detector.js +0 -47
- package/dist/types/legacy.d.ts +0 -151
- package/dist/types/legacy.js +0 -5
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
// Planu — engine/legacy/seam-finder.ts
|
|
2
|
-
// Finds dependency break points (seams) in TypeScript/JavaScript files.
|
|
3
|
-
// Patterns: static calls, globals, new() inline, hardcoded paths/URLs.
|
|
4
|
-
import { glob } from 'glob';
|
|
5
|
-
import { analyzeFile } from './ast-analyzer.js';
|
|
6
|
-
const SEAM_PATTERNS = [
|
|
7
|
-
{
|
|
8
|
-
category: 'static-call',
|
|
9
|
-
regex: /\b([A-Z]\w+)\.(\w+)\s*\(/,
|
|
10
|
-
breakability: 'moderate',
|
|
11
|
-
describe: (m) => `Static call to ${m[1] ?? '?'}.${m[2] ?? '?'}() — hard to mock/replace`,
|
|
12
|
-
suggest: () => 'Inject an interface/dependency instead of calling static method directly',
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
category: 'global-env',
|
|
16
|
-
regex: /process\.env\.(\w+)/,
|
|
17
|
-
breakability: 'easy',
|
|
18
|
-
describe: (m) => `Direct process.env.${m[1] ?? '?'} access — not injectable`,
|
|
19
|
-
suggest: (m) => `Extract to a config object or inject \`${m[1] ?? 'CONFIG'}\` via parameter`,
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
category: 'inline-new',
|
|
23
|
-
regex: /=\s*new\s+([A-Z]\w+)\s*\(/,
|
|
24
|
-
breakability: 'hard',
|
|
25
|
-
describe: (m) => `Inline \`new ${m[1] ?? '?'}()\` inside function — cannot be replaced`,
|
|
26
|
-
suggest: (m) => `Inject a factory or pass \`${m[1] ?? 'Dep'}Factory\` as a parameter`,
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
category: 'hardcoded-path',
|
|
30
|
-
regex: /['"`](\/[a-zA-Z0-9_\-/]+\.[a-zA-Z]{1,5})['"`]/,
|
|
31
|
-
breakability: 'easy',
|
|
32
|
-
describe: (m) => `Hardcoded file path: ${m[1] ?? '?'}`,
|
|
33
|
-
suggest: () => 'Move path to a config constant or inject via parameter',
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
category: 'hardcoded-url',
|
|
37
|
-
regex: /['"`](https?:\/\/[^'"`\s]+)['"`]/,
|
|
38
|
-
breakability: 'easy',
|
|
39
|
-
describe: (m) => `Hardcoded URL: ${(m[1] ?? '').slice(0, 60)}`,
|
|
40
|
-
suggest: () => 'Move URL to a config object or environment variable',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
category: 'module-singleton',
|
|
44
|
-
regex: /^(?:import\s+\{[^}]+\}\s+from|const\s+\w+\s*=\s*require\s*\()/m,
|
|
45
|
-
breakability: 'moderate',
|
|
46
|
-
describe: () => 'Module-level singleton import — creates hidden dependency',
|
|
47
|
-
suggest: () => 'Pass the dependency via constructor or function parameter for testability',
|
|
48
|
-
},
|
|
49
|
-
];
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Scanner
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
let seamCounter = 0;
|
|
54
|
-
function makeSeamId() {
|
|
55
|
-
seamCounter++;
|
|
56
|
-
return `SM-${String(seamCounter).padStart(4, '0')}`;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Scans a single file for seams.
|
|
60
|
-
*/
|
|
61
|
-
async function scanFile(filePath) {
|
|
62
|
-
const analysis = await analyzeFile(filePath);
|
|
63
|
-
if (analysis === null) {
|
|
64
|
-
return [];
|
|
65
|
-
}
|
|
66
|
-
const seams = [];
|
|
67
|
-
for (const fn of analysis.functions) {
|
|
68
|
-
const bodyLines = fn.body.split('\n');
|
|
69
|
-
for (const pattern of SEAM_PATTERNS) {
|
|
70
|
-
for (let i = 0; i < bodyLines.length; i++) {
|
|
71
|
-
const line = bodyLines[i] ?? '';
|
|
72
|
-
const match = line.match(pattern.regex);
|
|
73
|
-
if (match) {
|
|
74
|
-
const absoluteLine = fn.line + i;
|
|
75
|
-
seams.push({
|
|
76
|
-
id: makeSeamId(),
|
|
77
|
-
category: pattern.category,
|
|
78
|
-
file: filePath,
|
|
79
|
-
line: absoluteLine,
|
|
80
|
-
functionName: fn.name,
|
|
81
|
-
description: pattern.describe(match),
|
|
82
|
-
pattern: match[0],
|
|
83
|
-
suggestion: pattern.suggest(match),
|
|
84
|
-
breakability: pattern.breakability,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return seams;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Resolves the list of files to scan.
|
|
94
|
-
*/
|
|
95
|
-
async function resolveFiles(projectPath, files) {
|
|
96
|
-
if (files !== undefined && files.length > 0) {
|
|
97
|
-
return files;
|
|
98
|
-
}
|
|
99
|
-
return glob('src/**/*.{ts,tsx,js,jsx}', {
|
|
100
|
-
cwd: projectPath,
|
|
101
|
-
absolute: true,
|
|
102
|
-
ignore: ['**/node_modules/**', '**/dist/**', '**/*.test.*', '**/*.spec.*'],
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Scans the given files (or project) for seams.
|
|
107
|
-
*/
|
|
108
|
-
export async function findSeams(projectPath, files) {
|
|
109
|
-
seamCounter = 0;
|
|
110
|
-
const resolvedFiles = await resolveFiles(projectPath, files);
|
|
111
|
-
const BATCH = 20;
|
|
112
|
-
const allSeams = [];
|
|
113
|
-
for (let i = 0; i < resolvedFiles.length; i += BATCH) {
|
|
114
|
-
const batch = resolvedFiles.slice(i, i + BATCH);
|
|
115
|
-
const batchResults = await Promise.all(batch.map((f) => scanFile(f)));
|
|
116
|
-
for (const r of batchResults) {
|
|
117
|
-
allSeams.push(...r);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
// Sort by breakability (hard first = most valuable to break), then file
|
|
121
|
-
const breakOrder = { hard: 0, moderate: 1, easy: 2 };
|
|
122
|
-
allSeams.sort((a, b) => {
|
|
123
|
-
const bk = breakOrder[a.breakability] - breakOrder[b.breakability];
|
|
124
|
-
if (bk !== 0) {
|
|
125
|
-
return bk;
|
|
126
|
-
}
|
|
127
|
-
return a.file.localeCompare(b.file);
|
|
128
|
-
});
|
|
129
|
-
const hardCount = allSeams.filter((s) => s.breakability === 'hard').length;
|
|
130
|
-
const modCount = allSeams.filter((s) => s.breakability === 'moderate').length;
|
|
131
|
-
const easyCount = allSeams.filter((s) => s.breakability === 'easy').length;
|
|
132
|
-
return {
|
|
133
|
-
projectPath,
|
|
134
|
-
filesScanned: resolvedFiles.length,
|
|
135
|
-
seams: allSeams,
|
|
136
|
-
summary: `Scanned ${resolvedFiles.length} files. Found ${allSeams.length} seams: ${hardCount} hard, ${modCount} moderate, ${easyCount} easy to break.`,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
//# sourceMappingURL=seam-finder.js.map
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import type { ToolResult, CharacterizeLegacyInput } from '../../types/index.js';
|
|
3
|
-
export declare const CharacterizeLegacyCodeInputSchema: {
|
|
4
|
-
projectPath: z.ZodString;
|
|
5
|
-
files: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
6
|
-
outputFile: z.ZodOptional<z.ZodString>;
|
|
7
|
-
};
|
|
8
|
-
export declare function handleCharacterizeLegacyCode(args: CharacterizeLegacyInput): Promise<ToolResult>;
|
|
9
|
-
//# sourceMappingURL=characterize-legacy-code.d.ts.map
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// Planu — tools/legacy/characterize-legacy-code.ts
|
|
2
|
-
// Tool handler for characterize_legacy_code.
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { writeFile, mkdir } from 'node:fs/promises';
|
|
5
|
-
import { dirname, join } from 'node:path';
|
|
6
|
-
import { generateCharacterizationTests } from '../../engine/legacy/characterization-generator.js';
|
|
7
|
-
import { compactResult } from '../output-formatter.js';
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
// Input schema
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
export const CharacterizeLegacyCodeInputSchema = {
|
|
12
|
-
projectPath: z.string().max(4096).describe('Absolute path to the project root to analyze'),
|
|
13
|
-
files: z
|
|
14
|
-
.array(z.string())
|
|
15
|
-
.optional()
|
|
16
|
-
.describe('Optional list of absolute file paths to analyze. If omitted, scans src/**/*.{ts,js}'),
|
|
17
|
-
outputFile: z
|
|
18
|
-
.string()
|
|
19
|
-
.optional()
|
|
20
|
-
.describe('Absolute path where the generated test file will be written. Defaults to tests/characterization/characterization.test.ts'),
|
|
21
|
-
};
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// Handler
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
export async function handleCharacterizeLegacyCode(args) {
|
|
26
|
-
const { projectPath, files, outputFile } = args;
|
|
27
|
-
const result = await generateCharacterizationTests(projectPath, files, outputFile);
|
|
28
|
-
if (result.tests.length === 0) {
|
|
29
|
-
return compactResult(`## Characterization Tests\n\nNo functions found to characterize in ${result.filesAnalyzed} file(s).\n\nTip: ensure the target files export named functions.`);
|
|
30
|
-
}
|
|
31
|
-
// Write the generated test file
|
|
32
|
-
const outPath = result.outputFile;
|
|
33
|
-
const allTestCode = [
|
|
34
|
-
'// AUTO-GENERATED by Planu characterize_legacy_code',
|
|
35
|
-
'// Fill in the TODO assertions with observed output values.',
|
|
36
|
-
'// DO NOT delete these tests until proper unit tests replace them.',
|
|
37
|
-
'',
|
|
38
|
-
...result.tests.map((t) => t.testCode),
|
|
39
|
-
].join('\n\n');
|
|
40
|
-
await mkdir(dirname(outPath), { recursive: true });
|
|
41
|
-
await writeFile(outPath, allTestCode, 'utf-8');
|
|
42
|
-
const outRelative = outPath.startsWith(join(projectPath, ''))
|
|
43
|
-
? outPath.slice(projectPath.length + 1)
|
|
44
|
-
: outPath;
|
|
45
|
-
const lines = [
|
|
46
|
-
`## Characterization Tests Generated`,
|
|
47
|
-
``,
|
|
48
|
-
`**Files analyzed**: ${result.filesAnalyzed}`,
|
|
49
|
-
`**Test stubs generated**: ${result.tests.length}`,
|
|
50
|
-
`**Output**: \`${outRelative}\``,
|
|
51
|
-
``,
|
|
52
|
-
`### Functions characterized`,
|
|
53
|
-
];
|
|
54
|
-
for (const test of result.tests.slice(0, 20)) {
|
|
55
|
-
lines.push(`- \`${test.functionName}\` in \`${test.sourceFile}\``);
|
|
56
|
-
}
|
|
57
|
-
if (result.tests.length > 20) {
|
|
58
|
-
lines.push(`- _...and ${result.tests.length - 20} more_`);
|
|
59
|
-
}
|
|
60
|
-
lines.push(``, `### Next steps`, `1. Open \`${outRelative}\``, `2. Run each function with the sample inputs shown`, `3. Replace \`// TODO: assert current output\` with the observed value`, `4. Run the test suite — all characterization tests must pass before refactoring`);
|
|
61
|
-
return compactResult(lines.join('\n'));
|
|
62
|
-
}
|
|
63
|
-
//# sourceMappingURL=characterize-legacy-code.js.map
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import type { ToolResult, LegacyAnalysisInput } from '../../types/index.js';
|
|
3
|
-
export declare const DetectHyrumRisksInputSchema: {
|
|
4
|
-
projectPath: z.ZodString;
|
|
5
|
-
files: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
6
|
-
};
|
|
7
|
-
export declare function handleDetectHyrumRisks(args: LegacyAnalysisInput): Promise<ToolResult>;
|
|
8
|
-
//# sourceMappingURL=detect-hyrum-risks.d.ts.map
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// Planu — tools/legacy/detect-hyrum-risks.ts
|
|
2
|
-
// Tool handler for detect_hyrum_risks.
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { scanHyrumRisks } from '../../engine/legacy/hyrum-scanner.js';
|
|
5
|
-
import { compactResult, formatTable } from '../output-formatter.js';
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// Input schema
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
export const DetectHyrumRisksInputSchema = {
|
|
10
|
-
projectPath: z.string().max(4096).describe('Absolute path to the project root to scan'),
|
|
11
|
-
files: z
|
|
12
|
-
.array(z.string())
|
|
13
|
-
.optional()
|
|
14
|
-
.describe('Optional list of absolute file paths to scan. If omitted, scans src/**/*.{ts,js}'),
|
|
15
|
-
};
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Handler
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
export async function handleDetectHyrumRisks(args) {
|
|
20
|
-
const { projectPath, files } = args;
|
|
21
|
-
const result = await scanHyrumRisks(projectPath, files);
|
|
22
|
-
if (result.risks.length === 0) {
|
|
23
|
-
return compactResult(`## Hyrum Risk Scan\n\n${result.summary}\n\nNo observable behavior risks detected.`);
|
|
24
|
-
}
|
|
25
|
-
const tableRows = result.risks.slice(0, 30).map((r) => ({
|
|
26
|
-
ID: r.id,
|
|
27
|
-
Severity: r.severity,
|
|
28
|
-
Category: r.category,
|
|
29
|
-
Function: r.functionName,
|
|
30
|
-
File: r.file.split('/').slice(-2).join('/'),
|
|
31
|
-
Description: r.description.slice(0, 80),
|
|
32
|
-
}));
|
|
33
|
-
const table = formatTable(tableRows, ['ID', 'Severity', 'Category', 'Function', 'File', 'Description'], `Hyrum Risks (${result.risks.length})`);
|
|
34
|
-
const highRisks = result.risks.filter((r) => r.severity === 'high');
|
|
35
|
-
const highSection = highRisks.length > 0
|
|
36
|
-
? [
|
|
37
|
-
``,
|
|
38
|
-
`### High-Severity Risks (action required)`,
|
|
39
|
-
...highRisks
|
|
40
|
-
.slice(0, 5)
|
|
41
|
-
.map((r) => `**${r.id}** \`${r.functionName}\` — ${r.description}\n` +
|
|
42
|
-
`\`\`\`\n${r.snippet}\n\`\`\``),
|
|
43
|
-
].join('\n')
|
|
44
|
-
: '';
|
|
45
|
-
return compactResult([`## Hyrum Risk Detection`, ``, result.summary, ``, table, highSection].join('\n'));
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=detect-hyrum-risks.js.map
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import type { ToolResult, RefactorSafetyNetInput } from '../../types/index.js';
|
|
3
|
-
export declare const RefactorWithSafetyNetInputSchema: {
|
|
4
|
-
projectPath: z.ZodString;
|
|
5
|
-
files: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
6
|
-
coverageThreshold: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
7
|
-
dryRun: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
8
|
-
};
|
|
9
|
-
export declare function handleRefactorWithSafetyNet(args: RefactorSafetyNetInput): Promise<ToolResult>;
|
|
10
|
-
//# sourceMappingURL=refactor-with-safety-net.d.ts.map
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// Planu — tools/legacy/refactor-with-safety-net.ts
|
|
2
|
-
// Tool handler for refactor_with_safety_net.
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { buildSafetyNetPlan } from '../../engine/legacy/safety-net-orchestrator.js';
|
|
5
|
-
import { compactResult } from '../output-formatter.js';
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// Input schema
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
export const RefactorWithSafetyNetInputSchema = {
|
|
10
|
-
projectPath: z
|
|
11
|
-
.string()
|
|
12
|
-
.max(4096)
|
|
13
|
-
.describe('Absolute path to the project root to analyze and plan'),
|
|
14
|
-
files: z
|
|
15
|
-
.array(z.string())
|
|
16
|
-
.optional()
|
|
17
|
-
.describe('Optional list of absolute file paths to target. If omitted, scans src/**/*.{ts,js}'),
|
|
18
|
-
coverageThreshold: z
|
|
19
|
-
.number()
|
|
20
|
-
.min(0)
|
|
21
|
-
.max(100)
|
|
22
|
-
.optional()
|
|
23
|
-
.default(80)
|
|
24
|
-
.describe('Minimum coverage percentage required before refactoring. Default: 80'),
|
|
25
|
-
dryRun: z
|
|
26
|
-
.boolean()
|
|
27
|
-
.optional()
|
|
28
|
-
.default(false)
|
|
29
|
-
.describe('If true, show the plan without executing any changes. Default: false (plan is always returned — this flag is reserved for future execution mode)'),
|
|
30
|
-
};
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Handler
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
export async function handleRefactorWithSafetyNet(args) {
|
|
35
|
-
const { projectPath, files, coverageThreshold = 80 } = args;
|
|
36
|
-
const plan = await buildSafetyNetPlan(projectPath, files, coverageThreshold);
|
|
37
|
-
const lines = [`## Refactor with Safety Net`, ``, plan.summary, ``, `### Steps`];
|
|
38
|
-
for (const step of plan.steps) {
|
|
39
|
-
lines.push(`**${step.order}. ${step.label}** (~${step.estimatedMinutes} min)`);
|
|
40
|
-
lines.push(` ${step.description}`);
|
|
41
|
-
}
|
|
42
|
-
if (plan.refactorSteps.length > 0) {
|
|
43
|
-
lines.push(``, `### Identified Refactor Actions`);
|
|
44
|
-
for (const rs of plan.refactorSteps.slice(0, 15)) {
|
|
45
|
-
const riskBadge = rs.risk === 'high' ? ' ⚠️ high risk' : rs.risk === 'medium' ? ' medium risk' : '';
|
|
46
|
-
lines.push(`- **${rs.pattern}**${riskBadge}: ${rs.change}`);
|
|
47
|
-
}
|
|
48
|
-
if (plan.refactorSteps.length > 15) {
|
|
49
|
-
lines.push(`- _...and ${plan.refactorSteps.length - 15} more_`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
lines.push(``, `### Coverage target`, `≥ ${plan.coverageBaseline}% line coverage required before starting refactoring.`, ``, `### Automated tools`, `- \`characterize_legacy_code\` — generates test stubs (Step 2)`, `- \`seams_detector\` — identifies injection points (Step 4)`, `- \`detect_hyrum_risks\` — flags observable behaviors to preserve`);
|
|
53
|
-
return compactResult(lines.join('\n'));
|
|
54
|
-
}
|
|
55
|
-
//# sourceMappingURL=refactor-with-safety-net.js.map
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import type { ToolResult, LegacyAnalysisInput } from '../../types/index.js';
|
|
3
|
-
export declare const SeamsDetectorInputSchema: {
|
|
4
|
-
projectPath: z.ZodString;
|
|
5
|
-
files: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
6
|
-
};
|
|
7
|
-
export declare function handleSeamsDetector(args: LegacyAnalysisInput): Promise<ToolResult>;
|
|
8
|
-
//# sourceMappingURL=seams-detector.d.ts.map
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// Planu — tools/legacy/seams-detector.ts
|
|
2
|
-
// Tool handler for seams_detector.
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { findSeams } from '../../engine/legacy/seam-finder.js';
|
|
5
|
-
import { compactResult, formatTable } from '../output-formatter.js';
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// Input schema
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
export const SeamsDetectorInputSchema = {
|
|
10
|
-
projectPath: z.string().max(4096).describe('Absolute path to the project root to scan'),
|
|
11
|
-
files: z
|
|
12
|
-
.array(z.string())
|
|
13
|
-
.optional()
|
|
14
|
-
.describe('Optional list of absolute file paths to scan. If omitted, scans src/**/*.{ts,js}'),
|
|
15
|
-
};
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Handler
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
export async function handleSeamsDetector(args) {
|
|
20
|
-
const { projectPath, files } = args;
|
|
21
|
-
const result = await findSeams(projectPath, files);
|
|
22
|
-
if (result.seams.length === 0) {
|
|
23
|
-
return compactResult(`## Seams Detector\n\n${result.summary}\n\nNo dependency break points detected.`);
|
|
24
|
-
}
|
|
25
|
-
const tableRows = result.seams.slice(0, 30).map((s) => ({
|
|
26
|
-
ID: s.id,
|
|
27
|
-
Breakability: s.breakability,
|
|
28
|
-
Category: s.category,
|
|
29
|
-
Function: s.functionName,
|
|
30
|
-
File: s.file.split('/').slice(-2).join('/'),
|
|
31
|
-
Suggestion: s.suggestion.slice(0, 80),
|
|
32
|
-
}));
|
|
33
|
-
const table = formatTable(tableRows, ['ID', 'Breakability', 'Category', 'Function', 'File', 'Suggestion'], `Seams Found (${result.seams.length})`);
|
|
34
|
-
const hardSeams = result.seams.filter((s) => s.breakability === 'hard');
|
|
35
|
-
const hardSection = hardSeams.length > 0
|
|
36
|
-
? [
|
|
37
|
-
``,
|
|
38
|
-
`### Hard-to-Break Seams (high priority)`,
|
|
39
|
-
...hardSeams
|
|
40
|
-
.slice(0, 5)
|
|
41
|
-
.map((s) => `**${s.id}** \`${s.functionName}\` — ${s.description}\n` +
|
|
42
|
-
`_Suggestion_: ${s.suggestion}`),
|
|
43
|
-
].join('\n')
|
|
44
|
-
: '';
|
|
45
|
-
return compactResult([`## Seams Detector`, ``, result.summary, ``, table, hardSection].join('\n'));
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=seams-detector.js.map
|
package/dist/types/legacy.d.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
export type HyrumRiskCategory = 'exception-type' | 'return-shape' | 'ordering-guarantee' | 'null-vs-undefined' | 'error-message-text' | 'implicit-return';
|
|
2
|
-
export interface HyrumRisk {
|
|
3
|
-
/** Unique identifier for the risk */
|
|
4
|
-
id: string;
|
|
5
|
-
/** Category of observable behavior risk */
|
|
6
|
-
category: HyrumRiskCategory;
|
|
7
|
-
/** File where the risk was detected */
|
|
8
|
-
file: string;
|
|
9
|
-
/** Line number (1-based) */
|
|
10
|
-
line: number;
|
|
11
|
-
/** Name of the function/method containing the risk */
|
|
12
|
-
functionName: string;
|
|
13
|
-
/** Human-readable description of the observable behavior */
|
|
14
|
-
description: string;
|
|
15
|
-
/** Severity: high = likely depended on, medium = possibly, low = unlikely */
|
|
16
|
-
severity: 'high' | 'medium' | 'low';
|
|
17
|
-
/** Code snippet showing the problematic pattern */
|
|
18
|
-
snippet: string;
|
|
19
|
-
}
|
|
20
|
-
export interface HyrumScanResult {
|
|
21
|
-
projectPath: string;
|
|
22
|
-
filesScanned: number;
|
|
23
|
-
risks: HyrumRisk[];
|
|
24
|
-
summary: string;
|
|
25
|
-
}
|
|
26
|
-
export type SeamCategory = 'static-call' | 'global-env' | 'inline-new' | 'hardcoded-path' | 'hardcoded-url' | 'module-singleton';
|
|
27
|
-
export interface Seam {
|
|
28
|
-
/** Unique identifier for the seam */
|
|
29
|
-
id: string;
|
|
30
|
-
/** Category of the seam */
|
|
31
|
-
category: SeamCategory;
|
|
32
|
-
/** File where the seam was detected */
|
|
33
|
-
file: string;
|
|
34
|
-
/** Line number (1-based) */
|
|
35
|
-
line: number;
|
|
36
|
-
/** Name of the enclosing function/method */
|
|
37
|
-
functionName: string;
|
|
38
|
-
/** Human-readable description of the seam */
|
|
39
|
-
description: string;
|
|
40
|
-
/** The pattern that was matched */
|
|
41
|
-
pattern: string;
|
|
42
|
-
/** Suggested refactoring approach */
|
|
43
|
-
suggestion: string;
|
|
44
|
-
/** How easy it is to break this seam: easy | moderate | hard */
|
|
45
|
-
breakability: 'easy' | 'moderate' | 'hard';
|
|
46
|
-
}
|
|
47
|
-
export interface SeamScanResult {
|
|
48
|
-
projectPath: string;
|
|
49
|
-
filesScanned: number;
|
|
50
|
-
seams: Seam[];
|
|
51
|
-
summary: string;
|
|
52
|
-
}
|
|
53
|
-
export interface SampleInput {
|
|
54
|
-
/** Parameter name */
|
|
55
|
-
name: string;
|
|
56
|
-
/** Suggested sample value (as source code string) */
|
|
57
|
-
value: string;
|
|
58
|
-
/** Inferred TypeScript type */
|
|
59
|
-
type: string;
|
|
60
|
-
}
|
|
61
|
-
export interface CharacterizationTest {
|
|
62
|
-
/** Unique identifier */
|
|
63
|
-
id: string;
|
|
64
|
-
/** File being characterized */
|
|
65
|
-
sourceFile: string;
|
|
66
|
-
/** Function/method to characterize */
|
|
67
|
-
functionName: string;
|
|
68
|
-
/** Generated test stub code */
|
|
69
|
-
testCode: string;
|
|
70
|
-
/** Detected sample inputs for the function */
|
|
71
|
-
sampleInputs: SampleInput[];
|
|
72
|
-
/** Note to the developer about filling in expected values */
|
|
73
|
-
developerNote: string;
|
|
74
|
-
}
|
|
75
|
-
export interface CharacterizationResult {
|
|
76
|
-
projectPath: string;
|
|
77
|
-
filesAnalyzed: number;
|
|
78
|
-
tests: CharacterizationTest[];
|
|
79
|
-
outputFile: string;
|
|
80
|
-
summary: string;
|
|
81
|
-
}
|
|
82
|
-
export type SafetyNetStepStatus = 'pending' | 'in-progress' | 'done' | 'skipped' | 'failed';
|
|
83
|
-
export interface SafetyNetStep {
|
|
84
|
-
/** Step number (1-based) */
|
|
85
|
-
order: number;
|
|
86
|
-
/** Short label for the step */
|
|
87
|
-
label: string;
|
|
88
|
-
/** Detailed description of what to do */
|
|
89
|
-
description: string;
|
|
90
|
-
/** Status of the step */
|
|
91
|
-
status: SafetyNetStepStatus;
|
|
92
|
-
/** Estimated effort in minutes */
|
|
93
|
-
estimatedMinutes: number;
|
|
94
|
-
}
|
|
95
|
-
export interface RefactorStep {
|
|
96
|
-
/** Target file to refactor */
|
|
97
|
-
file: string;
|
|
98
|
-
/** Description of what to change */
|
|
99
|
-
change: string;
|
|
100
|
-
/** Pattern: extract-function | inject-dependency | replace-singleton | introduce-seam */
|
|
101
|
-
pattern: 'extract-function' | 'inject-dependency' | 'replace-singleton' | 'introduce-seam';
|
|
102
|
-
/** Whether a test must exist before this step */
|
|
103
|
-
requiresTest: boolean;
|
|
104
|
-
/** Risk level for this step */
|
|
105
|
-
risk: 'low' | 'medium' | 'high';
|
|
106
|
-
}
|
|
107
|
-
export interface SafetyNetPlan {
|
|
108
|
-
projectPath: string;
|
|
109
|
-
targetFiles: string[];
|
|
110
|
-
coverageBaseline: number;
|
|
111
|
-
steps: SafetyNetStep[];
|
|
112
|
-
refactorSteps: RefactorStep[];
|
|
113
|
-
summary: string;
|
|
114
|
-
}
|
|
115
|
-
export interface LegacyFunctionInfo {
|
|
116
|
-
name: string;
|
|
117
|
-
line: number;
|
|
118
|
-
params: string[];
|
|
119
|
-
body: string;
|
|
120
|
-
snippet: string;
|
|
121
|
-
}
|
|
122
|
-
export interface LegacyFileAnalysis {
|
|
123
|
-
filePath: string;
|
|
124
|
-
functions: LegacyFunctionInfo[];
|
|
125
|
-
lines: string[];
|
|
126
|
-
}
|
|
127
|
-
export interface HyrumRiskPattern {
|
|
128
|
-
category: HyrumRiskCategory;
|
|
129
|
-
regex: RegExp;
|
|
130
|
-
severity: HyrumRisk['severity'];
|
|
131
|
-
describe: (match: RegExpMatchArray) => string;
|
|
132
|
-
}
|
|
133
|
-
export interface LegacySeamPattern {
|
|
134
|
-
category: SeamCategory;
|
|
135
|
-
regex: RegExp;
|
|
136
|
-
breakability: Seam['breakability'];
|
|
137
|
-
describe: (match: RegExpMatchArray) => string;
|
|
138
|
-
suggest: (match: RegExpMatchArray) => string;
|
|
139
|
-
}
|
|
140
|
-
export interface LegacyAnalysisInput {
|
|
141
|
-
projectPath: string;
|
|
142
|
-
files?: string[];
|
|
143
|
-
}
|
|
144
|
-
export interface CharacterizeLegacyInput extends LegacyAnalysisInput {
|
|
145
|
-
outputFile?: string;
|
|
146
|
-
}
|
|
147
|
-
export interface RefactorSafetyNetInput extends LegacyAnalysisInput {
|
|
148
|
-
coverageThreshold?: number;
|
|
149
|
-
dryRun?: boolean;
|
|
150
|
-
}
|
|
151
|
-
//# sourceMappingURL=legacy.d.ts.map
|