sillyspec 3.25.9 → 3.26.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/.claude/skills/sillyspec-auto/SKILL.md +8 -13
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +8 -6
- package/.claude/skills/sillyspec-continue/SKILL.md +3 -3
- package/.claude/skills/sillyspec-doctor/SKILL.md +1 -1
- package/.claude/skills/sillyspec-execute/SKILL.md +10 -4
- package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
- package/.claude/skills/sillyspec-quick/SKILL.md +3 -1
- package/.claude/skills/sillyspec-verify/SKILL.md +1 -1
- package/.sillyspec-platform-cleaned +1 -0
- package/.sillyspec-platform.json +7 -0
- package/README.md +9 -0
- package/package.json +2 -2
- package/src/change-list.js +11 -8
- package/src/change-risk-profile.js +22 -0
- package/src/db.js +170 -161
- package/src/dispatch/backends/local-agent.js +152 -0
- package/src/dispatch/backends/sillyhub-mcp.js +242 -0
- package/src/dispatch/probe.js +210 -0
- package/src/dispatch/strategy.js +121 -0
- package/src/doctor-diagnostics.js +19 -25
- package/src/fs-atomic.js +69 -69
- package/src/git-helper.js +66 -0
- package/src/hooks/worktree-guard.js +64 -55
- package/src/index.js +326 -36
- package/src/init.js +7 -14
- package/src/local-detect.js +120 -110
- package/src/machine-interface.js +54 -19
- package/src/migrate.js +14 -4
- package/src/modules.js +16 -5
- package/src/progress/change-registry.js +85 -70
- package/src/progress/consistency-doctor.js +71 -14
- package/src/progress/shared.js +8 -5
- package/src/progress/stage-machine.js +67 -65
- package/src/progress/step-store.js +72 -67
- package/src/progress.js +448 -236
- package/src/quicklog.js +59 -12
- package/src/run/command.js +78 -39
- package/src/run/complete-handlers.js +1028 -889
- package/src/run/complete.js +132 -212
- package/src/run/concurrent-detect.js +132 -0
- package/src/run/gates.js +302 -19
- package/src/run/prompt.js +22 -68
- package/src/run/quick-audit.js +10 -9
- package/src/run/shared.js +722 -600
- package/src/run/stage.js +43 -24
- package/src/scan-postcheck.js +9 -31
- package/src/sillyhub-mcp/client.js +394 -0
- package/src/sillyhub-mcp/config.js +62 -0
- package/src/spec-dir-typo.js +71 -0
- package/src/stage-contract-engine.js +22 -1
- package/src/stage-contract-spec.js +11 -4
- package/src/stage-contract.js +26 -3
- package/src/stage-review.js +118 -10
- package/src/stages/archive.js +4 -6
- package/src/stages/brainstorm-auto.js +26 -20
- package/src/stages/brainstorm.js +29 -5
- package/src/stages/cmd-existence.js +145 -0
- package/src/stages/doctor.js +10 -7
- package/src/stages/execute.js +64 -22
- package/src/stages/plan-postcheck.js +152 -0
- package/src/stages/plan.js +16 -31
- package/src/stages/quick.js +25 -9
- package/src/stages/scan.js +53 -8
- package/src/stages/status.js +0 -1
- package/src/stages/verify.js +14 -9
- package/src/sync.js +433 -16
- package/src/task-review.js +59 -1
- package/src/verify-postcheck.js +938 -862
- package/src/version.js +19 -0
- package/src/worktree-apply.js +218 -93
- package/src/worktree-deps.js +89 -5
- package/src/worktree.js +213 -111
- package/templates/prompts/taskcard-rules.md +27 -0
- package/templates/prompts/verify-probes.md +3 -3
- package/.claude/skills/sillyspec-propose/SKILL.md +0 -87
- package/src/stages/propose.js +0 -181
|
@@ -12,32 +12,33 @@ export class ChangeRegistry {
|
|
|
12
12
|
* 列出所有活跃变更名
|
|
13
13
|
* SQL: SELECT name FROM changes WHERE status = 'active'
|
|
14
14
|
*/
|
|
15
|
-
|
|
16
|
-
const db =
|
|
15
|
+
listChanges(cwd) {
|
|
16
|
+
const db = this.pm._ensureDB(cwd);
|
|
17
17
|
const sqlDb = db.getDb();
|
|
18
|
-
const rows = sqlDb.
|
|
19
|
-
|
|
20
|
-
return rows[0].values.map(r => r[0]);
|
|
18
|
+
const rows = sqlDb.prepare("SELECT name FROM changes WHERE status = 'active' ORDER BY name").all();
|
|
19
|
+
return rows.map(r => r.name);
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
23
|
* 注册变更到活跃列表
|
|
25
24
|
* SQL: INSERT OR IGNORE → 若已 archived 则 UPDATE status='active'
|
|
26
25
|
*/
|
|
27
|
-
|
|
26
|
+
registerChange(cwd, changeName) {
|
|
28
27
|
if (!changeName) {
|
|
29
28
|
console.warn('⚠️ registerChange: changeName 为空,跳过');
|
|
30
29
|
return;
|
|
31
30
|
}
|
|
32
|
-
const db =
|
|
33
|
-
db.transaction((
|
|
31
|
+
const db = this.pm._ensureDB(cwd);
|
|
32
|
+
db.transaction(() => {
|
|
33
|
+
const sqlDb = db.getDb();
|
|
34
34
|
const now = new Date().toISOString();
|
|
35
35
|
// 尝试插入新行
|
|
36
|
-
sqlDb.
|
|
36
|
+
const ins = sqlDb.prepare(
|
|
37
37
|
`INSERT OR IGNORE INTO changes (name, created_at, last_active)
|
|
38
|
-
VALUES (?, ?, ?)
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
VALUES (?, ?, ?)`
|
|
39
|
+
).run(changeName, now, now);
|
|
40
|
+
// 只在真正创建 change 时标脏(D-013/task-04);已存在行不标(读路径/重复调用不误判脏)
|
|
41
|
+
if (ins.changes > 0) this.pm._touchLocalModified(cwd, changeName, now);
|
|
41
42
|
// 注意:不复活已归档的变更——归档是不可逆操作
|
|
42
43
|
// 如果变更已存在且为 archived,保持 archived 状态不变
|
|
43
44
|
});
|
|
@@ -49,15 +50,18 @@ export class ChangeRegistry {
|
|
|
49
50
|
* @param {string} changeName - 变更名
|
|
50
51
|
* @param {{ status: string, mode?: string, reason?: string }} isolation
|
|
51
52
|
*/
|
|
52
|
-
|
|
53
|
-
const db =
|
|
54
|
-
const sqlDb = db.getDb();
|
|
53
|
+
updateChangeIsolation(cwd, changeName, isolation) {
|
|
54
|
+
const db = this.pm._ensureDB(cwd);
|
|
55
55
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
db.transaction(() => {
|
|
57
|
+
const sqlDb = db.getDb();
|
|
58
|
+
const now = new Date().toISOString();
|
|
59
|
+
sqlDb.prepare(
|
|
60
|
+
`UPDATE changes SET isolation_status = ?, isolation_mode = ?, isolation_reason = ?, last_active = ? WHERE name = ?`
|
|
61
|
+
).run(isolation.status, isolation.mode || null, isolation.reason || null, now, changeName);
|
|
62
|
+
// 本地脏度(D-013 / task-04):隔离状态变更也是本地推进
|
|
63
|
+
this.pm._touchLocalModified(cwd, changeName, now);
|
|
64
|
+
});
|
|
61
65
|
} catch (err) {
|
|
62
66
|
console.warn('⚠️ 更新 isolation 状态失败:', err.message);
|
|
63
67
|
}
|
|
@@ -69,56 +73,57 @@ export class ChangeRegistry {
|
|
|
69
73
|
* @param {string} changeName - 变更名
|
|
70
74
|
* @returns {{ status: string|null, mode: string|null, reason: string|null }|null}
|
|
71
75
|
*/
|
|
72
|
-
|
|
73
|
-
const db =
|
|
76
|
+
readChangeIsolation(cwd, changeName) {
|
|
77
|
+
const db = this.pm._ensureDB(cwd);
|
|
74
78
|
const sqlDb = db.getDb();
|
|
75
79
|
try {
|
|
76
|
-
const
|
|
77
|
-
`SELECT isolation_status, isolation_mode, isolation_reason FROM changes WHERE name =
|
|
78
|
-
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
const [status, mode, reason] = rows[0].values[0];
|
|
80
|
+
const row = sqlDb.prepare(
|
|
81
|
+
`SELECT isolation_status, isolation_mode, isolation_reason FROM changes WHERE name = ?`
|
|
82
|
+
).get(changeName);
|
|
83
|
+
if (row === undefined) return null;
|
|
84
|
+
const { isolation_status: status, isolation_mode: mode, isolation_reason: reason } = row;
|
|
82
85
|
return { status: status || null, mode: mode || null, reason: reason || null };
|
|
83
86
|
} catch {
|
|
84
87
|
return null;
|
|
85
88
|
}
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
|
|
91
|
+
_updatePlatformLastSync(cwd, changeName) {
|
|
89
92
|
if (!changeName) return;
|
|
90
|
-
const db =
|
|
91
|
-
db.transaction((
|
|
92
|
-
sqlDb.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
);
|
|
93
|
+
const db = this.pm._ensureDB(cwd);
|
|
94
|
+
db.transaction(() => {
|
|
95
|
+
const sqlDb = db.getDb();
|
|
96
|
+
sqlDb.prepare(
|
|
97
|
+
'UPDATE changes SET platform_last_sync = ?, platform_sync_enabled = 1 WHERE name = ?'
|
|
98
|
+
).run(new Date().toISOString(), changeName);
|
|
96
99
|
});
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
|
|
102
|
+
_updateApprovalStatus(cwd, changeName, status, reason = null) {
|
|
100
103
|
if (!changeName || !status) return;
|
|
101
|
-
const db =
|
|
102
|
-
db.transaction((
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
const db = this.pm._ensureDB(cwd);
|
|
105
|
+
db.transaction(() => {
|
|
106
|
+
const sqlDb = db.getDb();
|
|
107
|
+
const row = sqlDb.prepare('SELECT id FROM changes WHERE name = ?').get(changeName);
|
|
108
|
+
if (row === undefined) return;
|
|
109
|
+
const changeId = row.id;
|
|
106
110
|
const now = new Date().toISOString();
|
|
107
|
-
sqlDb.
|
|
111
|
+
sqlDb.prepare(
|
|
108
112
|
`INSERT INTO approvals (change_id, status, requested_at, approved_at, rejection_reason)
|
|
109
113
|
VALUES (?, ?, ?, ?, ?)
|
|
110
114
|
ON CONFLICT(change_id) DO UPDATE SET
|
|
111
115
|
status = excluded.status,
|
|
112
116
|
approved_at = excluded.approved_at,
|
|
113
|
-
rejection_reason = excluded.rejection_reason
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
]
|
|
117
|
+
rejection_reason = excluded.rejection_reason`
|
|
118
|
+
).run(
|
|
119
|
+
changeId,
|
|
120
|
+
status,
|
|
121
|
+
now,
|
|
122
|
+
status === 'approved' ? now : null,
|
|
123
|
+
status === 'rejected' ? reason : null,
|
|
121
124
|
);
|
|
125
|
+
// 本地脏度(D-013 / task-04):审批状态变更也是本地推进
|
|
126
|
+
this.pm._touchLocalModified(cwd, changeName, now);
|
|
122
127
|
});
|
|
123
128
|
}
|
|
124
129
|
|
|
@@ -128,7 +133,7 @@ export class ChangeRegistry {
|
|
|
128
133
|
* @param {string} oldName - 旧变更名
|
|
129
134
|
* @param {string} newName - 新变更名
|
|
130
135
|
*/
|
|
131
|
-
|
|
136
|
+
renameChange(cwd, oldName, newName) {
|
|
132
137
|
if (!oldName || !newName) {
|
|
133
138
|
console.warn('⚠️ renameChange: 旧名或新名为空,跳过');
|
|
134
139
|
return;
|
|
@@ -137,21 +142,23 @@ export class ChangeRegistry {
|
|
|
137
142
|
console.warn('⚠️ renameChange: 新旧名称相同,跳过');
|
|
138
143
|
return;
|
|
139
144
|
}
|
|
140
|
-
const db =
|
|
145
|
+
const db = this.pm._ensureDB(cwd);
|
|
141
146
|
// 检查旧名是否存在
|
|
142
|
-
const existing = db.transaction((
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
const existing = db.transaction(() => {
|
|
148
|
+
const sqlDb = db.getDb();
|
|
149
|
+
const row = sqlDb.prepare(`SELECT name, status FROM changes WHERE name = ?`).get(oldName);
|
|
150
|
+
if (row === undefined) return null;
|
|
151
|
+
return { name: row.name, status: row.status };
|
|
146
152
|
});
|
|
147
153
|
if (!existing) {
|
|
148
154
|
console.error(`❌ 变更 ${oldName} 不存在`);
|
|
149
155
|
return;
|
|
150
156
|
}
|
|
151
157
|
// 检查新名是否已存在
|
|
152
|
-
const conflict = db.transaction((
|
|
153
|
-
const
|
|
154
|
-
|
|
158
|
+
const conflict = db.transaction(() => {
|
|
159
|
+
const sqlDb = db.getDb();
|
|
160
|
+
const row = sqlDb.prepare(`SELECT name FROM changes WHERE name = ?`).get(newName);
|
|
161
|
+
return row !== undefined;
|
|
155
162
|
});
|
|
156
163
|
if (conflict) {
|
|
157
164
|
console.error(`❌ 变更 ${newName} 已存在`);
|
|
@@ -164,8 +171,11 @@ export class ChangeRegistry {
|
|
|
164
171
|
const newDir = this.pm._changePath(cwd, newName);
|
|
165
172
|
const now = new Date().toISOString();
|
|
166
173
|
try {
|
|
167
|
-
db.transaction((
|
|
168
|
-
sqlDb
|
|
174
|
+
db.transaction(() => {
|
|
175
|
+
const sqlDb = db.getDb();
|
|
176
|
+
sqlDb.prepare(`UPDATE changes SET name = ?, last_active = ? WHERE name = ?`).run(newName, now, oldName);
|
|
177
|
+
// 本地脏度(D-013 / task-04):重命名也是本地推进(标新名)
|
|
178
|
+
this.pm._touchLocalModified(cwd, newName, now);
|
|
169
179
|
});
|
|
170
180
|
} catch (e) {
|
|
171
181
|
console.error(`❌ 重命名失败:更新数据库时出错(${e.message})`);
|
|
@@ -179,8 +189,11 @@ export class ChangeRegistry {
|
|
|
179
189
|
renamed = false;
|
|
180
190
|
// FS 重命名失败:回滚 DB 恢复 oldName,保持 DB 与目录一致
|
|
181
191
|
try {
|
|
182
|
-
db.transaction((
|
|
183
|
-
sqlDb
|
|
192
|
+
db.transaction(() => {
|
|
193
|
+
const sqlDb = db.getDb();
|
|
194
|
+
sqlDb.prepare(`UPDATE changes SET name = ?, last_active = ? WHERE name = ?`).run(oldName, now, newName);
|
|
195
|
+
// 回滚也是写(恢复 oldName),对称标脏旧名
|
|
196
|
+
this.pm._touchLocalModified(cwd, oldName, now);
|
|
184
197
|
});
|
|
185
198
|
console.error(`❌ 重命名失败:移动目录出错(${e.message}),已回滚数据库`);
|
|
186
199
|
} catch (rollbackErr) {
|
|
@@ -199,18 +212,20 @@ export class ChangeRegistry {
|
|
|
199
212
|
* 从活跃列表移除变更(归档时调用,不物理删除)
|
|
200
213
|
* SQL: UPDATE changes SET status = 'archived'
|
|
201
214
|
*/
|
|
202
|
-
|
|
215
|
+
unregisterChange(cwd, changeName) {
|
|
203
216
|
if (!changeName) {
|
|
204
217
|
console.warn('⚠️ unregisterChange: changeName 为空,跳过');
|
|
205
218
|
return;
|
|
206
219
|
}
|
|
207
|
-
const db =
|
|
208
|
-
db.transaction((
|
|
220
|
+
const db = this.pm._ensureDB(cwd);
|
|
221
|
+
db.transaction(() => {
|
|
222
|
+
const sqlDb = db.getDb();
|
|
209
223
|
const now = new Date().toISOString();
|
|
210
|
-
sqlDb.
|
|
211
|
-
`UPDATE changes SET status = 'archived', last_active = ? WHERE name =
|
|
212
|
-
|
|
213
|
-
|
|
224
|
+
sqlDb.prepare(
|
|
225
|
+
`UPDATE changes SET status = 'archived', last_active = ? WHERE name = ?`
|
|
226
|
+
).run(now, changeName);
|
|
227
|
+
// 本地脏度(D-013 / task-04):归档也是本地状态推进
|
|
228
|
+
this.pm._touchLocalModified(cwd, changeName, now);
|
|
214
229
|
});
|
|
215
230
|
}
|
|
216
231
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// W6 Step9a: Revision v1 状态一致性检查 + 修复 + --force 审计日志。
|
|
2
2
|
// 从 src/progress.js 单体 ProgressManager 抽出。持有 pm 引用(构造注入),调 pm.read / pm._write /
|
|
3
3
|
// pm._ensureRuntimeDir / pm._runtimePath(persistence-core 留 facade 本体)。
|
|
4
|
-
import { appendFileSync } from 'fs';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { appendFileSync, existsSync, readdirSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { summarizeTaskCompletion } from '../task-review.js';
|
|
7
|
+
import { STAGE_ORDER, MAIN_FLOW_ORDER } from './shared.js';
|
|
7
8
|
|
|
8
9
|
export class ConsistencyDoctor {
|
|
9
10
|
constructor(pm) {
|
|
@@ -32,8 +33,8 @@ export class ConsistencyDoctor {
|
|
|
32
33
|
* @param {string|null} changeName
|
|
33
34
|
* @returns {{ ok: boolean, issues: string[], warnings: string[] }}
|
|
34
35
|
*/
|
|
35
|
-
|
|
36
|
-
const data =
|
|
36
|
+
checkConsistency(cwd, changeName = null) {
|
|
37
|
+
const data = this.pm.read(cwd, changeName);
|
|
37
38
|
if (!data) {
|
|
38
39
|
return { ok: false, issues: ['无法读取进度数据'], warnings: [] };
|
|
39
40
|
}
|
|
@@ -71,9 +72,11 @@ export class ConsistencyDoctor {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
// d. 下游 completed 不能出现在上游 stale/revising 之后
|
|
74
|
-
|
|
75
|
+
// 用 MAIN_FLOW_ORDER(不含 scan)算上下游:scan 是 auxiliary 永不要求 completed,
|
|
76
|
+
// 把 scan 当上游会让「scan stale/revising」误报 brainstorm 不该 completed(plan-c 同根因)。
|
|
77
|
+
const stageIdx = MAIN_FLOW_ORDER.indexOf(stageName);
|
|
75
78
|
for (let i = 0; i < stageIdx; i++) {
|
|
76
|
-
const upstream =
|
|
79
|
+
const upstream = MAIN_FLOW_ORDER[i];
|
|
77
80
|
const upData = data.stages[upstream];
|
|
78
81
|
if (upData && (upData.status === 'stale' || upData.status === 'revising')) {
|
|
79
82
|
if (sd.status === 'completed') {
|
|
@@ -91,6 +94,12 @@ export class ConsistencyDoctor {
|
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
// FR-07 / AC-03 / design §7 / D-02:lost-update 间接信号
|
|
98
|
+
// (.runtime/worktrees/<change> 目录残留 vs DB current_stage≠execute)。
|
|
99
|
+
// 只读诊断并入 issues 报告,不写 DB / 不删 worktree / 不自动修复。
|
|
100
|
+
const lostUpdateSignals = this.detectLostUpdateSignals(cwd);
|
|
101
|
+
issues.push(...lostUpdateSignals);
|
|
102
|
+
|
|
94
103
|
// 输出报告
|
|
95
104
|
console.log('');
|
|
96
105
|
console.log(' ═══════════════════════════════════════');
|
|
@@ -114,6 +123,55 @@ export class ConsistencyDoctor {
|
|
|
114
123
|
return { ok: issues.length === 0, issues, warnings };
|
|
115
124
|
}
|
|
116
125
|
|
|
126
|
+
/**
|
|
127
|
+
* 检测 lost-update 间接信号(FR-07 / AC-03 / design §7 / D-02)。
|
|
128
|
+
*
|
|
129
|
+
* 判据:.runtime/worktrees/<change> 目录存在但 DB current_stage ≠ 'execute'
|
|
130
|
+
* → worktree 残留但进度被回退,是并发场景下 lost-update 的间接痕迹。
|
|
131
|
+
*
|
|
132
|
+
* 信号定义严格遵循 design §7 / FR-07:仅 current_stage !== 'execute' 判信号,
|
|
133
|
+
* 不扩展到 quick 等其它 stage;DB 无对应行(data=null)的 worktree 目录不算信号。
|
|
134
|
+
*
|
|
135
|
+
* 只读诊断:不写 DB、不删 worktree 目录、不自动修复
|
|
136
|
+
* (修复仍走 doctor --align / repairConsistency 既有逻辑)。
|
|
137
|
+
*
|
|
138
|
+
* @param {string} cwd
|
|
139
|
+
* @returns {string[]} issue 描述数组(每条含 change 名、实际 current_stage、worktree 目录路径)
|
|
140
|
+
*/
|
|
141
|
+
detectLostUpdateSignals(cwd) {
|
|
142
|
+
const worktreesRoot = this.pm._runtimePath(cwd, 'worktrees');
|
|
143
|
+
// 零信号兼容既有 fixture:worktrees 目录不存在或读取失败时直接返回空数组
|
|
144
|
+
if (!existsSync(worktreesRoot)) return [];
|
|
145
|
+
|
|
146
|
+
let entries;
|
|
147
|
+
try {
|
|
148
|
+
entries = readdirSync(worktreesRoot, { withFileTypes: true });
|
|
149
|
+
} catch {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const issues = [];
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
// Dirent.isDirectory 在 Windows / Linux / macOS 上一致判定目录项
|
|
156
|
+
if (!entry.isDirectory()) continue;
|
|
157
|
+
const changeName = entry.name;
|
|
158
|
+
const worktreeDir = join(worktreesRoot, changeName);
|
|
159
|
+
|
|
160
|
+
// 复用 pm.read(task-08 已同步化,直接拿返回值,无 await),不新开 DB 连接
|
|
161
|
+
const data = this.pm.read(cwd, changeName);
|
|
162
|
+
// DB 无对应行(data=null)不算信号,跳过
|
|
163
|
+
if (!data) continue;
|
|
164
|
+
// 仅 current_stage !== 'execute' 判信号
|
|
165
|
+
if (data.currentStage !== 'execute') {
|
|
166
|
+
issues.push(
|
|
167
|
+
`lost-update 信号: change "${changeName}" 的 worktree 目录仍存在(${worktreeDir}),` +
|
|
168
|
+
`但 current_stage 为 "${data.currentStage}"(非 execute,疑进度被回退)`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return issues;
|
|
173
|
+
}
|
|
174
|
+
|
|
117
175
|
/**
|
|
118
176
|
* Revision v1.2 状态修复
|
|
119
177
|
* 默认 dry-run,--apply 才真正修改 DB。
|
|
@@ -125,10 +183,10 @@ export class ConsistencyDoctor {
|
|
|
125
183
|
* @param {string|null} [opts.changeName]
|
|
126
184
|
* @returns {{ fixable: object[], manual: string[], applied: object[] }}
|
|
127
185
|
*/
|
|
128
|
-
|
|
186
|
+
repairConsistency(cwd, opts = {}) {
|
|
129
187
|
const { apply = false, changeName = null } = opts;
|
|
130
188
|
|
|
131
|
-
const data =
|
|
189
|
+
const data = this.pm.read(cwd, changeName);
|
|
132
190
|
if (!data) {
|
|
133
191
|
console.log('❌ 无法读取进度数据');
|
|
134
192
|
return { fixable: [], manual: ['无法读取进度数据'], applied: [] };
|
|
@@ -156,10 +214,10 @@ export class ConsistencyDoctor {
|
|
|
156
214
|
});
|
|
157
215
|
}
|
|
158
216
|
|
|
159
|
-
// Fix b: 上游 stale/revising,下游仍 completed → cascade stale
|
|
160
|
-
const stageIdx =
|
|
217
|
+
// Fix b: 上游 stale/revising,下游仍 completed → cascade stale(同 d,用 MAIN_FLOW_ORDER 排除 scan)
|
|
218
|
+
const stageIdx = MAIN_FLOW_ORDER.indexOf(stageName);
|
|
161
219
|
for (let i = 0; i < stageIdx; i++) {
|
|
162
|
-
const upstream =
|
|
220
|
+
const upstream = MAIN_FLOW_ORDER[i];
|
|
163
221
|
const upData = data.stages[upstream];
|
|
164
222
|
if (upData && (upData.status === 'stale' || upData.status === 'revising')) {
|
|
165
223
|
if (sd.status === 'completed') {
|
|
@@ -206,7 +264,6 @@ export class ConsistencyDoctor {
|
|
|
206
264
|
const changeDir = this.pm._changePath(cwd, changeName);
|
|
207
265
|
if (changeDir && existsSync(changeDir)) {
|
|
208
266
|
const runtimeRoot = this.pm._runtimePath(cwd);
|
|
209
|
-
const { summarizeTaskCompletion } = await import('../task-review.js');
|
|
210
267
|
const summary = summarizeTaskCompletion({ changeDir, runtimeRoot, changeName });
|
|
211
268
|
if (summary.source === 'review.json' && summary.total > 0 && summary.pending.length === 0) {
|
|
212
269
|
const desc = `execute: ${badSteps.length} 个 step 状态脱钩(${badSteps.map(st => st.name).join(', ')})——review.json 客观产出全通过(${summary.completed}/${summary.total}),自动标 completed`;
|
|
@@ -286,7 +343,7 @@ export class ConsistencyDoctor {
|
|
|
286
343
|
|
|
287
344
|
if (apply && applied.length > 0) {
|
|
288
345
|
data.lastActive = now;
|
|
289
|
-
|
|
346
|
+
this.pm._write(cwd, data, changeName);
|
|
290
347
|
console.log(`\n ✅ 已修复 ${applied.length} 项`);
|
|
291
348
|
}
|
|
292
349
|
|
package/src/progress/shared.js
CHANGED
|
@@ -18,13 +18,16 @@ export const STAGE_LABELS = {
|
|
|
18
18
|
archive: '📦 归档变更',
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// 完整阶段顺序(含 scan),仅用于展示/迭代顺序
|
|
22
22
|
export const STAGE_ORDER = ['scan', 'brainstorm', 'plan', 'execute', 'verify', 'archive'];
|
|
23
|
-
// 主流程阶段(不含 scan/quick/explore 等辅助阶段)——
|
|
24
|
-
|
|
23
|
+
// 主流程阶段(不含 scan/quick/explore 等辅助阶段)—— 用于下游 cascade / 一致性上下游判定。
|
|
24
|
+
// scan 是 auxiliary(AUXILIARY_STAGES,按需显式跑、可 failed_post_check、永不要求 completed),
|
|
25
|
+
// 把它算进主流程上下游会让「scan stale/revising」误报 brainstorm/plan/execute 不该 completed
|
|
26
|
+
// (consistency-doctor 坑 + prompt-control-debt plan-c 同根因)。
|
|
27
|
+
export const MAIN_FLOW_ORDER = ['brainstorm', 'plan', 'execute', 'verify', 'archive'];
|
|
25
28
|
|
|
26
|
-
// progress 数据版本(v3 = SQLite)
|
|
27
|
-
export const CURRENT_VERSION =
|
|
29
|
+
// progress 数据版本(v3 = SQLite;v4 = changes 加 base_ts / 本地脏度两列,platform-sync §8)
|
|
30
|
+
export const CURRENT_VERSION = 4;
|
|
28
31
|
|
|
29
32
|
// 空阶段骨架
|
|
30
33
|
export function emptyStage() {
|