@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.
- package/LICENSE +21 -0
- package/README.md +87 -52
- package/cli/bin/loom.js +438 -149
- package/cli/help/concepts.md +93 -72
- package/cli/help/doctor.md +71 -121
- package/cli/help/loop.md +120 -135
- package/cli/help/patch.md +33 -0
- package/cli/help/preview.md +2 -1
- package/cli/help/version.md +92 -16
- package/cli/help/workflow.md +89 -100
- package/cli/src/activate.js +302 -73
- package/cli/src/auto.js +41 -18
- package/cli/src/diagnostics.js +223 -50
- package/cli/src/guide.js +127 -38
- package/cli/src/init.js +50 -29
- package/cli/src/intent-draft.js +303 -0
- package/cli/src/intent-map.js +540 -54
- package/cli/src/patch.js +214 -0
- package/cli/src/philosophy.js +181 -156
- package/cli/src/preview-prompt.md +13 -6
- package/cli/src/preview.js +1 -0
- package/cli/src/shared/intent-ref.js +38 -0
- package/cli/src/shared/proof-reference.js +19 -0
- package/cli/src/shared/verification-method.js +32 -0
- package/cli/src/verify.js +204 -51
- package/cli/src/version.js +5 -4
- package/dimensions/PART_DECOMPOSITION.md +42 -203
- package/dimensions/SEARCH_METHODOLOGY.md +101 -97
- package/dimensions/examples/AGENT_SYSTEM/README.md +1 -1
- package/dimensions/examples/CLI_TOOL/README.md +1 -1
- package/dimensions/universal/COLLABORATION_PHILOSOPHY.md +28 -77
- package/dimensions/universal/ENGINEERING_CREED.md +30 -74
- package/dimensions/universal/PRODUCT_PHILOSOPHY.md +32 -70
- package/meta/BASELINE.md +91 -276
- package/meta/INTENT_LOOP.md +242 -737
- package/meta/PHILOSOPHY_WEAVER.md +110 -343
- package/meta/ROLE_ACTIVATION.md +103 -267
- package/package.json +4 -3
- package/roles/architect.md +71 -111
- package/roles/forge.md +87 -126
- package/roles/keeper.md +99 -223
- package/roles/visionary.md +57 -86
- package/templates/INTENT_MAP_TEMPLATE.json +24 -10
- package/templates/PHILOSOPHY_TEMPLATE.md +44 -75
- package/templates/VISION_TEMPLATE.md +44 -67
package/cli/src/activate.js
CHANGED
|
@@ -1,73 +1,302 @@
|
|
|
1
|
-
// activate —
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const VALID_ROLES = ['weaver', 'visionary', 'architect', 'forge', 'keeper'];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
2
|
+
// 存储机制:.loom/auto 文件内容 = 模式名(auto-loop / auto-design),不存在 = manual
|
|
3
3
|
// 心跳:每次 guide 调用时写 .loom/heartbeat.json(时间戳 + stage + next_command)
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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 {{
|
|
62
|
+
* @returns {{ mode: string, heartbeat: object|null }}
|
|
40
63
|
*/
|
|
41
64
|
export function autoStatus(loomRoot) {
|
|
42
|
-
const
|
|
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 {
|
|
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
|
-
*
|
|
83
|
-
*
|
|
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
|
|
90
|
-
if (
|
|
91
|
-
|
|
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
|
}
|