claude-coder 1.8.3 → 1.9.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/README.md +59 -12
- package/bin/cli.js +20 -37
- package/package.json +4 -1
- package/recipes/_shared/roles/developer.md +11 -0
- package/recipes/_shared/roles/product.md +12 -0
- package/recipes/_shared/roles/tester.md +12 -0
- package/recipes/_shared/test/report-format.md +86 -0
- package/recipes/backend/base.md +27 -0
- package/recipes/backend/components/auth.md +18 -0
- package/recipes/backend/components/crud-api.md +18 -0
- package/recipes/backend/components/file-service.md +15 -0
- package/recipes/backend/manifest.json +20 -0
- package/recipes/backend/test/api-test.md +25 -0
- package/recipes/console/base.md +37 -0
- package/recipes/console/components/modal-form.md +20 -0
- package/recipes/console/components/pagination.md +17 -0
- package/recipes/console/components/search.md +17 -0
- package/recipes/console/components/table-list.md +18 -0
- package/recipes/console/components/tabs.md +14 -0
- package/recipes/console/components/tree.md +15 -0
- package/recipes/console/components/upload.md +15 -0
- package/recipes/console/manifest.json +24 -0
- package/recipes/console/test/crud-e2e.md +47 -0
- package/recipes/h5/base.md +26 -0
- package/recipes/h5/components/animation.md +11 -0
- package/recipes/h5/components/countdown.md +11 -0
- package/recipes/h5/components/share.md +11 -0
- package/recipes/h5/components/swiper.md +11 -0
- package/recipes/h5/manifest.json +21 -0
- package/recipes/h5/test/h5-e2e.md +20 -0
- package/src/commands/auth.js +87 -15
- package/src/commands/setup-modules/helpers.js +4 -3
- package/src/commands/setup-modules/mcp.js +44 -24
- package/src/commands/setup-modules/safety.js +1 -15
- package/src/commands/setup.js +8 -8
- package/src/common/assets.js +10 -1
- package/src/common/config.js +2 -2
- package/src/common/indicator.js +158 -120
- package/src/common/utils.js +60 -8
- package/src/core/coding.js +16 -38
- package/src/core/go.js +31 -77
- package/src/core/hooks.js +56 -89
- package/src/core/init.js +94 -100
- package/src/core/plan.js +85 -223
- package/src/core/prompts.js +36 -16
- package/src/core/repair.js +7 -17
- package/src/core/runner.js +306 -43
- package/src/core/scan.js +38 -34
- package/src/core/session.js +253 -39
- package/src/core/simplify.js +45 -24
- package/src/core/state.js +105 -0
- package/src/index.js +76 -0
- package/templates/codingSystem.md +2 -2
- package/templates/codingUser.md +1 -1
- package/templates/guidance.json +22 -3
- package/templates/planSystem.md +2 -2
- package/templates/scanSystem.md +3 -3
- package/templates/scanUser.md +1 -1
- package/templates/web-testing.md +17 -0
- package/types/index.d.ts +217 -0
- package/src/core/context.js +0 -117
- package/src/core/harness.js +0 -484
- package/src/core/query.js +0 -50
- package/templates/playwright.md +0 -17
package/src/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { loadConfig, log } = require('./common/config');
|
|
6
|
+
const { assets } = require('./common/assets');
|
|
7
|
+
const { Session } = require('./core/session');
|
|
8
|
+
|
|
9
|
+
/** @typedef {{ max?: number, pause?: number, dryRun?: boolean, readFile?: string, model?: string, n?: number, planOnly?: boolean, interactive?: boolean, reset?: boolean, deployTemplates?: boolean, projectRoot?: string, reqFile?: string }} MainOpts */
|
|
10
|
+
|
|
11
|
+
function checkReady(command) {
|
|
12
|
+
if (['init', 'scan'].includes(command)) return;
|
|
13
|
+
|
|
14
|
+
if (!assets.exists('profile')) {
|
|
15
|
+
throw new Error('文件缺失: project_profile.json,请运行 claude-coder init 初始化项目');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 应用入口:初始化资产、加载配置、分发命令
|
|
21
|
+
* @param {string} command - 命令名称(init | scan | plan | run | go | simplify)
|
|
22
|
+
* @param {string} input - 位置参数(需求文本等)
|
|
23
|
+
* @param {MainOpts} [opts={}] - CLI 选项
|
|
24
|
+
* @returns {Promise<Object|void>}
|
|
25
|
+
*/
|
|
26
|
+
async function main(command, input, opts = {}) {
|
|
27
|
+
assets.init(opts.projectRoot || process.cwd());
|
|
28
|
+
assets.ensureDirs();
|
|
29
|
+
const config = loadConfig();
|
|
30
|
+
|
|
31
|
+
if (!opts.model) opts.model = config.defaultOpus || config.model;
|
|
32
|
+
|
|
33
|
+
if (opts.readFile) {
|
|
34
|
+
const reqPath = path.resolve(assets.projectRoot, opts.readFile);
|
|
35
|
+
if (!fs.existsSync(reqPath)) {
|
|
36
|
+
throw new Error(`文件不存在: ${reqPath}`);
|
|
37
|
+
}
|
|
38
|
+
opts.reqFile = reqPath;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
checkReady(command);
|
|
42
|
+
|
|
43
|
+
const displayModel = opts.model || '(default)';
|
|
44
|
+
log('ok', `模型: ${config.provider || 'claude'} (${displayModel}), 命令: ${command}`);
|
|
45
|
+
|
|
46
|
+
switch (command) {
|
|
47
|
+
case 'init': {
|
|
48
|
+
const { executeInit } = require('./core/init');
|
|
49
|
+
return executeInit(config, opts);
|
|
50
|
+
}
|
|
51
|
+
case 'scan': {
|
|
52
|
+
const { executeScan } = require('./core/scan');
|
|
53
|
+
return executeScan(config, opts);
|
|
54
|
+
}
|
|
55
|
+
case 'simplify': {
|
|
56
|
+
const { executeSimplify } = require('./core/simplify');
|
|
57
|
+
return executeSimplify(config, input, opts);
|
|
58
|
+
}
|
|
59
|
+
case 'plan': {
|
|
60
|
+
const { executePlan } = require('./core/plan');
|
|
61
|
+
return executePlan(config, input, opts);
|
|
62
|
+
}
|
|
63
|
+
case 'run': {
|
|
64
|
+
const { executeRun } = require('./core/runner');
|
|
65
|
+
return executeRun(config, opts);
|
|
66
|
+
}
|
|
67
|
+
case 'go': {
|
|
68
|
+
const { executeGo } = require('./core/go');
|
|
69
|
+
return executeGo(config, input, opts);
|
|
70
|
+
}
|
|
71
|
+
default:
|
|
72
|
+
throw new Error(`未知命令: ${command}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { main, Session };
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
|
|
46
46
|
**Step 3 — 收尾(必须执行)**
|
|
47
47
|
1. 根据 prompt 提示管理后台服务
|
|
48
|
-
2.
|
|
49
|
-
3.
|
|
48
|
+
2. 写 session_result.json:notes 只写未解决问题
|
|
49
|
+
3. `git add -A && git commit -m "feat(task-id): 描述"`
|
|
50
50
|
|
|
51
51
|
## 工具规范
|
|
52
52
|
|
package/templates/codingUser.md
CHANGED
package/templates/guidance.json
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"name": "playwright",
|
|
5
5
|
"matcher": "^mcp__playwright__",
|
|
6
6
|
"file": {
|
|
7
|
-
"path": "assets/
|
|
7
|
+
"path": "assets/web-testing.md",
|
|
8
8
|
"injectOnce": true
|
|
9
9
|
},
|
|
10
10
|
"toolTips": {
|
|
11
11
|
"injectOnce": false,
|
|
12
12
|
"extractor": "browser_(\\w+)",
|
|
13
13
|
"items": {
|
|
14
|
-
"snapshot": "snapshot
|
|
14
|
+
"snapshot": "snapshot 频率注意控制。",
|
|
15
15
|
"wait_for": "设置合理 timeout,AI 生成任务建议 60-180s。",
|
|
16
16
|
"click": "点击前确认元素已通过 snapshot 获取 ref。",
|
|
17
17
|
"type": "大量文本输入可使用 fill_form 批量操作。",
|
|
@@ -19,6 +19,25 @@
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
|
+
{
|
|
23
|
+
"name": "chrome-devtools",
|
|
24
|
+
"matcher": "^mcp__chrome.devtools__",
|
|
25
|
+
"file": {
|
|
26
|
+
"path": "assets/web-testing.md",
|
|
27
|
+
"injectOnce": true
|
|
28
|
+
},
|
|
29
|
+
"toolTips": {
|
|
30
|
+
"injectOnce": false,
|
|
31
|
+
"extractor": "^(\\w+)$",
|
|
32
|
+
"items": {
|
|
33
|
+
"take_snapshot": "snapshot 频率注意控制。",
|
|
34
|
+
"wait_for": "设置合理 timeout,AI 生成任务建议 60-180s。",
|
|
35
|
+
"click": "点击前确认元素已通过 take_snapshot 获取定位。",
|
|
36
|
+
"type_text": "大量文本输入可使用 fill_form 批量操作。",
|
|
37
|
+
"navigate_page": "导航后必须 take_snapshot 确认页面加载成功。"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
22
41
|
{
|
|
23
42
|
"name": "frontend-component",
|
|
24
43
|
"matcher": "^(Edit|MultiEdit|Write)$",
|
|
@@ -50,4 +69,4 @@
|
|
|
50
69
|
}
|
|
51
70
|
}
|
|
52
71
|
]
|
|
53
|
-
}
|
|
72
|
+
}
|
package/templates/planSystem.md
CHANGED
|
@@ -69,8 +69,8 @@ test 类任务 steps 用标签标记优先级:`【P0】` 必测 | `【P1】`
|
|
|
69
69
|
2. 读取 `.claude-coder/tasks.json` + `project_profile.json`,了解现状
|
|
70
70
|
3. 识别功能点,判断单任务还是需拆分;对比已有任务避免重叠
|
|
71
71
|
4. 确定 `depends_on`,按规范追加任务到 tasks.json
|
|
72
|
-
5.
|
|
73
|
-
6.
|
|
72
|
+
5. 写入 session_result.json:`{ "session_result": "success", "status_before": "N/A", "status_after": "N/A", "notes": "追加了 N 个任务:简述" }`
|
|
73
|
+
6. `git add -A && git commit -m "chore: add new tasks"`
|
|
74
74
|
|
|
75
75
|
## 反面案例
|
|
76
76
|
|
package/templates/scanSystem.md
CHANGED
|
@@ -108,13 +108,13 @@
|
|
|
108
108
|
"existing_docs": ["README.md", ".claude/CLAUDE.md"],
|
|
109
109
|
"has_tests": false,
|
|
110
110
|
"has_docker": false,
|
|
111
|
-
"mcp_tools": { "
|
|
111
|
+
"mcp_tools": { "web_test_tool": "playwright | chrome-devtools | none" },
|
|
112
112
|
"custom_init": [],
|
|
113
113
|
"scan_files_checked": []
|
|
114
114
|
}
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
**注意**:
|
|
118
|
-
- `existing_docs
|
|
118
|
+
- `existing_docs`:列出项目中重要的可读文档路径,比如 README.md、API 文档、架构文档等。
|
|
119
119
|
- `services` 的 `command` 必须来自实际配置文件或标准命令
|
|
120
|
-
- `mcp_tools`:检查 `.claude-coder/.env`
|
|
120
|
+
- `mcp_tools`:检查 `.claude-coder/.env` 中 `WEB_TEST_TOOL` 变量的值(playwright / chrome-devtools / 空)
|
package/templates/scanUser.md
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
profile 质量要求(harness 会校验):
|
|
6
6
|
- services 数组必须包含所有可启动服务(command、port、health_check),不得为空
|
|
7
7
|
- existing_docs 必须列出所有实际存在的文档路径
|
|
8
|
-
- 检查 .claude/CLAUDE.md
|
|
8
|
+
- 检查 .claude/CLAUDE.md 是否存在,若无则生成,并加入 existing_docs
|
|
9
9
|
|
|
10
10
|
注意:本次只扫描项目,不分解任务。
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
【浏览器测试规则提醒】
|
|
2
|
+
|
|
3
|
+
## Smart Snapshot 策略(节省 40-60% Token)
|
|
4
|
+
- 必须:首次加载页面、关键断言点、操作失败时
|
|
5
|
+
- 跳过:连续同类操作间、等待循环中(改用 wait_for 类工具)
|
|
6
|
+
- 高效模式:导航 → 快照 → 填写 → 选择 → 点击 → 等待 → 快照(仅 2 次)
|
|
7
|
+
|
|
8
|
+
## 等待策略
|
|
9
|
+
- 瞬时操作(导航、点击):直接操作,不等待
|
|
10
|
+
- 短等(表单提交):wait_for 类工具,text="成功" timeout=10000
|
|
11
|
+
- 长等(AI 生成、SSE 流式、文件处理):wait_for + 合理 timeout(60-180s)
|
|
12
|
+
- 禁止轮询快照等待,Token 消耗从 ~60K+ 降至 ~5K
|
|
13
|
+
|
|
14
|
+
## 失败处理
|
|
15
|
+
- 阻断性(服务未启动、500 错误、凭证缺失):立即停止
|
|
16
|
+
- 非阻断性(样式异常、console warning):记录后继续
|
|
17
|
+
- 失败流程:快照(记录状态)→ 控制台消息(错误日志)→ 停止该场景
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { WriteStream } from 'fs';
|
|
2
|
+
|
|
3
|
+
// ─── Config ───────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export interface Config {
|
|
6
|
+
provider?: string;
|
|
7
|
+
model?: string;
|
|
8
|
+
defaultOpus?: string;
|
|
9
|
+
maxTurns?: number;
|
|
10
|
+
stallTimeout: number;
|
|
11
|
+
editThreshold?: number;
|
|
12
|
+
webTestTool?: 'playwright' | 'chrome-devtools' | '';
|
|
13
|
+
webTestMode?: 'persistent' | 'isolated' | 'extension' | '';
|
|
14
|
+
simplifyInterval: number;
|
|
15
|
+
simplifyCommits: number;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ─── Main Options ─────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export interface MainOpts {
|
|
22
|
+
max?: number;
|
|
23
|
+
pause?: number;
|
|
24
|
+
dryRun?: boolean;
|
|
25
|
+
readFile?: string;
|
|
26
|
+
model?: string;
|
|
27
|
+
n?: number;
|
|
28
|
+
planOnly?: boolean;
|
|
29
|
+
interactive?: boolean;
|
|
30
|
+
reset?: boolean;
|
|
31
|
+
deployTemplates?: boolean;
|
|
32
|
+
projectRoot?: string;
|
|
33
|
+
reqFile?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ─── Session ──────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
export interface SessionRunOptions {
|
|
39
|
+
logFileName: string;
|
|
40
|
+
logStream?: WriteStream;
|
|
41
|
+
sessionNum?: number;
|
|
42
|
+
label?: string;
|
|
43
|
+
execute: (session: Session) => Promise<Record<string, unknown>>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface QueryResult {
|
|
47
|
+
messages: SDKMessage[];
|
|
48
|
+
success: boolean;
|
|
49
|
+
subtype: string | null;
|
|
50
|
+
cost: number | null;
|
|
51
|
+
usage: { input_tokens: number; output_tokens: number } | null;
|
|
52
|
+
turns: number | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RunQueryOpts {
|
|
56
|
+
onMessage?: (message: SDKMessage, messages: SDKMessage[]) => void | 'break';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SDKMessage {
|
|
60
|
+
type: 'assistant' | 'tool_result' | 'result' | string;
|
|
61
|
+
message?: {
|
|
62
|
+
content?: Array<{
|
|
63
|
+
type: 'text' | 'tool_use' | string;
|
|
64
|
+
text?: string;
|
|
65
|
+
name?: string;
|
|
66
|
+
input?: Record<string, unknown>;
|
|
67
|
+
}>;
|
|
68
|
+
};
|
|
69
|
+
result?: string;
|
|
70
|
+
subtype?: string;
|
|
71
|
+
total_cost_usd?: number;
|
|
72
|
+
usage?: { input_tokens: number; output_tokens: number };
|
|
73
|
+
num_turns?: number;
|
|
74
|
+
is_error?: boolean;
|
|
75
|
+
content?: string;
|
|
76
|
+
[key: string]: unknown;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SessionRunResult {
|
|
80
|
+
exitCode: number;
|
|
81
|
+
logFile: string | null;
|
|
82
|
+
stalled: boolean;
|
|
83
|
+
[key: string]: unknown;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export declare class Session {
|
|
87
|
+
static _sdk: unknown;
|
|
88
|
+
|
|
89
|
+
/** 确保 SDK 已加载(懒加载单例) */
|
|
90
|
+
static ensureSDK(config: Config): Promise<unknown>;
|
|
91
|
+
|
|
92
|
+
/** 创建 Session 实例并执行回调,自动管理生命周期 */
|
|
93
|
+
static run(type: string, config: Config, options: SessionRunOptions): Promise<SessionRunResult>;
|
|
94
|
+
|
|
95
|
+
readonly config: Config;
|
|
96
|
+
readonly type: string;
|
|
97
|
+
readonly indicator: Indicator;
|
|
98
|
+
logStream: WriteStream | null;
|
|
99
|
+
logFile: string | null;
|
|
100
|
+
hooks: unknown;
|
|
101
|
+
abortController: AbortController;
|
|
102
|
+
|
|
103
|
+
constructor(
|
|
104
|
+
type: string,
|
|
105
|
+
config: Config,
|
|
106
|
+
options: { logFileName: string; logStream?: WriteStream; sessionNum?: number; label?: string },
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
/** 构建 SDK query 选项,自动附加 hooks、abortController、权限模式 */
|
|
110
|
+
buildQueryOptions(overrides?: Record<string, unknown>): Record<string, unknown>;
|
|
111
|
+
|
|
112
|
+
/** 执行一次 SDK 查询,遍历消息流并收集结果 */
|
|
113
|
+
runQuery(prompt: string, queryOpts: Record<string, unknown>, opts?: RunQueryOpts): Promise<QueryResult>;
|
|
114
|
+
|
|
115
|
+
/** 检查会话是否因停顿超时 */
|
|
116
|
+
isStalled(): boolean;
|
|
117
|
+
|
|
118
|
+
/** 结束会话:清理 hooks、关闭日志流、停止 indicator */
|
|
119
|
+
finish(): void;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─── Indicator ────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
export declare class Indicator {
|
|
125
|
+
phase: 'thinking' | 'coding';
|
|
126
|
+
step: string;
|
|
127
|
+
toolTarget: string;
|
|
128
|
+
sessionNum: number;
|
|
129
|
+
startTime: number;
|
|
130
|
+
stallTimeoutMin: number;
|
|
131
|
+
toolRunning: boolean;
|
|
132
|
+
currentToolName: string;
|
|
133
|
+
projectRoot: string;
|
|
134
|
+
|
|
135
|
+
/** 启动 indicator 渲染循环 */
|
|
136
|
+
start(sessionNum: number, stallTimeoutMin: number, projectRoot: string): void;
|
|
137
|
+
|
|
138
|
+
/** 停止渲染循环并清除终端行 */
|
|
139
|
+
stop(): void;
|
|
140
|
+
|
|
141
|
+
updatePhase(phase: 'thinking' | 'coding'): void;
|
|
142
|
+
updateStep(step: string): void;
|
|
143
|
+
appendActivity(toolName: string, summary: string): void;
|
|
144
|
+
updateActivity(): void;
|
|
145
|
+
|
|
146
|
+
/** 标记工具开始执行 */
|
|
147
|
+
startTool(name: string): void;
|
|
148
|
+
|
|
149
|
+
/** 标记工具执行结束 */
|
|
150
|
+
endTool(): void;
|
|
151
|
+
|
|
152
|
+
pauseRendering(): void;
|
|
153
|
+
resumeRendering(): void;
|
|
154
|
+
|
|
155
|
+
/** 生成当前状态行文本 */
|
|
156
|
+
getStatusLine(): string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 根据工具名称和输入推断当前阶段和步骤,更新 indicator 状态 */
|
|
160
|
+
export declare function inferPhaseStep(
|
|
161
|
+
indicator: Indicator,
|
|
162
|
+
toolName: string,
|
|
163
|
+
toolInput: Record<string, unknown> | string,
|
|
164
|
+
): void;
|
|
165
|
+
|
|
166
|
+
// ─── Main ─────────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
/** 应用入口:初始化资产、加载配置、分发命令 */
|
|
169
|
+
export declare function main(
|
|
170
|
+
command: string,
|
|
171
|
+
input: string,
|
|
172
|
+
opts?: MainOpts,
|
|
173
|
+
): Promise<unknown>;
|
|
174
|
+
|
|
175
|
+
// ─── Logging ──────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
/** 处理 SDK 消息并写入日志流 */
|
|
178
|
+
export declare function logMessage(
|
|
179
|
+
message: SDKMessage,
|
|
180
|
+
logStream: WriteStream,
|
|
181
|
+
indicator?: Indicator,
|
|
182
|
+
): void;
|
|
183
|
+
|
|
184
|
+
/** 从消息列表中提取 type='result' 的消息 */
|
|
185
|
+
export declare function extractResult(messages: SDKMessage[]): SDKMessage | null;
|
|
186
|
+
|
|
187
|
+
/** 从消息列表中提取结果文本 */
|
|
188
|
+
export declare function extractResultText(messages: SDKMessage[]): string;
|
|
189
|
+
|
|
190
|
+
// ─── AssetManager ─────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
export declare class AssetManager {
|
|
193
|
+
projectRoot: string | null;
|
|
194
|
+
loopDir: string | null;
|
|
195
|
+
assetsDir: string | null;
|
|
196
|
+
bundledDir: string;
|
|
197
|
+
|
|
198
|
+
init(projectRoot?: string): void;
|
|
199
|
+
path(name: string): string | null;
|
|
200
|
+
dir(name: string): string | null;
|
|
201
|
+
exists(name: string): boolean;
|
|
202
|
+
read(name: string): string | null;
|
|
203
|
+
readJson(name: string, fallback?: unknown): unknown;
|
|
204
|
+
write(name: string, content: string): void;
|
|
205
|
+
writeJson(name: string, data: unknown): void;
|
|
206
|
+
render(name: string, vars?: Record<string, string>): string;
|
|
207
|
+
ensureDirs(): void;
|
|
208
|
+
deployAll(): string[];
|
|
209
|
+
deployRecipes(): string[];
|
|
210
|
+
|
|
211
|
+
/** 解析 recipes 目录:先查项目 .claude-coder/recipes/,再 fallback 到 bundled */
|
|
212
|
+
recipesDir(): string;
|
|
213
|
+
|
|
214
|
+
clearCache(): void;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export declare const assets: AssetManager;
|
package/src/core/context.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { loadConfig, buildEnvVars, log } = require('../common/config');
|
|
6
|
-
const { Indicator } = require('../common/indicator');
|
|
7
|
-
const { logMessage: baseLogMessage } = require('../common/logging');
|
|
8
|
-
const { createHooks } = require('./hooks');
|
|
9
|
-
const { assets } = require('../common/assets');
|
|
10
|
-
|
|
11
|
-
class SessionContext {
|
|
12
|
-
constructor(type, opts = {}) {
|
|
13
|
-
this.type = type;
|
|
14
|
-
this.opts = opts;
|
|
15
|
-
assets.init(opts.projectRoot || process.cwd());
|
|
16
|
-
this.config = loadConfig();
|
|
17
|
-
this._applyEnvConfig();
|
|
18
|
-
this.indicator = new Indicator();
|
|
19
|
-
this.logStream = null;
|
|
20
|
-
this.logFile = null;
|
|
21
|
-
this.hooks = null;
|
|
22
|
-
this.cleanup = null;
|
|
23
|
-
this._isStalled = () => false;
|
|
24
|
-
this.abortController = new AbortController();
|
|
25
|
-
this._lastStatusKey = '';
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
_applyEnvConfig() {
|
|
29
|
-
Object.assign(process.env, buildEnvVars(this.config));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
initLogging(logFileName, externalLogStream) {
|
|
33
|
-
if (externalLogStream) {
|
|
34
|
-
this.logStream = externalLogStream;
|
|
35
|
-
this._externalLogStream = true;
|
|
36
|
-
} else {
|
|
37
|
-
const logsDir = assets.dir('logs');
|
|
38
|
-
this.logFile = path.join(logsDir, logFileName);
|
|
39
|
-
this.logStream = fs.createWriteStream(this.logFile, { flags: 'a' });
|
|
40
|
-
this._externalLogStream = false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
initHooks(hookType) {
|
|
45
|
-
const stallTimeoutMs = this.config.stallTimeout * 1000;
|
|
46
|
-
const completionTimeoutMs = this.config.completionTimeout * 1000;
|
|
47
|
-
const result = createHooks(hookType, this.indicator, this.logStream, {
|
|
48
|
-
stallTimeoutMs,
|
|
49
|
-
abortController: this.abortController,
|
|
50
|
-
editThreshold: this.config.editThreshold,
|
|
51
|
-
completionTimeoutMs,
|
|
52
|
-
});
|
|
53
|
-
this.hooks = result.hooks;
|
|
54
|
-
this.cleanup = result.cleanup;
|
|
55
|
-
this._isStalled = result.isStalled;
|
|
56
|
-
return Math.floor(stallTimeoutMs / 60000);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
startIndicator(sessionNum, stallTimeoutMin) {
|
|
60
|
-
this.indicator.start(sessionNum, stallTimeoutMin);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
isStalled() {
|
|
64
|
-
return this._isStalled();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async runQuery(sdk, prompt, queryOpts) {
|
|
68
|
-
const collected = [];
|
|
69
|
-
const session = sdk.query({ prompt, options: queryOpts });
|
|
70
|
-
|
|
71
|
-
for await (const message of session) {
|
|
72
|
-
if (this._isStalled()) {
|
|
73
|
-
log('warn', '停顿超时,中断消息循环');
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
collected.push(message);
|
|
77
|
-
this._logMessage(message);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return collected;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
_logMessage(message) {
|
|
84
|
-
const hasText = message.type === 'assistant'
|
|
85
|
-
&& message.message?.content?.some(b => b.type === 'text' && b.text);
|
|
86
|
-
|
|
87
|
-
if (hasText && this.indicator) {
|
|
88
|
-
this.indicator.pauseRendering();
|
|
89
|
-
process.stderr.write('\r\x1b[K');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
baseLogMessage(message, this.logStream, this.indicator);
|
|
93
|
-
|
|
94
|
-
if (hasText && this.indicator) {
|
|
95
|
-
const contentKey = `${this.indicator.phase}|${this.indicator.step}|${this.indicator.toolTarget}`;
|
|
96
|
-
if (contentKey !== this._lastStatusKey) {
|
|
97
|
-
this._lastStatusKey = contentKey;
|
|
98
|
-
const statusLine = this.indicator.getStatusLine();
|
|
99
|
-
if (statusLine) process.stderr.write(statusLine + '\n');
|
|
100
|
-
}
|
|
101
|
-
this.indicator.resumeRendering();
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
finish() {
|
|
106
|
-
if (this.cleanup) this.cleanup();
|
|
107
|
-
if (this.logStream && !this._externalLogStream) this.logStream.end();
|
|
108
|
-
this.indicator.stop();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
errorFinish(err) {
|
|
112
|
-
this.finish();
|
|
113
|
-
log('error', err.message);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
module.exports = { SessionContext };
|