@planu/cli 3.9.11 → 3.9.13
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 +12 -0
- package/dist/config/criteria-injection-rules.json +1 -1
- package/dist/config/dor-dod-items.json +3 -3
- package/dist/config/hook-templates/planu-spec-sanctity.sh +14 -5
- package/dist/config/server-instructions.js +1 -1
- package/dist/config/server-instructions.ts +1 -1
- package/dist/config/skill-templates/planu-new-spec.md +1 -1
- package/dist/config/skill-templates/planu-resume-work.md +1 -1
- package/dist/config/subagent-templates/planu-readiness-auditor.md +3 -3
- package/dist/config/subagent-templates/planu-spec-implementer.md +1 -1
- package/dist/config/workflow-conventions-catalog.json +3 -3
- package/dist/core/spec-api.js +1 -1
- package/dist/engine/agent-generator/builders.js +1 -1
- package/dist/engine/autopilot/bootstrap.js +0 -138
- package/dist/engine/autopilot/handlers-b2.d.ts +2 -2
- package/dist/engine/autopilot/handlers-b2.js +15 -19
- package/dist/engine/ci-generator/local-script.js +4 -4
- package/dist/engine/ci-generator/planu-steps.js +4 -5
- package/dist/engine/compliance/auto-remediator.js +0 -1
- package/dist/engine/drift/violation-resolver.js +0 -1
- package/dist/engine/health/auto-fixer.js +1 -33
- package/dist/engine/housekeeping/ephemeral-artifacts-cleaner.d.ts +2 -2
- package/dist/engine/housekeeping/ephemeral-artifacts-cleaner.js +7 -28
- package/dist/engine/living-spec-analyzer.js +3 -34
- package/dist/engine/living-specs/index.d.ts +2 -2
- package/dist/engine/living-specs/index.js +10 -35
- package/dist/engine/planu-config-writer.js +1 -1
- package/dist/engine/progress-writer.d.ts +2 -2
- package/dist/engine/progress-writer.js +2 -2
- package/dist/engine/scan-project/index.js +2 -2
- package/dist/engine/skill-generator/skills-content.js +1 -1
- package/dist/engine/spec-format/lean-technical-generator.d.ts +1 -1
- package/dist/engine/spec-format/lean-technical-generator.js +2 -2
- package/dist/engine/spec-format/technical-md-populator.d.ts +1 -1
- package/dist/engine/spec-format/technical-md-populator.js +2 -2
- package/dist/engine/spec-migrator/drift-detector.js +1 -1
- package/dist/engine/spec-migrator/lean-migration.js +5 -4
- package/dist/engine/spec-migrator/planu-root-cleaner.js +1 -1
- package/dist/engine/spec-registry/packager.d.ts +1 -1
- package/dist/engine/spec-registry/packager.js +2 -2
- package/dist/engine/spec-registry/validator.js +1 -2
- package/dist/engine/spec-splitter.js +2 -2
- package/dist/engine/spec-summary-html/report-renderer.d.ts +3 -4
- package/dist/engine/spec-summary-html/report-renderer.js +6 -135
- package/dist/engine/spec-summary-html.js +1 -1
- package/dist/engine/universal-rules/rules/planu-english-specs.js +4 -6
- package/dist/server/routes/specs.js +1 -1
- package/dist/tools/create-spec/autopilot-analyzer.js +1 -1
- package/dist/tools/create-spec/spec-builder.js +1 -1
- package/dist/tools/decompose-spec.js +1 -1
- package/dist/tools/git/pr-ops.js +2 -2
- package/dist/tools/git/sync-ops.js +1 -1
- package/dist/tools/reconcile-spec-living-handler.js +1 -1
- package/dist/tools/reconcile-spec.js +1 -1
- package/dist/tools/registry/install.js +27 -29
- package/dist/tools/reverse-engineer/handler.js +1 -1
- package/dist/tools/schemas/registry.js +1 -1
- package/dist/tools/spec-coverage.js +2 -2
- package/dist/tools/spec-templates.d.ts +1 -1
- package/dist/tools/spec-templates.js +17 -9
- package/dist/tools/tool-registry/group-infra.js +1 -1
- package/dist/tools/tool-registry/group-platform.js +1 -1
- package/dist/tools/update-status/index.js +1 -1
- package/dist/types/impact-detection.d.ts +6 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/types/spec-templates.d.ts +3 -5
- package/package.json +7 -7
- package/src/i18n/messages/en.json +3 -3
- package/src/i18n/messages/es.json +3 -3
- package/src/i18n/messages/pt.json +3 -3
- package/dist/engine/implementation-brief/convention-extractor.d.ts +0 -5
- package/dist/engine/implementation-brief/convention-extractor.js +0 -75
- package/dist/engine/implementation-brief/extension-points.d.ts +0 -2
- package/dist/engine/implementation-brief/extension-points.js +0 -32
- package/dist/engine/implementation-brief/generator.d.ts +0 -3
- package/dist/engine/implementation-brief/generator.js +0 -139
- package/dist/engine/implementation-brief/helpers-scanner.d.ts +0 -3
- package/dist/engine/implementation-brief/helpers-scanner.js +0 -163
- package/dist/engine/implementation-brief/test-pattern-matcher.d.ts +0 -3
- package/dist/engine/implementation-brief/test-pattern-matcher.js +0 -90
- package/dist/engine/risk-analyzer/risk-generator.d.ts +0 -6
- package/dist/engine/risk-analyzer/risk-generator.js +0 -94
- package/dist/types/implementation-brief.d.ts +0 -41
- package/dist/types/implementation-brief.js +0 -3
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
// engine/implementation-brief/generator.ts — SPEC-586: Orchestrate implementation brief generation
|
|
2
|
-
import { writeFile, mkdir } from 'node:fs/promises';
|
|
3
|
-
import { join, dirname } from 'node:path';
|
|
4
|
-
import { scanReusableHelpers } from './helpers-scanner.js';
|
|
5
|
-
import { findTestPatterns } from './test-pattern-matcher.js';
|
|
6
|
-
import { extractConventions } from './convention-extractor.js';
|
|
7
|
-
import { enumerateExtensionPoints } from './extension-points.js';
|
|
8
|
-
import { predictTestBreaks } from '../impact-detector/test-break-predictor.js';
|
|
9
|
-
const MAX_HELPERS = 10;
|
|
10
|
-
function renderBrief(brief) {
|
|
11
|
-
const lines = [];
|
|
12
|
-
lines.push(`# Implementation Brief — ${brief.specId}`);
|
|
13
|
-
lines.push(`_Generated ${brief.generatedAt}_`);
|
|
14
|
-
lines.push('');
|
|
15
|
-
lines.push('## Reusable helpers');
|
|
16
|
-
if (brief.reusableHelpers.length === 0) {
|
|
17
|
-
lines.push('_None found._');
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
for (const h of brief.reusableHelpers) {
|
|
21
|
-
lines.push(`- **${h.symbol}** — \`${h.path}:${h.line}\` — ${h.purpose}`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
lines.push('');
|
|
25
|
-
lines.push('## Test patterns');
|
|
26
|
-
if (brief.testPatterns.length === 0) {
|
|
27
|
-
lines.push('_No similar tests found._');
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
for (const t of brief.testPatterns) {
|
|
31
|
-
lines.push(`- \`${t.testPath}\` — ${t.whyRelevant}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
lines.push('');
|
|
35
|
-
lines.push('## Applicable conventions');
|
|
36
|
-
if (brief.conventions.excerpts.length === 0) {
|
|
37
|
-
lines.push('_No matching rules found._');
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
for (const e of brief.conventions.excerpts) {
|
|
41
|
-
lines.push(`### ${e.rule}`);
|
|
42
|
-
lines.push('');
|
|
43
|
-
lines.push(e.excerpt);
|
|
44
|
-
lines.push('');
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
lines.push('## Existing extension points');
|
|
48
|
-
if (brief.extensionPoints.length === 0) {
|
|
49
|
-
lines.push('_None detected._');
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
for (const ep of brief.extensionPoints) {
|
|
53
|
-
lines.push(`- \`${ep}\``);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
lines.push('');
|
|
57
|
-
lines.push('## Anticipated test breaks');
|
|
58
|
-
if (brief.anticipatedBreaks.length === 0) {
|
|
59
|
-
lines.push('_No anticipated breaks detected._');
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
for (const b of brief.anticipatedBreaks) {
|
|
63
|
-
lines.push(`- \`${b.testPath}\` — pattern: **${b.pattern}** — ${b.remediation}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
lines.push('');
|
|
67
|
-
return lines.join('\n');
|
|
68
|
-
}
|
|
69
|
-
function renderPrompt(specId, description, brief) {
|
|
70
|
-
const lines = [];
|
|
71
|
-
lines.push(`# Implementation prompt — ${specId}`);
|
|
72
|
-
lines.push('');
|
|
73
|
-
// What to build — first non-empty line of description (up to 120 chars)
|
|
74
|
-
const goal = description
|
|
75
|
-
.split('\n')
|
|
76
|
-
.find((l) => l.trim().length > 0)
|
|
77
|
-
?.slice(0, 120) ?? specId;
|
|
78
|
-
lines.push('## What to build');
|
|
79
|
-
lines.push(goal.trim());
|
|
80
|
-
lines.push('');
|
|
81
|
-
/* v8 ignore start */
|
|
82
|
-
// Files to CREATE — infer from helpers/extension points
|
|
83
|
-
if (brief.extensionPoints.length > 0) {
|
|
84
|
-
lines.push('## Files to CREATE (exact paths)');
|
|
85
|
-
for (const ep of brief.extensionPoints.slice(0, 5)) {
|
|
86
|
-
lines.push(`- ${ep}`);
|
|
87
|
-
}
|
|
88
|
-
lines.push('');
|
|
89
|
-
}
|
|
90
|
-
// Reusable helpers — key symbols to import (avoids discovery)
|
|
91
|
-
if (brief.reusableHelpers.length > 0) {
|
|
92
|
-
lines.push('## Reusable helpers (import, do not rewrite)');
|
|
93
|
-
for (const h of brief.reusableHelpers.slice(0, 5)) {
|
|
94
|
-
lines.push(`- \`${h.symbol}\` from \`${h.path}\` — ${h.purpose}`);
|
|
95
|
-
}
|
|
96
|
-
lines.push('');
|
|
97
|
-
}
|
|
98
|
-
// Anticipated test breaks
|
|
99
|
-
if (brief.anticipatedBreaks.length > 0) {
|
|
100
|
-
lines.push('## Tests that will break (exact fixes)');
|
|
101
|
-
for (const b of brief.anticipatedBreaks) {
|
|
102
|
-
lines.push(`- \`${b.testPath}\` — ${b.remediation}`);
|
|
103
|
-
}
|
|
104
|
-
lines.push('');
|
|
105
|
-
}
|
|
106
|
-
/* v8 ignore stop */
|
|
107
|
-
lines.push('## Verify with');
|
|
108
|
-
lines.push('```');
|
|
109
|
-
lines.push('pnpm typecheck && pnpm lint && pnpm test');
|
|
110
|
-
lines.push('```');
|
|
111
|
-
return lines.join('\n');
|
|
112
|
-
}
|
|
113
|
-
export async function generateImplementationBrief(input) {
|
|
114
|
-
const { specId, specPath, projectPath, description, tags, target, scope } = input;
|
|
115
|
-
const [helpers, testPatterns, conventions, extensionPoints, anticipatedBreaks] = await Promise.all([
|
|
116
|
-
scanReusableHelpers(projectPath, description, tags).catch(() => []),
|
|
117
|
-
findTestPatterns(projectPath, description, tags, scope).catch(() => []),
|
|
118
|
-
extractConventions(projectPath, tags, target, scope).catch(() => ({ excerpts: [] })),
|
|
119
|
-
enumerateExtensionPoints(projectPath).catch(() => []),
|
|
120
|
-
predictTestBreaks(projectPath).catch(() => []),
|
|
121
|
-
]);
|
|
122
|
-
const brief = {
|
|
123
|
-
specId,
|
|
124
|
-
generatedAt: new Date().toISOString(),
|
|
125
|
-
reusableHelpers: helpers.slice(0, MAX_HELPERS),
|
|
126
|
-
testPatterns,
|
|
127
|
-
conventions,
|
|
128
|
-
extensionPoints,
|
|
129
|
-
anticipatedBreaks,
|
|
130
|
-
};
|
|
131
|
-
const specDir = dirname(specPath);
|
|
132
|
-
await mkdir(specDir, { recursive: true });
|
|
133
|
-
// Write both implementation-brief.md and prompt.md (SPEC-629) in parallel
|
|
134
|
-
await Promise.all([
|
|
135
|
-
writeFile(join(specDir, 'implementation-brief.md'), renderBrief(brief), 'utf-8'),
|
|
136
|
-
writeFile(join(specDir, 'prompt.md'), renderPrompt(specId, description, brief), 'utf-8'),
|
|
137
|
-
]);
|
|
138
|
-
}
|
|
139
|
-
//# sourceMappingURL=generator.js.map
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
// engine/implementation-brief/helpers-scanner.ts — SPEC-586: Semantic search for reusable helpers
|
|
2
|
-
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
3
|
-
import { join, relative, extname } from 'node:path';
|
|
4
|
-
const MAX_FILES = 300;
|
|
5
|
-
const MAX_DEPTH = 4;
|
|
6
|
-
const TOP_N = 10;
|
|
7
|
-
const CODE_EXT = new Set(['.ts', '.js', '.py', '.go', '.rs', '.java', '.kt', '.rb', '.cs']);
|
|
8
|
-
function extractKeywords(text) {
|
|
9
|
-
return text
|
|
10
|
-
.toLowerCase()
|
|
11
|
-
.replace(/[^a-z0-9\s-]/g, ' ')
|
|
12
|
-
.split(/\s+/)
|
|
13
|
-
.filter((w) => w.length > 3)
|
|
14
|
-
.filter((w) => !STOP_WORDS.has(w));
|
|
15
|
-
}
|
|
16
|
-
const STOP_WORDS = new Set([
|
|
17
|
-
'this',
|
|
18
|
-
'that',
|
|
19
|
-
'with',
|
|
20
|
-
'from',
|
|
21
|
-
'have',
|
|
22
|
-
'will',
|
|
23
|
-
'when',
|
|
24
|
-
'then',
|
|
25
|
-
'given',
|
|
26
|
-
'spec',
|
|
27
|
-
'planu',
|
|
28
|
-
'tool',
|
|
29
|
-
'file',
|
|
30
|
-
'path',
|
|
31
|
-
'type',
|
|
32
|
-
'into',
|
|
33
|
-
'also',
|
|
34
|
-
'each',
|
|
35
|
-
'only',
|
|
36
|
-
'over',
|
|
37
|
-
'such',
|
|
38
|
-
'more',
|
|
39
|
-
'some',
|
|
40
|
-
'after',
|
|
41
|
-
'must',
|
|
42
|
-
]);
|
|
43
|
-
function scoreText(text, keywords) {
|
|
44
|
-
const lower = text.toLowerCase();
|
|
45
|
-
let score = 0;
|
|
46
|
-
for (const kw of keywords) {
|
|
47
|
-
if (lower.includes(kw)) {
|
|
48
|
-
score += kw.length;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return score;
|
|
52
|
-
}
|
|
53
|
-
async function scanDir(dir, projectPath, keywords, results, depth, budget) {
|
|
54
|
-
if (depth > MAX_DEPTH || budget.remaining <= 0) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
let entries;
|
|
58
|
-
try {
|
|
59
|
-
entries = await readdir(dir);
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
for (const entry of entries) {
|
|
65
|
-
if (budget.remaining <= 0) {
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
if (entry.startsWith('.') || entry === 'node_modules' || entry === 'dist') {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
const full = join(dir, entry);
|
|
72
|
-
let s;
|
|
73
|
-
try {
|
|
74
|
-
s = await stat(full);
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
if (s.isDirectory()) {
|
|
80
|
-
await scanDir(full, projectPath, keywords, results, depth + 1, budget);
|
|
81
|
-
}
|
|
82
|
-
else if (CODE_EXT.has(extname(entry))) {
|
|
83
|
-
const b = budget;
|
|
84
|
-
b.remaining -= 1;
|
|
85
|
-
const rel = relative(projectPath, full);
|
|
86
|
-
const score = scoreText(rel, keywords);
|
|
87
|
-
if (score > 0) {
|
|
88
|
-
results.push({ path: rel, score });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function parseFunctionSymbol(line) {
|
|
94
|
-
const match = /export\s+(?:async\s+)?function\s+(\w+)|export\s+const\s+(\w+)\s*=|export\s+(?:type|interface|class)\s+(\w+)/.exec(line);
|
|
95
|
-
if (!match) {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
return match[1] ?? match[2] ?? match[3] ?? null;
|
|
99
|
-
}
|
|
100
|
-
function extractPurpose(lines, exportLine) {
|
|
101
|
-
for (let i = exportLine - 1; i >= Math.max(0, exportLine - 3); i--) {
|
|
102
|
-
const line = lines[i]?.trim() ?? '';
|
|
103
|
-
if (line.startsWith('//') || line.startsWith('*') || line.startsWith('/**')) {
|
|
104
|
-
return line.replace(/^[/*\s]+/, '').slice(0, 80);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return 'helper function';
|
|
108
|
-
}
|
|
109
|
-
async function extractHelpers(filePath, keywords, projectPath) {
|
|
110
|
-
let content;
|
|
111
|
-
try {
|
|
112
|
-
content = await readFile(filePath, 'utf-8');
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
return [];
|
|
116
|
-
}
|
|
117
|
-
const lines = content.split('\n');
|
|
118
|
-
const helpers = [];
|
|
119
|
-
for (let i = 0; i < lines.length; i++) {
|
|
120
|
-
const line = lines[i] ?? '';
|
|
121
|
-
if (!line.includes('export')) {
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
const symbol = parseFunctionSymbol(line);
|
|
125
|
-
if (!symbol) {
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
const symbolScore = scoreText(symbol, keywords);
|
|
129
|
-
if (symbolScore === 0) {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
helpers.push({
|
|
133
|
-
symbol,
|
|
134
|
-
path: relative(projectPath, filePath),
|
|
135
|
-
line: i + 1,
|
|
136
|
-
purpose: extractPurpose(lines, i),
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
return helpers;
|
|
140
|
-
}
|
|
141
|
-
export async function scanReusableHelpers(projectPath, description, tags) {
|
|
142
|
-
const srcDir = join(projectPath, 'src');
|
|
143
|
-
const keywords = extractKeywords(`${description} ${tags.join(' ')}`);
|
|
144
|
-
if (keywords.length === 0) {
|
|
145
|
-
return [];
|
|
146
|
-
}
|
|
147
|
-
const candidates = [];
|
|
148
|
-
const budget = { remaining: MAX_FILES };
|
|
149
|
-
await scanDir(srcDir, projectPath, keywords, candidates, 0, budget);
|
|
150
|
-
candidates.sort((a, b) => b.score - a.score);
|
|
151
|
-
const top = candidates.slice(0, 20);
|
|
152
|
-
const helpers = [];
|
|
153
|
-
for (const c of top) {
|
|
154
|
-
const full = join(projectPath, c.path);
|
|
155
|
-
const extracted = await extractHelpers(full, keywords, projectPath);
|
|
156
|
-
helpers.push(...extracted);
|
|
157
|
-
if (helpers.length >= TOP_N) {
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return helpers.slice(0, TOP_N);
|
|
162
|
-
}
|
|
163
|
-
//# sourceMappingURL=helpers-scanner.js.map
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
// engine/implementation-brief/test-pattern-matcher.ts — SPEC-586: Find similar test patterns
|
|
2
|
-
import { readdir, stat } from 'node:fs/promises';
|
|
3
|
-
import { join, relative } from 'node:path';
|
|
4
|
-
const MAX_TEST_FILES = 200;
|
|
5
|
-
const TOP_N = 3;
|
|
6
|
-
function extractKeywords(text) {
|
|
7
|
-
return text
|
|
8
|
-
.toLowerCase()
|
|
9
|
-
.replace(/[^a-z0-9\s]/g, ' ')
|
|
10
|
-
.split(/\s+/)
|
|
11
|
-
.filter((w) => w.length > 3);
|
|
12
|
-
}
|
|
13
|
-
function scoreMatch(filePath, keywords) {
|
|
14
|
-
const lower = filePath.toLowerCase();
|
|
15
|
-
let score = 0;
|
|
16
|
-
for (const kw of keywords) {
|
|
17
|
-
if (lower.includes(kw)) {
|
|
18
|
-
score += kw.length;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return score;
|
|
22
|
-
}
|
|
23
|
-
async function collectTestFiles(dir, results, budget, depth) {
|
|
24
|
-
if (budget.remaining <= 0 || depth > 5) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
let entries;
|
|
28
|
-
try {
|
|
29
|
-
entries = await readdir(dir);
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
for (const entry of entries) {
|
|
35
|
-
if (budget.remaining <= 0) {
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
if (entry.startsWith('.') || entry === 'node_modules') {
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
const full = join(dir, entry);
|
|
42
|
-
let s;
|
|
43
|
-
try {
|
|
44
|
-
s = await stat(full);
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (s.isDirectory()) {
|
|
50
|
-
await collectTestFiles(full, results, budget, depth + 1);
|
|
51
|
-
}
|
|
52
|
-
else if (entry.endsWith('.test.ts') || entry.endsWith('.test.js')) {
|
|
53
|
-
const b = budget;
|
|
54
|
-
b.remaining -= 1;
|
|
55
|
-
results.push(full);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function explainRelevance(filePath, keywords) {
|
|
60
|
-
const matched = [];
|
|
61
|
-
const lower = filePath.toLowerCase();
|
|
62
|
-
for (const kw of keywords) {
|
|
63
|
-
if (lower.includes(kw)) {
|
|
64
|
-
matched.push(kw);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return matched.length > 0
|
|
68
|
-
? `matches keywords: ${matched.slice(0, 3).join(', ')}`
|
|
69
|
-
: 'structural similarity';
|
|
70
|
-
}
|
|
71
|
-
export async function findTestPatterns(projectPath, description, tags, scope) {
|
|
72
|
-
const testsDir = join(projectPath, 'tests');
|
|
73
|
-
const keywords = extractKeywords(`${description} ${tags.join(' ')} ${scope}`);
|
|
74
|
-
if (keywords.length === 0) {
|
|
75
|
-
return [];
|
|
76
|
-
}
|
|
77
|
-
const testFiles = [];
|
|
78
|
-
const budget = { remaining: MAX_TEST_FILES };
|
|
79
|
-
await collectTestFiles(testsDir, testFiles, budget, 0);
|
|
80
|
-
const scored = testFiles
|
|
81
|
-
.map((f) => ({ path: f, score: scoreMatch(relative(projectPath, f), keywords) }))
|
|
82
|
-
.filter((x) => x.score > 0)
|
|
83
|
-
.sort((a, b) => b.score - a.score)
|
|
84
|
-
.slice(0, TOP_N);
|
|
85
|
-
return scored.map((s) => ({
|
|
86
|
-
testPath: relative(projectPath, s.path),
|
|
87
|
-
whyRelevant: explainRelevance(relative(projectPath, s.path), keywords),
|
|
88
|
-
}));
|
|
89
|
-
}
|
|
90
|
-
//# sourceMappingURL=test-pattern-matcher.js.map
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { RiskAnalysisContext, RiskRegister } from '../../types/index.js';
|
|
2
|
-
/**
|
|
3
|
-
* Generate an initial risk register from spec metadata.
|
|
4
|
-
*/
|
|
5
|
-
export declare function generateRiskRegister(ctx: RiskAnalysisContext, totalBudget?: number): RiskRegister;
|
|
6
|
-
//# sourceMappingURL=risk-generator.d.ts.map
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
function buildRiskTemplates(ctx) {
|
|
2
|
-
const templates = [];
|
|
3
|
-
if (ctx.difficulty >= 4) {
|
|
4
|
-
templates.push({
|
|
5
|
-
description: 'High complexity may lead to estimation overrun',
|
|
6
|
-
category: 'technical',
|
|
7
|
-
probability: 40,
|
|
8
|
-
impactDays: 3,
|
|
9
|
-
mitigationPlan: 'Break down into smaller tasks and reassess estimates',
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
if (ctx.scope === 'cross-module' || ctx.scope === 'architectural') {
|
|
13
|
-
templates.push({
|
|
14
|
-
description: 'Cross-module changes risk breaking existing integrations',
|
|
15
|
-
category: 'integration',
|
|
16
|
-
probability: 35,
|
|
17
|
-
impactDays: 2,
|
|
18
|
-
mitigationPlan: 'Add integration tests covering all affected modules before merging',
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
if (ctx.dependencyCount > 3) {
|
|
22
|
-
templates.push({
|
|
23
|
-
description: 'Multiple dependencies increase coordination overhead',
|
|
24
|
-
category: 'dependency',
|
|
25
|
-
probability: 30,
|
|
26
|
-
impactDays: 2,
|
|
27
|
-
mitigationPlan: 'Establish dependency contracts early and monitor for version conflicts',
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
if (ctx.risk === 'high' || ctx.risk === 'critical') {
|
|
31
|
-
templates.push({
|
|
32
|
-
description: 'Spec flagged as high-risk requires extra review',
|
|
33
|
-
category: 'organizational',
|
|
34
|
-
probability: 50,
|
|
35
|
-
impactDays: 3,
|
|
36
|
-
mitigationPlan: 'Schedule mandatory peer review and add approval gates before deployment',
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
if (ctx.tags.includes('security')) {
|
|
40
|
-
templates.push({
|
|
41
|
-
description: 'Security-sensitive changes require audit',
|
|
42
|
-
category: 'security',
|
|
43
|
-
probability: 25,
|
|
44
|
-
impactDays: 4,
|
|
45
|
-
mitigationPlan: 'Engage security team for review and run SAST/DAST tools',
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
if (ctx.fileCount > 10) {
|
|
49
|
-
templates.push({
|
|
50
|
-
description: 'Large number of files increases merge conflict risk',
|
|
51
|
-
category: 'technical',
|
|
52
|
-
probability: 30,
|
|
53
|
-
impactDays: 1,
|
|
54
|
-
mitigationPlan: 'Use feature flags and incremental PRs to reduce merge surface',
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
return templates;
|
|
58
|
-
}
|
|
59
|
-
function buildRisks(ctx, templates) {
|
|
60
|
-
return templates.map((t, index) => ({
|
|
61
|
-
id: `R${index + 1}`,
|
|
62
|
-
description: t.description,
|
|
63
|
-
category: t.category,
|
|
64
|
-
probability: t.probability,
|
|
65
|
-
impactUsd: 0,
|
|
66
|
-
impactDays: t.impactDays,
|
|
67
|
-
owner: '',
|
|
68
|
-
status: 'identified',
|
|
69
|
-
mitigationPlan: t.mitigationPlan,
|
|
70
|
-
specId: ctx.specId,
|
|
71
|
-
}));
|
|
72
|
-
}
|
|
73
|
-
function computeAggregateScore(risks, totalBudgetDays) {
|
|
74
|
-
const rawScore = risks.reduce((acc, r) => acc + (r.probability / 100) * r.impactDays, 0);
|
|
75
|
-
const denominator = Math.max(totalBudgetDays, 1);
|
|
76
|
-
const score = (rawScore / denominator) * 100;
|
|
77
|
-
return Math.min(score, 100);
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Generate an initial risk register from spec metadata.
|
|
81
|
-
*/
|
|
82
|
-
export function generateRiskRegister(ctx, totalBudget) {
|
|
83
|
-
const budgetDays = totalBudget ?? 30;
|
|
84
|
-
const templates = buildRiskTemplates(ctx);
|
|
85
|
-
const risks = buildRisks(ctx, templates);
|
|
86
|
-
const aggregateRiskScore = computeAggregateScore(risks, budgetDays);
|
|
87
|
-
return {
|
|
88
|
-
specId: ctx.specId,
|
|
89
|
-
risks,
|
|
90
|
-
aggregateRiskScore,
|
|
91
|
-
generatedAt: new Date().toISOString(),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
//# sourceMappingURL=risk-generator.js.map
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export interface ReusableHelper {
|
|
2
|
-
symbol: string;
|
|
3
|
-
path: string;
|
|
4
|
-
line: number;
|
|
5
|
-
purpose: string;
|
|
6
|
-
}
|
|
7
|
-
export interface TestPatternMatch {
|
|
8
|
-
testPath: string;
|
|
9
|
-
whyRelevant: string;
|
|
10
|
-
}
|
|
11
|
-
export interface ConventionExcerpt {
|
|
12
|
-
rule: string;
|
|
13
|
-
excerpt: string;
|
|
14
|
-
}
|
|
15
|
-
export type AnticipatedBreakPattern = 'tool-count' | 'snapshot' | 'license-plans-sync' | 'streams-anchor' | 'other';
|
|
16
|
-
export interface AnticipatedBreak {
|
|
17
|
-
testPath: string;
|
|
18
|
-
pattern: AnticipatedBreakPattern;
|
|
19
|
-
remediation: string;
|
|
20
|
-
}
|
|
21
|
-
export interface ImplementationBrief {
|
|
22
|
-
specId: string;
|
|
23
|
-
generatedAt: string;
|
|
24
|
-
reusableHelpers: ReusableHelper[];
|
|
25
|
-
testPatterns: TestPatternMatch[];
|
|
26
|
-
conventions: {
|
|
27
|
-
excerpts: ConventionExcerpt[];
|
|
28
|
-
};
|
|
29
|
-
extensionPoints: string[];
|
|
30
|
-
anticipatedBreaks: AnticipatedBreak[];
|
|
31
|
-
}
|
|
32
|
-
export interface ImplementationBriefInput {
|
|
33
|
-
specId: string;
|
|
34
|
-
specPath: string;
|
|
35
|
-
projectPath: string;
|
|
36
|
-
description: string;
|
|
37
|
-
tags: string[];
|
|
38
|
-
target: string;
|
|
39
|
-
scope: string;
|
|
40
|
-
}
|
|
41
|
-
//# sourceMappingURL=implementation-brief.d.ts.map
|