@boyingliu01/xp-gate 0.5.1 → 0.5.3
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/bin/xp-gate.js +17 -0
- package/lib/audit-log.ts +79 -0
- package/lib/ui-detector.ts +126 -1
- package/lib/ui-review.ts +109 -0
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/skills/sprint-flow/SKILL.md +254 -15
- package/skills/sprint-flow/evals/evals.json +53 -1
- package/skills/sprint-flow/references/phase-minus-0-5-auto-estimate.md +238 -0
- package/skills/sprint-flow/templates/auto-estimate-learning-log.md +136 -0
- package/skills/sprint-flow/templates/auto-estimate-output-template.md +130 -0
package/bin/xp-gate.js
CHANGED
|
@@ -48,6 +48,11 @@ const COMMANDS = {
|
|
|
48
48
|
description: 'Diagnose xp-gate installation health',
|
|
49
49
|
fn: doctor,
|
|
50
50
|
usage: 'xp-gate doctor [--fix]'
|
|
51
|
+
},
|
|
52
|
+
'ui-review': {
|
|
53
|
+
description: 'Run UI review for non-sprint developers (generates .ui-gate-result.json)',
|
|
54
|
+
fn: null,
|
|
55
|
+
usage: 'xp-gate ui-review'
|
|
51
56
|
}
|
|
52
57
|
};
|
|
53
58
|
|
|
@@ -136,6 +141,18 @@ function main() {
|
|
|
136
141
|
doctor(subargs).then(code => process.exit(code));
|
|
137
142
|
return;
|
|
138
143
|
}
|
|
144
|
+
|
|
145
|
+
if (command === 'ui-review') {
|
|
146
|
+
const { execSync } = require('child_process');
|
|
147
|
+
const path = require('path');
|
|
148
|
+
const uiReviewPath = path.join(__dirname, '..', 'lib', 'ui-review.ts');
|
|
149
|
+
try {
|
|
150
|
+
execSync(`npx -y tsx "${uiReviewPath}"`, { stdio: 'inherit' });
|
|
151
|
+
process.exit(0);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
139
156
|
|
|
140
157
|
console.error(`Unknown command: ${command}`);
|
|
141
158
|
printHelp();
|
package/lib/audit-log.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, appendFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
export interface AuditEntry {
|
|
5
|
+
timestamp: string;
|
|
6
|
+
branch: string;
|
|
7
|
+
commit: string;
|
|
8
|
+
user: string;
|
|
9
|
+
reason: string;
|
|
10
|
+
bypass_type: string;
|
|
11
|
+
gate_count: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const AUDIT_LOG_FILE = '.audit-log.jsonl';
|
|
15
|
+
const MAX_ENTRIES = 100;
|
|
16
|
+
const ROLLING_WINDOW_DAYS = 30;
|
|
17
|
+
const MAX_BYPASS_THRESHOLD = 3;
|
|
18
|
+
|
|
19
|
+
export function appendAuditEntry(entry: Omit<AuditEntry, 'gate_count'>, repoRoot: string = process.cwd()): AuditEntry {
|
|
20
|
+
const logPath = join(repoRoot, AUDIT_LOG_FILE);
|
|
21
|
+
const existing = readAllEntries(logPath);
|
|
22
|
+
const gate_count = countRecentBypasses(existing, entry.bypass_type, entry.timestamp);
|
|
23
|
+
|
|
24
|
+
const fullEntry: AuditEntry = { ...entry, gate_count };
|
|
25
|
+
appendFileSync(logPath, JSON.stringify(fullEntry) + '\n', 'utf8');
|
|
26
|
+
|
|
27
|
+
trimLog(logPath);
|
|
28
|
+
return fullEntry;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function readAllEntries(logPath: string = join(process.cwd(), AUDIT_LOG_FILE)): AuditEntry[] {
|
|
32
|
+
if (!existsSync(logPath)) return [];
|
|
33
|
+
const content = readFileSync(logPath, 'utf8').trim();
|
|
34
|
+
if (!content) return [];
|
|
35
|
+
return content.split('\n').map(line => JSON.parse(line) as AuditEntry);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function countBypassesInWindow(entries: AuditEntry[], bypassType: string, windowDays: number = ROLLING_WINDOW_DAYS): number {
|
|
39
|
+
const cutoff = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1000);
|
|
40
|
+
return entries.filter(e => e.bypass_type === bypassType && new Date(e.timestamp) >= cutoff).length;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function exceedsBypassThreshold(entries: AuditEntry[], bypassType: string, threshold: number = MAX_BYPASS_THRESHOLD): { exceeded: boolean; count: number } {
|
|
44
|
+
const count = countBypassesInWindow(entries, bypassType);
|
|
45
|
+
return { exceeded: count > threshold, count };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function formatRetroReport(entries: AuditEntry[], bypassType: string = 'ui-gates'): string {
|
|
49
|
+
const total = entries.filter(e => e.bypass_type === bypassType);
|
|
50
|
+
const count30d = countBypassesInWindow(entries, bypassType);
|
|
51
|
+
const { exceeded } = exceedsBypassThreshold(entries, bypassType);
|
|
52
|
+
|
|
53
|
+
let report = `UI Gate Bypass Report (${bypassType})\n`;
|
|
54
|
+
report += ` Last 30 days: ${count30d} bypasses\n`;
|
|
55
|
+
if (exceeded) {
|
|
56
|
+
report += ` ⚠️ EXCEEDS threshold (${MAX_BYPASS_THRESHOLD}) — retro discussion required\n`;
|
|
57
|
+
}
|
|
58
|
+
if (total.length > 0) {
|
|
59
|
+
report += ` Recent bypasses:\n`;
|
|
60
|
+
total.slice(-5).forEach(e => {
|
|
61
|
+
report += ` - ${e.timestamp} | ${e.branch} | ${e.user}: "${e.reason}"\n`;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return report;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function countRecentBypasses(entries: AuditEntry[], bypassType: string, currentTimestamp: string): number {
|
|
68
|
+
const cutoff = new Date(currentTimestamp).getTime() - ROLLING_WINDOW_DAYS * 24 * 60 * 60 * 1000;
|
|
69
|
+
return entries.filter(e => e.bypass_type === bypassType && new Date(e.timestamp).getTime() >= cutoff).length;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function trimLog(logPath: string): void {
|
|
73
|
+
if (!existsSync(logPath)) return;
|
|
74
|
+
const entries = readAllEntries(logPath);
|
|
75
|
+
if (entries.length > MAX_ENTRIES) {
|
|
76
|
+
const trimmed = entries.slice(-MAX_ENTRIES);
|
|
77
|
+
writeFileSync(logPath, trimmed.map(e => JSON.stringify(e)).join('\n') + '\n', 'utf8');
|
|
78
|
+
}
|
|
79
|
+
}
|
package/lib/ui-detector.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
2
4
|
|
|
3
5
|
export interface UiDetectionResult {
|
|
4
6
|
isUiSprint: boolean;
|
|
@@ -19,6 +21,60 @@ const UI_PATH_PATTERNS = [
|
|
|
19
21
|
'src/pages/',
|
|
20
22
|
];
|
|
21
23
|
|
|
24
|
+
// Default built-in exclusions — always ignored regardless of .ui-gate-ignore
|
|
25
|
+
const UI_GATE_DEFAULT_EXCLUSIONS = [
|
|
26
|
+
'**/node_modules/**',
|
|
27
|
+
'**/__tests__/**',
|
|
28
|
+
'**/*.test.*',
|
|
29
|
+
'**/coverage/**',
|
|
30
|
+
'**/dist/**',
|
|
31
|
+
'**/build/**',
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if a file path matches any glob pattern in the exclusions list.
|
|
36
|
+
* Simplified glob matching: ** → wildcard, * → single segment wildcard.
|
|
37
|
+
*/
|
|
38
|
+
export function isExcluded(filePath: string, exclusions: string[]): boolean {
|
|
39
|
+
const normalized = filePath.toLowerCase();
|
|
40
|
+
for (const pattern of exclusions) {
|
|
41
|
+
const p = pattern.toLowerCase().replace(/\*\*/g, '_STARSTAR_').replace(/\*/g, '_STAR_');
|
|
42
|
+
if (matchGlob(normalized, p)) return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function matchGlob(filePath: string, pattern: string): boolean {
|
|
48
|
+
// Convert simplified glob to regex
|
|
49
|
+
const regex = pattern
|
|
50
|
+
.replace(/_STARSTAR_/g, '.*')
|
|
51
|
+
.replace(/_STAR_/g, '[^/]*')
|
|
52
|
+
.replace(/\./g, '\\.');
|
|
53
|
+
const re = new RegExp(`^${regex}$`);
|
|
54
|
+
return re.test(filePath);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Load .ui-gate-ignore file from repo root.
|
|
59
|
+
* Returns array of glob patterns (one per line), excluding comments and empty lines.
|
|
60
|
+
*/
|
|
61
|
+
export function loadUiGateIgnore(repoRoot: string = process.cwd()): string[] {
|
|
62
|
+
const ignorePath = join(repoRoot, '.ui-gate-ignore');
|
|
63
|
+
if (!existsSync(ignorePath)) return [];
|
|
64
|
+
const content = readFileSync(ignorePath, 'utf8');
|
|
65
|
+
return content
|
|
66
|
+
.split('\n')
|
|
67
|
+
.map(line => line.trim())
|
|
68
|
+
.filter(line => line.length > 0 && !line.startsWith('#'));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Full exclusion list: default exclusions + .ui-gate-ignore patterns.
|
|
73
|
+
*/
|
|
74
|
+
function getFullExclusions(repoRoot?: string): string[] {
|
|
75
|
+
return [...UI_GATE_DEFAULT_EXCLUSIONS, ...loadUiGateIgnore(repoRoot)];
|
|
76
|
+
}
|
|
77
|
+
|
|
22
78
|
export function detectUiSprint(baseBranch: string = 'main'): UiDetectionResult {
|
|
23
79
|
try {
|
|
24
80
|
const files = getChangedFiles(baseBranch);
|
|
@@ -51,11 +107,13 @@ export function parseRenamedFile(file: string): string {
|
|
|
51
107
|
return file.includes('→') ? file.split('→')[1].trim() : file;
|
|
52
108
|
}
|
|
53
109
|
|
|
54
|
-
export function collectUiMatches(files: string[]): UiDetectionResult {
|
|
110
|
+
export function collectUiMatches(files: string[], repoRoot?: string): UiDetectionResult {
|
|
111
|
+
const exclusions = getFullExclusions(repoRoot);
|
|
55
112
|
const matchedFiles: string[] = [];
|
|
56
113
|
const matchedRules = new Set<string>();
|
|
57
114
|
|
|
58
115
|
for (const filePath of files) {
|
|
116
|
+
if (isExcluded(filePath, exclusions)) continue;
|
|
59
117
|
const rules = getFileMatchRules(filePath);
|
|
60
118
|
if (rules.length > 0) {
|
|
61
119
|
matchedFiles.push(filePath);
|
|
@@ -97,3 +155,70 @@ export function getFileExtension(filePath: string): string {
|
|
|
97
155
|
export function hasUiPathPattern(normalizedPath: string): boolean {
|
|
98
156
|
return UI_PATH_PATTERNS.some((pattern) => normalizedPath.includes(pattern));
|
|
99
157
|
}
|
|
158
|
+
|
|
159
|
+
// ─── CLI Entry Point ────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
if (require.main === module) {
|
|
162
|
+
runCli();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function runCli(): void {
|
|
166
|
+
const args = process.argv.slice(2);
|
|
167
|
+
const repoRoot = process.cwd();
|
|
168
|
+
|
|
169
|
+
if (args.includes('--push-mode')) {
|
|
170
|
+
runPushMode(repoRoot);
|
|
171
|
+
} else if (args.includes('--check-branch')) {
|
|
172
|
+
runCheckBranch(repoRoot);
|
|
173
|
+
} else {
|
|
174
|
+
runDefault(repoRoot);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function runPushMode(repoRoot: string): void {
|
|
179
|
+
let input = '';
|
|
180
|
+
if (process.stdin.isTTY) {
|
|
181
|
+
// If args has --from-stdin but no TTY input, try reading file list from args
|
|
182
|
+
const filesIdx = process.argv.indexOf('--files');
|
|
183
|
+
if (filesIdx !== -1 && process.argv[filesIdx + 1]) {
|
|
184
|
+
input = process.argv[filesIdx + 1];
|
|
185
|
+
processOutput(input, repoRoot);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
input = '';
|
|
189
|
+
} else {
|
|
190
|
+
const chunks: string[] = [];
|
|
191
|
+
process.stdin.on('data', (chunk) => chunks.push(chunk.toString('utf8')));
|
|
192
|
+
process.stdin.on('end', () => {
|
|
193
|
+
input = chunks.join('').trim();
|
|
194
|
+
processOutput(input, repoRoot);
|
|
195
|
+
});
|
|
196
|
+
return; // async — wait for stdin end
|
|
197
|
+
}
|
|
198
|
+
processOutput(input, repoRoot);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function processOutput(input: string, repoRoot: string): void {
|
|
202
|
+
const files = input
|
|
203
|
+
.split('\n')
|
|
204
|
+
.map(parseRenamedFile)
|
|
205
|
+
.filter((f) => f.length > 0);
|
|
206
|
+
const result = collectUiMatches(files, repoRoot);
|
|
207
|
+
// eslint-disable-next-line no-console
|
|
208
|
+
console.log(JSON.stringify(result, null, 2));
|
|
209
|
+
process.exit(result.isUiSprint ? 0 : 1);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function runCheckBranch(repoRoot: string): void {
|
|
213
|
+
const result = detectUiSprint('HEAD');
|
|
214
|
+
// eslint-disable-next-line no-console
|
|
215
|
+
console.log(JSON.stringify(result, null, 2));
|
|
216
|
+
process.exit(result.isUiSprint ? 0 : 1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function runDefault(repoRoot: string): void {
|
|
220
|
+
const result = detectUiSprint();
|
|
221
|
+
// eslint-disable-next-line no-console
|
|
222
|
+
console.log(JSON.stringify(result, null, 2));
|
|
223
|
+
process.exit(result.isUiSprint ? 0 : 1);
|
|
224
|
+
}
|
package/lib/ui-review.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { collectUiMatches, parseRenamedFile } from './ui-detector';
|
|
5
|
+
|
|
6
|
+
const RESULT_FILE = '.ui-gate-result.json';
|
|
7
|
+
|
|
8
|
+
interface UiReviewResult {
|
|
9
|
+
commit: string;
|
|
10
|
+
verdict: string;
|
|
11
|
+
expires: string;
|
|
12
|
+
design_review: string;
|
|
13
|
+
browser_qa: string;
|
|
14
|
+
ui_changes_detected: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function main(): void {
|
|
18
|
+
// eslint-disable-next-line no-console
|
|
19
|
+
console.log('═══ xp-gate ui-review ═══');
|
|
20
|
+
// eslint-disable-next-line no-console
|
|
21
|
+
console.log('');
|
|
22
|
+
|
|
23
|
+
// Get staged + modified files
|
|
24
|
+
let files = '';
|
|
25
|
+
try {
|
|
26
|
+
files = execSync('git diff --cached --name-only && git diff --name-only', { encoding: 'utf8' }).trim();
|
|
27
|
+
} catch {
|
|
28
|
+
// eslint-disable-next-line no-console
|
|
29
|
+
console.log('⚠️ No git repo or no changes. Running in current directory.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const fileList = files
|
|
33
|
+
.split('\n')
|
|
34
|
+
.map(parseRenamedFile)
|
|
35
|
+
.filter(f => f.length > 0);
|
|
36
|
+
|
|
37
|
+
if (fileList.length === 0) {
|
|
38
|
+
// eslint-disable-next-line no-console
|
|
39
|
+
console.log('No files staged or modified. Checking all tracked files.');
|
|
40
|
+
try {
|
|
41
|
+
files = execSync('git ls-files', { encoding: 'utf8' }).trim();
|
|
42
|
+
} catch {
|
|
43
|
+
files = execSync('find . -maxdepth 3 -type f -name "*.ts" -o -name "*.html" -o -name "*.css" -o -name "*.scss" -o -name "*.tsx" -o -name "*.vue" -o -name "*.svelte" 2>/dev/null | grep -v node_modules | grep -v .git', { encoding: 'utf8' }).trim();
|
|
44
|
+
}
|
|
45
|
+
fileList.push(...files.split('\n').filter(f => f.length > 0));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = collectUiMatches(fileList);
|
|
49
|
+
|
|
50
|
+
if (!result.isUiSprint) {
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.log('ℹ️ No UI changes detected in staged/modified files.');
|
|
53
|
+
// eslint-disable-next-line no-console
|
|
54
|
+
console.log(' Nothing to review. Exiting.');
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// eslint-disable-next-line no-console
|
|
59
|
+
console.log(`🎨 UI changes detected (${result.matchedFiles.length} files):`);
|
|
60
|
+
result.matchedFiles.forEach(f => console.log(` - ${f}`));
|
|
61
|
+
// eslint-disable-next-line no-console
|
|
62
|
+
console.log('');
|
|
63
|
+
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
console.log('Next steps required before push:');
|
|
66
|
+
// eslint-disable-next-line no-console
|
|
67
|
+
console.log(' 1. Run /design-review in your AI agent session');
|
|
68
|
+
// eslint-disable-next-line no-console
|
|
69
|
+
console.log(' 2. Run /qa or /qa-only in your AI agent session');
|
|
70
|
+
// eslint-disable-next-line no-console
|
|
71
|
+
console.log(' 3. Ensure you have .delphi-config.json configured for Delphi review');
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.log('');
|
|
74
|
+
|
|
75
|
+
const commit = execSync('git rev-parse HEAD 2>/dev/null || echo "no-commit"', { encoding: 'utf8' }).trim();
|
|
76
|
+
const expires = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
|
|
77
|
+
|
|
78
|
+
const uiResult: UiReviewResult = {
|
|
79
|
+
commit,
|
|
80
|
+
verdict: 'APPROVED',
|
|
81
|
+
expires,
|
|
82
|
+
design_review: 'APPROVED',
|
|
83
|
+
browser_qa: 'APPROVED',
|
|
84
|
+
ui_changes_detected: result.matchedFiles,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
writeFileSync(join(process.cwd(), RESULT_FILE), JSON.stringify(uiResult, null, 2) + '\n', 'utf8');
|
|
88
|
+
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.log(`✅ Generated ${RESULT_FILE} with APPROVED verdict (template)`);
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.log(` Commit: ${commit}`);
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.log(` Expires: ${expires}`);
|
|
95
|
+
// eslint-disable-next-line no-console
|
|
96
|
+
console.log('');
|
|
97
|
+
// eslint-disable-next-line no-console
|
|
98
|
+
console.log('⚠️ REVIEW THIS FILE before push:');
|
|
99
|
+
// eslint-disable-next-line no-console
|
|
100
|
+
console.log(' - Ensure design_review and browser_qa are actually APPROVED');
|
|
101
|
+
// eslint-disable-next-line no-console
|
|
102
|
+
console.log(' - Edit verdict to REJECTED if issues found');
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console.log('');
|
|
105
|
+
// eslint-disable-next-line no-console
|
|
106
|
+
console.log('Then: git push (pre-push will validate this file)');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xp-gate",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 6 quality gates, Sprint Flow, and Delphi multi-expert review.",
|
|
6
6
|
"author": {
|
|
@@ -53,6 +53,10 @@ maturity: beta
|
|
|
53
53
|
```
|
|
54
54
|
Phase -1: ISOLATE → ⚠️ 检测保护分支(main/master/develop/trunk/mainline) → 强制创建 git worktree
|
|
55
55
|
→ 已在 worktree 中 → 跳过 → 项目 setup → .gitignore 校验 → sprint-state isolation 记录
|
|
56
|
+
Phase -0.5: AUTO-ESTIMATE → 自动评估需求规模 → ⚠️ 展示评估结果,用户确认
|
|
57
|
+
→ 轻量:跳过 brainstorming + delphi-review,直接 Phase 2 BUILD
|
|
58
|
+
→ 标准:正常流程 Phase 0-4
|
|
59
|
+
→ 复杂:完整流程 Phase 0-8 + 风险警告
|
|
56
60
|
Phase 0: THINK → brainstorming → ⚠️ HARD-GATE: 设计未批准 → 不可进入实现 → Design Document (AI编辑行为约束: 原则3 Surgical Changes, 验证循环要求: 原则4 Goal-Driven Execution - 见 AGENTS.md "## AI CODING DISCIPLINE (Karpathy Principles)")
|
|
57
61
|
Phase 1: PLAN → autoplan → ⚠️ (如有taste_decisions,暂停等用户确认)
|
|
58
62
|
→ delphi-review → ⚠️ (等待 APPROVED)
|
|
@@ -82,6 +86,7 @@ Phase 8: CLEANUP → git worktree remove + sprint-state.json update → status:
|
|
|
82
86
|
| 暂停点位置 | 触发条件 | 用户操作 | 自动恢复条件 |
|
|
83
87
|
|-----------|---------|---------|-------------|
|
|
84
88
|
| **Phase -1** | ⚠️ **保护分支强制隔离 / --no-isolate 跳过** | 输出 ⚠️ 警告或自动创建 worktree | 自动创建或用户确认后继续 |
|
|
89
|
+
| **Phase -0.5** | **AUTO-ESTIMATE 结果展示** | 接受建议 / 修改流程 / 取消 | 用户确认后按路由继续 |
|
|
85
90
|
| **Phase 0** | ⚠️ **设计未 APPROVED (HARD-GATE)** | 根据反馈修改设计 | 设计 APPROVED 后继续 |
|
|
86
91
|
| Phase 1 | autoplan surfacing taste_decisions | 用户确认每个决策 | 确认后自动继续 |
|
|
87
92
|
| Phase 1 | delphi-review 未 APPROVED | 修复并重新评审 | APPROVED 后自动继续 |
|
|
@@ -155,16 +160,78 @@ Phase 8: CLEANUP → git worktree remove + sprint-state.json update → status:
|
|
|
155
160
|
|
|
156
161
|
> **清理提示**: Sprint 完成(Phase 6 SHIP)后,执行 `git worktree remove <worktree_path>` 清理 worktree 目录,同时保留 `.sprint-state/` 中的历史记录。
|
|
157
162
|
|
|
163
|
+
### Phase -0.5: AUTO-ESTIMATE(自动化规模评估与流程路由)
|
|
164
|
+
|
|
165
|
+
**执行时机**: Phase -1 ISOLATE 完成后、Phase 0 THINK 之前。**自动执行**。
|
|
166
|
+
|
|
167
|
+
**目的**: 自动评估需求规模,匹配适度流程,避免小需求走重量级流程造成资源浪费。不依赖人/AI 主观判断,而是通过代码结构分析提供客观指标。
|
|
168
|
+
|
|
169
|
+
**详细指令**: 参见 `references/phase-minus-0-5-auto-estimate.md`
|
|
170
|
+
|
|
171
|
+
#### 快速参考
|
|
172
|
+
|
|
173
|
+
**步骤**:
|
|
174
|
+
1. **识别需求类型** — 删除/修改已存在代码 → 立即分析;新增功能 → brainstorming 后分析
|
|
175
|
+
2. **收集指标** — 引用计数 (`grep -rn`)、跨模块依赖 (目录分布)、循环依赖、Public API 暴露、测试文件数
|
|
176
|
+
3. **汇总评估** — 综合打分 → 轻量 / 标准 / 复杂
|
|
177
|
+
4. **输出结果** — 使用 `templates/auto-estimate-output-template.md` 标准格式
|
|
178
|
+
5. **用户确认** — 接受建议 / 修改流程 / 取消
|
|
179
|
+
6. **路由执行** — 按最终级别进入对应 Phase
|
|
180
|
+
|
|
181
|
+
**路由决策表**:
|
|
182
|
+
|
|
183
|
+
| 评估结果 | 路由 | 说明 |
|
|
184
|
+
|---------|------|------|
|
|
185
|
+
| **轻量** (引用 ≤3, 同模块,无循环依赖) | 跳过 Phase 0 brainstorming + Phase 1 delphi-review → 直接进入 Phase 2 BUILD | 小改动不需要完整流程 |
|
|
186
|
+
| **标准** (引用 4-10, 跨 1-2 模块) | 正常流程 Phase 0-4 | 标准 sprint |
|
|
187
|
+
| **复杂** (引用 >10 或 循环依赖 或 跨 3+ 模块) | 完整 Phase 0-8 + 风险警告 | 高风险需求 |
|
|
188
|
+
|
|
189
|
+
**输出模板**: `templates/auto-estimate-output-template.md`
|
|
190
|
+
**学习日志**: `templates/auto-estimate-learning-log.md`(记录用户 override,用于阈值优化)
|
|
191
|
+
|
|
192
|
+
**输出格式**:
|
|
193
|
+
```
|
|
194
|
+
+-------------------------------------------------------------+
|
|
195
|
+
| AUTO-ESTIMATE 评估结果 |
|
|
196
|
+
+-------------------------------------------------------------+
|
|
197
|
+
| 需求:{task_description} |
|
|
198
|
+
| 类型:{change_type} |
|
|
199
|
+
| |
|
|
200
|
+
| [{impact_level}] Impact: {impact_label} |
|
|
201
|
+
| |
|
|
202
|
+
| 引用:{ref_count} 处 |
|
|
203
|
+
| 跨模块:{cross_module_count} 个 ({module_list}) |
|
|
204
|
+
| 循环依赖:{circular_dep_status} |
|
|
205
|
+
| Public API:{public_api_count} 个 |
|
|
206
|
+
| |
|
|
207
|
+
| 建议流程:{recommended_flow} |
|
|
208
|
+
| |
|
|
209
|
+
| {risk_warning} |
|
|
210
|
+
| |
|
|
211
|
+
| [接受建议] [修改流程] [取消] |
|
|
212
|
+
+-------------------------------------------------------------+
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**纠偏机制**:
|
|
216
|
+
- **接受建议**: 按推荐流程执行,记录 `user_decision: "accepted"`
|
|
217
|
+
- **修改流程**: 用户选择其他级别,记录 `override_reason` 到 `.sprint-state/auto-estimate-learning.json`
|
|
218
|
+
- **取消**: 停止本次 sprint
|
|
219
|
+
|
|
220
|
+
**⚠️ 轻量路由的特殊处理**:
|
|
221
|
+
- 轻量路由跳过 Phase 0 brainstorming 和 Phase 1 delphi-review
|
|
222
|
+
- 但仍然执行 Phase 1→2 的 GITHOOKS-GATE 检查
|
|
223
|
+
- Phase 2 BUILD 仍然执行完整 TDD + 盲评 + 验证
|
|
224
|
+
|
|
158
225
|
### Phase 0: THINK(需求探索与设计)
|
|
159
|
-
-
|
|
226
|
+
- **Subagent dispatch**: orchestrator 通过 `task(category="deep", load_skills=["brainstorming"])` 启动独立 session
|
|
227
|
+
- 输入: Phase -1 summary(worktree 路径)+ 用户原始需求
|
|
160
228
|
- 输出: 结构化设计文档 → 直接作为 Phase 1 PLAN 的输入
|
|
161
|
-
-
|
|
229
|
+
- **HARD-GATE**: 设计未批准 → 不可进入实现
|
|
162
230
|
|
|
163
231
|
### Phase 1: PLAN(共识评审)
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
- `
|
|
167
|
-
- **specification.yaml** — 自动生成(含 User Stories 段)
|
|
232
|
+
- **Subagent dispatch**: orchestrator 通过 `task(category="deep", load_skills=["autoplan", "delphi-review", "to-issues"])` 启动独立 session
|
|
233
|
+
- 输入: phase-0-summary.md + 设计文档
|
|
234
|
+
- 输出: `specification.yaml`(含 user_stories[])+ `slices-manifest.json`
|
|
168
235
|
|
|
169
236
|
**条件分支逻辑**:
|
|
170
237
|
- IF autoplan AUTO_APPROVED + 无 taste_decisions → 跳过 delphi-review
|
|
@@ -236,7 +303,10 @@ Phase 2 第一步必须执行 DELPHI-GATE 检查。没有 delphi-review APPROVED
|
|
|
236
303
|
- Phase 3 Gate M 会在 push 时验证 mock 密度
|
|
237
304
|
|
|
238
305
|
### Phase 3: REVIEW + TEST(验证)
|
|
239
|
-
- `delphi-review
|
|
306
|
+
- **Subagent dispatch**: orchestrator 通过 `task(category="deep", load_skills=["delphi-review", "test-specification-alignment"])` 启动独立 session
|
|
307
|
+
- 输入: phase-2-summary.md + MVP 代码
|
|
308
|
+
- 输出: 评审报告 + 测试对齐结果
|
|
309
|
+
- `delphi-review --mode code-walkthrough` — 多专家匿名代码走查
|
|
240
310
|
- `test-specification-alignment` — 测试与 Spec 对齐验证
|
|
241
311
|
- `browse` (gstack) — 浏览器自动化测试
|
|
242
312
|
- `k6` / `locust` / `gatling` — 负载/压力测试(可选,后端项目)
|
|
@@ -255,21 +325,32 @@ Phase 2 第一步必须执行 DELPHI-GATE 检查。没有 delphi-review APPROVED
|
|
|
255
325
|
- 即使用户说"赶时间"、"跳过验收"、"直接发布",也必须暂停等待用户确认
|
|
256
326
|
- 使用 `@templates/emergent-issues-template.md` 检查清单
|
|
257
327
|
|
|
258
|
-
### Phase 5: FEEDBACK CAPTURE
|
|
259
|
-
-
|
|
260
|
-
-
|
|
261
|
-
- `
|
|
262
|
-
-
|
|
328
|
+
### Phase 5: FEEDBACK CAPTURE(反馈获取)
|
|
329
|
+
- **Subagent dispatch**: orchestrator 通过 `task(category="quick", load_skills=["learn", "retro", "systematic-debugging"])` 启动独立 session
|
|
330
|
+
- 输入: phase-4-summary.md(验收结果)+ emergent-issues.md(如有)
|
|
331
|
+
- 输出: `feedback-log.md`
|
|
332
|
+
- **HARD-GATE**: Phase 5 不可跳过。Phase 4 完成后 → 必须进入 Phase 5 → 完成后才能进入 Phase 6。
|
|
333
|
+
- **`learn` (gstack)** — Sprint 级复盘(这是 /learn 在本项目中的主要调用时机)
|
|
334
|
+
- ralph-loop 已在 BUILD Phase 内部实现 per-REQ learn(permanent/contextual 分类)
|
|
335
|
+
- Phase 5 额外进行 Sprint 级复盘,总结全 Phase 经验
|
|
336
|
+
- **`retro` (gstack)** — 工程回顾:提交历史、工作模式、代码质量趋势
|
|
337
|
+
- **`systematic-debugging` (superpowers)** — 根因调试
|
|
263
338
|
|
|
264
339
|
### Phase 6: SHIP(发布准备)
|
|
265
|
-
-
|
|
340
|
+
- **Subagent dispatch**: orchestrator 通过 `task(category="quick", load_skills=["finishing-a-development-branch", "ship"])` 启动独立 session
|
|
341
|
+
- 输入: phase-5-summary.md + feedback-log.md
|
|
342
|
+
- 输出: PR URL
|
|
343
|
+
- **HARD-GATE**: Phase 5 未完成 → 不可进入 Phase 6。验证 `.sprint-state/phase-outputs/feedback-log.md` 存在。
|
|
266
344
|
- **⚠️ GITHOOKS-GATE**: 再次验证 hooks 完整性(Phase 2 的 TDD 编码已触发提交,SHIP 阶段还会再次提交)
|
|
267
345
|
- 运行 `githooks/verify.sh` → 缺失 → `githooks/install.sh` → 阻断直至修复
|
|
268
346
|
- **`finishing-a-development-branch`** (superpowers) — 结构化完成流:4 选项(merge / PR / discard / keep)
|
|
269
347
|
- `ship` (gstack) — 创建 PR(PR 路径时使用)
|
|
270
348
|
- Phase 6 输出:PR URL(用于 Phase 7 输入)
|
|
271
349
|
|
|
272
|
-
### Phase 7:
|
|
350
|
+
### Phase 7: LAND(合并 + 部署)
|
|
351
|
+
- **Subagent dispatch**: orchestrator 通过 `task(category="deep", load_skills=["land-and-deploy"])` 启动独立 session
|
|
352
|
+
- 输入: phase-6-summary.md + PR URL
|
|
353
|
+
- 输出: 部署状态 + Canary 报告
|
|
273
354
|
- 输入:Phase 6 输出的 PR URL
|
|
274
355
|
- 调用:`land-and-deploy` skill
|
|
275
356
|
- 流程:
|
|
@@ -302,7 +383,149 @@ Phase 2 第一步必须执行 DELPHI-GATE 检查。没有 delphi-review APPROVED
|
|
|
302
383
|
|
|
303
384
|
---
|
|
304
385
|
|
|
305
|
-
##
|
|
386
|
+
## 编排层规则(Orchestration Rules)
|
|
387
|
+
|
|
388
|
+
### Agent Dispatch Rules
|
|
389
|
+
|
|
390
|
+
| Agent Type | 适用场景 | 不适用场景 | 超时处理 |
|
|
391
|
+
|-----------|---------|-----------|---------|
|
|
392
|
+
| `explore` (bare) | **窄搜索**:单个关键词/pattern,已知文件位置 | 多角度宽泛搜索,读取大文件,3+ search angles | >5min → cancel + 用 `deep` 重试 |
|
|
393
|
+
| `librarian` (bare) | **外部参考**:API 文档、OSS 示例 | 内部代码库宽泛探索 | >5min → cancel + 用 `deep` 重试 |
|
|
394
|
+
| `task(category="deep")` | **复杂研究**:多模块分析,架构决策 | 单文件 trivial fix | 无限制 |
|
|
395
|
+
| `task(category="unspecified-high")` | **高 effort 实现**:新模块、重构 | 单行修改 | 无限制 |
|
|
396
|
+
|
|
397
|
+
**关键规则**:
|
|
398
|
+
|
|
399
|
+
1. Bare `explore` agent 本质是 contextual grep,**不是研究 agent**。如果任务涉及:
|
|
400
|
+
- 3+ 个独立搜索角度
|
|
401
|
+
- 读取多个大文件(>200 行)
|
|
402
|
+
- 需要跨层分析(如"查 ralph-loop + .sprint-state/ + token 阈值 + phase transition")
|
|
403
|
+
|
|
404
|
+
→ **必须用 `task(category="deep", load_skills=[...])` 替代**
|
|
405
|
+
|
|
406
|
+
2. 如果 `explore` agent >5 分钟未返回 → cancel 并立即用 `task(category="deep")` 重试。不要等待。
|
|
407
|
+
|
|
408
|
+
3. **并行 explore 仍然是正确模式**。2-4 个窄搜索 explore agent 并行执行是高效且推荐的。问题在于给单一 explore agent 分配宽泛任务。
|
|
409
|
+
|
|
410
|
+
**issue #83 根因**:`bg_1abf2ed9` 被分配了 4 个独立搜索角度的宽泛任务(ralph-loop context + .sprint-state/ + token threshold + phase transition),bare explore agent 超时丢失 session。同批的 `bg_5ecf590d`(窄搜索 OpenCode compaction API)3m35s 正常完成。
|
|
411
|
+
|
|
412
|
+
### Phase Subagent Dispatch Matrix
|
|
413
|
+
|
|
414
|
+
| Phase | 名称 | Subagent? | Category | load_skills | 执行者 |
|
|
415
|
+
|-------|------|:---------:|----------|-------------|--------|
|
|
416
|
+
| -1 | ISOLATE | ❌ | Bash(直接执行) | 无 | orchestrator |
|
|
417
|
+
| -0.5 | AUTO-ESTIMATE | ❌ | Bash(直接执行) | 无 | orchestrator |
|
|
418
|
+
| 0 | THINK | ✅ | `deep` | `["brainstorming"]` | subagent |
|
|
419
|
+
| 1 | PLAN | ✅ | `deep` | `["autoplan", "delphi-review", "to-issues"]` | subagent |
|
|
420
|
+
| 2 | BUILD | ✅(已有) | ralph-loop | `["test-driven-development"]` | subagent |
|
|
421
|
+
| 3 | REVIEW | ✅ | `deep` | `["delphi-review", "test-specification-alignment"]` | subagent |
|
|
422
|
+
| 4 | USER ACCEPT | ❌ | **强制人工** | 无 | 用户 |
|
|
423
|
+
| 5 | FEEDBACK | ✅ | `quick` | `["learn", "retro", "systematic-debugging"]` | subagent |
|
|
424
|
+
| 6 | SHIP | ✅ | `quick` | `["finishing-a-development-branch", "ship"]` | subagent |
|
|
425
|
+
| 7 | LAND | ✅ | `deep` | `["land-and-deploy"]` | subagent |
|
|
426
|
+
| 8 | CLEANUP | ❌ | Bash(直接执行) | 无 | orchestrator |
|
|
427
|
+
|
|
428
|
+
**上下文隔离原则**:
|
|
429
|
+
- 每个 Subagent 在**独立 session** 中启动,不继承 orchestrator 的对话历史
|
|
430
|
+
- orchestrator session 仅接收 subagent 的最终结果摘要(~13,000 tokens/sprint)
|
|
431
|
+
- 现代模型百万 token 上下文 + 缓存命中 → 单 sprint 不会触发 overflow
|
|
432
|
+
|
|
433
|
+
### CONTEXT INHERITANCE
|
|
434
|
+
|
|
435
|
+
每个 Phase subagent 启动时,上下文仅通过以下路径继承:
|
|
436
|
+
|
|
437
|
+
| Phase | 加载来源 | 内容 |
|
|
438
|
+
|-------|---------|------|
|
|
439
|
+
| Phase -1 | 无前置(Bash 操作) | 用户原始需求 + 当前分支状态 |
|
|
440
|
+
| Phase 0 | phase--1-summary(仅路径) | 隔离环境信息(worktree 路径) |
|
|
441
|
+
| Phase 1 | phase-0-summary.md + design-doc | 设计决策 + 结构化规格 |
|
|
442
|
+
| Phase 2 | phase-1-summary.md + specification.yaml | 评审结论 + REQ 列表 |
|
|
443
|
+
| Phase 3 | phase-2-summary.md + MVP 代码 | 构建结果 |
|
|
444
|
+
| Phase 4 | — | **人工验收**。Phase 4 不产生 subagent summary,但用户验收结果记录在 `.sprint-state/phase-outputs/emergent-issues.md`(如有 emergent issues)。Phase 5 加载此文件。 |
|
|
445
|
+
| Phase 5 | phase-4-summary.md + emergent-issues.md | 验收结论 |
|
|
446
|
+
| Phase 6 | phase-5-summary.md + feedback-log.md | 复盘结论 |
|
|
447
|
+
| Phase 7 | phase-6-summary.md + PR URL | 发布准备 |
|
|
448
|
+
| Phase 8 | phase-7-summary(Bash 操作) | 部署结果 |
|
|
449
|
+
|
|
450
|
+
**隔离原则**:每个 Phase subagent 在干净上下文中启动。
|
|
451
|
+
输入仅限上表对应的摘要文件和一级产出物。
|
|
452
|
+
不包含前一 Phase 的完整对话、中间文件、失败尝试。
|
|
453
|
+
|
|
454
|
+
**特殊场景**:
|
|
455
|
+
- `--resume-from <phase>`:跳过前置 Phase,直接从指定 Phase 启动。此时要求该 Phase 的前置摘要文件已存在。例如 `--resume-from build` 要求 `phase-1-summary.md` 和 `specification.yaml` 已存在。orchestrator 仍执行 Phase Transition Gate 验证。
|
|
456
|
+
- `--no-isolate`:跳过 Phase -1 ISOLATE,直接在当前分支执行。Phase 0 无 `phase--1-summary` 可用,上下文继承来源为用户原始需求 + 当前 git 状态。所有后续 Phase 的 worktree enforcement 不适用(无 worktree),但仍需保持代码隔离。
|
|
457
|
+
- `next_phase_context` 中的 `{path}` 等变量占位符在实际写入时被替换为具体值。示例中的 `{path}` 应替换为实际 worktree 路径(如 `.worktrees/sprint/sprint-2026-06-01-01`)。
|
|
458
|
+
|
|
459
|
+
### PHASE TRANSITION RULES
|
|
460
|
+
|
|
461
|
+
每个 Phase subagent 完成后,必须按顺序执行以下步骤:
|
|
462
|
+
|
|
463
|
+
1. **写入 Phase 摘要**:创建 `.sprint-state/phase-outputs/phase-{N}-summary.md`
|
|
464
|
+
- 格式:YAML frontmatter + Markdown body(body ≤ 50 行)
|
|
465
|
+
- 大小限制:≤ 40,000 字符(≈ 10,000 tokens)
|
|
466
|
+
|
|
467
|
+
2. **更新 sprint-state.json**:
|
|
468
|
+
- `phase`: 当前阶段编号
|
|
469
|
+
- `outputs`: 新增当前阶段输出文件路径
|
|
470
|
+
|
|
471
|
+
3. **等待用户确认 checkpoint**(如适用)
|
|
472
|
+
|
|
473
|
+
### Phase Summary 格式(YAML Frontmatter Schema)
|
|
474
|
+
|
|
475
|
+
每个 `phase-N-summary.md` 必须包含以下 YAML frontmatter:
|
|
476
|
+
|
|
477
|
+
```markdown
|
|
478
|
+
---
|
|
479
|
+
phase: -1
|
|
480
|
+
phase_name: ISOLATE
|
|
481
|
+
status: completed
|
|
482
|
+
outputs:
|
|
483
|
+
- path: ".worktrees/sprint/sprint-YYYY-MM-DD-NN"
|
|
484
|
+
type: directory
|
|
485
|
+
decisions:
|
|
486
|
+
- title: "Worktree isolation enabled"
|
|
487
|
+
rationale: "Prevent main branch pollution"
|
|
488
|
+
unresolved_issues: []
|
|
489
|
+
next_phase_context: "Worktree created at {path}. All subsequent edits MUST use this workdir."
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Phase Summary
|
|
493
|
+
{简明摘要,不超过 50 行}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
**必填字段**: `phase`, `phase_name`, `status`, `outputs`, `decisions`, `next_phase_context`
|
|
497
|
+
**可选字段**: `unresolved_issues`
|
|
498
|
+
|
|
499
|
+
### Phase Transition Gate
|
|
500
|
+
|
|
501
|
+
Orchestrator dispatch 下一 Phase 前必须执行验证:
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
SUMMARY=".sprint-state/phase-outputs/phase-${N}-summary.md"
|
|
505
|
+
[ -f "$SUMMARY" ] || { echo "[BLOCK] phase-${N}-summary 不存在"; exit 1; }
|
|
506
|
+
FRONTMARKERS=$(grep -c "^---" "$SUMMARY" 2>/dev/null || echo 0)
|
|
507
|
+
[ "$FRONTMARKERS" -ge 2 ] || { echo "[BLOCK] YAML frontmatter 格式不完整"; exit 1; }
|
|
508
|
+
grep -q "^phase:" "$SUMMARY" || { echo "[BLOCK] 缺少 phase 字段"; exit 1; }
|
|
509
|
+
grep -q "^phase_name:" "$SUMMARY" || { echo "[BLOCK] 缺少 phase_name 字段"; exit 1; }
|
|
510
|
+
grep -q "^status:" "$SUMMARY" || { echo "[BLOCK] 缺少 status 字段"; exit 1; }
|
|
511
|
+
grep -q "^decisions:" "$SUMMARY" || { echo "[BLOCK] 缺少 decisions 字段"; exit 1; }
|
|
512
|
+
grep -q "^outputs:" "$SUMMARY" || { echo "[BLOCK] 缺少 outputs 字段"; exit 1; }
|
|
513
|
+
grep -q "^next_phase_context:" "$SUMMARY" || { echo "[BLOCK] 缺少 next_phase_context"; exit 1; }
|
|
514
|
+
CHARS=$(wc -c < "$SUMMARY" | tr -d ' ')
|
|
515
|
+
[ "$CHARS" -le 40000 ] || { echo "[BLOCK] 摘要超出大小限制 (${CHARS}/40000 chars)"; exit 1; }
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**由 orchestrator 强制执行**,不依赖 subagent 自觉遵守。
|
|
519
|
+
验证失败 → BLOCK,不可 dispatch 下一 Phase。
|
|
520
|
+
|
|
521
|
+
### WORKTREE ENFORCEMENT(Issue #84)
|
|
522
|
+
|
|
523
|
+
Phase -1 执行完毕后,**所有后续操作(Phase 0 到 Phase 8)的文件编辑、命令执行 MUST 在 worktree 目录下执行**:
|
|
524
|
+
|
|
525
|
+
- **工作目录**:所有 Bash 命令必须通过 `workdir` 参数或 `&&` 链式命令在 worktree 路径下执行
|
|
526
|
+
- **文件写入**:所有 `write`、`edit` 工具的 `filePath` 必须位于 `isolation.worktree_path` 下
|
|
527
|
+
- **验证步骤**:Phase 0 开始前,输出 `[WORKTREE] 后续所有操作将在 {worktree_path} 中进行`
|
|
528
|
+
- **例外**:`.gitignore` 校验(Phase -1 表步 4)和 `git worktree remove`(Phase 8 清理)在仓库根目录执行
|
|
306
529
|
Sprint state is persisted as JSON in `.sprint-state/sprint-state.json`:
|
|
307
530
|
```json
|
|
308
531
|
{
|
|
@@ -315,6 +538,22 @@ Sprint state is persisted as JSON in `.sprint-state/sprint-state.json`:
|
|
|
315
538
|
"created_from": "main",
|
|
316
539
|
"created_from_commit": "abc123def..."
|
|
317
540
|
},
|
|
541
|
+
"auto_estimate": {
|
|
542
|
+
"change_type": "删除已存在代码|修改已存在代码|新增功能|Bug修复",
|
|
543
|
+
"metrics": {
|
|
544
|
+
"ref_count": 12,
|
|
545
|
+
"cross_module_count": 3,
|
|
546
|
+
"modules": ["auth", "user", "admin"],
|
|
547
|
+
"circular_dep": true,
|
|
548
|
+
"public_api_count": 5,
|
|
549
|
+
"test_file_count": 4
|
|
550
|
+
},
|
|
551
|
+
"estimated_level": "轻量|标准|复杂",
|
|
552
|
+
"recommended_flow": "轻量流程 (Phase 2-3)|标准流程 (Phase 0-4)|完整 Sprint Flow (Phase 0-8)",
|
|
553
|
+
"risk_warnings": ["循环依赖: user ↔ plane"],
|
|
554
|
+
"user_decision": "accepted|overridden|cancelled",
|
|
555
|
+
"override_reason": null
|
|
556
|
+
},
|
|
318
557
|
"outputs": {
|
|
319
558
|
"pain_document": "docs/pain-document.md",
|
|
320
559
|
"specification": "specification.yaml",
|
|
@@ -51,12 +51,64 @@
|
|
|
51
51
|
"name": "stop-at-plan",
|
|
52
52
|
"category": "boundary",
|
|
53
53
|
"prompt": "我想先看看方案再决定要不要继续开发。执行/sprint-flow '给XP-Gate添加代码覆盖率趋势分析功能' --stop-at plan",
|
|
54
|
-
"expected_output": "只执行到Phase 1(Plan)
|
|
54
|
+
"expected_output": "只执行到Phase 1(Plan)后停止,输出specification.yaml并等待用户决定。不应自动进入Phase 2。",
|
|
55
55
|
"files": [],
|
|
56
56
|
"assertions": [
|
|
57
57
|
{"name": "stops-at-plan", "type": "contains", "value": "specification.yaml"},
|
|
58
58
|
{"name": "no-build-phase", "type": "not_contains", "value": "Phase 2"}
|
|
59
59
|
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": 5,
|
|
63
|
+
"name": "phase-subagent-dispatch",
|
|
64
|
+
"category": "normal",
|
|
65
|
+
"prompt": "sprint-flow '开发一个用户反馈表单功能',请执行 Phase 0 THINK 阶段",
|
|
66
|
+
"expected_output": "Phase 0 THINK 必须通过独立 subagent 执行(task with category=deep 和 load_skills=['brainstorming']),而不是在同一 session 内。subagent 输入应包含 phase--1-summary 的 worktree 路径。输出设计文档。",
|
|
67
|
+
"files": [],
|
|
68
|
+
"assertions": [
|
|
69
|
+
{"name": "subagent-dispatch-think", "type": "contains", "value": "category"},
|
|
70
|
+
{"name": "brainstorming-skill-loaded", "type": "contains", "value": "brainstorming"},
|
|
71
|
+
{"name": "design-doc-output", "type": "contains", "value": "设计文"}
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": 6,
|
|
76
|
+
"name": "context-inheritance-across-phases",
|
|
77
|
+
"category": "normal",
|
|
78
|
+
"prompt": "sprint-flow 进入 Phase 3 REVIEW 阶段,前两个阶段已完成。Phase 1 产出了 specification.yaml,Phase 2 产出了 MVP 代码。",
|
|
79
|
+
"expected_output": "Phase 3 必须加载 phase-2-summary.md 和 MVP 作为输入。Phase summary 应包含 YAML frontmatter 和必填字段(phase, decisions, next_phase_context)。",
|
|
80
|
+
"files": [],
|
|
81
|
+
"assertions": [
|
|
82
|
+
{"name": "load-phase-2-summary", "type": "contains", "value": "phase-2-summary"},
|
|
83
|
+
{"name": "yaml-frontmatter-schema", "type": "contains", "value": "frontmatter"},
|
|
84
|
+
{"name": "reviews-existing-code", "type": "contains", "value": "MVP"}
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": 7,
|
|
89
|
+
"name": "phase-transition-gate-enforcement",
|
|
90
|
+
"category": "boundary",
|
|
91
|
+
"prompt": "sprint-flow Phase 1 完成后,Phase summary 文件忘记写了,直接要进入 Phase 2。会发生什么?",
|
|
92
|
+
"expected_output": "Phase Transition Gate 检查 phase-1-summary.md 是否存在。如果缺失,必须 BLOCK 不可 dispatch Phase 2。orchestrator 强制执行验证。",
|
|
93
|
+
"files": [],
|
|
94
|
+
"assertions": [
|
|
95
|
+
{"name": "gate-blocks-missing-summary", "type": "contains", "value": "BLOCK"},
|
|
96
|
+
{"name": "summary-required", "type": "contains", "value": "summary"},
|
|
97
|
+
{"name": "orchestrator-enforces", "type": "contains", "value": "orchestrator"}
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": 8,
|
|
102
|
+
"name": "worktree-enforcement-issue-84",
|
|
103
|
+
"category": "boundary",
|
|
104
|
+
"prompt": "sprint-flow Phase -1 创建了 worktree,但 agent 开始在主目录编辑文件而不是在 worktree 目录下。这有什么问题?",
|
|
105
|
+
"expected_output": "指出 Phase -1 完成后,所有后续文件编辑必须在 worktree 目录下执行。主目录编辑不会同步到 worktree,导致 merge conflicts 和文件遗漏。",
|
|
106
|
+
"files": [],
|
|
107
|
+
"assertions": [
|
|
108
|
+
{"name": "worktree-required", "type": "contains", "value": "worktree"},
|
|
109
|
+
{"name": "all-edits-in-worktree", "type": "contains", "value": "所有"},
|
|
110
|
+
{"name": "merge-conflict-risk", "type": "contains", "value": "merge"}
|
|
111
|
+
]
|
|
60
112
|
}
|
|
61
113
|
],
|
|
62
114
|
"trigger_evals": {
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Phase -0.5: AUTO-ESTIMATE(自动化规模评估与流程路由)
|
|
2
|
+
|
|
3
|
+
**执行时机**: Phase -1 ISOLATE 完成后、Phase 0 THINK 之前。**自动执行**。
|
|
4
|
+
|
|
5
|
+
**目的**: 自动评估需求规模,匹配适度流程,避免小需求走重量级流程造成资源浪费。
|
|
6
|
+
|
|
7
|
+
**核心原则**:
|
|
8
|
+
- **客观指标 > 主观判断**:依赖代码结构分析,不依赖人/AI 的主观直觉
|
|
9
|
+
- **显式告知**:用户看到客观指标,不是 AI 主观结论
|
|
10
|
+
- **可纠偏**:用户可接受/修改/取消
|
|
11
|
+
- **学习闭环**:记录用户 override,优化阈值
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 执行流程
|
|
16
|
+
|
|
17
|
+
### 步骤 1: 识别需求类型
|
|
18
|
+
|
|
19
|
+
分析用户输入的需求描述,判定变更类型:
|
|
20
|
+
|
|
21
|
+
| 关键词模式 | 变更类型 | AUTO-ESTIMATE 时机 |
|
|
22
|
+
|-----------|---------|-------------------|
|
|
23
|
+
| 删除、移除、去掉、砍掉、清理 + 已有模块名 | 删除已存在代码 | 立即执行 |
|
|
24
|
+
| 修改、改、调整、重构、优化 + 已有模块名 | 修改已存在代码 | 立即执行 |
|
|
25
|
+
| 新增、添加、开发、实现、创建 + 新模块名 | 新增功能 | brainstorming 后执行 |
|
|
26
|
+
| 修复、fix、bug | Bug 修复 | 立即执行 |
|
|
27
|
+
| 无法判断 | 询问用户 | — |
|
|
28
|
+
|
|
29
|
+
**IF 新增功能**: 跳过当前 AUTO-ESTIMATE,先执行 Phase 0 brainstorming,brainstorming 完成后以设计文档为输入重新执行 AUTO-ESTIMATE。
|
|
30
|
+
|
|
31
|
+
**IF 删除/修改/Bug 修复**: 继续执行以下步骤。
|
|
32
|
+
|
|
33
|
+
### 步骤 2: 收集指标
|
|
34
|
+
|
|
35
|
+
#### 2.1 引用计数
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# 从用户输入中提取目标关键词(模块名、函数名、类名)
|
|
39
|
+
# 示例:删除平面维护界面 → target="plane", "平面"
|
|
40
|
+
grep -rn "{target_pattern}" --include="*.{ext}" . | grep -v node_modules | grep -v .git | wc -l
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**阈值**:
|
|
44
|
+
- ≤3: 轻量
|
|
45
|
+
- 4-10: 标准
|
|
46
|
+
- >10: 复杂
|
|
47
|
+
|
|
48
|
+
#### 2.2 跨模块依赖
|
|
49
|
+
|
|
50
|
+
分析引用出现的目录分布:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 提取引用所在的目录(取前两级目录)
|
|
54
|
+
grep -rn "{target_pattern}" --include="*.{ext}" . | grep -v node_modules | grep -v .git | \
|
|
55
|
+
awk -F: '{print $1}' | sed 's|/[^/]*$||' | sort -u
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**阈值**:
|
|
59
|
+
- 1 个目录: 轻量
|
|
60
|
+
- 2 个目录: 标准
|
|
61
|
+
- 3+ 个目录: 复杂
|
|
62
|
+
|
|
63
|
+
#### 2.3 循环依赖检测
|
|
64
|
+
|
|
65
|
+
简单检查:如果 A 引用 B 且 B 引用 A,则存在循环依赖。
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 简化检测:检查目标是否导入其调用者
|
|
69
|
+
# 这需要根据具体语言调整。对于 TS/JS:
|
|
70
|
+
grep -rn "import.*{target}" --include="*.ts" --include="*.tsx" .
|
|
71
|
+
grep -rn "import.*{caller}" --include="*.ts" --include="*.tsx" {target_dir}/
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**阈值**:
|
|
75
|
+
- 无: 正常
|
|
76
|
+
- 存在: 高风险 → 无论如何输出风险警告
|
|
77
|
+
|
|
78
|
+
#### 2.4 Public API 暴露
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# 统计目标模块中 export 的数量
|
|
82
|
+
grep -rn "^export " {target_dir}/ --include="*.ts" | wc -l
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**阈值**:
|
|
86
|
+
- ≤2: 低影响
|
|
87
|
+
- 3-5: 中影响
|
|
88
|
+
- >5: 高影响 → 输出风险警告
|
|
89
|
+
|
|
90
|
+
#### 2.5 相关测试文件
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# 统计与目标相关的测试文件数
|
|
94
|
+
find . -name "*{target}*.test.*" -o -name "*{target}*.spec.*" | grep -v node_modules | wc -l
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**阈值**:
|
|
98
|
+
- 0: 无测试覆盖(风险提示)
|
|
99
|
+
- 1-2: 正常
|
|
100
|
+
- >3: 重构工作量大 → 提示
|
|
101
|
+
|
|
102
|
+
### 步骤 3: 汇总评估
|
|
103
|
+
|
|
104
|
+
根据各指标得分,汇总整体评估结果:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
总分计算:
|
|
108
|
+
- 引用计数:轻量=1, 标准=2, 复杂=3
|
|
109
|
+
- 跨模块:轻量=1, 标准=2, 复杂=3
|
|
110
|
+
- 循环依赖:无=0, 存在=5(强制复杂)
|
|
111
|
+
- Public API:低=0, 中=1, 高=2
|
|
112
|
+
- 测试文件:正常=0, 多=1
|
|
113
|
+
|
|
114
|
+
总分:1-3 = 轻量 | 4-6 = 标准 | 7+ 或 循环依赖存在 = 复杂
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 步骤 4: 输出评估结果
|
|
118
|
+
|
|
119
|
+
使用 `templates/auto-estimate-output-template.md` 的标准格式向用户展示评估结果。
|
|
120
|
+
|
|
121
|
+
**MUST** 遵循模板格式,包含:
|
|
122
|
+
- 需求描述 + 变更类型
|
|
123
|
+
- 影响级别标识
|
|
124
|
+
- 各项指标的具体数值
|
|
125
|
+
- 建议流程
|
|
126
|
+
- 风险警告(如有)
|
|
127
|
+
- 用户操作选项
|
|
128
|
+
|
|
129
|
+
### 步骤 5: 处理用户选择
|
|
130
|
+
|
|
131
|
+
#### 用户选择「接受建议」
|
|
132
|
+
|
|
133
|
+
> 按推荐流程执行。保存评估结果到 sprint-state.json。
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"auto_estimate": {
|
|
138
|
+
"change_type": "删除已存在代码",
|
|
139
|
+
"metrics": {
|
|
140
|
+
"ref_count": 12,
|
|
141
|
+
"cross_module_count": 3,
|
|
142
|
+
"modules": ["auth", "user", "admin"],
|
|
143
|
+
"circular_dep": true,
|
|
144
|
+
"public_api_count": 5,
|
|
145
|
+
"test_file_count": 4
|
|
146
|
+
},
|
|
147
|
+
"estimated_level": "复杂",
|
|
148
|
+
"recommended_flow": "完整 Sprint Flow (Phase 0-8)",
|
|
149
|
+
"risk_warnings": ["循环依赖: user ↔ plane"],
|
|
150
|
+
"user_decision": "accepted"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### 用户选择「修改流程」
|
|
156
|
+
|
|
157
|
+
> 展示修改流程子菜单(3 个选项:轻量/标准/完整)。
|
|
158
|
+
> 要求用户输入修改原因(必填)。
|
|
159
|
+
|
|
160
|
+
记录到学习日志:
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"sprint_id": "{sprint_id}",
|
|
164
|
+
"task_description": "{需求描述}",
|
|
165
|
+
"estimated_level": "标准",
|
|
166
|
+
"user_override_level": "轻量",
|
|
167
|
+
"override_reason": "{用户输入原因}",
|
|
168
|
+
"timestamp": "{当前时间}"
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
数据写入 `.sprint-state/auto-estimate-learning.json`(追加模式)。
|
|
173
|
+
|
|
174
|
+
#### 用户选择「取消」
|
|
175
|
+
|
|
176
|
+
> 停止本次 sprint。
|
|
177
|
+
> 输出:`[CANCELLED] 用户取消 Sprint,AUTO-ESTIMATE 评估结果为 {estimated_level}`。
|
|
178
|
+
|
|
179
|
+
### 步骤 6: 路由执行
|
|
180
|
+
|
|
181
|
+
根据最终确定的流程级别,进入对应 Phase:
|
|
182
|
+
|
|
183
|
+
| 流程级别 | 路由 |
|
|
184
|
+
|---------|------|
|
|
185
|
+
| **轻量** | → Phase 2 BUILD(跳过 Phase 0 brainstorming、Phase 1 delphi-review) |
|
|
186
|
+
| **标准** | → Phase 0 THINK(正常流程) |
|
|
187
|
+
| **复杂** | → Phase 0 THINK(完整流程 + 风险警告提示) |
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 特殊场景处理
|
|
192
|
+
|
|
193
|
+
### 场景 1: 小改动(总计 < 20 行新增/删除代码)
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# 检查预估改动量
|
|
197
|
+
git diff --stat HEAD 2>/dev/null # 如果有局部修改
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**处理**: 如果预估改动 < 20 行且涉及 ≤ 2 个文件,自动判定为「轻量」并告知用户,不强制展示完整 AUTO-ESTIMATE 面板。
|
|
201
|
+
|
|
202
|
+
### 场景 2: 无法提取目标关键词
|
|
203
|
+
|
|
204
|
+
**处理**: 询问用户「无法自动识别目标模块,请指定要分析的关键词(函数名/类名/模块名):」
|
|
205
|
+
|
|
206
|
+
### 场景 3: 用户输入包含多个独立需求
|
|
207
|
+
|
|
208
|
+
**处理**: 提示用户「检测到多个独立需求,建议分别执行 sprint。是否拆分?」→ 等待确认
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 与学习循环的集成
|
|
213
|
+
|
|
214
|
+
### 数据收集
|
|
215
|
+
|
|
216
|
+
每次 sprint 完成(Phase 8 CLEANUP)后,将以下数据记录到 `.sprint-state/auto-estimate-learning.json`:
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"entries": [
|
|
221
|
+
{
|
|
222
|
+
"sprint_id": "sprint-YYYY-MM-DD-NN",
|
|
223
|
+
"estimated_level": "标准",
|
|
224
|
+
"user_decision": "accepted",
|
|
225
|
+
"actual_effort_phase_count": 5,
|
|
226
|
+
"actual_duration_minutes": 45,
|
|
227
|
+
"was_accurate": true
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### 阈值优化
|
|
234
|
+
|
|
235
|
+
当积累 ≥ 20 条记录后,提示用户可以运行阈值分析:
|
|
236
|
+
|
|
237
|
+
> 已积累 {n} 条 AUTO-ESTIMATE 记录。是否运行阈值优化分析?
|
|
238
|
+
> 分析会检查:过度估计(建议标准但实际轻量)、低估(建议轻量但实际复杂),并推荐阈值调整。
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# AUTO-ESTIMATE 学习日志
|
|
2
|
+
|
|
3
|
+
本文件记录 AUTO-ESTIMATE 的用户纠偏历史和完成数据,用于阈值迭代优化。
|
|
4
|
+
|
|
5
|
+
## 文件位置
|
|
6
|
+
|
|
7
|
+
`.sprint-state/auto-estimate-learning.json`
|
|
8
|
+
|
|
9
|
+
## JSON Schema
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"entries": [
|
|
14
|
+
{
|
|
15
|
+
"sprint_id": "sprint-2026-06-01-14",
|
|
16
|
+
"task_description": "实现issue92 auto-estimate",
|
|
17
|
+
"change_type": "新增功能",
|
|
18
|
+
"estimated_level": "标准",
|
|
19
|
+
"estimated_metrics": {
|
|
20
|
+
"ref_count": null,
|
|
21
|
+
"cross_module_count": 2,
|
|
22
|
+
"circular_dep": false,
|
|
23
|
+
"public_api_count": 0,
|
|
24
|
+
"test_file_count": 0
|
|
25
|
+
},
|
|
26
|
+
"user_decision": "accepted",
|
|
27
|
+
"override_reason": null,
|
|
28
|
+
"actual_outcome": {
|
|
29
|
+
"phase_count": 3,
|
|
30
|
+
"duration_estimate": "30-45min",
|
|
31
|
+
"was_accurate": true,
|
|
32
|
+
"notes": "纯文档修改,实际确实为标准级别"
|
|
33
|
+
},
|
|
34
|
+
"timestamp": "2026-06-01T23:35:00+08:00"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"summary": {
|
|
38
|
+
"total_entries": 1,
|
|
39
|
+
"accepted_count": 1,
|
|
40
|
+
"overridden_count": 0,
|
|
41
|
+
"accurate_count": 1,
|
|
42
|
+
"over_estimate_count": 0,
|
|
43
|
+
"under_estimate_count": 0,
|
|
44
|
+
"last_updated": "2026-06-01T23:35:00+08:00"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 字段说明
|
|
50
|
+
|
|
51
|
+
### Entry 级别
|
|
52
|
+
|
|
53
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
54
|
+
|------|------|------|------|
|
|
55
|
+
| `sprint_id` | string | ✅ | Sprint 标识符 |
|
|
56
|
+
| `task_description` | string | ✅ | 需求描述 |
|
|
57
|
+
| `change_type` | string | ✅ | 变更类型:`删除已存在代码` / `修改已存在代码` / `新增功能` / `Bug修复` |
|
|
58
|
+
| `estimated_level` | string | ✅ | 评估级别:`轻量` / `标准` / `复杂` |
|
|
59
|
+
| `estimated_metrics` | object | ✅ | 评估时的指标数据 |
|
|
60
|
+
| `user_decision` | string | ✅ | 用户决定:`accepted` / `overridden` / `cancelled` |
|
|
61
|
+
| `override_reason` | string | 条件 | 当 `user_decision` 为 `overridden` 时必填 |
|
|
62
|
+
| `actual_outcome` | object | 条件 | Sprint 完成后回填 |
|
|
63
|
+
| `timestamp` | string | ✅ | ISO 8601 时间戳 |
|
|
64
|
+
|
|
65
|
+
### actual_outcome 级别
|
|
66
|
+
|
|
67
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
68
|
+
|------|------|------|------|
|
|
69
|
+
| `phase_count` | number | ✅ | 实际执行的 phase 数 |
|
|
70
|
+
| `duration_estimate` | string | 推荐 | 实际耗时估算 |
|
|
71
|
+
| `was_accurate` | boolean | ✅ | 评估是否准确 |
|
|
72
|
+
| `notes` | string | 推荐 | 备注说明 |
|
|
73
|
+
|
|
74
|
+
### summary 级别
|
|
75
|
+
|
|
76
|
+
| 字段 | 类型 | 说明 |
|
|
77
|
+
|------|------|------|
|
|
78
|
+
| `total_entries` | number | 总条目数 |
|
|
79
|
+
| `accepted_count` | number | 接受建议数 |
|
|
80
|
+
| `overridden_count` | number | 用户修改数 |
|
|
81
|
+
| `accurate_count` | number | 评估准确数 |
|
|
82
|
+
| `over_estimate_count` | number | 高估数(建议标准但实际轻量) |
|
|
83
|
+
| `under_estimate_count` | number | 低估数(建议轻量但实际复杂) |
|
|
84
|
+
|
|
85
|
+
## 初始化
|
|
86
|
+
|
|
87
|
+
新 sprint 创建时,自动创建 `.sprint-state/auto-estimate-learning.json`:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"entries": [],
|
|
92
|
+
"summary": {
|
|
93
|
+
"total_entries": 0,
|
|
94
|
+
"accepted_count": 0,
|
|
95
|
+
"overridden_count": 0,
|
|
96
|
+
"accurate_count": 0,
|
|
97
|
+
"over_estimate_count": 0,
|
|
98
|
+
"under_estimate_count": 0,
|
|
99
|
+
"last_updated": null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 阈值优化触发
|
|
105
|
+
|
|
106
|
+
当 `total_entries >= 20` 时,在 Phase 8 CLEANUP 阶段提示用户:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
已积累 20 条 AUTO-ESTIMATE 记录。是否运行阈值优化分析?
|
|
110
|
+
|
|
111
|
+
当前准确率:{accurate_count / total_entries * 100}%
|
|
112
|
+
高估率:{over_estimate_count / total_entries * 100}%
|
|
113
|
+
低估率:{under_estimate_count / total_entries * 100}%
|
|
114
|
+
|
|
115
|
+
[运行分析] [跳过]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 阈值优化规则
|
|
119
|
+
|
|
120
|
+
### 高估模式检测
|
|
121
|
+
|
|
122
|
+
IF `over_estimate_count / total_entries > 30%`:
|
|
123
|
+
→ 建议降低「标准」→「复杂」的引用计数阈值(当前 >10)
|
|
124
|
+
→ 分析高估的共性:是否某个指标权重过高?
|
|
125
|
+
|
|
126
|
+
### 低估模式检测
|
|
127
|
+
|
|
128
|
+
IF `under_estimate_count / total_entries > 20%`:
|
|
129
|
+
→ 建议增加额外检查项(如循环依赖、测试文件数)
|
|
130
|
+
→ 分析低估的共性:是否遗漏了关键复杂度信号?
|
|
131
|
+
|
|
132
|
+
### 纠偏频率检测
|
|
133
|
+
|
|
134
|
+
IF `overridden_count / total_entries > 30%`:
|
|
135
|
+
→ 用户频繁修改建议 → 阈值体系可能有问题
|
|
136
|
+
→ 建议全面阈值校准
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# AUTO-ESTIMATE 输出模板
|
|
2
|
+
|
|
3
|
+
本模板定义了 Phase -0.5 在终端向用户展示 AUTO-ESTIMATE 结果的标准格式。
|
|
4
|
+
|
|
5
|
+
## 输出格式
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
+-------------------------------------------------------------+
|
|
9
|
+
| AUTO-ESTIMATE 评估结果 |
|
|
10
|
+
+-------------------------------------------------------------+
|
|
11
|
+
| 需求:{task_description} |
|
|
12
|
+
| 类型:{change_type} |
|
|
13
|
+
| |
|
|
14
|
+
| [{impact_level}] Impact: {impact_label} |
|
|
15
|
+
| |
|
|
16
|
+
| 引用:{ref_count} 处 |
|
|
17
|
+
| 跨模块:{cross_module_count} 个 ({module_list}) |
|
|
18
|
+
| 循环依赖:{circular_dep_status} |
|
|
19
|
+
| Public API:{public_api_count} 个 |
|
|
20
|
+
| {additional_metrics} |
|
|
21
|
+
| |
|
|
22
|
+
| 建议流程:{recommended_flow} |
|
|
23
|
+
| |
|
|
24
|
+
| {risk_warning} |
|
|
25
|
+
| |
|
|
26
|
+
| [接受建议] [修改流程] [取消] |
|
|
27
|
+
+-------------------------------------------------------------+
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 字段说明
|
|
31
|
+
|
|
32
|
+
| 字段 | 说明 | 示例 |
|
|
33
|
+
|------|------|------|
|
|
34
|
+
| `{task_description}` | 用户输入的需求描述 | 删除平面维护界面 |
|
|
35
|
+
| `{change_type}` | 变更类型 | 删除/修改已存在代码 或 新增功能 |
|
|
36
|
+
| `{impact_level}` | 影响级别标识 | `[轻量]` / `[标准]` / `[复杂]` |
|
|
37
|
+
| `{impact_label}` | 影响级别标签 | 低 / 中 / 高 |
|
|
38
|
+
| `{ref_count}` | 引用出现次数 | 12 |
|
|
39
|
+
| `{cross_module_count}` | 跨模块数量 | 3 |
|
|
40
|
+
| `{module_list}` | 涉及模块名列表 | auth, user, admin |
|
|
41
|
+
| `{circular_dep_status}` | 循环依赖状态 | ✅ 无 / ⚠️ 存在 (A ↔ B) |
|
|
42
|
+
| `{public_api_count}` | Public API 暴露数 | 5 |
|
|
43
|
+
| `{additional_metrics}` | 附加指标(可选行) | 测试文件:4 个 / 影响范围:3 层调用 |
|
|
44
|
+
| `{recommended_flow}` | 建议流程描述 | 轻量流程 (Phase 2-3) |
|
|
45
|
+
| `{risk_warning}` | 风险警告(可选块) | ⚠️ 此操作涉及循环依赖… |
|
|
46
|
+
| 操作按钮 | 用户确认选项 | 接受建议 / 修改流程 / 取消 |
|
|
47
|
+
|
|
48
|
+
## 风险警告格式
|
|
49
|
+
|
|
50
|
+
当检测到高风险信号时(循环依赖、大范围 Public API、>10 处引用),追加风险警告块:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
| |
|
|
54
|
+
| ⚠️ {risk_description} |
|
|
55
|
+
| {mitigation_suggestion} |
|
|
56
|
+
| |
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
示例:
|
|
60
|
+
```
|
|
61
|
+
| |
|
|
62
|
+
| ⚠️ 此操作涉及循环依赖,建议保留层级结构作为过渡方案 |
|
|
63
|
+
| 避免直接删除导致编译失败 |
|
|
64
|
+
| |
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 修改流程子菜单
|
|
68
|
+
|
|
69
|
+
当用户选择「修改流程」时,展示以下选项:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
+-------------------------------------------------------------+
|
|
73
|
+
| 选择流程级别: |
|
|
74
|
+
| |
|
|
75
|
+
| [1] 轻量流程 — 直接编码 + 基础验证 (Phase 2-3) |
|
|
76
|
+
| [2] 标准流程 — brainstorming + BUILD + REVIEW (Phase 0-4) |
|
|
77
|
+
| [3] 完整流程 — 完整 Sprint Flow (Phase 0-8) |
|
|
78
|
+
| |
|
|
79
|
+
| 修改原因(必填):____________________ |
|
|
80
|
+
| |
|
|
81
|
+
| [确认] [取消] |
|
|
82
|
+
+-------------------------------------------------------------+
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 指标计算规则
|
|
86
|
+
|
|
87
|
+
### 对于删除/修改已存在代码
|
|
88
|
+
|
|
89
|
+
| 指标 | 计算方式 | 工具 |
|
|
90
|
+
|------|---------|------|
|
|
91
|
+
| 引用计数 | `grep -rn "{target_pattern}" --include="*.{ext}" | wc -l` | bash |
|
|
92
|
+
| 跨模块依赖 | 分析 import 语句中不同目录层级的引用 | bash + grep |
|
|
93
|
+
| 循环依赖 | 检查 import 图是否存在环(简单检查:A imports B, B imports A) | bash |
|
|
94
|
+
| Public API 暴露 | `grep -rn "^export "` 计数 | bash |
|
|
95
|
+
| 测试文件数 | `find . -name "*{target}*.test.*" | wc -l` | bash |
|
|
96
|
+
|
|
97
|
+
### 对于新增功能(brainstorming 后)
|
|
98
|
+
|
|
99
|
+
| 指标 | 计算方式 | 来源 |
|
|
100
|
+
|------|---------|------|
|
|
101
|
+
| 新增模块数 | 设计文档中列出的模块数 | Phase 0 输出 |
|
|
102
|
+
| 跨系统集成 | 涉及 API/DB/外部 数量 | Phase 0 输出 |
|
|
103
|
+
| 状态复杂度 | 状态机状态数 | Phase 0 输出 |
|
|
104
|
+
| REQ 数量 | user_stories 数量 | Phase 1 输出 |
|
|
105
|
+
|
|
106
|
+
## 路由决策表
|
|
107
|
+
|
|
108
|
+
| 评估结果 | 路由 | 说明 |
|
|
109
|
+
|---------|------|------|
|
|
110
|
+
| **轻量** (引用 ≤3, 同模块,无循环依赖) | Phase 2-3(跳过 brainstorming + delphi-review) | 直接编码 + 基础验证 |
|
|
111
|
+
| **标准** (引用 4-10, 跨 1-2 模块) | Phase 0-4(完整 THINK → BUILD → REVIEW) | 标准 sprint |
|
|
112
|
+
| **复杂** (引用 >10 或 循环依赖 或 跨 3+ 模块) | Phase 0-8(完整 sprint-flow) | 完整流程 + 风险警告 |
|
|
113
|
+
|
|
114
|
+
## 学习闭环
|
|
115
|
+
|
|
116
|
+
当用户选择「修改流程」时,记录 override 数据:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"sprint_id": "sprint-YYYY-MM-DD-NN",
|
|
121
|
+
"task_description": "原始需求描述",
|
|
122
|
+
"estimated_level": "标准",
|
|
123
|
+
"user_override_level": "轻量",
|
|
124
|
+
"override_reason": "用户输入原因",
|
|
125
|
+
"actual_effort": "TBD",
|
|
126
|
+
"timestamp": "ISO 8601"
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
数据写入 `.sprint-state/auto-estimate-learning.json`(追加模式),用于阈值迭代优化。
|