gsd-lite 0.5.15 → 0.6.1

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.
@@ -13,7 +13,7 @@
13
13
  "name": "gsd",
14
14
  "source": "./",
15
15
  "description": "AI orchestration tool — GSD management shell + Superpowers quality core. 5 commands, 4 agents, 5 workflows, MCP server, context monitoring.",
16
- "version": "0.5.15",
16
+ "version": "0.6.1",
17
17
  "keywords": [
18
18
  "orchestration",
19
19
  "mcp",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd",
3
- "version": "0.5.15",
3
+ "version": "0.6.1",
4
4
  "description": "AI orchestration tool for Claude Code — GSD management shell + Superpowers quality core",
5
5
  "author": {
6
6
  "name": "sdsrss",
package/README.md CHANGED
@@ -213,8 +213,8 @@ All state lives in `.gsd/state.json` — a single source of truth with:
213
213
  |-----------|-----|----------|
214
214
  | Commands | 32 | **6** |
215
215
  | Agents | 12 | **4** |
216
- | Source files | 100+ | **~35** |
217
- | Installer | 2465 lines | **~80 lines** |
216
+ | Source files | 100+ | **~48** |
217
+ | Installer | 2465 lines | **~290 lines** |
218
218
  | User interactions | 6+ confirmations | **Typically 2** |
219
219
  | TDD / Anti-rationalization | No | **Yes** |
220
220
  | State machine recovery | Partial | **Full (12 modes)** |
@@ -249,7 +249,7 @@ gsd-lite/
249
249
  ├── references/ # 8 reference docs
250
250
  ├── hooks/ # Session lifecycle (StatusLine + PostToolUse + SessionStart + Stop + AutoUpdate)
251
251
  │ └── lib/ # Shared hook utilities (gsd-finder)
252
- ├── tests/ # 826 tests (unit + simulation + E2E)
252
+ ├── tests/ # 866 tests (unit + simulation + E2E)
253
253
  ├── cli.js # Install/uninstall CLI entry
254
254
  ├── install.js # Installation script
255
255
  └── uninstall.js # Uninstall script
@@ -258,7 +258,7 @@ gsd-lite/
258
258
  ## Testing
259
259
 
260
260
  ```bash
261
- npm test # Run all 826 tests
261
+ npm test # Run all 866 tests
262
262
  npm run test:coverage # Tests + coverage report (94%+ lines, 83%+ branches)
263
263
  npm run lint # Biome lint
264
264
  node --test tests/file.js # Run a single test file
@@ -55,8 +55,11 @@ Phase 3 假设测试:
55
55
  3. 验证: 有效 → Phase 4 / 无效 → 新假设
56
56
 
57
57
  Phase 4 修复方向建议:
58
- 1. 提出修复方案 (针对根因,不是症状)
59
- 2. 建议失败测试用例 ( executor 实现)
58
+ 调试器不直接写代码 返回 fix_direction + 测试用例描述,由 executor 实施。
59
+ 1. 提出修复方案 (针对根因,不是症状) 写入 `fix_direction`
60
+ 2. 描述回归测试用例 (测什么、预期 vs 实际) → 供 executor 实现
61
+ - 不要自己写测试代码,你没有 Write 工具
62
+ - 用自然语言描述: 输入、操作步骤、预期结果、实际错误行为
60
63
  3. 评估修复影响范围 (哪些下游可能受影响)
61
64
  → 3 次修复方向均被 executor 验证无效 → 停止。标记 architecture_concern: true。报告给编排器。
62
65
  </four_phases>
@@ -66,6 +69,10 @@ Phase 4 修复方向建议:
66
69
  {
67
70
  "task_id": "2.3",
68
71
  "outcome": "root_cause_found | fix_suggested | failed",
72
+ // outcome 判定:
73
+ // root_cause_found — 根因已确认,有明确的修复方向
74
+ // fix_suggested — 部分理解,建议一个尝试方向 (根因尚未完全确认)
75
+ // failed — 穷尽假设仍无法定位根因,或 fix_attempts >= 3
69
76
  "root_cause": "Description of the identified root cause",
70
77
  "evidence": [
71
78
  { "id": "ev:repro:error-xyz", "scope": "task:2.3", "command": "npm test", "exit_code": 1, "stdout": "...", "stderr": "...", "timestamp": "ISO8601" }
@@ -73,8 +80,8 @@ Phase 4 修复方向建议:
73
80
  "hypothesis_tested": [
74
81
  { "hypothesis": "X causes Y", "result": "confirmed | rejected", "evidence": "non-empty string (required)" }
75
82
  ],
76
- "fix_direction": "Suggested fix approach for executor",
77
- "fix_attempts": 0,
83
+ "fix_direction": "Suggested fix approach for executor (include suggested test case description)",
84
+ "fix_attempts": 0, // 由编排器 dispatch context 中的 debug_context 传入,记录已尝试的修复方向次数
78
85
  "blockers": [],
79
86
  "architecture_concern": false
80
87
  }
@@ -51,6 +51,14 @@ L1 普通编码任务 → executor 自审 + 阶段结束时批量 review
51
51
  L2 关键任务 → 单任务独立 review
52
52
  (涉及认证/支付/数据安全/核心架构的任务)
53
53
 
54
+ L3 最高风险任务 → 单任务独立 review + 人工确认
55
+ (auth/payment/security architecture 等最高风险任务)
56
+ - 与 L2 相同的双阶段审查流程,外加:
57
+ - 结果中必须包含 `requires_human_confirmation: true`
58
+ - review summary 必须明确列出安全影响 (security implications)
59
+ - 质量审查阶段必须检查 OWASP Top 10 相关问题
60
+ - 审查通过后 task 进入 `awaiting_user` 而非 `accepted`,需用户显式确认
61
+
54
62
  判定规则按影响面,不按关键词猜测:
55
63
  - 改 auth/payment/permission/public API/DB migration/core architecture → L2
56
64
  - 纯 docs/comment/style/config 且无运行时语义变化 → L0
@@ -107,7 +115,9 @@ Minor = 建议修复 (命名/风格)
107
115
  {
108
116
  "scope": "task | phase",
109
117
  "scope_id": "2.3 (task scope: string ID) | 2 (phase scope: number ID)",
110
- "review_level": "L2 | L1-batch | L1",
118
+ "review_level": "L2 | L3 | L1-batch | L1",
119
+ "requires_human_confirmation": false, // L3 时必须为 true
120
+ "security_implications": [], // L3 时必须列出安全影响
111
121
  "spec_passed": true,
112
122
  "quality_passed": false,
113
123
  "critical_issues": [
@@ -141,4 +151,5 @@ checkpoint commit ≠ accepted
141
151
  L0: checkpoint commit = accepted
142
152
  L1: checkpoint commit → phase batch review 通过 → accepted
143
153
  L2: checkpoint commit → immediate independent review 通过 → accepted
154
+ L3: checkpoint commit → immediate independent review 通过 → awaiting_user → 用户确认 → accepted
144
155
  </checkpoint_topology>
@@ -102,4 +102,3 @@ Suggested Actions:
102
102
  - If a check cannot be performed (e.g., tool unavailable), report INFO rather than FAIL
103
103
  - Always show all 5 checks in the summary, even if some are INFO/skipped
104
104
  </rules>
105
- </output>
package/commands/prd.md CHANGED
@@ -17,13 +17,30 @@ argument-hint: File path to requirements doc, or inline description text
17
17
 
18
18
  <process>
19
19
 
20
+ ## STEP 0 — 已有项目检测
21
+
22
+ 调用 `health` 工具(MCP tool 名称: health)。如果返回 state_exists=true 且项目未完成/未失败:
23
+ - 告知用户: "检测到进行中的 GSD 项目。"
24
+ - 显示当前项目状态 (项目名、当前阶段、workflow_mode)
25
+ - 提供选项:
26
+ - (a) 恢复现有项目 → 转到 `/gsd:resume`
27
+ - (b) 覆盖并重新开始 → 继续 STEP 1(现有 state.json 将被覆盖)
28
+ - (c) 取消
29
+ - 等待用户选择后再继续
30
+
31
+ 如果无 state 或项目已完成/已失败 → 直接进入 STEP 1。
32
+
20
33
  ## STEP 1: 解析输入
21
34
 
22
35
  判断 `$ARGUMENTS` 的类型:
23
36
 
24
37
  **如果是文件路径** (包含 `/` 或 `.` 且文件存在):
25
38
  - 使用 Read 工具读取文件内容
26
- - 如果文件不存在 → 告知用户并停止
39
+ - 如果文件不存在:
40
+ - 告知用户文件不存在
41
+ - 提示常见原因: 路径拼写错误、当前工作目录不正确
42
+ - 建议: "请确认文件路径,或使用绝对路径重试。需要我帮你查找文件吗?"
43
+ - 停止
27
44
 
28
45
  **如果是文本描述**:
29
46
  - 直接作为需求描述使用
@@ -9,19 +9,20 @@ description: Resume project execution from saved state with workspace validation
9
9
 
10
10
  <process>
11
11
 
12
- ## STEP 1: 读取状态
12
+ ## STEP 1: 调用 orchestrator-resume 获取状态摘要
13
13
 
14
- 读取 `.gsd/state.json`:
15
- - 如果文件不存在 → 告知用户 "未找到 GSD 项目状态,请先运行 /gsd:start 或 /gsd:prd",停止
16
- - 如果文件损坏或解析失败告知用户并停止
14
+ 调用 MCP tool `orchestrator-resume`,使用响应中的 `summary` 字段展示状态给用户:
15
+ - 如果响应为 error 且 message 包含 "No .gsd directory" → 告知用户 "未找到 GSD 项目状态,请先运行 /gsd:start 或 /gsd:prd",停止
16
+ - 如果响应为 error 告知用户错误信息并停止
17
17
 
18
- 提取关键 canonical fields:
18
+ `summary` 字段包含:
19
19
  - `workflow_mode` — 当前工作流状态
20
- - `current_phase` / `current_task` 当前执行位置
21
- - `current_review` — 当前审查状态
22
- - `git_head` — 上次记录的 Git HEAD
23
- - `plan_version` — 计划版本号
24
- - `research.expires_at` — 研究过期时间
20
+ - `current_phase` 当前阶段 (格式: "N/M")
21
+ - `current_task` — 当前任务 (id + name)
22
+ - `phase_progress` — 阶段进度 (格式: "done/total")
23
+ - `recent_decisions` — 最近 2-3 个决策 (如有)
24
+
25
+ 注意: 不需要单独读取 state.json,`orchestrator-resume` 的响应已包含所有需要展示的信息。
25
26
 
26
27
  ## STEP 2: 前置校验
27
28
 
@@ -55,10 +56,15 @@ description: Resume project execution from saved state with workspace validation
55
56
  - 或 research.decision_index 中有条目的 expires_at 已过期
56
57
  - → 覆写 `workflow_mode = research_refresh_needed`
57
58
 
58
- 5. **全部通过:**
59
+ 5. **Dirty-phase 回滚检测:**
60
+ - 检查已完成 phase 中是否有 `needs_revalidation` 状态的 task
61
+ - 如有 → 回滚 `current_phase` 到最早的 dirty phase
62
+ - → 覆写 `workflow_mode = executing_task`
63
+
64
+ 6. **全部通过:**
59
65
  - 保持原 `workflow_mode` 不变
60
66
 
61
- 校验顺序: 1→2→3→4,首个命中的覆写生效 (不累积)
67
+ 校验顺序: 1→2→3→4→5,首个命中的覆写生效 (不累积)
62
68
  </HARD-GATE>
63
69
 
64
70
  ## STEP 3: 按 workflow_mode 恢复
@@ -106,8 +112,8 @@ description: Resume project execution from saved state with workspace validation
106
112
  ### `awaiting_clear` — 继续自动执行
107
113
 
108
114
  - 上下文已通过 /clear 清理
109
- - 直接继续自动执行主路径
110
- - `current_phase` + `current_task` 恢复调度
115
+ - 再次验证上下文健康度 ≥ 40%,不足则要求再次 /clear
116
+ - 验证通过后从 `current_phase` + `current_task` 恢复调度
111
117
 
112
118
  ---
113
119
 
@@ -208,17 +214,17 @@ description: Resume project execution from saved state with workspace validation
208
214
 
209
215
  ## STEP 4: 显示当前进度 + 下一动作
210
216
 
211
- 每次恢复后都展示简要进度面板:
217
+ 每次恢复后使用 `orchestrator-resume` 响应中的 `summary` 字段展示简要进度面板:
212
218
 
213
219
  ```
214
- 项目: {project}
215
- 模式: {workflow_mode}
216
- 进度: Phase {current_phase}/{total_phases} | Task {done}/{tasks}
217
- 当前: {current_task} — {task_name}
218
- 下一步: {根据 workflow_mode 推导的下一动作}
220
+ 模式: {summary.workflow_mode}
221
+ 进度: Phase {summary.current_phase} | Task {summary.phase_progress}
222
+ 当前: {summary.current_task.id} {summary.current_task.name}
223
+ 决策: {summary.recent_decisions (如有)}
224
+ 下一步: {根据 action 推导的下一动作}
219
225
  ```
220
226
 
221
- 所有展示数据从 canonical fields 实时推导,不使用 derived fields
227
+ 注意: 所有展示数据直接取自 `summary` 字段,不需要额外读取 state.json
222
228
 
223
229
  ## STEP 5: 自动执行循环
224
230
 
package/commands/start.md CHANGED
@@ -58,3 +58,11 @@ argument-hint: Optional feature or project description
58
58
  使用 Read 工具读取 `workflows/execution-flow.md`,严格按照其中 STEP 5-12 执行。
59
59
 
60
60
  </process>
61
+
62
+ <EXTREMELY-IMPORTANT>
63
+ ## 编排器纪律
64
+ - 只有编排器写 state.json,子代理不直接写
65
+ - 所有摘要/提示在展示时从 canonical fields 推导,不持久化 derived fields
66
+ - 子代理返回结构化 JSON,不解析自然语言
67
+ - 上下文 < 35% → 保存状态 + workflow_mode = awaiting_clear + 停止执行
68
+ </EXTREMELY-IMPORTANT>
@@ -7,6 +7,7 @@ const fs = require('node:fs');
7
7
  const path = require('node:path');
8
8
  const os = require('node:os');
9
9
  const { execSync, spawnSync } = require('node:child_process');
10
+ const { semverSortComparator } = require('./lib/semver-sort.cjs');
10
11
 
11
12
  // ── Configuration ──────────────────────────────────────────
12
13
  const GITHUB_REPO = 'sdsrss/gsd-lite';
@@ -195,12 +196,13 @@ async function withFileLock(fn) {
195
196
  }
196
197
  }
197
198
 
199
+ if (!acquired) {
200
+ return null;
201
+ }
198
202
  try {
199
203
  return await fn();
200
204
  } finally {
201
- if (acquired) {
202
- try { fs.rmSync(STATE_LOCK_FILE, { force: true }); } catch {}
203
- }
205
+ try { fs.rmSync(STATE_LOCK_FILE, { force: true }); } catch {}
204
206
  }
205
207
  }
206
208
 
@@ -209,7 +211,7 @@ async function withFileLock(fn) {
209
211
  // 1. PLUGIN_AUTO_UPDATE env set → recursive guard (auto-update already in progress)
210
212
  // 2. Running from a git clone → dev mode (developer working on source)
211
213
  function shouldSkipUpdateCheck() {
212
- if (process.env.PLUGIN_AUTO_UPDATE) return true;
214
+ if (process.env.PLUGIN_AUTO_UPDATE === '1') return true;
213
215
  return isDevMode();
214
216
  }
215
217
 
@@ -297,15 +299,8 @@ async function fetchLatestRelease(token) {
297
299
  }
298
300
 
299
301
  // ── Version Comparison (semver) ────────────────────────────
300
- function compareVersions(a, b) {
301
- const pa = a.split('.').map(Number);
302
- const pb = b.split('.').map(Number);
303
- for (let i = 0; i < 3; i++) {
304
- if ((pa[i] || 0) > (pb[i] || 0)) return 1;
305
- if ((pa[i] || 0) < (pb[i] || 0)) return -1;
306
- }
307
- return 0;
308
- }
302
+ // Reuse shared comparator; callers only check sign (> 0, < 0, === 0)
303
+ const compareVersions = semverSortComparator;
309
304
 
310
305
  function getCurrentVersion(mode = getInstallMode()) {
311
306
  const candidates = mode === 'manual'
@@ -460,18 +455,25 @@ function pruneOldCacheVersions(cacheBase, keepCount = 3, verbose = false) {
460
455
  .map(e => e.name);
461
456
  if (entries.length <= keepCount) return;
462
457
 
463
- // Sort by semver: split into [major, minor, patch] and compare numerically
464
- const sorted = entries.slice().sort((a, b) => {
465
- const pa = a.split('.').map(Number);
466
- const pb = b.split('.').map(Number);
467
- for (let i = 0; i < 3; i++) {
468
- if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
469
- }
470
- return 0;
471
- });
458
+ // Sort by semver using shared comparator
459
+ const sorted = entries.slice().sort(semverSortComparator);
460
+
461
+ // Detect versions with active processes to avoid disrupting running sessions
462
+ let activeVersions;
463
+ try {
464
+ const psOutput = spawnSync('ps', ['aux'], { stdio: 'pipe', timeout: 5000 });
465
+ const lines = (psOutput.stdout || '').toString();
466
+ activeVersions = new Set(
467
+ entries.filter(ver => lines.includes(`/cache/gsd/gsd/${ver}/`))
468
+ );
469
+ } catch { activeVersions = new Set(); }
472
470
 
473
471
  const toRemove = sorted.slice(0, sorted.length - keepCount);
474
472
  for (const ver of toRemove) {
473
+ if (activeVersions.has(ver)) {
474
+ if (verbose) console.log(` Skipped ${ver} (active process detected)`);
475
+ continue;
476
+ }
475
477
  const verPath = path.join(cacheBase, ver);
476
478
  fs.rmSync(verPath, { recursive: true, force: true });
477
479
  if (verbose) console.log(` Pruned old cache: ${ver}`);
@@ -112,7 +112,7 @@ process.stdin.on('end', () => {
112
112
  if (isCritical) {
113
113
  message = `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. `
114
114
  + 'Context is nearly exhausted. Complete current task checkpoint immediately, '
115
- + 'set workflow_mode = awaiting_clear via gsd-state-update, and tell user to /clear then /gsd:resume.';
115
+ + 'set workflow_mode = awaiting_clear via state-update, and tell user to /clear then /gsd:resume.';
116
116
  } else {
117
117
  message = `CONTEXT WARNING: Usage at ${usedPct}%. Remaining: ${remaining}%. `
118
118
  + 'Context is getting limited. Avoid starting new complex work. Complete current task then save state.';
@@ -9,26 +9,42 @@
9
9
  const fs = require('node:fs');
10
10
  const path = require('node:path');
11
11
 
12
+ const _findCache = new Map();
13
+
12
14
  /**
13
15
  * Walk from startDir up to filesystem root looking for a .gsd directory
14
16
  * that contains state.json. Returns the absolute path to .gsd or null.
17
+ * Results are cached per startDir (positive hits only — null is not cached
18
+ * so that a later-created .gsd directory can be discovered).
15
19
  */
16
20
  function findGsdDir(startDir) {
21
+ if (_findCache.has(startDir)) return _findCache.get(startDir);
22
+
17
23
  let dir = startDir;
18
24
  while (true) {
19
25
  const candidate = path.join(dir, '.gsd');
20
26
  try {
21
27
  if (fs.statSync(candidate).isDirectory()) {
22
28
  // Only return if state.json exists (not just an empty .gsd dir)
23
- if (fs.existsSync(path.join(candidate, 'state.json'))) return candidate;
29
+ if (fs.existsSync(path.join(candidate, 'state.json'))) {
30
+ _findCache.set(startDir, candidate);
31
+ return candidate;
32
+ }
24
33
  }
25
34
  } catch { /* skip */ }
26
35
  const parent = path.dirname(dir);
27
- if (parent === dir) return null;
36
+ if (parent === dir) return null; // Don't cache negative results
28
37
  dir = parent;
29
38
  }
30
39
  }
31
40
 
41
+ /**
42
+ * Clear the findGsdDir result cache. Useful for testing.
43
+ */
44
+ function clearFindGsdDirCache() {
45
+ _findCache.clear();
46
+ }
47
+
32
48
  /**
33
49
  * Read and parse .gsd/state.json. Returns parsed object or null on any failure.
34
50
  */
@@ -81,4 +97,4 @@ function getProgress(state) {
81
97
  };
82
98
  }
83
99
 
84
- module.exports = { findGsdDir, readState, getProgress };
100
+ module.exports = { findGsdDir, clearFindGsdDirCache, readState, getProgress };
@@ -0,0 +1,20 @@
1
+ // Shared semver sort comparator for use by install.js and gsd-auto-update.cjs
2
+ 'use strict';
3
+
4
+ /**
5
+ * Compare two semver version strings (e.g. "1.2.3") for sorting.
6
+ * Returns negative if a < b, positive if a > b, 0 if equal.
7
+ * @param {string} a
8
+ * @param {string} b
9
+ * @returns {number}
10
+ */
11
+ function semverSortComparator(a, b) {
12
+ const pa = a.split('.').map(Number);
13
+ const pb = b.split('.').map(Number);
14
+ for (let i = 0; i < 3; i++) {
15
+ if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
16
+ }
17
+ return 0;
18
+ }
19
+
20
+ module.exports = { semverSortComparator };
package/install.js CHANGED
@@ -6,8 +6,11 @@ import { join, dirname } from 'node:path';
6
6
  import { homedir } from 'node:os';
7
7
  import { fileURLToPath, pathToFileURL } from 'node:url';
8
8
  import { execSync } from 'node:child_process';
9
+ import { createRequire } from 'node:module';
9
10
 
10
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const _require = createRequire(import.meta.url);
13
+ const { semverSortComparator } = _require('./hooks/lib/semver-sort.cjs');
11
14
  const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
12
15
  const RUNTIME_DIR = join(CLAUDE_DIR, 'gsd');
13
16
  const DRY_RUN = process.argv.includes('--dry-run');
@@ -116,8 +119,23 @@ export function main() {
116
119
  }
117
120
 
118
121
  // Reset managed runtime directory to avoid stale files on reinstall
122
+ // Preserve runtime/ subdirectory (update-state.json, update-notification.json)
119
123
  if (!DRY_RUN && existsSync(RUNTIME_DIR)) {
124
+ const runtimeSubdir = join(RUNTIME_DIR, 'runtime');
125
+ const preserveRuntime = existsSync(runtimeSubdir);
126
+ let runtimeBackup;
127
+ if (preserveRuntime) {
128
+ runtimeBackup = join(RUNTIME_DIR, '..', '.gsd-runtime-backup');
129
+ try { cpSync(runtimeSubdir, runtimeBackup, { recursive: true }); } catch { runtimeBackup = null; }
130
+ }
120
131
  rmSync(RUNTIME_DIR, { recursive: true, force: true });
132
+ if (runtimeBackup) {
133
+ try {
134
+ mkdirSync(join(RUNTIME_DIR, 'runtime'), { recursive: true });
135
+ cpSync(runtimeBackup, join(RUNTIME_DIR, 'runtime'), { recursive: true });
136
+ rmSync(runtimeBackup, { recursive: true, force: true });
137
+ } catch { /* best effort */ }
138
+ }
121
139
  }
122
140
 
123
141
  // 1. Commands
@@ -242,19 +260,22 @@ export function main() {
242
260
  const entries = readdirSync(cacheBase, { withFileTypes: true })
243
261
  .filter(e => e.isDirectory()).map(e => e.name);
244
262
  if (entries.length > 3) {
245
- const sorted = entries.slice().sort((a, b) => {
246
- const pa = a.split('.').map(Number);
247
- const pb = b.split('.').map(Number);
248
- for (let i = 0; i < 3; i++) {
249
- if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
250
- }
251
- return 0;
252
- });
263
+ const sorted = entries.slice().sort(semverSortComparator);
264
+ // Detect versions with active processes to avoid disrupting running sessions
265
+ let activeVersions;
266
+ try {
267
+ const psOut = execSync('ps aux', { stdio: 'pipe', timeout: 5000 }).toString();
268
+ activeVersions = new Set(entries.filter(v => psOut.includes(`/cache/gsd/gsd/${v}/`)));
269
+ } catch { activeVersions = new Set(); }
270
+
253
271
  const toRemove = sorted.slice(0, sorted.length - 3);
272
+ let pruned = 0;
254
273
  for (const ver of toRemove) {
274
+ if (activeVersions.has(ver)) continue; // skip versions with running processes
255
275
  rmSync(join(cacheBase, ver), { recursive: true, force: true });
276
+ pruned++;
256
277
  }
257
- log(` ✓ Pruned ${toRemove.length} old cache version(s), kept latest 3`);
278
+ if (pruned > 0) log(` ✓ Pruned ${pruned} old cache version(s), kept latest 3`);
258
279
  }
259
280
  } catch { /* best effort */ }
260
281
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-lite",
3
- "version": "0.5.15",
3
+ "version": "0.6.1",
4
4
  "description": "AI orchestration tool for Claude Code — GSD management shell + Superpowers quality core",
5
5
  "type": "module",
6
6
  "bin": {
@@ -81,7 +81,7 @@ executor 上下文传递协议 (orchestrator → executor):
81
81
  **审查级别运行时重分类:**
82
82
  - executor 报告 `contract_changed: true` + 涉及 auth/payment/public API → 自动升级为 L2
83
83
  - executor 标注 `[LEVEL-UP]` → 编排器采纳
84
- - 不主动降级 (安全优先)
84
+ - 不主动降级 (安全优先),L1 + high confidence + 有 evidence 且无测试失败 → L0 例外
85
85
 
86
86
  ### 11.6 — 处理 reviewer 结果
87
87
 
@@ -7,7 +7,7 @@
7
7
  | L0 | 无运行时语义变化 (docs/config/style) | checkpoint 后直接 accepted |
8
8
  | L1 | 普通编码任务 (默认) | phase 结束后批量审查 |
9
9
  | L2 | 高风险 (auth/payment/public API/DB migration) | checkpoint 后立即独立审查 |
10
- | L3 | 最高风险 (复杂架构/关键系统) | L2 相同: checkpoint 后立即独立审查 |
10
+ | L3 | 最高风险 (auth/payment/security architecture) | checkpoint 后立即独立审查 + 人工确认 |
11
11
 
12
12
  ## 运行时重分类
13
13
 
@@ -21,7 +21,10 @@
21
21
  /\b(auth|payment|security|public.?api|login|token|credential|session|oauth)\b/i
22
22
  ```
23
23
 
24
- 规则: 只升不降 (安全优先)。当前级别为 L2 L3 时直接保持不变。
24
+ 规则: L2/L3 只升不降。L1 在满足条件时可降为 L0:
25
+ - `confidence: 'high'` + `contract_changed: false`
26
+ - 有 evidence 且无测试失败
27
+ - 此时自审即可,无需独立审查
25
28
 
26
29
  ## 决策树
27
30
 
@@ -31,6 +34,7 @@ task.level 当前值?
31
34
  └── L0 或 L1
32
35
  ├── executor decisions 含 [LEVEL-UP]? -> 升级为 L2
33
36
  ├── contract_changed: true + task.name 匹配敏感关键词? -> 升级为 L2
37
+ ├── L1 + confidence: 'high' + !contract_changed + 有 evidence 且无测试失败? -> 降为 L0
34
38
  └── 否 -> 保持当前级别
35
39
  ```
36
40
 
@@ -63,15 +67,30 @@ executor checkpointed
63
67
  -> 批量审查所有 checkpointed task (排除 L0)
64
68
  ```
65
69
 
66
- ### L2/L3 流程
70
+ ### L2 流程
67
71
 
68
72
  ```
69
73
  executor checkpointed
70
- -> handleExecutorResult 检测 (reviewLevel === 'L2' || reviewLevel === 'L3') && review_required !== false
74
+ -> handleExecutorResult 检测 reviewLevel === 'L2' && review_required !== false
71
75
  -> 设置 current_review = { scope: 'task', scope_id: task.id, stage: 'spec' }
72
76
  -> workflow_mode = 'reviewing_task'
73
- -> 派发 reviewer (scope='task', review_level='L2' 或 'L3')
74
- -> 审查通过后才释放下游依赖
77
+ -> 派发 reviewer (scope='task', review_level='L2')
78
+ -> 审查通过后 → accepted,释放下游依赖
79
+ ```
80
+
81
+ ### L3 流程
82
+
83
+ ```
84
+ executor checkpointed
85
+ -> handleExecutorResult 检测 reviewLevel === 'L3' && review_required !== false
86
+ -> 设置 current_review = { scope: 'task', scope_id: task.id, stage: 'spec' }
87
+ -> workflow_mode = 'reviewing_task'
88
+ -> 派发 reviewer (scope='task', review_level='L3')
89
+ -> reviewer 返回 requires_human_confirmation: true + security_implications
90
+ -> 审查通过后 → task 进入 awaiting_user (非 accepted)
91
+ -> 编排器向用户展示审查摘要 + 安全影响
92
+ -> 用户显式确认 → accepted,释放下游依赖
93
+ -> 用户拒绝 → 返工
75
94
  ```
76
95
 
77
96
  ## Reviewer 结果处理
@@ -62,7 +62,7 @@ stateDiagram-v2
62
62
  | `reviewing` | `accepted`, `active` |
63
63
  | `accepted` | *(终态,无后续转换)* |
64
64
  | `blocked` | `active` |
65
- | `failed` | *(终态,无后续转换)* |
65
+ | `failed` | `active` (H-3: 用户显式恢复) |
66
66
 
67
67
  ### Mermaid 图
68
68
 
@@ -81,15 +81,16 @@ stateDiagram-v2
81
81
 
82
82
  blocked --> active : 阻塞解除
83
83
 
84
+ failed --> active : H-3 用户显式恢复 (resume 提供 retry/skip/replan)
85
+
84
86
  accepted --> [*]
85
- failed --> [*]
86
87
  ```
87
88
 
88
89
  ### 关键路径说明
89
90
 
90
91
  - **正常路径**: `pending -> active -> reviewing -> accepted`
91
92
  - **返工路径**: `active -> reviewing -> active -> reviewing -> accepted` (最多循环)
92
- - **失败路径**: `active -> failed` (终态,不可恢复)
93
+ - **失败路径**: `active -> failed -> active` (H-3: 可通过 resume 恢复)
93
94
  - **Phase 推进**: 当前 phase `accepted` 后,下一个 `pending` phase 自动转为 `active`
94
95
 
95
96
  来源: `PHASE_LIFECYCLE` in `src/schema.js`