gsd-lite 0.1.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-plugin/marketplace.json +21 -0
- package/.claude-plugin/mcp.json +8 -0
- package/.claude-plugin/plugin.json +17 -0
- package/README.md +145 -0
- package/agents/gsd-debugger.md +92 -0
- package/agents/gsd-executor.md +86 -0
- package/agents/gsd-researcher.md +41 -0
- package/agents/gsd-reviewer.md +127 -0
- package/cli.js +37 -0
- package/commands/gsd-prd.md +154 -0
- package/commands/gsd-resume.md +216 -0
- package/commands/gsd-start.md +317 -0
- package/commands/gsd-status.md +114 -0
- package/commands/gsd-stop.md +50 -0
- package/hooks/context-monitor.js +64 -0
- package/hooks/hooks.json +19 -0
- package/install.js +151 -0
- package/package.json +51 -0
- package/references/anti-rationalization-full.md +112 -0
- package/references/git-worktrees.md +77 -0
- package/references/questioning.md +103 -0
- package/references/testing-patterns.md +110 -0
- package/src/schema.js +471 -0
- package/src/server.js +240 -0
- package/src/tools/orchestrator.js +986 -0
- package/src/tools/state.js +926 -0
- package/src/tools/verify.js +89 -0
- package/src/utils.js +73 -0
- package/uninstall.js +85 -0
- package/workflows/debugging.md +187 -0
- package/workflows/deviation-rules.md +128 -0
- package/workflows/research.md +139 -0
- package/workflows/review-cycle.md +153 -0
- package/workflows/tdd-cycle.md +154 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { stat, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { execFile as execFileCb } from 'node:child_process';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
|
|
6
|
+
const execFile = promisify(execFileCb);
|
|
7
|
+
|
|
8
|
+
// M-2: Detection priority — first lockfile match wins (pnpm > yarn > npm > bun)
|
|
9
|
+
const LOCKFILE_MAP = {
|
|
10
|
+
'pnpm-lock.yaml': 'pnpm',
|
|
11
|
+
'yarn.lock': 'yarn',
|
|
12
|
+
'package-lock.json': 'npm',
|
|
13
|
+
'bun.lockb': 'bun',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function detectPackageManager(cwd = process.cwd()) {
|
|
17
|
+
for (const [file, pm] of Object.entries(LOCKFILE_MAP)) {
|
|
18
|
+
try {
|
|
19
|
+
await stat(join(cwd, file));
|
|
20
|
+
return pm;
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function summarizeOutput(output, lines) {
|
|
27
|
+
return String(output || '').trim().split('\n').slice(-lines).join('\n');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function runCommand(command, args, cwd) {
|
|
31
|
+
try {
|
|
32
|
+
const { stdout } = await execFile(command, args, {
|
|
33
|
+
cwd,
|
|
34
|
+
encoding: 'utf-8',
|
|
35
|
+
timeout: 120000,
|
|
36
|
+
});
|
|
37
|
+
return { exit_code: 0, summary: summarizeOutput(stdout, 3) };
|
|
38
|
+
} catch (err) {
|
|
39
|
+
return {
|
|
40
|
+
exit_code: typeof err.code === 'number' ? err.code : (err.status || 1),
|
|
41
|
+
summary: summarizeOutput(err.stderr || err.stdout || err.message || '', 5),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function runTests(pm, cwd, pattern) {
|
|
47
|
+
const args = ['test'];
|
|
48
|
+
if (pattern) args.push('--', pattern);
|
|
49
|
+
return runCommand(pm, args, cwd);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function hasPackageScript(cwd, scriptName) {
|
|
53
|
+
try {
|
|
54
|
+
const pkg = JSON.parse(await readFile(join(cwd, 'package.json'), 'utf-8'));
|
|
55
|
+
return typeof pkg.scripts?.[scriptName] === 'string';
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function runLint(pm, cwd) {
|
|
62
|
+
if (!await hasPackageScript(cwd, 'lint')) {
|
|
63
|
+
return { exit_code: 0, summary: 'skipped: no lint script found' };
|
|
64
|
+
}
|
|
65
|
+
return runCommand(pm, ['run', 'lint'], cwd);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function runTypeCheck(cwd) {
|
|
69
|
+
// M-8: Only run tsc if tsconfig.json exists
|
|
70
|
+
try {
|
|
71
|
+
await stat(join(cwd, 'tsconfig.json'));
|
|
72
|
+
} catch {
|
|
73
|
+
return { exit_code: 0, summary: 'skipped: no tsconfig.json found' };
|
|
74
|
+
}
|
|
75
|
+
return runCommand('npx', ['tsc', '--noEmit'], cwd);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function runAll(cwd = process.cwd()) {
|
|
79
|
+
const pm = await detectPackageManager(cwd);
|
|
80
|
+
if (!pm) {
|
|
81
|
+
const errResult = { exit_code: -1, summary: 'No package manager detected' };
|
|
82
|
+
return { lint: errResult, typecheck: errResult, test: errResult };
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
lint: await runLint(pm, cwd),
|
|
86
|
+
typecheck: await runTypeCheck(cwd),
|
|
87
|
+
test: await runTests(pm, cwd),
|
|
88
|
+
};
|
|
89
|
+
}
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { readFile, writeFile, rename, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { statSync } from 'node:fs';
|
|
3
|
+
import { join, dirname, resolve } from 'node:path';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
export function getGsdDir(startDir = process.cwd()) {
|
|
7
|
+
let dir = resolve(startDir);
|
|
8
|
+
while (true) {
|
|
9
|
+
const candidate = join(dir, '.gsd');
|
|
10
|
+
try {
|
|
11
|
+
const s = statSync(candidate);
|
|
12
|
+
if (s.isDirectory()) return candidate;
|
|
13
|
+
} catch {}
|
|
14
|
+
const parent = dirname(dir);
|
|
15
|
+
if (parent === dir) return null;
|
|
16
|
+
dir = parent;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getStatePath(startDir = process.cwd()) {
|
|
21
|
+
const gsdDir = getGsdDir(startDir);
|
|
22
|
+
if (!gsdDir) return null;
|
|
23
|
+
return join(gsdDir, 'state.json');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getGitHead(cwd = process.cwd()) {
|
|
27
|
+
try {
|
|
28
|
+
return execSync('git rev-parse --short HEAD', {
|
|
29
|
+
cwd,
|
|
30
|
+
encoding: 'utf-8',
|
|
31
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
32
|
+
}).trim();
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function ensureDir(dirPath) {
|
|
39
|
+
await mkdir(dirPath, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read and parse a JSON file.
|
|
44
|
+
* Returns { ok: true, data } on success, { ok: false, error } on failure.
|
|
45
|
+
*/
|
|
46
|
+
export async function readJson(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
const content = await readFile(filePath, 'utf-8');
|
|
49
|
+
return { ok: true, data: JSON.parse(content) };
|
|
50
|
+
} catch (err) {
|
|
51
|
+
return { ok: false, error: err.message };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Atomically write JSON data (write to .tmp then rename).
|
|
57
|
+
*/
|
|
58
|
+
export async function writeJson(filePath, data) {
|
|
59
|
+
const tmpPath = filePath + '.tmp';
|
|
60
|
+
await ensureDir(dirname(filePath));
|
|
61
|
+
await writeFile(tmpPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
62
|
+
await rename(tmpPath, filePath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Atomically write text content (write to .tmp then rename). [I-3]
|
|
67
|
+
*/
|
|
68
|
+
export async function writeAtomic(filePath, content) {
|
|
69
|
+
const tmpPath = filePath + '.tmp';
|
|
70
|
+
await ensureDir(dirname(filePath));
|
|
71
|
+
await writeFile(tmpPath, content, 'utf-8');
|
|
72
|
+
await rename(tmpPath, filePath);
|
|
73
|
+
}
|
package/uninstall.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Plugin uninstaller for GSD-Lite
|
|
3
|
+
|
|
4
|
+
import { existsSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { pathToFileURL } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const CLAUDE_DIR = join(homedir(), '.claude');
|
|
10
|
+
const RUNTIME_DIR = join(CLAUDE_DIR, 'gsd-lite');
|
|
11
|
+
|
|
12
|
+
function log(msg) { console.log(msg); }
|
|
13
|
+
|
|
14
|
+
function removeDir(path, label) {
|
|
15
|
+
if (existsSync(path)) {
|
|
16
|
+
rmSync(path, { recursive: true, force: true });
|
|
17
|
+
log(` ✓ Removed ${label}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function main() {
|
|
22
|
+
log('GSD-Lite Uninstaller\n');
|
|
23
|
+
|
|
24
|
+
log('Removing files...');
|
|
25
|
+
|
|
26
|
+
removeDir(join(CLAUDE_DIR, 'commands', 'gsd'), 'commands/gsd/');
|
|
27
|
+
// Agents now namespaced under gsd/ [I-5]
|
|
28
|
+
removeDir(join(CLAUDE_DIR, 'agents', 'gsd'), 'agents/gsd/');
|
|
29
|
+
removeDir(join(CLAUDE_DIR, 'workflows', 'gsd'), 'workflows/gsd/');
|
|
30
|
+
removeDir(join(CLAUDE_DIR, 'references', 'gsd'), 'references/gsd/');
|
|
31
|
+
removeDir(RUNTIME_DIR, 'gsd-lite runtime/');
|
|
32
|
+
|
|
33
|
+
// Remove hook file
|
|
34
|
+
const hookFile = join(CLAUDE_DIR, 'hooks', 'context-monitor.js');
|
|
35
|
+
if (existsSync(hookFile)) {
|
|
36
|
+
rmSync(hookFile);
|
|
37
|
+
log(' ✓ Removed hooks/context-monitor.js');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Deregister MCP server and hooks [M-10]
|
|
41
|
+
const settingsPath = join(CLAUDE_DIR, 'settings.json');
|
|
42
|
+
try {
|
|
43
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
44
|
+
let changed = false;
|
|
45
|
+
if (settings.mcpServers && settings.mcpServers['gsd-lite']) {
|
|
46
|
+
delete settings.mcpServers['gsd-lite'];
|
|
47
|
+
changed = true;
|
|
48
|
+
}
|
|
49
|
+
// Remove top-level statusLine if GSD's
|
|
50
|
+
if (settings.statusLine?.command?.includes('context-monitor.js')) {
|
|
51
|
+
delete settings.statusLine;
|
|
52
|
+
changed = true;
|
|
53
|
+
}
|
|
54
|
+
if (settings.hooks) {
|
|
55
|
+
// Remove legacy StatusLine string entry
|
|
56
|
+
if (typeof settings.hooks.StatusLine === 'string'
|
|
57
|
+
&& settings.hooks.StatusLine.includes('context-monitor.js')) {
|
|
58
|
+
delete settings.hooks.StatusLine;
|
|
59
|
+
changed = true;
|
|
60
|
+
}
|
|
61
|
+
// Remove GSD PostToolUse entry from array
|
|
62
|
+
if (Array.isArray(settings.hooks.PostToolUse)) {
|
|
63
|
+
const len = settings.hooks.PostToolUse.length;
|
|
64
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(e =>
|
|
65
|
+
!e.hooks?.some(h => h.command?.includes('context-monitor.js')));
|
|
66
|
+
if (settings.hooks.PostToolUse.length < len) changed = true;
|
|
67
|
+
if (settings.hooks.PostToolUse.length === 0) delete settings.hooks.PostToolUse;
|
|
68
|
+
} else if (typeof settings.hooks.PostToolUse === 'string'
|
|
69
|
+
&& settings.hooks.PostToolUse.includes('context-monitor.js')) {
|
|
70
|
+
delete settings.hooks.PostToolUse;
|
|
71
|
+
changed = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (changed) {
|
|
75
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
76
|
+
log(' ✓ MCP server + hooks deregistered from settings.json');
|
|
77
|
+
}
|
|
78
|
+
} catch {}
|
|
79
|
+
|
|
80
|
+
log('\n✓ GSD-Lite uninstalled.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
84
|
+
main();
|
|
85
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# 4 阶段根因分析
|
|
2
|
+
|
|
3
|
+
> 本文档是 debugger 内联规则的扩展指南,按需加载。
|
|
4
|
+
> 冲突时以 `gsd-debugger.md` 内联规则为准。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 铁律 (与 debugger 一致)
|
|
9
|
+
|
|
10
|
+
**NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST**
|
|
11
|
+
|
|
12
|
+
如果你还没完成 Phase 1,你不能提出修复方案。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 触发条件
|
|
17
|
+
|
|
18
|
+
debugger 由编排器在以下情况派发:
|
|
19
|
+
|
|
20
|
+
- executor 对同一 task 连续 3 次返回 `failed`
|
|
21
|
+
- executor 返回 `[FAILED]` 且错误指纹重复
|
|
22
|
+
- 编排器判断 executor 的 bug 修复尝试没有收敛
|
|
23
|
+
|
|
24
|
+
编排器传入: 错误信息 + executor 的修复尝试记录 + 相关代码路径
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Phase 1: 根因调查
|
|
29
|
+
|
|
30
|
+
**目标:** 理解问题,不是解决问题。
|
|
31
|
+
|
|
32
|
+
### 步骤 1: 仔细阅读错误信息
|
|
33
|
+
|
|
34
|
+
- 完整阅读 error message + stack trace (不要只看第一行)
|
|
35
|
+
- 区分根因错误 vs 衍生错误 (通常最底部的 cause 最重要)
|
|
36
|
+
- 提取关键信息: 错误类型、发生位置、输入数据
|
|
37
|
+
|
|
38
|
+
### 步骤 2: 可靠复现
|
|
39
|
+
|
|
40
|
+
- 用相同的输入 / 环境复现问题
|
|
41
|
+
- 确保**每次都能触发**,而非偶发
|
|
42
|
+
- 记录复现步骤 (作为 evidence)
|
|
43
|
+
- 如果不能复现 → 检查环境差异 (版本、配置、数据状态)
|
|
44
|
+
|
|
45
|
+
### 步骤 3: 检查最近变更
|
|
46
|
+
|
|
47
|
+
- `git diff` — 最近修改了什么?
|
|
48
|
+
- `git log --oneline -10` — 近期提交历史
|
|
49
|
+
- 新依赖引入了吗?版本对吗?
|
|
50
|
+
- 配置文件变更?环境变量变更?
|
|
51
|
+
|
|
52
|
+
### 步骤 4: 追踪数据流
|
|
53
|
+
|
|
54
|
+
- 坏数据从哪里来?逐层回溯
|
|
55
|
+
- 在关键节点打日志 / 断点,观察数据变化
|
|
56
|
+
- 画出数据流路径: 输入 → 处理A → 处理B → 输出
|
|
57
|
+
- 找到第一个数据变"坏"的位置
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## HARD-GATE: 根因调查 → 模式分析
|
|
62
|
+
|
|
63
|
+
**根因调查必须完成后才能进入 Phase 2。**
|
|
64
|
+
没有根因证据,不允许提出任何修复方案。
|
|
65
|
+
|
|
66
|
+
完成标准:
|
|
67
|
+
- [ ] 错误已可靠复现
|
|
68
|
+
- [ ] 已阅读完整错误信息和 stack trace
|
|
69
|
+
- [ ] 已检查最近变更
|
|
70
|
+
- [ ] 已追踪数据流到出错点
|
|
71
|
+
- [ ] 有初步根因方向 (即使不确定)
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Phase 2: 模式分析
|
|
76
|
+
|
|
77
|
+
**目标:** 通过对比找到问题模式。
|
|
78
|
+
|
|
79
|
+
### 步骤 1: 找到类似的可工作代码
|
|
80
|
+
|
|
81
|
+
- 同项目中类似功能 (能正常工作的)
|
|
82
|
+
- 同框架的官方示例
|
|
83
|
+
- 之前的工作版本 (`git log` / `git stash list`)
|
|
84
|
+
|
|
85
|
+
### 步骤 2: 系统对比
|
|
86
|
+
|
|
87
|
+
- 逐行对比工作代码 vs 出错代码
|
|
88
|
+
- 列出**所有**不同点,不管看起来是否相关
|
|
89
|
+
- 关注: 导入路径、参数顺序、类型声明、配置项、初始化顺序
|
|
90
|
+
|
|
91
|
+
### 步骤 3: 不要假设 "那个不重要"
|
|
92
|
+
|
|
93
|
+
- 每一个差异都可能是根因
|
|
94
|
+
- 特别是那些"看起来无关紧要"的差异 — 这往往是盲点
|
|
95
|
+
- 记录每个差异及其可能的影响
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Phase 3: 假设测试
|
|
100
|
+
|
|
101
|
+
**目标:** 验证你的根因假设。
|
|
102
|
+
|
|
103
|
+
### 步骤 1: 明确陈述假设
|
|
104
|
+
|
|
105
|
+
格式: "我认为 **X** 是根因,因为 **Y** (证据)"
|
|
106
|
+
|
|
107
|
+
- 假设必须可证伪 — 如果不能被推翻,它不是好假设
|
|
108
|
+
- 不要同时测试多个假设
|
|
109
|
+
|
|
110
|
+
### 步骤 2: 最小变更测试
|
|
111
|
+
|
|
112
|
+
- **一次只改一个变量**
|
|
113
|
+
- 改变最小化 — 不要顺手重构
|
|
114
|
+
- 观察结果:
|
|
115
|
+
- 问题消失 → 假设可能成立,进一步验证
|
|
116
|
+
- 问题仍在 → 假设被推翻,恢复变更,提出新假设
|
|
117
|
+
|
|
118
|
+
### 步骤 3: 验证循环
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
假设 → 最小变更 → 观察
|
|
122
|
+
├── 有效 → Phase 4 (实施修复)
|
|
123
|
+
└── 无效 → 恢复变更 → 新假设 → 重复
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**注意:** 如果已经测试了 2+ 个假设都无效 → 质疑你的前提假设,重新审视 Phase 1 的数据。
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Phase 4: 实施修复
|
|
131
|
+
|
|
132
|
+
**目标:** 修复根因 (不是症状)。
|
|
133
|
+
|
|
134
|
+
### 步骤 1: 写失败测试
|
|
135
|
+
|
|
136
|
+
- 写一个测试来复现这个 bug
|
|
137
|
+
- 运行测试,确认它失败
|
|
138
|
+
- 这个测试同时也是回归保护
|
|
139
|
+
|
|
140
|
+
### 步骤 2: 修复根因
|
|
141
|
+
|
|
142
|
+
- 修复你在 Phase 3 验证的根因
|
|
143
|
+
- 不是绕过 / 特殊处理 / 补丁式修复
|
|
144
|
+
- 问自己: "这个修复解决的是**为什么出错**,还是**出错后怎么办**?"
|
|
145
|
+
|
|
146
|
+
### 步骤 3: 验证修复
|
|
147
|
+
|
|
148
|
+
- 新测试通过 (bug 被修复)
|
|
149
|
+
- 全部已有测试通过 (无回归)
|
|
150
|
+
- 手动验证 (如适用)
|
|
151
|
+
|
|
152
|
+
### 3 次修复失败规则
|
|
153
|
+
|
|
154
|
+
> 3 次修复失败 → 停止。质疑架构。报告给编排器。
|
|
155
|
+
|
|
156
|
+
- 同一个 bug,3 次修复尝试都失败了
|
|
157
|
+
- 说明你可能在修错东西,或者问题出在更深的架构层
|
|
158
|
+
- 返回 `outcome: "failed"`, `architecture_concern: true`
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 常见根因模式
|
|
163
|
+
|
|
164
|
+
| 模式 | 症状 | 真实根因 |
|
|
165
|
+
|------|------|----------|
|
|
166
|
+
| 环境不一致 | "在我机器上能跑" | Node 版本 / 依赖版本 / 环境变量差异 |
|
|
167
|
+
| 初始化顺序 | 间歇性失败 | 异步初始化未 await / 依赖注入顺序 |
|
|
168
|
+
| 类型不匹配 | 静默错误 | `string` vs `number` / `null` vs `undefined` |
|
|
169
|
+
| 竞态条件 | 偶发失败 | 并发访问共享状态 / 缺少锁 |
|
|
170
|
+
| 缓存失效 | 旧数据 | 缓存 key 不匹配 / TTL 未设置 / 缓存层级冲突 |
|
|
171
|
+
| 隐式依赖 | 移动代码后崩溃 | 全局状态 / 文件路径假设 / 环境变量 |
|
|
172
|
+
| 边界溢出 | 极端输入崩溃 | 未校验输入范围 / 整数溢出 |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 红旗 / 停止信号
|
|
177
|
+
|
|
178
|
+
遇到以下情况时**立即停止当前动作**:
|
|
179
|
+
|
|
180
|
+
| 红旗 | 为什么危险 | 正确做法 |
|
|
181
|
+
|------|------------|----------|
|
|
182
|
+
| "快速修一下先" | 你在跳过根因调查 | 回到 Phase 1 |
|
|
183
|
+
| "应该是 X 的问题" | 没有证据的假设 | 先调查,再假设 |
|
|
184
|
+
| "再试一个修复" (已 2+) | 你在盲目尝试 | 停止,质疑架构 |
|
|
185
|
+
| "这个问题太复杂了" | 可能需要拆分 | 报告给编排器 |
|
|
186
|
+
| 修复 A 引出 bug B | 症状修复,根因未解 | 回到 Phase 1 重新调查 |
|
|
187
|
+
| 修复越来越大 | 你在补丁叠补丁 | 停止,质疑设计 |
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# 偏差处理规则
|
|
2
|
+
|
|
3
|
+
> 本文档是 executor 内联 `<deviation_rules>` 的扩展指南,按需加载。
|
|
4
|
+
> 冲突时以 `gsd-executor.md` 内联规则为准。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 决策树: 执行中遇到偏差时
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
执行 task 时发现偏差
|
|
12
|
+
│
|
|
13
|
+
├── Bug (不影响架构)?
|
|
14
|
+
│ └── AUTO-FIX: 直接修复,不中断
|
|
15
|
+
│
|
|
16
|
+
├── 缺少导入/类型声明?
|
|
17
|
+
│ └── AUTO-ADD: 直接补充,不中断
|
|
18
|
+
│
|
|
19
|
+
├── 需要架构变更?
|
|
20
|
+
│ └── ANNOTATE: 标注到 summary + decisions
|
|
21
|
+
│ → 返回 orchestrator 决策
|
|
22
|
+
│ → 不自行实施架构变更
|
|
23
|
+
│
|
|
24
|
+
└── 同一 task 3 次修复失败?
|
|
25
|
+
└── STOP: 返回 outcome="failed"
|
|
26
|
+
→ 由编排器决定:
|
|
27
|
+
├── 派发 debugger 分析根因
|
|
28
|
+
├── 标记 task failed
|
|
29
|
+
└── 标记 phase failed
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 四种偏差类型详解
|
|
35
|
+
|
|
36
|
+
### AUTO-FIX: 自动修复 Bug
|
|
37
|
+
|
|
38
|
+
**条件:** bug 不影响架构设计,是实现层的错误。
|
|
39
|
+
|
|
40
|
+
示例:
|
|
41
|
+
- off-by-one 错误
|
|
42
|
+
- 拼写错误导致的引用失败
|
|
43
|
+
- 逻辑条件反转 (`>` 写成 `<`)
|
|
44
|
+
- 异步函数忘记 `await`
|
|
45
|
+
- null/undefined 未检查
|
|
46
|
+
|
|
47
|
+
处理: 修复 → 运行测试 → checkpoint commit → 在 summary 中简要提及
|
|
48
|
+
|
|
49
|
+
### AUTO-ADD: 自动补充遗漏
|
|
50
|
+
|
|
51
|
+
**条件:** 缺少的是声明性内容,不涉及行为设计决策。
|
|
52
|
+
|
|
53
|
+
示例:
|
|
54
|
+
- 缺少 `import` 语句
|
|
55
|
+
- 缺少 TypeScript 类型声明
|
|
56
|
+
- 缺少 `export`
|
|
57
|
+
- 缺少必要的依赖声明 (`package.json`)
|
|
58
|
+
|
|
59
|
+
处理: 补充 → 验证编译/类型检查通过 → 不需要单独 checkpoint
|
|
60
|
+
|
|
61
|
+
### ANNOTATE: 架构变更标注
|
|
62
|
+
|
|
63
|
+
**条件:** 修改会影响系统架构、模块边界或共享契约。
|
|
64
|
+
|
|
65
|
+
示例:
|
|
66
|
+
- 需要新增数据库表/字段
|
|
67
|
+
- 需要修改 API 端点的 request/response 结构
|
|
68
|
+
- 需要引入新的依赖模块
|
|
69
|
+
- 需要改变模块间的调用关系
|
|
70
|
+
- 需要修改共享类型定义
|
|
71
|
+
|
|
72
|
+
处理:
|
|
73
|
+
1. **不自行实施** — 只标注,不改
|
|
74
|
+
2. 在 `decisions` 中添加: `[DECISION] 发现需要架构变更: ...`
|
|
75
|
+
3. 在 `summary` 中描述变更需求和理由
|
|
76
|
+
4. 返回给 orchestrator,由其决定是否批准并调整计划
|
|
77
|
+
|
|
78
|
+
### STOP: 3 次失败停止
|
|
79
|
+
|
|
80
|
+
**条件:** 同一错误指纹 (file+line 或 msg[:50]) 出现 3 次。
|
|
81
|
+
|
|
82
|
+
处理:
|
|
83
|
+
1. 返回 `outcome: "failed"`
|
|
84
|
+
2. 附上 3 次尝试的记录 (每次: 假设 + 修改 + 结果)
|
|
85
|
+
3. 编排器决定下一步:
|
|
86
|
+
- 派发 debugger → 系统性根因分析
|
|
87
|
+
- 标记 task failed → 跳过 (如非关键)
|
|
88
|
+
- 标记 phase failed → 停止执行 (如关键路径)
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## `contract_changed` 判定指南
|
|
93
|
+
|
|
94
|
+
executor 完成 task 后必须在 result 中报告 `contract_changed` 字段。
|
|
95
|
+
|
|
96
|
+
| 场景 | `contract_changed` | 理由 |
|
|
97
|
+
|------|---------------------|------|
|
|
98
|
+
| 改了函数/方法签名 (参数、返回类型) | `true` | 调用方需要适配 |
|
|
99
|
+
| 改了 API endpoint 的 request/response schema | `true` | 前端/客户端需要适配 |
|
|
100
|
+
| 改了数据库 schema (表结构、字段) | `true` | migration + 依赖代码需要适配 |
|
|
101
|
+
| 改了共享类型定义 / 接口 | `true` | 所有使用方需要适配 |
|
|
102
|
+
| 只改了内部实现逻辑,不影响外部调用方 | `false` | 封装边界内,无外部影响 |
|
|
103
|
+
| 拿不准时 | `true` | **安全优先** |
|
|
104
|
+
|
|
105
|
+
`contract_changed: true` 的后果:
|
|
106
|
+
- 编排器触发下游失效传播 (`needs_revalidation`)
|
|
107
|
+
- 如果涉及 auth/payment/public API → 审查级别自动升级为 L2
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 审查级别重分类规则
|
|
112
|
+
|
|
113
|
+
executor 执行时可能发现实际影响面与 planner 预判不同,可以建议重分类:
|
|
114
|
+
|
|
115
|
+
### 升级 (executor 可建议)
|
|
116
|
+
|
|
117
|
+
- 在 `decisions` 中标注: `[LEVEL-UP] 建议升级为 L2 因为 ...`
|
|
118
|
+
- 编排器采纳建议
|
|
119
|
+
|
|
120
|
+
### 自动升级 (编排器自动)
|
|
121
|
+
|
|
122
|
+
- `contract_changed: true` + 涉及 auth/payment/public API → 自动升级为 L2
|
|
123
|
+
|
|
124
|
+
### 降级 (不允许)
|
|
125
|
+
|
|
126
|
+
- 编排器不主动降级
|
|
127
|
+
- planner 标了 L2 但实际很简单 → 仍按 L2 审查
|
|
128
|
+
- 原则: **安全优先**
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# 研究工作流
|
|
2
|
+
|
|
3
|
+
> 本文档是 researcher 内联规则的扩展指南,按需加载。
|
|
4
|
+
> 冲突时以 `gsd-researcher.md` 内联规则为准。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 源优先级 (与 researcher 一致)
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
1. Context7 MCP — 最新文档,无幻觉 (最优先)
|
|
12
|
+
2. 官方文档 — Context7 覆盖不足时
|
|
13
|
+
3. WebSearch — 对比和趋势 (补充)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
选择原则:
|
|
17
|
+
- Context7 能回答 → 不查 WebSearch
|
|
18
|
+
- 官方文档有明确说明 → 不依赖社区经验
|
|
19
|
+
- 多个源矛盾 → 以版本最新、权威性最高的为准
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 研究触发规则 (智能判断)
|
|
24
|
+
|
|
25
|
+
| 场景 | 决策 | 理由 |
|
|
26
|
+
|------|------|------|
|
|
27
|
+
| 新项目 | **必须研究** | 技术栈选型需要依据 |
|
|
28
|
+
| 涉及新技术栈 | **必须研究** | 不熟悉的领域需要 pitfall 分析 |
|
|
29
|
+
| 简单 bug 修复 | **跳过研究** | 已有代码即上下文 |
|
|
30
|
+
| 已有研究且未过期 | **跳过研究** | 复用缓存 |
|
|
31
|
+
| 用户明确要求 | **研究** | 用户意图优先 |
|
|
32
|
+
| 已有研究但需求方向变了 | **增量研究** | 只研究新方向,不重做已有部分 |
|
|
33
|
+
|
|
34
|
+
编排器在 `/gsd:start` 或 `/gsd:prd` 流程中根据上述规则判断是否派发 researcher。
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 研究过期规则 (TTL)
|
|
39
|
+
|
|
40
|
+
**默认启发式,不是固定定律。**
|
|
41
|
+
|
|
42
|
+
| 领域 | 默认 TTL | 理由 |
|
|
43
|
+
|------|----------|------|
|
|
44
|
+
| 前端框架 / 云服务 / 安全 | 3 天 | 高波动,API 变化频繁 |
|
|
45
|
+
| 中等波动领域 (通用 Web 开发) | 7 天 | 默认值 |
|
|
46
|
+
| 稳定后端 / 企业内部 / 基础设施 | 14-30 天 | 低波动,变化缓慢 |
|
|
47
|
+
|
|
48
|
+
**立即过期触发:**
|
|
49
|
+
- `package.json` 主依赖大版本有变更 → 立即过期
|
|
50
|
+
- 用户说"重新研究" → 强制过期
|
|
51
|
+
|
|
52
|
+
**TTL 存储在** `state.json` 的 `research.expires_at` 字段。
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 缓存策略与 Decision ID
|
|
57
|
+
|
|
58
|
+
### Decision ID 生成
|
|
59
|
+
|
|
60
|
+
每个关键推荐生成一个 decision id,格式: `decision:<topic>`
|
|
61
|
+
|
|
62
|
+
示例:
|
|
63
|
+
- `decision:jwt-rotation` — JWT 刷新策略选型
|
|
64
|
+
- `decision:orm-choice` — ORM 工具选型
|
|
65
|
+
- `decision:deploy-platform` — 部署平台选型
|
|
66
|
+
|
|
67
|
+
Decision ID 供 plan/task 的 `research_basis` 字段引用,建立研究→计划的追溯链。
|
|
68
|
+
|
|
69
|
+
### 研究刷新后的 Decision ID 处理
|
|
70
|
+
|
|
71
|
+
| 场景 | 处理方式 |
|
|
72
|
+
|------|----------|
|
|
73
|
+
| 新研究 decision 与旧 ID 相同且结论一致 | 保留引用,更新 `expires_at` |
|
|
74
|
+
| 新研究 decision 与旧 ID 相同但结论变了 | 标记所有引用该 decision 的 task 为 `needs_revalidation` |
|
|
75
|
+
| 旧 decision ID 在新研究中不再存在 | 标记引用 task 为 `needs_revalidation` + 警告编排器 |
|
|
76
|
+
| 新研究产生了全新 decision ID | 不影响已有 task,供后续 planning 使用 |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 输出结构
|
|
81
|
+
|
|
82
|
+
研究结果写入 `.gsd/research/` 目录:
|
|
83
|
+
|
|
84
|
+
### STACK.md — 技术栈推荐
|
|
85
|
+
|
|
86
|
+
- 推荐的技术栈组合 + 理由
|
|
87
|
+
- 版本建议 (具体版本号)
|
|
88
|
+
- 替代方案对比
|
|
89
|
+
- 每项标注置信度 + 来源
|
|
90
|
+
|
|
91
|
+
### ARCHITECTURE.md — 架构模式
|
|
92
|
+
|
|
93
|
+
- 推荐的架构模式 (标识 ⭐)
|
|
94
|
+
- 备选方案 + 优劣对比
|
|
95
|
+
- 与当前项目的适配分析
|
|
96
|
+
|
|
97
|
+
### PITFALLS.md — 领域陷阱
|
|
98
|
+
|
|
99
|
+
- 来自真实项目经验的陷阱
|
|
100
|
+
- 每个陷阱: 描述 + 规避方案 + 置信度
|
|
101
|
+
- 按严重程度排序
|
|
102
|
+
|
|
103
|
+
### SUMMARY.md — 研究摘要
|
|
104
|
+
|
|
105
|
+
- 关键发现摘要
|
|
106
|
+
- 路线图建议
|
|
107
|
+
- volatility 评估
|
|
108
|
+
- `expires_at` 过期时间
|
|
109
|
+
- key decision ids 索引
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 置信度标注
|
|
114
|
+
|
|
115
|
+
每个发现/推荐必须标注:
|
|
116
|
+
|
|
117
|
+
| 置信度 | 含义 | 来源要求 |
|
|
118
|
+
|--------|------|----------|
|
|
119
|
+
| **HIGH** | 可直接采用 | 官方文档 / Context7 明确说明 |
|
|
120
|
+
| **MEDIUM** | 建议采用,但需验证 | 社区广泛使用 / 多源一致 |
|
|
121
|
+
| **LOW** | 参考用,需要进一步调查 | 单一来源 / 经验推测 |
|
|
122
|
+
|
|
123
|
+
来源标注格式: `[Context7]` / `[官方文档]` / `[社区经验]`
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 结果契约 (与 researcher 一致)
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"decision_ids": ["decision:jwt-rotation", "decision:orm-choice"],
|
|
132
|
+
"volatility": "medium",
|
|
133
|
+
"expires_at": "2026-03-17T10:30:00Z",
|
|
134
|
+
"sources": [
|
|
135
|
+
{ "id": "src1", "type": "Context7", "ref": "Next.js auth docs" },
|
|
136
|
+
{ "id": "src2", "type": "官方文档", "ref": "Prisma migration guide" }
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
```
|