@haaaiawd/loom 0.9.0 → 1.0.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -52
  3. package/cli/bin/loom.js +438 -149
  4. package/cli/help/concepts.md +93 -72
  5. package/cli/help/doctor.md +71 -121
  6. package/cli/help/loop.md +120 -135
  7. package/cli/help/patch.md +33 -0
  8. package/cli/help/preview.md +2 -1
  9. package/cli/help/version.md +92 -16
  10. package/cli/help/workflow.md +89 -100
  11. package/cli/src/activate.js +302 -73
  12. package/cli/src/auto.js +41 -18
  13. package/cli/src/diagnostics.js +223 -50
  14. package/cli/src/guide.js +127 -38
  15. package/cli/src/init.js +50 -29
  16. package/cli/src/intent-draft.js +303 -0
  17. package/cli/src/intent-map.js +540 -54
  18. package/cli/src/patch.js +214 -0
  19. package/cli/src/philosophy.js +181 -156
  20. package/cli/src/preview-prompt.md +13 -6
  21. package/cli/src/preview.js +1 -0
  22. package/cli/src/shared/intent-ref.js +38 -0
  23. package/cli/src/shared/proof-reference.js +19 -0
  24. package/cli/src/shared/verification-method.js +32 -0
  25. package/cli/src/verify.js +204 -51
  26. package/cli/src/version.js +5 -4
  27. package/dimensions/PART_DECOMPOSITION.md +42 -203
  28. package/dimensions/SEARCH_METHODOLOGY.md +101 -97
  29. package/dimensions/examples/AGENT_SYSTEM/README.md +1 -1
  30. package/dimensions/examples/CLI_TOOL/README.md +1 -1
  31. package/dimensions/universal/COLLABORATION_PHILOSOPHY.md +28 -77
  32. package/dimensions/universal/ENGINEERING_CREED.md +30 -74
  33. package/dimensions/universal/PRODUCT_PHILOSOPHY.md +32 -70
  34. package/meta/BASELINE.md +91 -276
  35. package/meta/INTENT_LOOP.md +242 -737
  36. package/meta/PHILOSOPHY_WEAVER.md +110 -343
  37. package/meta/ROLE_ACTIVATION.md +103 -267
  38. package/package.json +4 -3
  39. package/roles/architect.md +71 -111
  40. package/roles/forge.md +87 -126
  41. package/roles/keeper.md +99 -223
  42. package/roles/visionary.md +57 -86
  43. package/templates/INTENT_MAP_TEMPLATE.json +24 -10
  44. package/templates/PHILOSOPHY_TEMPLATE.md +44 -75
  45. package/templates/VISION_TEMPLATE.md +44 -67
