@slope-dev/slope 1.59.2 → 1.60.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.
- package/dist/cli/actor.d.ts +18 -0
- package/dist/cli/actor.d.ts.map +1 -0
- package/dist/cli/actor.js +133 -0
- package/dist/cli/actor.js.map +1 -0
- package/dist/cli/commands/agent.d.ts.map +1 -1
- package/dist/cli/commands/agent.js +8 -6
- package/dist/cli/commands/agent.js.map +1 -1
- package/dist/cli/commands/briefing.d.ts.map +1 -1
- package/dist/cli/commands/briefing.js +12 -1
- package/dist/cli/commands/briefing.js.map +1 -1
- package/dist/cli/commands/claim.d.ts.map +1 -1
- package/dist/cli/commands/claim.js +10 -6
- package/dist/cli/commands/claim.js.map +1 -1
- package/dist/cli/commands/commit-ready.d.ts.map +1 -1
- package/dist/cli/commands/commit-ready.js +4 -2
- package/dist/cli/commands/commit-ready.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +6 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/interview.d.ts.map +1 -1
- package/dist/cli/commands/interview.js +5 -0
- package/dist/cli/commands/interview.js.map +1 -1
- package/dist/cli/commands/release.d.ts.map +1 -1
- package/dist/cli/commands/release.js +7 -3
- package/dist/cli/commands/release.js.map +1 -1
- package/dist/cli/commands/retro.d.ts.map +1 -1
- package/dist/cli/commands/retro.js +27 -6
- package/dist/cli/commands/retro.js.map +1 -1
- package/dist/cli/commands/review-run.d.ts +2 -0
- package/dist/cli/commands/review-run.d.ts.map +1 -1
- package/dist/cli/commands/review-run.js +38 -8
- package/dist/cli/commands/review-run.js.map +1 -1
- package/dist/cli/commands/review-state.d.ts +2 -5
- package/dist/cli/commands/review-state.d.ts.map +1 -1
- package/dist/cli/commands/review-state.js +166 -43
- package/dist/cli/commands/review-state.js.map +1 -1
- package/dist/cli/commands/roadmap.d.ts.map +1 -1
- package/dist/cli/commands/roadmap.js +17 -4
- package/dist/cli/commands/roadmap.js.map +1 -1
- package/dist/cli/commands/sprint.d.ts.map +1 -1
- package/dist/cli/commands/sprint.js +255 -28
- package/dist/cli/commands/sprint.js.map +1 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +20 -4
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/ticket.d.ts.map +1 -1
- package/dist/cli/commands/ticket.js +31 -9
- package/dist/cli/commands/ticket.js.map +1 -1
- package/dist/cli/commands/version.d.ts +12 -0
- package/dist/cli/commands/version.d.ts.map +1 -1
- package/dist/cli/commands/version.js +169 -4
- package/dist/cli/commands/version.js.map +1 -1
- package/dist/cli/git-preflight.d.ts +9 -0
- package/dist/cli/git-preflight.d.ts.map +1 -0
- package/dist/cli/git-preflight.js +36 -0
- package/dist/cli/git-preflight.js.map +1 -0
- package/dist/cli/guards/phase-boundary.d.ts.map +1 -1
- package/dist/cli/guards/phase-boundary.js +52 -8
- package/dist/cli/guards/phase-boundary.js.map +1 -1
- package/dist/cli/guards/post-push.d.ts.map +1 -1
- package/dist/cli/guards/post-push.js +7 -1
- package/dist/cli/guards/post-push.js.map +1 -1
- package/dist/cli/guards/pr-review.d.ts +6 -0
- package/dist/cli/guards/pr-review.d.ts.map +1 -1
- package/dist/cli/guards/pr-review.js +22 -11
- package/dist/cli/guards/pr-review.js.map +1 -1
- package/dist/cli/guards/session-briefing.d.ts.map +1 -1
- package/dist/cli/guards/session-briefing.js +20 -2
- package/dist/cli/guards/session-briefing.js.map +1 -1
- package/dist/cli/guards/sprint-completion.js +14 -1
- package/dist/cli/guards/sprint-completion.js.map +1 -1
- package/dist/cli/guards/workflow-step-gate.d.ts.map +1 -1
- package/dist/cli/guards/workflow-step-gate.js +25 -6
- package/dist/cli/guards/workflow-step-gate.js.map +1 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/registry.js +20 -3
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli/reviewer-agents.d.ts +22 -0
- package/dist/cli/reviewer-agents.d.ts.map +1 -0
- package/dist/cli/reviewer-agents.js +141 -0
- package/dist/cli/reviewer-agents.js.map +1 -0
- package/dist/cli/sprint-state.d.ts +28 -2
- package/dist/cli/sprint-state.d.ts.map +1 -1
- package/dist/cli/sprint-state.js +146 -9
- package/dist/cli/sprint-state.js.map +1 -1
- package/dist/cli/workflow-resync.d.ts +2 -0
- package/dist/cli/workflow-resync.d.ts.map +1 -1
- package/dist/cli/workflow-resync.js +25 -4
- package/dist/cli/workflow-resync.js.map +1 -1
- package/dist/core/analyzers/git.d.ts.map +1 -1
- package/dist/core/analyzers/git.js +95 -5
- package/dist/core/analyzers/git.js.map +1 -1
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +1 -0
- package/dist/core/constants.js.map +1 -1
- package/dist/core/findings.d.ts +20 -0
- package/dist/core/findings.d.ts.map +1 -0
- package/dist/core/findings.js +85 -0
- package/dist/core/findings.js.map +1 -0
- package/dist/core/generators/first-sprint.js +1 -1
- package/dist/core/generators/first-sprint.js.map +1 -1
- package/dist/core/generators/roadmap.js +3 -3
- package/dist/core/generators/roadmap.js.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/interview-engine.d.ts.map +1 -1
- package/dist/core/interview-engine.js +5 -4
- package/dist/core/interview-engine.js.map +1 -1
- package/dist/core/interview-metaphor.d.ts +5 -0
- package/dist/core/interview-metaphor.d.ts.map +1 -0
- package/dist/core/interview-metaphor.js +20 -0
- package/dist/core/interview-metaphor.js.map +1 -0
- package/dist/core/interview-steps.d.ts.map +1 -1
- package/dist/core/interview-steps.js +3 -6
- package/dist/core/interview-steps.js.map +1 -1
- package/dist/core/interview.d.ts.map +1 -1
- package/dist/core/interview.js +5 -6
- package/dist/core/interview.js.map +1 -1
- package/dist/core/types.d.ts +11 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/store/index.d.ts +13 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +60 -7
- package/dist/store/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/workflows/sprint-standard.yaml +6 -0
- package/templates/claude-code/commands/post-sprint.md +2 -0
- package/templates/claude-code/commands/start-sprint.md +1 -0
- package/templates/codex/plugins/slope/.codex-plugin/plugin.json +1 -1
- package/templates/codex/plugins/slope/skills/slope-retro/SKILL.md +1 -1
- package/templates/generic/SLOPE-CHECKLIST.md +3 -2
- package/templates/pi/skills/post-sprint.md +2 -0
- package/templates/pi/skills/start-sprint.md +1 -0
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { execSync } from 'node:child_process';
|
|
11
11
|
import { loadConfig, detectLatestSprint } from '../../core/index.js';
|
|
12
|
+
import { buildReviewerAgentSpecs } from '../reviewer-agents.js';
|
|
12
13
|
function getPrDiff(prNumber) {
|
|
13
14
|
try {
|
|
14
15
|
const prArg = prNumber ? String(prNumber) : '';
|
|
@@ -22,11 +23,21 @@ function getPrDiff(prNumber) {
|
|
|
22
23
|
return null;
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
|
-
function
|
|
26
|
+
function reviewerAgentIntro(spec) {
|
|
27
|
+
if (!spec)
|
|
28
|
+
return [];
|
|
29
|
+
return [
|
|
30
|
+
'## Purpose-built Reviewer Agent',
|
|
31
|
+
spec.prompt,
|
|
32
|
+
'',
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
function buildArchitectPrompt(diff, files, sprint, reviewer) {
|
|
26
36
|
return [
|
|
27
37
|
'You are performing an ARCHITECT REVIEW of a pull request.',
|
|
28
38
|
'You have a clean context — no prior implementation knowledge.',
|
|
29
39
|
'',
|
|
40
|
+
...reviewerAgentIntro(reviewer),
|
|
30
41
|
'## Review Criteria',
|
|
31
42
|
'1. Does the design match codebase patterns? Check for duplication of existing infrastructure.',
|
|
32
43
|
'2. Are dependencies correct and ordering optimal?',
|
|
@@ -50,11 +61,12 @@ function buildArchitectPrompt(diff, files, sprint) {
|
|
|
50
61
|
'If no issues found, say "No architect findings."',
|
|
51
62
|
].join('\n');
|
|
52
63
|
}
|
|
53
|
-
function buildCodePrompt(diff, files, sprint) {
|
|
64
|
+
function buildCodePrompt(diff, files, sprint, reviewer) {
|
|
54
65
|
return [
|
|
55
66
|
'You are performing a CODE REVIEW of a pull request.',
|
|
56
67
|
'You have a clean context — no prior implementation knowledge.',
|
|
57
68
|
'',
|
|
69
|
+
...reviewerAgentIntro(reviewer),
|
|
58
70
|
'## Review Criteria',
|
|
59
71
|
'1. Correctness: Does the code do what it claims? Edge cases?',
|
|
60
72
|
'2. Error handling: Are errors caught and handled appropriately?',
|
|
@@ -109,18 +121,33 @@ export async function reviewRunCommand(args) {
|
|
|
109
121
|
}
|
|
110
122
|
const diffLines = pr.diff.split('\n').length;
|
|
111
123
|
const prompts = [];
|
|
124
|
+
const recommendations = [];
|
|
125
|
+
if (reviewType === 'architect' || reviewType === 'both') {
|
|
126
|
+
recommendations.push({ review_type: 'architect', priority: 'required', reason: 'PR architect review requested' });
|
|
127
|
+
}
|
|
128
|
+
if (reviewType === 'code' || reviewType === 'both') {
|
|
129
|
+
recommendations.push({ review_type: 'code', priority: 'optional', reason: 'PR code review requested' });
|
|
130
|
+
}
|
|
131
|
+
const reviewerAgents = buildReviewerAgentSpecs(recommendations, {
|
|
132
|
+
sprintNumber: sprint,
|
|
133
|
+
filePatterns: pr.files,
|
|
134
|
+
artifacts: pr.files,
|
|
135
|
+
});
|
|
136
|
+
const reviewerByLane = new Map(reviewerAgents.map(spec => [spec.lane, spec]));
|
|
112
137
|
if (reviewType === 'architect' || reviewType === 'both') {
|
|
138
|
+
const reviewer = reviewerByLane.get('architect');
|
|
113
139
|
prompts.push({
|
|
114
140
|
type: 'architect',
|
|
115
|
-
prompt: buildArchitectPrompt(pr.diff, pr.files, sprint),
|
|
116
|
-
context: { pr_number: pr.prNum, sprint, changed_files: pr.files, diff_lines: diffLines },
|
|
141
|
+
prompt: buildArchitectPrompt(pr.diff, pr.files, sprint, reviewer),
|
|
142
|
+
context: { pr_number: pr.prNum, sprint, changed_files: pr.files, diff_lines: diffLines, reviewer_agent: reviewer },
|
|
117
143
|
});
|
|
118
144
|
}
|
|
119
145
|
if (reviewType === 'code' || reviewType === 'both') {
|
|
146
|
+
const reviewer = reviewerByLane.get('code');
|
|
120
147
|
prompts.push({
|
|
121
148
|
type: 'code',
|
|
122
|
-
prompt: buildCodePrompt(pr.diff, pr.files, sprint),
|
|
123
|
-
context: { pr_number: pr.prNum, sprint, changed_files: pr.files, diff_lines: diffLines },
|
|
149
|
+
prompt: buildCodePrompt(pr.diff, pr.files, sprint, reviewer),
|
|
150
|
+
context: { pr_number: pr.prNum, sprint, changed_files: pr.files, diff_lines: diffLines, reviewer_agent: reviewer },
|
|
124
151
|
});
|
|
125
152
|
}
|
|
126
153
|
if (json) {
|
|
@@ -132,11 +159,14 @@ export async function reviewRunCommand(args) {
|
|
|
132
159
|
console.log(`Sprint: ${sprint ?? '?'} | Files: ${pr.files.length} | Diff: ${diffLines} lines\n`);
|
|
133
160
|
for (const p of prompts) {
|
|
134
161
|
console.log(`--- ${p.type.toUpperCase()} REVIEW PROMPT ---`);
|
|
135
|
-
|
|
162
|
+
if (p.context.reviewer_agent) {
|
|
163
|
+
console.log(`Suggested reviewer agent: ${p.context.reviewer_agent.name} (${p.context.reviewer_agent.id})`);
|
|
164
|
+
}
|
|
165
|
+
console.log('Use this with Codex or Claude Code Agent tool for an isolated review:\n');
|
|
136
166
|
console.log(p.prompt);
|
|
137
167
|
console.log('');
|
|
138
168
|
}
|
|
139
|
-
console.log('To run both reviews as subagents, use:');
|
|
169
|
+
console.log('To run both reviews as purpose-built subagents, use:');
|
|
140
170
|
console.log(' slope review run --json | # pass to Agent tool prompts');
|
|
141
171
|
console.log('');
|
|
142
172
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-run.js","sourceRoot":"","sources":["../../../src/cli/commands/review-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"review-run.js","sourceRoot":"","sources":["../../../src/cli/commands/review-run.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
export { loadFindings } from '../../core/findings.js';
|
|
2
|
+
export type { FindingsFile } from '../../core/findings.js';
|
|
2
3
|
export interface ReviewState {
|
|
3
4
|
rounds_required: number;
|
|
4
5
|
rounds_completed: number;
|
|
@@ -8,10 +9,6 @@ export interface ReviewState {
|
|
|
8
9
|
}
|
|
9
10
|
export declare function loadReviewState(cwd: string): ReviewState | null;
|
|
10
11
|
export declare function saveReviewState(cwd: string, state: ReviewState): void;
|
|
11
|
-
export interface FindingsFile {
|
|
12
|
-
sprints: Record<number, ReviewFinding[]>;
|
|
13
|
-
}
|
|
14
|
-
export declare function loadFindings(cwd: string): FindingsFile | null;
|
|
15
12
|
export declare const REVIEW_STATE_SUBCOMMANDS: string[];
|
|
16
13
|
export declare function shouldRouteToReviewState(args: string[]): boolean;
|
|
17
14
|
export declare function reviewStateCommand(args: string[]): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-state.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/review-state.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"review-state.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/review-state.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAO3D,MAAM,WAAW,WAAW;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAWD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAQ/D;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAIrE;AA+pBD,eAAO,MAAM,wBAAwB,UAYpC,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAIhE;AA2GD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqDtE"}
|
|
@@ -4,9 +4,12 @@ import { recommendReviews, amendScorecardWithFindings } from '../../core/review.
|
|
|
4
4
|
import { loadConfig, detectLatestSprint, normalizeScorecard, parseSprintNumber } from '../../core/index.js';
|
|
5
5
|
import { createDeferred, listDeferred, resolveDeferred } from '../../core/deferred.js';
|
|
6
6
|
import { HAZARD_SEVERITY_PENALTIES } from '../../core/constants.js';
|
|
7
|
+
import { FINDINGS_FILE, createFindingId, displayFindingId, formatCodificationMetadata, isCodificationCandidate, loadFindings, matchesFindingId, saveFindings, } from '../../core/findings.js';
|
|
8
|
+
export { loadFindings } from '../../core/findings.js';
|
|
7
9
|
import { findPlanContent, countTickets, countPackageRefs } from '../guards/plan-analysis.js';
|
|
8
10
|
import { inferSprintContext, loadRoadmapForInference } from '../sprint-inference.js';
|
|
9
11
|
import { isActiveSprintState, loadSprintState } from '../sprint-state.js';
|
|
12
|
+
import { buildReviewerAgentSpecs, formatReviewerAgentGuidance } from '../reviewer-agents.js';
|
|
10
13
|
const REVIEW_STATE_FILE = '.slope/review-state.json';
|
|
11
14
|
const TIER_ROUNDS = {
|
|
12
15
|
skip: 0,
|
|
@@ -136,31 +139,6 @@ function resetCommand(cwd) {
|
|
|
136
139
|
}
|
|
137
140
|
console.log('Review state cleared.');
|
|
138
141
|
}
|
|
139
|
-
// --- Findings File Management ---
|
|
140
|
-
const FINDINGS_FILE = '.slope/review-findings.json';
|
|
141
|
-
export function loadFindings(cwd) {
|
|
142
|
-
const filePath = join(cwd, FINDINGS_FILE);
|
|
143
|
-
if (!existsSync(filePath))
|
|
144
|
-
return null;
|
|
145
|
-
try {
|
|
146
|
-
const raw = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
147
|
-
// Migrate legacy single-sprint format
|
|
148
|
-
if (raw.sprints)
|
|
149
|
-
return { sprints: raw.sprints };
|
|
150
|
-
if (raw.sprint_number != null && raw.findings) {
|
|
151
|
-
return { sprints: { [raw.sprint_number]: raw.findings } };
|
|
152
|
-
}
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
function saveFindings(cwd, data) {
|
|
160
|
-
const dir = join(cwd, '.slope');
|
|
161
|
-
mkdirSync(dir, { recursive: true });
|
|
162
|
-
writeFileSync(join(cwd, FINDINGS_FILE), JSON.stringify(data, null, 2) + '\n');
|
|
163
|
-
}
|
|
164
142
|
function parseSprintArg(args) {
|
|
165
143
|
const sprintArg = args.find(a => a.startsWith('--sprint='));
|
|
166
144
|
if (!sprintArg)
|
|
@@ -187,10 +165,17 @@ function collectStringValues(value, out = []) {
|
|
|
187
165
|
}
|
|
188
166
|
return out;
|
|
189
167
|
}
|
|
168
|
+
function extractHazardLines(values) {
|
|
169
|
+
return values
|
|
170
|
+
.map(value => value.replace(/\s+/g, ' ').trim())
|
|
171
|
+
.filter(value => /\b(hazard|risk|gotcha|warning|failure|safety|security|boundary|review gate)\b/i.test(value))
|
|
172
|
+
.slice(0, 8);
|
|
173
|
+
}
|
|
190
174
|
function recommendationFromPlan(content) {
|
|
191
175
|
const filePatterns = [];
|
|
192
176
|
const slopeMatch = content.match(/\*\*Slope:\*\*\s*(\d+)/);
|
|
193
177
|
const sprintMatch = content.match(/Sprint\s+(\d+(?:\.\d+)?)/);
|
|
178
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
194
179
|
const fileMatches = content.matchAll(/`([^`]+\.[a-z]+)`/g);
|
|
195
180
|
for (const m of fileMatches)
|
|
196
181
|
filePatterns.push(m[1]);
|
|
@@ -199,6 +184,9 @@ function recommendationFromPlan(content) {
|
|
|
199
184
|
slope: slopeMatch ? parseInt(slopeMatch[1], 10) : 0,
|
|
200
185
|
sprintNumber: sprintMatch ? parseSprintNumber(sprintMatch[1]) ?? 0 : 0,
|
|
201
186
|
filePatterns,
|
|
187
|
+
theme: titleMatch?.[1],
|
|
188
|
+
artifacts: filePatterns,
|
|
189
|
+
hazards: extractHazardLines(content.split('\n')),
|
|
202
190
|
hasNewInfra: /\b(new module|new package|new service|new infrastructure)\b/i.test(content),
|
|
203
191
|
};
|
|
204
192
|
}
|
|
@@ -210,16 +198,23 @@ function recommendationFromRoadmapSprint(sprint) {
|
|
|
210
198
|
slope: sprint.slope ?? 0,
|
|
211
199
|
sprintNumber: sprint.id,
|
|
212
200
|
filePatterns,
|
|
201
|
+
theme: sprint.theme,
|
|
202
|
+
artifacts: filePatterns.length > 0 ? filePatterns : sprint.tickets?.map(ticket => `${ticket.key}: ${ticket.title}`),
|
|
203
|
+
hazards: extractHazardLines(stringValues),
|
|
213
204
|
hasNewInfra: /\b(new module|new package|new service|new infrastructure)\b/i.test(stringValues.join('\n')),
|
|
214
205
|
};
|
|
215
206
|
}
|
|
216
207
|
function recommendationFromScorecard(card) {
|
|
217
208
|
const stringValues = collectStringValues(card);
|
|
209
|
+
const filePatterns = stringValues.filter(value => /[/.]/.test(value));
|
|
218
210
|
return {
|
|
219
211
|
ticketCount: card.shots?.length ?? 0,
|
|
220
212
|
slope: card.slope ?? 0,
|
|
221
213
|
sprintNumber: card.sprint_number,
|
|
222
|
-
filePatterns
|
|
214
|
+
filePatterns,
|
|
215
|
+
theme: card.theme,
|
|
216
|
+
artifacts: filePatterns,
|
|
217
|
+
hazards: extractHazardLines(stringValues),
|
|
223
218
|
hasNewInfra: /\b(new module|new package|new service|new infrastructure)\b/i.test(stringValues.join('\n')),
|
|
224
219
|
};
|
|
225
220
|
}
|
|
@@ -247,6 +242,8 @@ function inferRecommendationContext(args, cwd) {
|
|
|
247
242
|
slope: 0,
|
|
248
243
|
filePatterns: [],
|
|
249
244
|
sprintNumber: explicitSprint,
|
|
245
|
+
artifacts: [],
|
|
246
|
+
hazards: [],
|
|
250
247
|
hasNewInfra: false,
|
|
251
248
|
};
|
|
252
249
|
}
|
|
@@ -269,6 +266,8 @@ function inferRecommendationContext(args, cwd) {
|
|
|
269
266
|
slope: 0,
|
|
270
267
|
filePatterns: [],
|
|
271
268
|
sprintNumber: inferred.sprint,
|
|
269
|
+
artifacts: [],
|
|
270
|
+
hazards: [],
|
|
272
271
|
hasNewInfra: false,
|
|
273
272
|
};
|
|
274
273
|
}
|
|
@@ -280,6 +279,8 @@ function inferRecommendationContext(args, cwd) {
|
|
|
280
279
|
slope: 0,
|
|
281
280
|
filePatterns: [],
|
|
282
281
|
sprintNumber: 0,
|
|
282
|
+
artifacts: [],
|
|
283
|
+
hazards: [],
|
|
283
284
|
hasNewInfra: false,
|
|
284
285
|
};
|
|
285
286
|
}
|
|
@@ -294,10 +295,22 @@ function recommendCommand(args, cwd) {
|
|
|
294
295
|
const priority = rec.priority.padEnd(13);
|
|
295
296
|
console.log(` ${type} ${priority} ${rec.reason}`);
|
|
296
297
|
}
|
|
298
|
+
const reviewerAgents = buildReviewerAgentSpecs(recs, {
|
|
299
|
+
sprintNumber: context.sprintNumber > 0 ? context.sprintNumber : undefined,
|
|
300
|
+
theme: context.theme,
|
|
301
|
+
filePatterns: context.filePatterns,
|
|
302
|
+
artifacts: context.artifacts,
|
|
303
|
+
hazards: context.hazards,
|
|
304
|
+
});
|
|
305
|
+
const guidance = formatReviewerAgentGuidance(reviewerAgents);
|
|
306
|
+
if (guidance)
|
|
307
|
+
console.log(`\n${guidance}`);
|
|
297
308
|
}
|
|
298
309
|
// --- Findings Subcommands ---
|
|
299
|
-
const VALID_REVIEW_TYPES = ['architect', 'code', 'ml-engineer', 'security', 'ux'];
|
|
310
|
+
const VALID_REVIEW_TYPES = ['architect', 'code', 'ml-engineer', 'security', 'ux', 'workaround'];
|
|
300
311
|
const VALID_SEVERITIES = ['minor', 'moderate', 'major', 'critical'];
|
|
312
|
+
const VALID_CODIFICATION_COSTS = ['s', 'm', 'l'];
|
|
313
|
+
const VALID_CODIFICATION_STATUSES = ['open', 'paid_down', 'wontfix'];
|
|
301
314
|
function findingsAddCommand(args, cwd) {
|
|
302
315
|
const typeArg = args.find(a => a.startsWith('--type='));
|
|
303
316
|
const ticketArg = args.find(a => a.startsWith('--ticket='));
|
|
@@ -305,9 +318,12 @@ function findingsAddCommand(args, cwd) {
|
|
|
305
318
|
const descArg = args.find(a => a.startsWith('--description='));
|
|
306
319
|
const sprintArg = args.find(a => a.startsWith('--sprint='));
|
|
307
320
|
const resolvedArg = args.includes('--resolved');
|
|
308
|
-
|
|
309
|
-
|
|
321
|
+
const recursArg = args.includes('--recurs');
|
|
322
|
+
const costArg = args.find(a => a.startsWith('--cost='));
|
|
323
|
+
if (!typeArg || !descArg) {
|
|
324
|
+
console.error('Error: --type and --description are required; --ticket is required except for --type=workaround.');
|
|
310
325
|
console.error('Usage: slope review findings add --type=architect --ticket=S34-1 --severity=moderate --description="..."');
|
|
326
|
+
console.error('Usage: slope review findings add --type=workaround --recurs --cost=s --description="..."');
|
|
311
327
|
process.exit(1);
|
|
312
328
|
}
|
|
313
329
|
const reviewType = typeArg.slice('--type='.length);
|
|
@@ -315,6 +331,27 @@ function findingsAddCommand(args, cwd) {
|
|
|
315
331
|
console.error(`Error: Invalid review type "${reviewType}". Use: ${VALID_REVIEW_TYPES.join(', ')}`);
|
|
316
332
|
process.exit(1);
|
|
317
333
|
}
|
|
334
|
+
if (!ticketArg && reviewType !== 'workaround') {
|
|
335
|
+
console.error('Error: --ticket is required unless --type=workaround.');
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
const cost = costArg ? costArg.slice('--cost='.length) : undefined;
|
|
339
|
+
if (cost && !VALID_CODIFICATION_COSTS.includes(cost)) {
|
|
340
|
+
console.error(`Error: Invalid codification cost "${cost}". Use: ${VALID_CODIFICATION_COSTS.join(', ')}`);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
if (reviewType === 'workaround' && !recursArg) {
|
|
344
|
+
console.error('Error: workaround findings must include --recurs so one-off workarounds are not logged as codification debt.');
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
if (recursArg && !cost) {
|
|
348
|
+
console.error('Error: recurring workaround findings require --cost=s|m|l.');
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
if (cost && !recursArg) {
|
|
352
|
+
console.error('Error: --cost requires --recurs.');
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
318
355
|
const severity = severityArg
|
|
319
356
|
? severityArg.slice('--severity='.length)
|
|
320
357
|
: 'moderate';
|
|
@@ -322,7 +359,7 @@ function findingsAddCommand(args, cwd) {
|
|
|
322
359
|
console.error(`Error: Invalid severity "${severity}". Use: ${VALID_SEVERITIES.join(', ')}`);
|
|
323
360
|
process.exit(1);
|
|
324
361
|
}
|
|
325
|
-
const ticketKey = ticketArg.slice('--ticket='.length);
|
|
362
|
+
const ticketKey = ticketArg ? ticketArg.slice('--ticket='.length) : 'workaround';
|
|
326
363
|
const description = descArg.slice('--description='.length);
|
|
327
364
|
// Determine sprint number
|
|
328
365
|
let sprintNumber = 0;
|
|
@@ -337,11 +374,15 @@ function findingsAddCommand(args, cwd) {
|
|
|
337
374
|
catch { /* fallback to 0 */ }
|
|
338
375
|
}
|
|
339
376
|
const finding = {
|
|
377
|
+
id: createFindingId(),
|
|
340
378
|
review_type: reviewType,
|
|
341
379
|
ticket_key: ticketKey,
|
|
342
380
|
severity,
|
|
343
381
|
description,
|
|
344
382
|
resolved: resolvedArg,
|
|
383
|
+
recurs: recursArg || undefined,
|
|
384
|
+
cost,
|
|
385
|
+
codification_status: recursArg ? 'open' : undefined,
|
|
345
386
|
};
|
|
346
387
|
const existing = loadFindings(cwd);
|
|
347
388
|
if (existing && existing.sprints[sprintNumber]) {
|
|
@@ -357,9 +398,21 @@ function findingsAddCommand(args, cwd) {
|
|
|
357
398
|
saveFindings(cwd, { sprints: { [sprintNumber]: [finding] } });
|
|
358
399
|
}
|
|
359
400
|
console.log(`Finding added: [${reviewType}] ${ticketKey} — ${description} (${severity})`);
|
|
401
|
+
console.log(` ID: ${finding.id}`);
|
|
402
|
+
const codification = formatCodificationMetadata(finding);
|
|
403
|
+
if (codification)
|
|
404
|
+
console.log(` ${codification}`);
|
|
360
405
|
}
|
|
361
406
|
function findingsListCommand(args, cwd) {
|
|
362
407
|
const sprintArg = args.find(a => a.startsWith('--sprint='));
|
|
408
|
+
const codificationStatusArg = args.find(a => a.startsWith('--codification-status='));
|
|
409
|
+
const codificationStatus = codificationStatusArg
|
|
410
|
+
? codificationStatusArg.slice('--codification-status='.length)
|
|
411
|
+
: undefined;
|
|
412
|
+
if (codificationStatus && !VALID_CODIFICATION_STATUSES.includes(codificationStatus)) {
|
|
413
|
+
console.error(`Error: Invalid codification status "${codificationStatus}". Use: ${VALID_CODIFICATION_STATUSES.join(', ')}`);
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
363
416
|
const data = loadFindings(cwd);
|
|
364
417
|
if (!data || Object.keys(data.sprints).length === 0) {
|
|
365
418
|
console.log('No review findings recorded.');
|
|
@@ -367,36 +420,101 @@ function findingsListCommand(args, cwd) {
|
|
|
367
420
|
}
|
|
368
421
|
if (sprintArg) {
|
|
369
422
|
const requestedSprint = parseSprintNumber(sprintArg.slice('--sprint='.length)) ?? 0;
|
|
370
|
-
const sprintFindings = data.sprints[requestedSprint];
|
|
423
|
+
const sprintFindings = filterFindingsForList(data.sprints[requestedSprint] ?? [], codificationStatus);
|
|
371
424
|
if (!sprintFindings || sprintFindings.length === 0) {
|
|
372
425
|
console.log(`No findings for Sprint ${requestedSprint}.`);
|
|
373
426
|
return;
|
|
374
427
|
}
|
|
375
428
|
console.log(`Sprint ${requestedSprint} findings (${sprintFindings.length} total):\n`);
|
|
376
|
-
console.log(' Ticket Type Severity Description');
|
|
377
|
-
for (const f of sprintFindings) {
|
|
429
|
+
console.log(' ID Ticket Type Severity Description');
|
|
430
|
+
for (const [index, f] of sprintFindings.entries()) {
|
|
431
|
+
const id = displayFindingId(f, requestedSprint, index).padEnd(8);
|
|
378
432
|
const ticket = f.ticket_key.padEnd(7);
|
|
379
433
|
const type = f.review_type.padEnd(14);
|
|
380
434
|
const severity = f.severity.padEnd(10);
|
|
381
|
-
|
|
435
|
+
const meta = formatCodificationMetadata(f);
|
|
436
|
+
console.log(` ${id} ${ticket} ${type} ${severity} ${f.description}${meta ? ` (${meta})` : ''}`);
|
|
382
437
|
}
|
|
383
438
|
}
|
|
384
439
|
else {
|
|
385
440
|
// Show all findings grouped by sprint
|
|
386
441
|
const sprintKeys = Object.keys(data.sprints).map(Number).sort((a, b) => a - b);
|
|
442
|
+
let printed = false;
|
|
387
443
|
for (const sprint of sprintKeys) {
|
|
388
|
-
const sprintFindings = data.sprints[sprint];
|
|
444
|
+
const sprintFindings = filterFindingsForList(data.sprints[sprint] ?? [], codificationStatus);
|
|
445
|
+
if (sprintFindings.length === 0)
|
|
446
|
+
continue;
|
|
447
|
+
printed = true;
|
|
389
448
|
console.log(`Sprint ${sprint} findings (${sprintFindings.length}):\n`);
|
|
390
|
-
console.log(' Ticket Type Severity Description');
|
|
391
|
-
for (const f of sprintFindings) {
|
|
449
|
+
console.log(' ID Ticket Type Severity Description');
|
|
450
|
+
for (const [index, f] of sprintFindings.entries()) {
|
|
451
|
+
const id = displayFindingId(f, sprint, index).padEnd(8);
|
|
392
452
|
const ticket = f.ticket_key.padEnd(7);
|
|
393
453
|
const type = f.review_type.padEnd(14);
|
|
394
454
|
const severity = f.severity.padEnd(10);
|
|
395
|
-
|
|
455
|
+
const meta = formatCodificationMetadata(f);
|
|
456
|
+
console.log(` ${id} ${ticket} ${type} ${severity} ${f.description}${meta ? ` (${meta})` : ''}`);
|
|
396
457
|
}
|
|
397
458
|
console.log('');
|
|
398
459
|
}
|
|
460
|
+
if (!printed) {
|
|
461
|
+
console.log(codificationStatus
|
|
462
|
+
? `No findings match codification status "${codificationStatus}".`
|
|
463
|
+
: 'No review findings recorded.');
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function filterFindingsForList(findings, codificationStatus) {
|
|
468
|
+
if (!codificationStatus)
|
|
469
|
+
return findings;
|
|
470
|
+
return findings.filter(finding => {
|
|
471
|
+
if (!isCodificationCandidate(finding))
|
|
472
|
+
return false;
|
|
473
|
+
return (finding.codification_status ?? 'open') === codificationStatus;
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
function findingsResolveCommand(args, cwd) {
|
|
477
|
+
const id = args.find(a => !a.startsWith('--')) ?? args.find(a => a.startsWith('--id='))?.slice('--id='.length);
|
|
478
|
+
const statusArg = args.find(a => a.startsWith('--status='));
|
|
479
|
+
const status = statusArg ? statusArg.slice('--status='.length) : 'paid_down';
|
|
480
|
+
if (!id) {
|
|
481
|
+
console.error('Error: finding id is required. Usage: slope review findings resolve <id> [--status=paid_down|wontfix]');
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
if (!['paid_down', 'wontfix'].includes(status)) {
|
|
485
|
+
console.error('Error: --status must be paid_down or wontfix.');
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
const data = loadFindings(cwd);
|
|
489
|
+
if (!data) {
|
|
490
|
+
console.error('Error: no review findings recorded.');
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
const now = new Date().toISOString();
|
|
494
|
+
for (const sprint of Object.keys(data.sprints).map(Number).sort((a, b) => a - b)) {
|
|
495
|
+
const findings = data.sprints[sprint] ?? [];
|
|
496
|
+
for (const [index, finding] of findings.entries()) {
|
|
497
|
+
if (!matchesFindingId(finding, sprint, index, id))
|
|
498
|
+
continue;
|
|
499
|
+
if (!isCodificationCandidate(finding)) {
|
|
500
|
+
console.error(`Error: finding ${id} is not a recurring workaround codification candidate.`);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
finding.resolved = true;
|
|
504
|
+
finding.codification_status = status;
|
|
505
|
+
finding.resolved_at = now;
|
|
506
|
+
if (status === 'paid_down')
|
|
507
|
+
finding.codified_at = now;
|
|
508
|
+
else
|
|
509
|
+
delete finding.codified_at;
|
|
510
|
+
saveFindings(cwd, data);
|
|
511
|
+
console.log(`Codification candidate ${status}: ${displayFindingId(finding, sprint, index)}`);
|
|
512
|
+
console.log(` S${sprint}: ${finding.description}`);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
399
515
|
}
|
|
516
|
+
console.error(`Error: no finding found matching id "${id}".`);
|
|
517
|
+
process.exit(1);
|
|
400
518
|
}
|
|
401
519
|
function findingsClearCommand(args, cwd) {
|
|
402
520
|
const filePath = join(cwd, FINDINGS_FILE);
|
|
@@ -414,8 +532,9 @@ function findingsCommand(args, cwd) {
|
|
|
414
532
|
console.log(`Usage: slope review findings <subcommand> [options]
|
|
415
533
|
|
|
416
534
|
Subcommands:
|
|
417
|
-
add Add a review finding
|
|
535
|
+
add Add a review finding; workaround candidates use --recurs --cost=s|m|l
|
|
418
536
|
list List recorded findings
|
|
537
|
+
resolve Mark a codification candidate paid_down or wontfix
|
|
419
538
|
clear Clear all findings
|
|
420
539
|
|
|
421
540
|
Use 'slope review findings add --help' for add options.`);
|
|
@@ -429,11 +548,14 @@ Use 'slope review findings add --help' for add options.`);
|
|
|
429
548
|
case 'list':
|
|
430
549
|
findingsListCommand(args.slice(1), cwd);
|
|
431
550
|
break;
|
|
551
|
+
case 'resolve':
|
|
552
|
+
findingsResolveCommand(args.slice(1), cwd);
|
|
553
|
+
break;
|
|
432
554
|
case 'clear':
|
|
433
555
|
findingsClearCommand(args.slice(1), cwd);
|
|
434
556
|
break;
|
|
435
557
|
default:
|
|
436
|
-
console.error(`Unknown findings subcommand: ${sub}. Use add, list, or clear.`);
|
|
558
|
+
console.error(`Unknown findings subcommand: ${sub}. Use add, list, resolve, or clear.`);
|
|
437
559
|
process.exit(1);
|
|
438
560
|
}
|
|
439
561
|
}
|
|
@@ -587,11 +709,12 @@ ux) apply to the current sprint based on active sprint state, roadmap,
|
|
|
587
709
|
scorecard, or plan metadata.`);
|
|
588
710
|
break;
|
|
589
711
|
case 'findings':
|
|
590
|
-
console.log(`Usage: slope review findings <add|list|clear> [options]
|
|
712
|
+
console.log(`Usage: slope review findings <add|list|resolve|clear> [options]
|
|
591
713
|
|
|
592
714
|
Subcommands:
|
|
593
|
-
add Record a review finding (--type, --ticket, --severity, --description)
|
|
594
|
-
list List recorded findings (optionally --sprint=N)
|
|
715
|
+
add Record a review finding (--type, --ticket, --severity, --description; workaround uses --recurs --cost=s|m|l)
|
|
716
|
+
list List recorded findings (optionally --sprint=N or --codification-status=open|paid_down|wontfix)
|
|
717
|
+
resolve Mark a recurring workaround candidate paid_down or wontfix by id
|
|
595
718
|
clear Clear all findings`);
|
|
596
719
|
break;
|
|
597
720
|
case 'amend':
|