@zhuan-ai/zhuanspec 2.11.19 → 2.12.7
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/index.js +53 -0
- package/dist/commands/accuracy.d.ts +15 -0
- package/dist/commands/accuracy.js +154 -0
- package/dist/commands/progress.d.ts +17 -0
- package/dist/commands/progress.js +128 -1
- package/dist/commands/review.js +31 -2
- package/dist/core/configurators/codex.d.ts +137 -0
- package/dist/core/configurators/codex.js +296 -0
- package/dist/core/configurators/registry.js +3 -0
- package/dist/core/configurators/slash/codex.d.ts +39 -8
- package/dist/core/configurators/slash/codex.js +158 -112
- package/dist/core/dashboard/metrics.d.ts +9 -0
- package/dist/core/dashboard/metrics.js +12 -0
- package/dist/core/hooks/collect-knowledge.js +7 -2
- package/dist/core/hooks/deviation-check.d.ts +11 -1
- package/dist/core/hooks/deviation-check.js +275 -14
- package/dist/core/hooks/deviation-handler.js +2 -2
- package/dist/core/hooks/init.js +42 -6
- package/dist/core/hooks/post-apply.js +21 -4
- package/dist/core/hooks/pre-apply.js +8 -0
- package/dist/core/hooks/record-progress.d.ts +39 -0
- package/dist/core/hooks/record-progress.js +143 -21
- package/dist/core/hooks/review-hooks.d.ts +26 -1
- package/dist/core/hooks/review-hooks.js +166 -12
- package/dist/core/hooks/summarize.js +61 -4
- package/dist/core/metrics/code-accuracy.d.ts +83 -0
- package/dist/core/metrics/code-accuracy.js +250 -0
- package/dist/core/styles/palette.d.ts +1 -0
- package/dist/core/styles/palette.js +3 -1
- package/dist/core/templates/agents-root-stub.d.ts +1 -1
- package/dist/core/templates/agents-root-stub.js +15 -0
- package/dist/core/templates/agents-template.d.ts +1 -1
- package/dist/core/templates/agents-template.js +40 -19
- package/dist/core/templates/codex-agents-template.d.ts +53 -0
- package/dist/core/templates/codex-agents-template.js +177 -0
- package/dist/core/templates/codex-hooks-template.d.ts +52 -0
- package/dist/core/templates/codex-hooks-template.js +114 -0
- package/dist/core/templates/codex-skills-template.d.ts +39 -0
- package/dist/core/templates/codex-skills-template.js +69 -0
- package/dist/core/templates/slash-command-templates.js +124 -48
- package/dist/core/validation/strict-rules.js +23 -0
- package/dist/utils/hook-host.d.ts +47 -0
- package/dist/utils/hook-host.js +129 -0
- package/dist/utils/hook-merge.d.ts +29 -0
- package/dist/utils/hook-merge.js +72 -0
- package/dist/utils/phase-utils.js +3 -3
- package/package.json +20 -22
- package/dist/core/hooks/review-orchestrator.d.ts +0 -40
- package/dist/core/hooks/review-orchestrator.js +0 -202
package/dist/cli/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { ShowCommand } from '../commands/show.js';
|
|
|
15
15
|
import { CompletionCommand } from '../commands/completion.js';
|
|
16
16
|
import { ReviewCommand } from '../commands/review.js';
|
|
17
17
|
import { ProgressCommand } from '../commands/progress.js';
|
|
18
|
+
import { AccuracyCommand } from '../commands/accuracy.js';
|
|
18
19
|
import { discoverSkills, formatSkillsList } from '../core/skill-discovery.js';
|
|
19
20
|
import { registerConfigCommand } from '../commands/config.js';
|
|
20
21
|
import { registerArtifactWorkflowCommands } from '../commands/artifact-workflow.js';
|
|
@@ -283,6 +284,58 @@ progressCmd
|
|
|
283
284
|
process.exit(1);
|
|
284
285
|
}
|
|
285
286
|
});
|
|
287
|
+
// Progress resolve-correction subcommand
|
|
288
|
+
progressCmd
|
|
289
|
+
.command('resolve-correction <change-id>')
|
|
290
|
+
.description('Clear pending user-correction marker (Apply phase)')
|
|
291
|
+
.option('--path <path>', 'Chosen resolution path: A|B|C|D')
|
|
292
|
+
.option('--note <text>', 'Optional note to attach to the correction-log entry')
|
|
293
|
+
.option('--mark-pitfall-saved', 'Also set askedPitfallSaved=true in progress.json to suppress Stop-hook ask')
|
|
294
|
+
.action(async (changeId, options) => {
|
|
295
|
+
try {
|
|
296
|
+
const progressCommand = new ProgressCommand();
|
|
297
|
+
await progressCommand.resolveCorrection(changeId, options);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
console.log();
|
|
301
|
+
ora().fail(`Error: ${error.message}`);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
// Progress show-correction subcommand
|
|
306
|
+
progressCmd
|
|
307
|
+
.command('show-correction <change-id>')
|
|
308
|
+
.description('Show pending-correction status and recent correction-log entries')
|
|
309
|
+
.option('--tail <n>', 'Show last N log entries (default 10)', (v) => parseInt(v, 10))
|
|
310
|
+
.action(async (changeId, options) => {
|
|
311
|
+
try {
|
|
312
|
+
const progressCommand = new ProgressCommand();
|
|
313
|
+
await progressCommand.showCorrection(changeId, options);
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
console.log();
|
|
317
|
+
ora().fail(`Error: ${error.message}`);
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
// Accuracy command
|
|
322
|
+
program
|
|
323
|
+
.command('accuracy [change-name]')
|
|
324
|
+
.description('Show Initial Code Accuracy Rate for a change')
|
|
325
|
+
.option('--json', 'Output as JSON')
|
|
326
|
+
.option('--force', 'Force recompute accuracy')
|
|
327
|
+
.option('--override <rate>', 'Manually override accuracy rate (0.0~1.0)')
|
|
328
|
+
.action(async (changeName, options) => {
|
|
329
|
+
try {
|
|
330
|
+
const accuracyCommand = new AccuracyCommand();
|
|
331
|
+
await accuracyCommand.execute(changeName, options);
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
console.log();
|
|
335
|
+
ora().fail(`Error: ${error.message}`);
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
286
339
|
program
|
|
287
340
|
.command('archive [change-name]')
|
|
288
341
|
.description('Archive a completed change and update main specs')
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accuracy Command - Show Initial Code Accuracy Rate
|
|
3
|
+
*
|
|
4
|
+
* Usage: zhuanspec accuracy <change-id>
|
|
5
|
+
* Options: --json, --force, --override <rate>
|
|
6
|
+
*/
|
|
7
|
+
export declare class AccuracyCommand {
|
|
8
|
+
execute(changeName?: string, options?: {
|
|
9
|
+
json?: boolean;
|
|
10
|
+
force?: boolean;
|
|
11
|
+
override?: string;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
private selectChangeInteractively;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=accuracy.d.ts.map
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accuracy Command - Show Initial Code Accuracy Rate
|
|
3
|
+
*
|
|
4
|
+
* Usage: zhuanspec accuracy <change-id>
|
|
5
|
+
* Options: --json, --force, --override <rate>
|
|
6
|
+
*/
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { promises as fs } from 'fs';
|
|
9
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
10
|
+
import { computeAccuracy, applyOverride, } from '../core/metrics/code-accuracy.js';
|
|
11
|
+
import { atomicWriteJson, recoverProgressJsonForWrite, } from '../core/hooks/record-progress.js';
|
|
12
|
+
export class AccuracyCommand {
|
|
13
|
+
async execute(changeName, options) {
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
if (!changeName) {
|
|
16
|
+
const selected = await this.selectChangeInteractively();
|
|
17
|
+
if (!selected) {
|
|
18
|
+
throw new Error('No change selected. Usage: zhuanspec accuracy <change-id>');
|
|
19
|
+
}
|
|
20
|
+
changeName = selected;
|
|
21
|
+
}
|
|
22
|
+
const changeDir = path.join(cwd, 'zhuanspec', 'changes', changeName);
|
|
23
|
+
if (!await FileSystemUtils.directoryExists(changeDir)) {
|
|
24
|
+
throw new Error(`Change '${changeName}' not found`);
|
|
25
|
+
}
|
|
26
|
+
const metricsDir = path.join(changeDir, 'metrics');
|
|
27
|
+
const accuracyPath = path.join(metricsDir, 'accuracy.json');
|
|
28
|
+
const progressPath = path.join(metricsDir, 'progress.json');
|
|
29
|
+
// Load progress with recovery support
|
|
30
|
+
let progress = null;
|
|
31
|
+
if (await FileSystemUtils.fileExists(progressPath)) {
|
|
32
|
+
progress = await recoverProgressJsonForWrite(progressPath);
|
|
33
|
+
}
|
|
34
|
+
if (!progress || !progress.accuracy?.snapshotTimestamp) {
|
|
35
|
+
console.log('ℹ️ No accuracy snapshot found for this change.');
|
|
36
|
+
console.log(' The snapshot is created after the Apply phase completes.');
|
|
37
|
+
console.log(' Make sure post-apply hook has been executed.');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// Handle --override
|
|
41
|
+
if (options?.override !== undefined) {
|
|
42
|
+
const overrideRate = parseFloat(options.override);
|
|
43
|
+
if (isNaN(overrideRate) || overrideRate < 0 || overrideRate > 1) {
|
|
44
|
+
throw new Error('Override rate must be a number between 0.0 and 1.0');
|
|
45
|
+
}
|
|
46
|
+
const result = applyOverride(progress, overrideRate);
|
|
47
|
+
// Persist to progress.json
|
|
48
|
+
await atomicWriteJson(progressPath, progress);
|
|
49
|
+
// Also update accuracy.json
|
|
50
|
+
if (await FileSystemUtils.fileExists(accuracyPath)) {
|
|
51
|
+
try {
|
|
52
|
+
const accuracyData = JSON.parse(await FileSystemUtils.readFile(accuracyPath));
|
|
53
|
+
if (!accuracyData.overrideHistory)
|
|
54
|
+
accuracyData.overrideHistory = [];
|
|
55
|
+
accuracyData.overrideHistory.push({
|
|
56
|
+
timestamp: new Date().toISOString(),
|
|
57
|
+
originalRate: result.originalRate,
|
|
58
|
+
overriddenRate: result.overriddenRate,
|
|
59
|
+
reason: 'CLI --override',
|
|
60
|
+
});
|
|
61
|
+
accuracyData.accuracyRate = result.overriddenRate;
|
|
62
|
+
accuracyData.accuracyRateRaw = result.originalRate;
|
|
63
|
+
await atomicWriteJson(accuracyPath, accuracyData);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Ignore accuracy.json update errors
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
console.log(`✓ Accuracy overridden: ${(result.originalRate * 100).toFixed(1)}% → ${(result.overriddenRate * 100).toFixed(1)}%`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Compute accuracy
|
|
73
|
+
const accResult = computeAccuracy(progress);
|
|
74
|
+
if (options?.json) {
|
|
75
|
+
console.log(JSON.stringify({
|
|
76
|
+
changeId: changeName,
|
|
77
|
+
accuracy: {
|
|
78
|
+
rate: accResult.rate,
|
|
79
|
+
rateRaw: accResult.rateRaw,
|
|
80
|
+
aiTotalLines: accResult.aiTotalLines,
|
|
81
|
+
userCorrectionLines: accResult.userCorrectionLines,
|
|
82
|
+
overridden: accResult.overridden,
|
|
83
|
+
},
|
|
84
|
+
snapshot: progress.accuracy,
|
|
85
|
+
correctionEdits: progress.accuracy.correctionEdits || [],
|
|
86
|
+
}, null, 2));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log('');
|
|
90
|
+
console.log(`📊 代码首次准确率: ${changeName}`);
|
|
91
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
92
|
+
console.log('');
|
|
93
|
+
console.log(` 准确率: ${(accResult.rate * 100).toFixed(1)}%${accResult.overridden ? ' (已手动修正)' : ''}`);
|
|
94
|
+
if (accResult.overridden) {
|
|
95
|
+
console.log(` 原始准确率: ${(accResult.rateRaw * 100).toFixed(1)}%`);
|
|
96
|
+
}
|
|
97
|
+
console.log(` AI产出: ${accResult.aiTotalLines} 行`);
|
|
98
|
+
console.log(` 用户纠正: ${accResult.userCorrectionLines} 行`);
|
|
99
|
+
if (progress.accuracy.snapshotTimestamp) {
|
|
100
|
+
console.log(` 快照时间: ${progress.accuracy.snapshotTimestamp}`);
|
|
101
|
+
}
|
|
102
|
+
console.log('');
|
|
103
|
+
// Show per-file correction details
|
|
104
|
+
const edits = progress.accuracy.correctionEdits || [];
|
|
105
|
+
if (edits.length > 0) {
|
|
106
|
+
console.log('📝 纠正明细:');
|
|
107
|
+
const byFile = new Map();
|
|
108
|
+
for (const e of edits) {
|
|
109
|
+
const existing = byFile.get(e.filePath) || { lines: 0, count: 0 };
|
|
110
|
+
existing.lines += e.editLines;
|
|
111
|
+
existing.count += 1;
|
|
112
|
+
byFile.set(e.filePath, existing);
|
|
113
|
+
}
|
|
114
|
+
for (const [file, stats] of byFile) {
|
|
115
|
+
console.log(` ${file}: ${stats.lines} 行 (${stats.count} 次)`);
|
|
116
|
+
}
|
|
117
|
+
console.log('');
|
|
118
|
+
}
|
|
119
|
+
console.log(`💡 手动修正: zhuanspec accuracy ${changeName} --override <0.0~1.0>`);
|
|
120
|
+
console.log('');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async selectChangeInteractively() {
|
|
124
|
+
const changesDir = path.join(process.cwd(), 'zhuanspec', 'changes');
|
|
125
|
+
let entries = [];
|
|
126
|
+
try {
|
|
127
|
+
entries = await fs.readdir(changesDir, { withFileTypes: true });
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
throw new Error("No zhuanspec changes directory found. Run 'zhuanspec init' first.");
|
|
131
|
+
}
|
|
132
|
+
const changes = entries
|
|
133
|
+
.filter((entry) => entry.isDirectory() && entry.name !== 'archive' && !entry.name.startsWith('.'))
|
|
134
|
+
.map((entry) => entry.name)
|
|
135
|
+
.sort((a, b) => a.localeCompare(b));
|
|
136
|
+
if (changes.length === 0) {
|
|
137
|
+
throw new Error('No active changes found. Create one first.');
|
|
138
|
+
}
|
|
139
|
+
if (!process.stdin.isTTY) {
|
|
140
|
+
throw new Error(`Missing change name. Available changes: ${changes.join(', ')}`);
|
|
141
|
+
}
|
|
142
|
+
const { select } = await import('@inquirer/prompts');
|
|
143
|
+
try {
|
|
144
|
+
return await select({
|
|
145
|
+
message: '选择要查看准确率的变更',
|
|
146
|
+
choices: changes.map((id) => ({ name: id, value: id })),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=accuracy.js.map
|
|
@@ -34,5 +34,22 @@ export declare class ProgressCommand {
|
|
|
34
34
|
* Usage: zhuanspec progress set-phase <change-id> <phase>
|
|
35
35
|
*/
|
|
36
36
|
setPhase(changeId: string, phase: string): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve pending user-correction: remove `.pending-correction`, append
|
|
39
|
+
* resolved entry to `.correction-log`, and optionally mark pitfall saved.
|
|
40
|
+
* Usage: zhuanspec progress resolve-correction <change-id> [--path A|B|C|D] [--note <text>] [--mark-pitfall-saved]
|
|
41
|
+
*/
|
|
42
|
+
resolveCorrection(changeId: string, options?: {
|
|
43
|
+
path?: string;
|
|
44
|
+
note?: string;
|
|
45
|
+
markPitfallSaved?: boolean;
|
|
46
|
+
}): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Show current pending correction status and recent correction-log entries.
|
|
49
|
+
* Usage: zhuanspec progress show-correction <change-id> [--tail <n>]
|
|
50
|
+
*/
|
|
51
|
+
showCorrection(changeId: string, options?: {
|
|
52
|
+
tail?: number;
|
|
53
|
+
}): Promise<void>;
|
|
37
54
|
}
|
|
38
55
|
//# sourceMappingURL=progress.d.ts.map
|
|
@@ -288,7 +288,7 @@ export class ProgressCommand {
|
|
|
288
288
|
const completedCount = tasks.filter(t => t.status === 'completed').length;
|
|
289
289
|
const totalTasks = tasks.length;
|
|
290
290
|
// Phase index
|
|
291
|
-
const phases = ['idle', 'propose', 'apply', 'review', 'archive'];
|
|
291
|
+
const phases = ['idle', 'techDesign', 'propose', 'apply', 'review', 'archive'];
|
|
292
292
|
const phaseIndex = phases.indexOf(phase) + 1;
|
|
293
293
|
// Progress percentage
|
|
294
294
|
const percentage = totalTasks > 0 ? (completedCount / totalTasks) * 100 : 0;
|
|
@@ -370,5 +370,132 @@ export class ProgressCommand {
|
|
|
370
370
|
console.log(`\n✓ Phase set: ${changeId} -> ${normalizedPhase}`);
|
|
371
371
|
console.log(`✓ Progress file: ${progressPath}\n`);
|
|
372
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Resolve pending user-correction: remove `.pending-correction`, append
|
|
375
|
+
* resolved entry to `.correction-log`, and optionally mark pitfall saved.
|
|
376
|
+
* Usage: zhuanspec progress resolve-correction <change-id> [--path A|B|C|D] [--note <text>] [--mark-pitfall-saved]
|
|
377
|
+
*/
|
|
378
|
+
async resolveCorrection(changeId, options) {
|
|
379
|
+
const cwd = resolveZhuanSpecRoot();
|
|
380
|
+
const changeDir = path.join(cwd, 'zhuanspec', 'changes', changeId);
|
|
381
|
+
if (!await FileSystemUtils.directoryExists(changeDir)) {
|
|
382
|
+
throw new Error(`Change '${changeId}' not found`);
|
|
383
|
+
}
|
|
384
|
+
const pendingMarker = path.join(changeDir, '.pending-correction');
|
|
385
|
+
const logPath = path.join(changeDir, '.correction-log');
|
|
386
|
+
let hadMarker = false;
|
|
387
|
+
// Remove pending marker (safe if missing)
|
|
388
|
+
try {
|
|
389
|
+
await fs.unlink(pendingMarker);
|
|
390
|
+
hadMarker = true;
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
// ignore missing marker
|
|
394
|
+
}
|
|
395
|
+
// Append resolved entry to correction-log
|
|
396
|
+
const entry = {
|
|
397
|
+
ts: new Date().toISOString(),
|
|
398
|
+
event: 'resolved',
|
|
399
|
+
path: options?.path,
|
|
400
|
+
note: options?.note,
|
|
401
|
+
};
|
|
402
|
+
try {
|
|
403
|
+
await fs.appendFile(logPath, JSON.stringify(entry) + '\n', 'utf-8');
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// ignore log append errors
|
|
407
|
+
}
|
|
408
|
+
// Optionally mark askedPitfallSaved=true in progress.json
|
|
409
|
+
if (options?.markPitfallSaved) {
|
|
410
|
+
const progressPath = path.join(changeDir, 'metrics', 'progress.json');
|
|
411
|
+
if (await FileSystemUtils.fileExists(progressPath)) {
|
|
412
|
+
try {
|
|
413
|
+
const raw = await FileSystemUtils.readFile(progressPath);
|
|
414
|
+
const progress = JSON.parse(raw);
|
|
415
|
+
progress.askedPitfallSaved = true;
|
|
416
|
+
progress.lastUpdatedAt = new Date().toISOString();
|
|
417
|
+
await fs.writeFile(progressPath, JSON.stringify(progress, null, 2), 'utf-8');
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
// ignore progress update errors
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
console.log(`\n✓ Correction resolved for change '${changeId}'`);
|
|
425
|
+
console.log(` - Pending marker: ${hadMarker ? 'removed' : 'not present'}`);
|
|
426
|
+
if (options?.path)
|
|
427
|
+
console.log(` - Path: ${options.path}`);
|
|
428
|
+
if (options?.note)
|
|
429
|
+
console.log(` - Note: ${options.note}`);
|
|
430
|
+
if (options?.markPitfallSaved)
|
|
431
|
+
console.log(` - askedPitfallSaved: true`);
|
|
432
|
+
console.log();
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Show current pending correction status and recent correction-log entries.
|
|
436
|
+
* Usage: zhuanspec progress show-correction <change-id> [--tail <n>]
|
|
437
|
+
*/
|
|
438
|
+
async showCorrection(changeId, options) {
|
|
439
|
+
const cwd = resolveZhuanSpecRoot();
|
|
440
|
+
const changeDir = path.join(cwd, 'zhuanspec', 'changes', changeId);
|
|
441
|
+
if (!await FileSystemUtils.directoryExists(changeDir)) {
|
|
442
|
+
throw new Error(`Change '${changeId}' not found`);
|
|
443
|
+
}
|
|
444
|
+
const pendingMarker = path.join(changeDir, '.pending-correction');
|
|
445
|
+
const logPath = path.join(changeDir, '.correction-log');
|
|
446
|
+
const tail = options?.tail && options.tail > 0 ? options.tail : 10;
|
|
447
|
+
console.log(`\nChange: ${changeId}`);
|
|
448
|
+
console.log('-'.repeat(60));
|
|
449
|
+
// Pending marker
|
|
450
|
+
if (await FileSystemUtils.fileExists(pendingMarker)) {
|
|
451
|
+
try {
|
|
452
|
+
const raw = await FileSystemUtils.readFile(pendingMarker);
|
|
453
|
+
const marker = JSON.parse(raw);
|
|
454
|
+
const ageMin = Math.round((Date.now() - (marker.createdAt || 0)) / 60000);
|
|
455
|
+
console.log(`Pending correction: YES (age ~${ageMin} min)`);
|
|
456
|
+
if (marker.prompt) {
|
|
457
|
+
const preview = String(marker.prompt).replace(/\s+/g, ' ').slice(0, 160);
|
|
458
|
+
console.log(` Prompt: ${preview}${marker.prompt.length > 160 ? '…' : ''}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
console.log('Pending correction: YES (unparseable marker)');
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
console.log('Pending correction: none');
|
|
467
|
+
}
|
|
468
|
+
// Correction log tail
|
|
469
|
+
if (await FileSystemUtils.fileExists(logPath)) {
|
|
470
|
+
const content = await FileSystemUtils.readFile(logPath);
|
|
471
|
+
const lines = content.split('\n').filter(l => l.trim().length > 0);
|
|
472
|
+
const recent = lines.slice(-tail);
|
|
473
|
+
console.log(`\nRecent correction-log (last ${recent.length} of ${lines.length}):`);
|
|
474
|
+
for (const line of recent) {
|
|
475
|
+
try {
|
|
476
|
+
const rec = JSON.parse(line);
|
|
477
|
+
console.log(` [${rec.ts}] ${rec.event}${rec.path ? ` path=${rec.path}` : ''}${rec.promptPreview ? ` — ${String(rec.promptPreview).slice(0, 80)}` : ''}${rec.note ? ` (${rec.note})` : ''}`);
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
console.log(` ${line}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
console.log('\nCorrection log: (empty)');
|
|
486
|
+
}
|
|
487
|
+
// askedPitfallSaved status
|
|
488
|
+
const progressPath = path.join(changeDir, 'metrics', 'progress.json');
|
|
489
|
+
if (await FileSystemUtils.fileExists(progressPath)) {
|
|
490
|
+
try {
|
|
491
|
+
const progress = JSON.parse(await FileSystemUtils.readFile(progressPath));
|
|
492
|
+
console.log(`\naskedPitfallSaved: ${progress.askedPitfallSaved === true ? 'true' : 'false'}`);
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
// ignore
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
console.log();
|
|
499
|
+
}
|
|
373
500
|
}
|
|
374
501
|
//# sourceMappingURL=progress.js.map
|
package/dist/commands/review.js
CHANGED
|
@@ -2,6 +2,7 @@ import { promises as fs } from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
4
4
|
import { setPhase } from '../utils/phase-utils.js';
|
|
5
|
+
import { ensureAccuracySnapshot } from '../core/metrics/code-accuracy.js';
|
|
5
6
|
import { codeReviewResultCheck, unitTestResultCheck, specConsistencyResultCheck, generateReviewReport, } from '../core/hooks/review-hooks.js';
|
|
6
7
|
export class ReviewCommand {
|
|
7
8
|
async execute(changeName, options) {
|
|
@@ -60,6 +61,17 @@ export class ReviewCommand {
|
|
|
60
61
|
catch (err) {
|
|
61
62
|
console.warn(`⚠️ Failed to update progress.phase to 'review': ${err instanceof Error ? err.message : String(err)}`);
|
|
62
63
|
}
|
|
64
|
+
// Accuracy snapshot 补救:若 apply 未走 post-apply hook(如手动 /zhuanspec:review)导致快照缺失,
|
|
65
|
+
// 在进入 review 的那一刻以 progress.linesAdded 作为 AI 基线衱5一份,避免首次准确率数据丢失。
|
|
66
|
+
try {
|
|
67
|
+
const created = await ensureAccuracySnapshot(changeDir);
|
|
68
|
+
if (created) {
|
|
69
|
+
console.log(`ℹ️ Accuracy snapshot 在 review 进入时补打成功(应优先走 post-apply hook,补打为兑底机制)`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
console.warn(`⚠️ Accuracy snapshot 补打失败(不阻断 review): ${err instanceof Error ? err.message : String(err)}`);
|
|
74
|
+
}
|
|
63
75
|
// Read hook results and generate report
|
|
64
76
|
const codeReviewResult = await codeReviewResultCheck(changeName);
|
|
65
77
|
const unitTestResult = await unitTestResultCheck(changeName);
|
|
@@ -82,10 +94,27 @@ export class ReviewCommand {
|
|
|
82
94
|
else {
|
|
83
95
|
console.log(`Review report for ${changeName}`);
|
|
84
96
|
console.log(`Overall: ${overallPass ? 'PASS ✅' : 'FAIL ❌'}`);
|
|
85
|
-
console.log(`Code Review: ${codeReviewResult.pass ? 'PASS' : 'FAIL'} (critical=${codeReviewResult.metrics.criticalCount ?? 0})`);
|
|
86
|
-
console.log(`Unit Test: ${unitTestResult.pass ? 'PASS' : 'FAIL'} (coverage=${unitTestResult.metrics.coverage ?? 0}%)`);
|
|
87
97
|
console.log(`Spec-Code: ${specConsistencyResult.pass ? 'PASS' : 'FAIL'} (consistency=${specConsistencyResult.metrics.consistencyRate ?? 0}%)`);
|
|
98
|
+
console.log(`Unit Test: ${unitTestResult.pass ? 'PASS' : 'FAIL'} (coverage=${unitTestResult.metrics.coverage ?? 0}%)`);
|
|
99
|
+
console.log(`Code Review: ${codeReviewResult.pass ? 'PASS' : 'FAIL'} (critical=${codeReviewResult.metrics.criticalCount ?? 0})`);
|
|
88
100
|
console.log(`Report: ${path.join('zhuanspec/changes', changeName, 'review/review-report.md')}`);
|
|
101
|
+
// 历史数据兼容提示:legacy schema 豁免时输出 ⚠️ 警告但不影响 PASS
|
|
102
|
+
if (codeReviewResult.metrics.legacyBypass) {
|
|
103
|
+
console.warn(`\n⚠️ [轨道 3 Code Review legacy schema] ${String(codeReviewResult.metrics.legacyReason ?? '')}\n → 下次请在 \`code-review-result.json\` 中设置 \`"schemaVersion": 2\` 并登记 \`skillReportPath\`。`);
|
|
104
|
+
}
|
|
105
|
+
if (unitTestResult.metrics.legacyBypass) {
|
|
106
|
+
console.warn(`\n⚠️ [轨道 2 Unit Test legacy schema] ${String(unitTestResult.metrics.legacyReason ?? '')}\n → 下次请在 \`unit-test-result.json\` 中设置 \`"schemaVersion": 2\` 并登记 \`newTestsGenerated\`/\`skipReason\`。`);
|
|
107
|
+
}
|
|
108
|
+
// 失败时输出中文 fixPrompt,确保用户明确看到失败原因与修复指引
|
|
109
|
+
if (!specConsistencyResult.pass && specConsistencyResult.fixPrompt) {
|
|
110
|
+
console.error(`\n[轨道 1 Spec-Code 失败]${specConsistencyResult.fixPrompt}`);
|
|
111
|
+
}
|
|
112
|
+
if (!unitTestResult.pass && unitTestResult.fixPrompt) {
|
|
113
|
+
console.error(`\n[轨道 2 Unit Test 失败]${unitTestResult.fixPrompt}`);
|
|
114
|
+
}
|
|
115
|
+
if (!codeReviewResult.pass && codeReviewResult.fixPrompt) {
|
|
116
|
+
console.error(`\n[轨道 3 Code Review 失败]${codeReviewResult.fixPrompt}`);
|
|
117
|
+
}
|
|
89
118
|
}
|
|
90
119
|
process.exitCode = overallPass ? 0 : 1;
|
|
91
120
|
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex root configurator.
|
|
3
|
+
*
|
|
4
|
+
* Unlike the Claude configurator (which owns `CLAUDE.md`) the Codex
|
|
5
|
+
* configurator shares `AGENTS.md` with the standard agents stub. Its main
|
|
6
|
+
* responsibility is to materialize the project-level Codex integration
|
|
7
|
+
* artefacts and the matching `trusted_level` entry in the global Codex config:
|
|
8
|
+
*
|
|
9
|
+
* <repo>/.codex/
|
|
10
|
+
* config.toml - ZhuanSpec hooks TOML block (wrapped in markers)
|
|
11
|
+
* agents/ - TOML subagents mirrored from .claude/agents/*.md
|
|
12
|
+
* skills/ - Project-level Skills mirrored from .claude/skills/
|
|
13
|
+
*
|
|
14
|
+
* ~/.codex/ (or $CODEX_HOME)
|
|
15
|
+
* config.toml - Per-project [projects."…"] trusted_level = "trusted"
|
|
16
|
+
* block (wrapped in TRUST markers keyed by absolute path)
|
|
17
|
+
*
|
|
18
|
+
* Codex does **not** load project-scoped hooks/subagents unless the project
|
|
19
|
+
* is explicitly marked trusted in the global config (see openai/codex#14601),
|
|
20
|
+
* so we opportunistically write that trust entry during `zhuanspec init`.
|
|
21
|
+
*
|
|
22
|
+
* Idempotency:
|
|
23
|
+
* - Hooks block is regenerated inside ZHUANSPEC markers on every run.
|
|
24
|
+
* - Trust entries are keyed by absolute path; repeated runs no-op.
|
|
25
|
+
* - Legacy `.codex/hooks.json` from the 2.12.0 implementation is removed.
|
|
26
|
+
*/
|
|
27
|
+
import { ToolConfigurator } from './base.js';
|
|
28
|
+
export interface CodexConfigureResult {
|
|
29
|
+
/** Absolute path of `<repo>/.codex/config.toml` that was written. */
|
|
30
|
+
configTomlPath: string;
|
|
31
|
+
/** Absolute paths of subagent role TOMLs that were written. */
|
|
32
|
+
agentsWritten: string[];
|
|
33
|
+
/** Absolute paths of skill files written under `<repo>/.codex/skills/`. */
|
|
34
|
+
skillsWritten: string[];
|
|
35
|
+
/** Absolute path of the global config.toml that received the trust entry. */
|
|
36
|
+
globalConfigPath: string;
|
|
37
|
+
/** Whether a new trust entry was added (false = already trusted). */
|
|
38
|
+
trustEntryAdded: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Absolute path of a legacy global `~/.codex/hooks.json` detected during
|
|
41
|
+
* configure. Codex warns when hooks are loaded from both `hooks.json` and
|
|
42
|
+
* `config.toml`; we surface the file here so callers can inform the user.
|
|
43
|
+
* `null` when no conflicting file was found.
|
|
44
|
+
*/
|
|
45
|
+
conflictingGlobalHooksJson: string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Absolute paths of legacy `~/.codex/prompts/zhuanspec-*.md` files that
|
|
48
|
+
* were cleaned up. Codex >= 0.117 ignores that directory entirely, so we
|
|
49
|
+
* opportunistically remove ZhuanSpec-managed prompts from pre-2.12.5
|
|
50
|
+
* installs to avoid user confusion. Empty when nothing needed removing.
|
|
51
|
+
*/
|
|
52
|
+
legacyPromptsCleaned: string[];
|
|
53
|
+
}
|
|
54
|
+
export declare class CodexConfigurator implements ToolConfigurator {
|
|
55
|
+
name: string;
|
|
56
|
+
/** Codex reads AGENTS.md for project instructions (no dedicated CODEX.md). */
|
|
57
|
+
configFileName: string;
|
|
58
|
+
isAvailable: boolean;
|
|
59
|
+
configure(projectPath: string, _zhuanspecDir: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Write:
|
|
62
|
+
* 1. `.codex/agents/zhuanspec-*.toml` mirrored from `.claude/agents/*.md`
|
|
63
|
+
* 2. `.codex/skills/*` mirrored from `.claude/skills/` (directory tree)
|
|
64
|
+
* 3. ZhuanSpec hooks block merged into `.codex/config.toml`
|
|
65
|
+
* 4. Per-project trust entry appended to `$CODEX_HOME/config.toml`
|
|
66
|
+
* 5. Remove legacy `.codex/hooks.json` left over from 2.12.0
|
|
67
|
+
*/
|
|
68
|
+
configureWithSummary(projectPath: string): Promise<CodexConfigureResult>;
|
|
69
|
+
/**
|
|
70
|
+
* Mirror `<repo>/.claude/agents/*.md` into `<repo>/.codex/agents/*.toml`.
|
|
71
|
+
* Returns the full file descriptors (not just paths) so the caller can
|
|
72
|
+
* register each role under `[agents.<slug>]` in the parent config.toml.
|
|
73
|
+
*/
|
|
74
|
+
private generateAgents;
|
|
75
|
+
/**
|
|
76
|
+
* Mirror `<repo>/.claude/skills/` directory tree into `<repo>/.codex/skills/`.
|
|
77
|
+
* Uses `fs.copyFile` so that executable bits on scripts (`.sh`, `.py`, etc.)
|
|
78
|
+
* are preserved. Silently no-ops when `.claude/skills/` is absent.
|
|
79
|
+
*/
|
|
80
|
+
private mirrorSkills;
|
|
81
|
+
/**
|
|
82
|
+
* Merge the ZhuanSpec managed block into `<repo>/.codex/config.toml`. The
|
|
83
|
+
* block contains two coordinated sections:
|
|
84
|
+
*
|
|
85
|
+
* 1. `[agents.<slug>]` registration tables (one per mirrored subagent)
|
|
86
|
+
* 2. `[[hooks.<Event>]]` default lifecycle hook tables
|
|
87
|
+
*
|
|
88
|
+
* User-owned config outside the marker block is preserved verbatim.
|
|
89
|
+
*/
|
|
90
|
+
private writeProjectConfigToml;
|
|
91
|
+
/**
|
|
92
|
+
* Build the `[agents.<slug>]` registration TOML block for all mirrored
|
|
93
|
+
* subagents. Each table points to the per-role file under `agents/<slug>.toml`.
|
|
94
|
+
* Returns an empty string when no agents were mirrored (no `.claude/agents`).
|
|
95
|
+
*/
|
|
96
|
+
private buildAgentsRegistrationBlock;
|
|
97
|
+
/**
|
|
98
|
+
* Detect whether a global `~/.codex/hooks.json` coexists with our
|
|
99
|
+
* project-level `config.toml` hooks. Codex warns (`loading hooks from both
|
|
100
|
+
* hooks.json and config.toml`) and we cannot safely delete a file the user
|
|
101
|
+
* owns — instead we return its path so the caller can prompt the user.
|
|
102
|
+
*/
|
|
103
|
+
private detectGlobalHooksJsonConflict;
|
|
104
|
+
/**
|
|
105
|
+
* Append (or keep) a per-project `[projects."<abs>"] trusted_level="trusted"`
|
|
106
|
+
* entry in `$CODEX_HOME/config.toml`. The block is wrapped in TRUST markers
|
|
107
|
+
* keyed by absolute path so repeated runs are idempotent and multiple
|
|
108
|
+
* projects can coexist in the same file.
|
|
109
|
+
*
|
|
110
|
+
* Conflict resolution with **unmanaged** native entries:
|
|
111
|
+
* Codex CLI's `/trust this workspace` and the legacy 2.12.0 implementation
|
|
112
|
+
* both write a plain `[projects."<abs>"]` table without ZHUANSPEC markers.
|
|
113
|
+
* If we unconditionally append our marked block, TOML parsing blows up
|
|
114
|
+
* with `duplicate key` and Codex refuses to start (observed in the wild).
|
|
115
|
+
* The user's explicit native entry always takes precedence: we detect it,
|
|
116
|
+
* skip our write, and if our marked block co-exists (legacy duplicate) we
|
|
117
|
+
* strip it to restore a parseable config.
|
|
118
|
+
*/
|
|
119
|
+
private ensureGlobalTrustEntry;
|
|
120
|
+
/**
|
|
121
|
+
* Detect whether `[projects."<projectPath>"]` is declared OUTSIDE our
|
|
122
|
+
* ZHUANSPEC:TRUST markers. Used to avoid producing TOML `duplicate key`
|
|
123
|
+
* when the user (or legacy 2.12.0) already wrote a native trust entry.
|
|
124
|
+
*/
|
|
125
|
+
private hasNativeTrustEntry;
|
|
126
|
+
/** Remove our marker-wrapped trust block for `projectPath` from `content`. */
|
|
127
|
+
private stripTrustBlock;
|
|
128
|
+
/** Build a full `# TRUST:START…END` block for the given project path. */
|
|
129
|
+
private buildTrustBlock;
|
|
130
|
+
/**
|
|
131
|
+
* Replace an existing TRUST block for `projectPath` in-place. Used to keep
|
|
132
|
+
* the block content in sync if the schema ever evolves (currently a no-op
|
|
133
|
+
* when the block already matches).
|
|
134
|
+
*/
|
|
135
|
+
private replaceTrustBlock;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=codex.d.ts.map
|