@@ -1,73 +1,302 @@
1
- // activate — 输出角色激活提示词
2
- // 把角色文件 + 哲学锚点 + 底线拼成激活上下文,供上层编排器使用。
3
-
4
- import { readFileSync, existsSync } from 'node:fs';
5
- import { join, resolve, dirname } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
- import { getLoomRoot } from './shared/paths.js';
8
-
9
- const __dirname = dirname(fileURLToPath(import.meta.url));
10
-
11
- /** 合法角色名 */
12
- const VALID_ROLES = ['weaver', 'visionary', 'architect', 'forge', 'keeper'];
13
-
14
- /** 角色文件映射 */
15
- const ROLE_FILES = {
16
- weaver: 'meta/PHILOSOPHY_WEAVER.md',
17
- visionary: 'roles/visionary.md',
18
- architect: 'roles/architect.md',
19
- forge: 'roles/forge.md',
20
- keeper: 'roles/keeper.md',
21
- };
22
-
23
- /**
24
- * 输出角色激活提示词。
25
- * @param {string} role — 角色名
26
- * @param {string} versionDir — .loom/v{N} 目录(可选,weaver 不需要)
27
- * @returns {string} 激活提示词
28
- */
29
- export function activateRole(role, versionDir) {
30
- if (!VALID_ROLES.includes(role)) {
31
- throw new Error(`未知角色: ${role}\n合法角色: ${VALID_ROLES.join(', ')}`);
32
- }
33
-
34
- const loomRoot = getLoomRoot();
35
- const parts = [];
36
-
37
- // 1. 角色文件
38
- const roleFile = join(loomRoot, ROLE_FILES[role]);
39
- if (!existsSync(roleFile)) {
40
- throw new Error(`角色文件不存在: ${roleFile}`);
41
- }
42
- parts.push(readFileSync(roleFile, 'utf-8'));
43
-
44
- // 2. BASELINE 摘要(不重复全文——全文见 meta/BASELINE.md)
45
- // 5 条底线压缩成摘要,角色需要知道底线存在 + 一句话内容。
46
- // 如果角色需要底线细节(如 Weaver 织造哲学时),自行 readFileSync 全文。
47
- parts.push('\n---\n\n## 强制加载:BASELINE 摘要\n\n');
48
- parts.push('> 完整底线见 `meta/BASELINE.md`。以下是 5 条底线的摘要——\n');
49
- parts.push('> 角色激活时必须知道这些底线存在,违反任何一条必须立即停止。\n');
50
- parts.push('> Philosophy Weaver 织造哲学时必须读取完整 BASELINE.md 作为硬约束输入。\n\n');
51
- parts.push('| 编号 | 底线 | 一句话 |\n');
52
- parts.push('|------|------|--------|\n');
53
- parts.push('| B1 | 必须有结构设计 | 编码前必须有明确的目录结构 + 模块职责边界 + 显式依赖关系 |\n');
54
- parts.push('| B2 | 禁止硬编码 | 密钥/配置/环境特定值/魔法数字不进代码,用环境变量或集中配置 |\n');
55
- parts.push('| B3 | 接口契约必须显式 | API/CLI/配置/错误语义/跨系统协议必须有显式定义,变更可追溯 |\n');
56
- parts.push('| B4 | 决策必须可追溯 | 影响架构/接口/技术栈/依赖的决策必须记录(ADR 或等效格式) |\n');
57
- parts.push('| B5 | 意图必须可回溯 | 每个实现单元有意图叙事("为什么存在"),可被 Keeper 引用对照 |\n');
58
- parts.push('\n> 底线不可被哲学覆盖。如果织造的哲学与底线冲突,底线优先。\n');
59
-
60
- // 3. 项目特定底线(如果有 versionDir)
61
- if (versionDir) {
62
- const projectBaseline = join(versionDir, '00_PHILOSOPHY/PROJECT_BASELINE.md');
63
- if (existsSync(projectBaseline)) {
64
- parts.push('\n---\n\n## 强制加载:项目特定底线\n\n' + readFileSync(projectBaseline, 'utf-8'));
65
- }
66
-
67
- // 4. 角色激活协议
68
- const activationFile = join(loomRoot, 'meta/ROLE_ACTIVATION.md');
69
- parts.push('\n---\n\n## 角色激活协议\n\n' + readFileSync(activationFile, 'utf-8'));
70
- }
71
-
72
- return parts.join('\n');
73
- }
1
+ // activate — compile a role- and intent-scoped Context Pack.
2
+
3
+ import { readFileSync, existsSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { getLoomRoot } from './shared/paths.js';
6
+ import { getIntent, getNarrative } from './intent-map.js';
7
+ import { getIntentDraft } from './intent-draft.js';
8
+ import { getPhilosophy } from './philosophy.js';
9
+ import { getVerificationContract } from './verify.js';
10
+ import { extractMdSection } from './shared/md-utils.js';
11
+
12
+ const VALID_ROLES = ['weaver', 'visionary', 'architect', 'forge', 'keeper'];
13
+
14
+ const ROLE_FILES = {
15
+ weaver: 'meta/PHILOSOPHY_WEAVER.md',
16
+ visionary: 'roles/visionary.md',
17
+ architect: 'roles/architect.md',
18
+ forge: 'roles/forge.md',
19
+ keeper: 'roles/keeper.md',
20
+ };
21
+
22
+ const ROLE_PHILOSOPHY_FILES = {
23
+ visionary: ['PRODUCT_PHILOSOPHY.md', 'DECISION_RUBRIC.md'],
24
+ architect: ['ENGINEERING_CREED.md', 'DECISION_RUBRIC.md'],
25
+ forge: ['ENGINEERING_CREED.md'],
26
+ keeper: ['PRODUCT_PHILOSOPHY.md', 'DECISION_RUBRIC.md'],
27
+ };
28
+
29
+ const BASELINE_SUMMARY = [
30
+ '- B1:实质修改前理解真实结构,不创建平行体系。',
31
+ '- B2:秘密、环境值和可变配置不写死。',
32
+ '- B3:用户或系统可观察的行为具有显式契约。',
33
+ '- B4:重要且会影响未来的判断可追溯。',
34
+ '- B5:完成关联原始意图和当前 revision 证据;质量提升具有基线相对证据。',
35
+ ].join('\n');
36
+
37
+ function section(title, body) {
38
+ const content = String(body || '').trim();
39
+ return `## ${title}\n\n${content || '无。'}`;
40
+ }
41
+
42
+ function readRole(role) {
43
+ const filePath = join(getLoomRoot(), ROLE_FILES[role]);
44
+ if (!existsSync(filePath)) throw new Error(`角色文件不存在: ${filePath}`);
45
+ return readFileSync(filePath, 'utf-8');
46
+ }
47
+
48
+ function getVerificationMethod(intent) {
49
+ return intent?.verification_method || intent?._optional?.verification_method || null;
50
+ }
51
+
52
+ function resolveContractReference(versionDir, value, label) {
53
+ if (!value) return null;
54
+ if (typeof value !== 'string') return JSON.stringify(value, null, 2);
55
+ const match = value.trim().match(/^(?:see\s+)?([^#]+)#([\w-]+)$/i);
56
+ if (!match) return value;
57
+ const [, file, anchor] = match;
58
+ if (file.includes('/') || file.includes('\\') || file !== '05_VERIFICATION.md') {
59
+ throw new Error(`${label}引用必须位于 05_VERIFICATION.md: ${value}`);
60
+ }
61
+ const filePath = join(versionDir, file);
62
+ if (!existsSync(filePath)) throw new Error(`${label}引用的文件不存在: ${filePath}`);
63
+ return extractMdSection(readFileSync(filePath, 'utf-8'), anchor, label);
64
+ }
65
+
66
+ function compileEnvelope(role, intentId) {
67
+ const scope = intentId ? `仅 ${intentId}` : '当前角色的项目阶段';
68
+ const lines = [
69
+ `- role: ${role}`,
70
+ `- scope: ${scope}`,
71
+ '- 本 Context Pack 不会清除现有会话记忆。',
72
+ '- system、developer 与用户指令优先;项目事实冲突时报告,不静默混用。',
73
+ '- 只在当前角色权限和明确作用域内行动。',
74
+ ];
75
+ if (role === 'keeper') {
76
+ lines.push('- Keeper 必须在新的 Agent thread 中运行;同一会话切换角色不构成独立验证。');
77
+ lines.push('- 无法获得独立上下文时降低声明,关键判断使用 pending_human。');
78
+ }
79
+ return lines.join('\n');
80
+ }
81
+
82
+ function compileObjective(role, versionDir, intentId) {
83
+ if (!intentId) {
84
+ return {
85
+ body: role === 'weaver'
86
+ ? '织造当前项目的 Project Doctrine。先读取真实项目与完整 BASELINE,不预写产品或架构。'
87
+ : `履行 ${role} 的当前项目阶段职责;未指定 Intent,不得自行选择并处理实现任务。`,
88
+ intent: null,
89
+ draft: null,
90
+ narrative: null,
91
+ };
92
+ }
93
+
94
+ if (role === 'visionary' || role === 'architect') {
95
+ const draft = getIntentDraft(versionDir, intentId);
96
+ const match = draft.narrative_ref.match(/^([^#]+)#([\w-]+)$/);
97
+ if (!match || match[1] !== '01_VISION.md') throw new Error(`draft narrative_ref 非法: ${draft.narrative_ref}`);
98
+ const narrative = extractMdSection(
99
+ readFileSync(join(versionDir, match[1]), 'utf-8'),
100
+ match[2],
101
+ 'draft 意图叙事',
102
+ );
103
+ const visibleDraft = role === 'visionary'
104
+ ? {
105
+ id: draft.id,
106
+ revision: draft.revision,
107
+ title: draft.title,
108
+ narrative_ref: draft.narrative_ref,
109
+ depends_on: draft.depends_on,
110
+ }
111
+ : draft;
112
+ return {
113
+ body: [
114
+ '只处理下面这个 draft。不要修改官方 Intent Map,不要处理其他 Intent,不要自行 finalize。',
115
+ '',
116
+ '### Draft',
117
+ '',
118
+ '```json',
119
+ JSON.stringify(visibleDraft, null, 2),
120
+ '```',
121
+ '',
122
+ '### Narrative',
123
+ '',
124
+ narrative,
125
+ ].join('\n'),
126
+ intent: null,
127
+ draft,
128
+ narrative,
129
+ };
130
+ }
131
+
132
+ const intent = getIntent(versionDir, intentId);
133
+ const narrative = getNarrative(versionDir, intentId);
134
+ const objectiveView = {
135
+ id: intent.id,
136
+ revision: intent.revision,
137
+ title: intent.title,
138
+ status: intent.status,
139
+ narrative_ref: intent.narrative_ref,
140
+ depends_on: intent.depends_on,
141
+ };
142
+ return {
143
+ body: [
144
+ '只实现或验证下面这个官方 Intent。不得加载或顺便处理其他 Intent。',
145
+ '',
146
+ '### Intent',
147
+ '',
148
+ '```json',
149
+ JSON.stringify(objectiveView, null, 2),
150
+ '```',
151
+ '',
152
+ '### Narrative',
153
+ '',
154
+ narrative,
155
+ ].join('\n'),
156
+ intent,
157
+ draft: null,
158
+ narrative,
159
+ };
160
+ }
161
+
162
+ function compileInvariants(role, versionDir) {
163
+ const blocks = [];
164
+ const baselinePath = join(getLoomRoot(), 'meta/BASELINE.md');
165
+ blocks.push(role === 'weaver' ? readFileSync(baselinePath, 'utf-8') : BASELINE_SUMMARY);
166
+ if (versionDir) {
167
+ const projectBaseline = join(versionDir, '00_PHILOSOPHY', 'PROJECT_BASELINE.md');
168
+ if (existsSync(projectBaseline)) {
169
+ blocks.push(`### Project Baseline\n\n${readFileSync(projectBaseline, 'utf-8')}`);
170
+ }
171
+ }
172
+ return blocks.join('\n\n');
173
+ }
174
+
175
+ function compileContracts(role, versionDir, objective) {
176
+ const subject = objective.intent || objective.draft;
177
+ if (!subject || role === 'visionary') {
178
+ return role === 'visionary'
179
+ ? 'Visionary 只定义目标、非目标和 narrative;不要编写 acceptance 或架构。'
180
+ : '按当前角色 Output Contract 交付。';
181
+ }
182
+
183
+ const blocks = [];
184
+ if (subject.acceptance) {
185
+ const acceptance = objective.intent
186
+ ? getVerificationContract(versionDir, subject.id)
187
+ : resolveContractReference(versionDir, subject.acceptance, 'draft 完成契约');
188
+ blocks.push(`### Acceptance / Reliability Floor\n\n${acceptance}`);
189
+ }
190
+ if (subject.quality_contract) {
191
+ blocks.push(
192
+ `### Quality Contract / Distinctive Ceiling\n\n` +
193
+ resolveContractReference(versionDir, subject.quality_contract, '质量契约'),
194
+ );
195
+ }
196
+ const verificationMethod = getVerificationMethod(subject);
197
+ blocks.push(`### Verification Method\n\n${verificationMethod || '未声明:Keeper 使用适当的静态检查;需要运行时或人类判断却缺少入口时回流 Architect。'}`);
198
+ const continuity = subject.continuity_required
199
+ ? '此 Intent 已启用状态守恒门:通过前必须证明“旧状态 → 本轮操作 → 新状态”的完整序列中,未获明确授权删除或替换的既有价值、数据和可见结果仍被保留。'
200
+ : '默认不启用状态守恒门;若本 Intent 会变更既有用户数据、持久状态、迁移结果或既有工作流,Architect 必须把 continuity_required 设为 true,并在 acceptance 写出保留项与时序验证。';
201
+ blocks.push(
202
+ '### Completion Gate / Codex Goal Alignment\n\n' +
203
+ '将当前 Intent 视为本轮 Codex goal 的可闭合单元。goal 保持 active,直到结果达成、适用的状态守恒和可复现证据同时成立;goal/status 只是运行记录,不能替代验证证据。\n\n' +
204
+ continuity,
205
+ );
206
+ return blocks.join('\n\n');
207
+ }
208
+
209
+ function compileProjectJudgment(role, versionDir, objective) {
210
+ if (!versionDir || role === 'weaver') {
211
+ return role === 'weaver'
212
+ ? '从用户目标、仓库事实与决策相关证据建立 Project Doctrine。'
213
+ : '当前版本目录不可用。';
214
+ }
215
+
216
+ const philosophyDir = join(versionDir, '00_PHILOSOPHY');
217
+ const blocks = [];
218
+ const missing = [];
219
+ const anchors = (objective.intent || objective.draft)?.philosophy_anchors || [];
220
+
221
+ if (anchors.length > 0) {
222
+ for (const anchor of anchors) {
223
+ blocks.push(`### ${anchor}\n\n${getPhilosophy(philosophyDir, anchor)}`);
224
+ }
225
+ } else {
226
+ for (const file of ROLE_PHILOSOPHY_FILES[role] || []) {
227
+ const filePath = join(philosophyDir, file);
228
+ if (!existsSync(filePath)) {
229
+ missing.push(file);
230
+ continue;
231
+ }
232
+ blocks.push(`### ${file}\n\n${readFileSync(filePath, 'utf-8')}`);
233
+ }
234
+ if (missing.length) {
235
+ blocks.push(
236
+ `### Context Warning\n\n缺少: ${missing.join(', ')}。` +
237
+ '不要用通用偏好伪造项目判断;回流 Weaver 或向用户索取必要决定。',
238
+ );
239
+ }
240
+ }
241
+ return blocks.join('\n\n');
242
+ }
243
+
244
+ function compileExpertiseInputs(role, objective) {
245
+ const subject = objective.intent || objective.draft;
246
+ if (!subject || !['architect', 'forge', 'keeper'].includes(role)) {
247
+ return '当前阶段不编译任务级 Expertise Pack。';
248
+ }
249
+ const needs = Array.isArray(subject.capability_needs) ? subject.capability_needs : [];
250
+ const lines = [
251
+ `- capability_needs: ${needs.length ? needs.join(', ') : '未声明;按当前任务发现必要能力'}`,
252
+ `- creative_scope: ${subject.creative_scope || '未声明;遵循最小完整干预'}`,
253
+ '- Skill、工具和资产名称只代表可发现入口;实际检查并加载后才进入 Expertise Pack。',
254
+ ];
255
+ if (role === 'keeper') {
256
+ lines.push('- 不继承 Forge Expertise Pack;按契约独立准备验证能力。');
257
+ }
258
+ return lines.join('\n');
259
+ }
260
+
261
+ function compileWorkingFacts(versionDir, objective) {
262
+ if (!versionDir) return '检查当前仓库、用户输入和可用工具。';
263
+ const subject = objective.intent || objective.draft;
264
+ const refs = [
265
+ '- architecture: `.loom/.../02_ARCHITECTURE.md`(只读取与当前决定相关部分)',
266
+ '- artifacts: 从真实工作区检查,不从会话记忆猜测。',
267
+ ];
268
+ const systemId = subject?._optional?.system_id || subject?.system_id;
269
+ if (systemId) refs.push(`- system_id: ${systemId}`);
270
+ if (subject?.quality_contract) refs.push('- baseline: 声明相对提升时,修改前证据必须在实现前保存。');
271
+ return refs.join('\n');
272
+ }
273
+
274
+ /**
275
+ * Compile a role-scoped Context Pack.
276
+ * @param {string} role
277
+ * @param {string | null} versionDir
278
+ * @param {string | null} intentId
279
+ */
280
+ export function activateRole(role, versionDir, intentId = null) {
281
+ if (!VALID_ROLES.includes(role)) {
282
+ throw new Error(`未知角色: ${role}\n合法角色: ${VALID_ROLES.join(', ')}`);
283
+ }
284
+ if (intentId && !versionDir) throw new Error('--intent 需要当前 LOOM 版本');
285
+ if (intentId && !['visionary', 'architect', 'forge', 'keeper'].includes(role)) {
286
+ throw new Error(`角色 ${role} 不支持 --intent;draft 用 visionary/architect,官方 Intent 用 forge/keeper`);
287
+ }
288
+
289
+ const objective = compileObjective(role, versionDir, intentId);
290
+ const parts = [
291
+ '# LOOM Context Pack',
292
+ section('1. Execution Envelope', compileEnvelope(role, intentId)),
293
+ section('2. Active Objective', objective.body),
294
+ section('3. Hard Invariants', compileInvariants(role, versionDir)),
295
+ section('4. Success Contracts', compileContracts(role, versionDir, objective)),
296
+ section('5. Project Judgment', compileProjectJudgment(role, versionDir, objective)),
297
+ section('6. Expertise Inputs', compileExpertiseInputs(role, objective)),
298
+ section('7. Working Facts', compileWorkingFacts(versionDir, objective)),
299
+ section('8. Role Contract / Output / Reflow / Stop', readRole(role)),
300
+ ];
301
+ return `${parts.join('\n\n---\n\n')}\n`;
302
+ }
package/cli/src/auto.js CHANGED
@@ -1,31 +1,54 @@
1
1
  // auto — AUTO 模式开关 + 心跳机制
2
- // 存储机制:.loom/auto 文件存在 = on,不存在 = off
2
+ // 存储机制:.loom/auto 文件内容 = 模式名(auto-loop / auto-design),不存在 = manual
3
3
  // 心跳:每次 guide 调用时写 .loom/heartbeat.json(时间戳 + stage + next_command)
4
- // AUTO on(默认):stage 4+(Intent Loop)自动跑,stage 1-3(哲学/愿景/架构)仍需人类 review
5
- // AUTO off:所有阶段都需人类确认,每步拆得更碎
4
+ //
5
+ // 三种模式:
6
+ // manual — 每步停,所有阶段需人类 review
7
+ // auto-loop — 只 Intent Loop(stage 4+)自动,stage 1-3 仍需人类 review(默认)
8
+ // auto-design — 哲学/愿景/架构也自动,全部阶段不需人类 review
6
9
 
7
10
  import { existsSync, writeFileSync, unlinkSync, readFileSync } from 'node:fs';
8
11
  import { join } from 'node:path';
9
12
 
13
+ const VALID_MODES = ['auto-loop', 'auto-design'];
14
+
10
15
  /**
11
- * 检查 AUTO 模式是否开启。
16
+ * 读取 .loom/auto 文件内容,返回模式名。
17
+ * 向后兼容:空文件 / 旧时间戳 / 未知内容 → auto-loop
18
+ * @param {string} loomRoot — .loom 目录路径
19
+ * @returns {string} 'manual' | 'auto-loop' | 'auto-design'
20
+ */
21
+ export function getAutoMode(loomRoot) {
22
+ const path = join(loomRoot, 'auto');
23
+ if (!existsSync(path)) return 'manual';
24
+ const content = readFileSync(path, 'utf-8').trim();
25
+ if (VALID_MODES.includes(content)) return content;
26
+ return 'auto-loop'; // 旧格式兼容
27
+ }
28
+
29
+ /**
30
+ * 检查 AUTO 模式是否开启(非 manual)。
12
31
  * @param {string} loomRoot — .loom 目录路径
13
32
  * @returns {boolean}
14
33
  */
15
34
  export function isAutoOn(loomRoot) {
16
- return existsSync(join(loomRoot, 'auto'));
35
+ return getAutoMode(loomRoot) !== 'manual';
17
36
  }
18
37
 
19
38
  /**
20
39
  * 开启 AUTO 模式。
21
40
  * @param {string} loomRoot — .loom 目录路径
41
+ * @param {string} mode — 'auto-loop' | 'auto-design'
22
42
  */
23
- export function autoOn(loomRoot) {
24
- writeFileSync(join(loomRoot, 'auto'), new Date().toISOString(), 'utf-8');
43
+ export function autoOn(loomRoot, mode = 'auto-loop') {
44
+ if (!VALID_MODES.includes(mode)) {
45
+ throw new Error(`非法 AUTO 模式: "${mode}" (合法: ${VALID_MODES.join(' | ')})`);
46
+ }
47
+ writeFileSync(join(loomRoot, 'auto'), mode, 'utf-8');
25
48
  }
26
49
 
27
50
  /**
28
- * 关闭 AUTO 模式。
51
+ * 关闭 AUTO 模式(切换到 manual)。
29
52
  * @param {string} loomRoot — .loom 目录路径
30
53
  */
31
54
  export function autoOff(loomRoot) {
@@ -36,14 +59,12 @@ export function autoOff(loomRoot) {
36
59
  /**
37
60
  * 获取 AUTO 状态描述。
38
61
  * @param {string} loomRoot — .loom 目录路径
39
- * @returns {{ on: boolean, since: string|null, heartbeat: object|null }}
62
+ * @returns {{ mode: string, heartbeat: object|null }}
40
63
  */
41
64
  export function autoStatus(loomRoot) {
42
- const path = join(loomRoot, 'auto');
43
- if (!existsSync(path)) return { on: false, since: null, heartbeat: null };
44
- const since = readFileSync(path, 'utf-8').trim();
65
+ const mode = getAutoMode(loomRoot);
45
66
  const heartbeat = readHeartbeat(loomRoot);
46
- return { on: true, since, heartbeat };
67
+ return { mode, heartbeat };
47
68
  }
48
69
 
49
70
  /**
@@ -79,15 +100,17 @@ export function readHeartbeat(loomRoot) {
79
100
 
80
101
  /**
81
102
  * 判断当前阶段是否需要人类 review。
82
- * AUTO on 时:stage 1-3(哲学/愿景/架构)需要人类 review,stage 4+ 自动跑
83
- * AUTO off 时:所有阶段都需要人类 review
103
+ * manual:全部需 review
104
+ * auto-loop:stage 1-3 review,stage 4+ 自动
105
+ * auto-design:全部自动
84
106
  * @param {string} loomRoot — .loom 目录路径
85
107
  * @param {number} stageNum — 阶段号
86
108
  * @returns {boolean} 是否需要人类 review
87
109
  */
88
110
  export function needsHumanReview(loomRoot, stageNum) {
89
- const autoOn = isAutoOn(loomRoot);
90
- if (!autoOn) return true; // AUTO off:所有阶段都需人类 review
91
- // AUTO on:stage 1-3 需人类 review,stage 4+ 自动
111
+ const mode = getAutoMode(loomRoot);
112
+ if (mode === 'manual') return true;
113
+ if (mode === 'auto-design') return false;
114
+ // auto-loop:stage 1-3 需 review,stage 4+ 自动
92
115
  return stageNum > 0 && stageNum < 4;
93
116
  }