@haaaiawd/loom 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.
@@ -0,0 +1,465 @@
1
+ #!/usr/bin/env node
2
+ // loom — LOOM 框架的 CLI 传感器层
3
+ // Agent 通过这个 CLI 访问 Intent Map / 哲学 / 验证记录,不直接读文件。
4
+
5
+ import { argv, cwd, exit } from 'node:process';
6
+ import { resolve, join } from 'node:path';
7
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
8
+
9
+ import { getNextIntent, getStatus, getDependencyGraph, getIntent, loadIntentMap, updateIntentStatus, getNarrative } from '../src/intent-map.js';
10
+ import { getPhilosophy, listPhilosophyFiles } from '../src/philosophy.js';
11
+ import { writeVerification, getVerificationHistory, getPendingVerifications, listVerifications, getVerificationContract } from '../src/verify.js';
12
+ import { initProject } from '../src/init.js';
13
+ import { activateRole } from '../src/activate.js';
14
+ import { listVersions, readCurrentPointer, newVersion, useVersion, diffVersions } from '../src/version.js';
15
+ import { doctor, contextSummary, traceIntent, reverseDep, reverseRef } from '../src/diagnostics.js';
16
+ import { getHelpTopic, listHelpTopics } from '../src/help.js';
17
+ import { guideProject } from '../src/guide.js';
18
+ import { isAutoOn, autoOn, autoOff, autoStatus } from '../src/auto.js';
19
+ import { generatePreviewPrompt } from '../src/preview.js';
20
+
21
+ // ─── 路径解析 ──────────────────────────────────────────
22
+ // LOOM 项目目录结构: .loom/v{N}/
23
+ // 优先读 .loom/current 指针;不存在则回退到自动探测最新版本。
24
+ // 也接受 --loom-dir 参数直接指定版本目录。
25
+
26
+ function findLoomRoot() {
27
+ const flagIdx = argv.indexOf('--loom-dir');
28
+ if (flagIdx !== -1 && argv[flagIdx + 1]) {
29
+ // --loom-dir 直接指向版本目录,反推 .loom root
30
+ const dir = resolve(argv[flagIdx + 1]);
31
+ return resolve(dir, '..');
32
+ }
33
+ return join(cwd(), '.loom');
34
+ }
35
+
36
+ function findLoomDir() {
37
+ const flagIdx = argv.indexOf('--loom-dir');
38
+ if (flagIdx !== -1 && argv[flagIdx + 1]) {
39
+ return resolve(argv[flagIdx + 1]);
40
+ }
41
+ const loomRoot = join(cwd(), '.loom');
42
+ if (!existsSync(loomRoot)) {
43
+ die(`找不到 .loom 目录: ${loomRoot}`);
44
+ }
45
+ const current = readCurrentPointer(loomRoot);
46
+ if (!current) {
47
+ die(`.loom 下没有版本目录 (v1, v2, ...)`);
48
+ }
49
+ return join(loomRoot, current);
50
+ }
51
+
52
+ function getPhilosophyDir(loomDir) {
53
+ return join(loomDir, '00_PHILOSOPHY');
54
+ }
55
+
56
+ function getVerificationsDir(loomDir) {
57
+ return join(loomDir, 'verifications');
58
+ }
59
+
60
+ // ─── 输出工具 ──────────────────────────────────────────
61
+
62
+ function output(data) {
63
+ if (typeof data === 'string') {
64
+ console.log(data);
65
+ } else {
66
+ console.log(JSON.stringify(data, null, 2));
67
+ }
68
+ }
69
+
70
+ function die(msg, code = 1) {
71
+ console.error(`错误: ${msg}`);
72
+ exit(code);
73
+ }
74
+
75
+ // ─── 命令路由 ──────────────────────────────────────────
76
+
77
+ const [cmd, sub, ...rest] = argv.slice(2);
78
+
79
+ try {
80
+ switch (cmd) {
81
+ case 'intent': {
82
+ const loomDir = findLoomDir();
83
+ switch (sub) {
84
+ case 'next':
85
+ output(getNextIntent(loomDir) ?? '没有可执行的 Intent');
86
+ break;
87
+ case 'status': {
88
+ const s = getStatus(loomDir);
89
+ console.log(`进度: ${s.counts.completed}/${s.counts.total} 完成`);
90
+ console.log(` pending: ${s.counts.pending} ${s.ids.pending.join(', ') || '-'}`);
91
+ console.log(` in_progress: ${s.counts.in_progress} ${s.ids.in_progress.join(', ') || '-'}`);
92
+ console.log(` completed: ${s.counts.completed} ${s.ids.completed.join(', ') || '-'}`);
93
+ console.log(` blocked: ${s.counts.blocked} ${s.ids.blocked.join(', ') || '-'}`);
94
+ break;
95
+ }
96
+ case 'graph':
97
+ output(getDependencyGraph(loomDir));
98
+ break;
99
+ case 'get': {
100
+ const id = rest[0];
101
+ if (!id) die('用法: loom intent get <id>');
102
+ output(getIntent(loomDir, id));
103
+ break;
104
+ }
105
+ case 'narrative': {
106
+ const id = rest[0];
107
+ if (!id) die('用法: loom intent narrative <id>');
108
+ output(getNarrative(loomDir, id));
109
+ break;
110
+ }
111
+ case 'validate':
112
+ loadIntentMap(loomDir);
113
+ console.log('Intent Map 校验通过');
114
+ break;
115
+ case 'trace': {
116
+ const id = rest[0];
117
+ if (!id) die('用法: loom intent trace <id>');
118
+ output(traceIntent(loomDir, getVerificationsDir(loomDir), getPhilosophyDir(loomDir), id));
119
+ break;
120
+ }
121
+ case 'reverse-dep': {
122
+ const id = rest[0];
123
+ if (!id) die('用法: loom intent reverse-dep <id>');
124
+ output(reverseDep(loomDir, id));
125
+ break;
126
+ }
127
+ case 'reverse-ref': {
128
+ const anchor = rest[0];
129
+ if (!anchor) die('用法: loom intent reverse-ref <anchor>\n例: loom intent reverse-ref PRODUCT_PHILOSOPHY.md#core-belief');
130
+ output(reverseRef(loomDir, anchor));
131
+ break;
132
+ }
133
+ case 'update': {
134
+ const id = rest[0];
135
+ const statusFlagIdx = argv.indexOf('--status');
136
+ const newStatus = statusFlagIdx !== -1 ? argv[statusFlagIdx + 1] : null;
137
+ if (!id || !newStatus) die('用法: loom intent update <id> --status <pending|in_progress|completed|blocked|needs_review>');
138
+ updateIntentStatus(loomDir, id, newStatus);
139
+ console.log(`${id} status 已更新为 ${newStatus}`);
140
+ break;
141
+ }
142
+ default:
143
+ die(`未知子命令: intent ${sub}\n用法: loom intent [next|status|graph|get <id>|narrative <id>|validate|trace <id>|reverse-dep <id>|reverse-ref <anchor>|update <id> --status <...>]`);
144
+ }
145
+ break;
146
+ }
147
+
148
+ case 'init': {
149
+ const result = initProject(cwd());
150
+ console.log('LOOM 项目已初始化');
151
+ console.log(` 创建: ${result.created.length} 项`);
152
+ for (const c of result.created) console.log(` + ${c}`);
153
+ if (result.skipped.length) {
154
+ console.log(` 跳过(已存在): ${result.skipped.length} 项`);
155
+ for (const s of result.skipped) console.log(` - ${s}`);
156
+ }
157
+ console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
158
+ console.log('To Agent: 运行 loom guide 诊断当前阶段,按引导执行');
159
+ console.log('To Human: 把以下指令给你的 AI agent:');
160
+ console.log(' "项目已用 LOOM 初始化。请运行 loom guide 看下一步,');
161
+ console.log(' 然后激活 Weaver 角色织造产品哲学。"');
162
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
163
+ break;
164
+ }
165
+
166
+ case 'activate': {
167
+ const role = sub;
168
+ if (!role) die('用法: loom activate <role>\n角色: weaver | visionary | architect | forge | keeper');
169
+ // weaver 不需要 loomDir(项目还没初始化时也能激活)
170
+ let loomDir = null;
171
+ if (role !== 'weaver') {
172
+ try { loomDir = findLoomDir(); } catch { /* 项目还没初始化,weaver 之外的角色会缺少项目上下文 */ }
173
+ }
174
+ const prompt = activateRole(role, loomDir);
175
+ output(prompt);
176
+ break;
177
+ }
178
+
179
+ case 'philosophy': {
180
+ const loomDir = findLoomDir();
181
+ switch (sub) {
182
+ case 'get': {
183
+ const anchor = rest[0];
184
+ if (!anchor) die('用法: loom philosophy get <anchor>\n例: loom philosophy get PRODUCT_PHILOSOPHY.md#core-belief');
185
+ output(getPhilosophy(getPhilosophyDir(loomDir), anchor));
186
+ break;
187
+ }
188
+ case 'list':
189
+ output(listPhilosophyFiles(getPhilosophyDir(loomDir)));
190
+ break;
191
+ default:
192
+ die(`未知子命令: philosophy ${sub}\n用法: loom philosophy [get <anchor>|list]`);
193
+ }
194
+ break;
195
+ }
196
+
197
+ case 'verify': {
198
+ const loomDir = findLoomDir();
199
+ const verificationsDir = getVerificationsDir(loomDir);
200
+ switch (sub) {
201
+ case 'contract': {
202
+ const id = rest[0];
203
+ if (!id) die('用法: loom verify contract <id>');
204
+ output(getVerificationContract(loomDir, id));
205
+ break;
206
+ }
207
+ case 'history': {
208
+ const id = rest[0];
209
+ if (!id) die('用法: loom verify history <id>');
210
+ const history = getVerificationHistory(verificationsDir, id);
211
+ output(history ?? `没有 ${id} 的验证记录`);
212
+ break;
213
+ }
214
+ case 'pending':
215
+ output(getPendingVerifications(loomDir, verificationsDir));
216
+ break;
217
+ case 'list':
218
+ output(listVerifications(verificationsDir));
219
+ break;
220
+ case 'write': {
221
+ // 支持两种输入方式:--json-file <path>(推荐)或 --json <string>
222
+ // verdict 合法值: passed | deviated | blocked | pending_human
223
+ const fileFlagIdx = argv.indexOf('--json-file');
224
+ const jsonFlagIdx = argv.indexOf('--json');
225
+ let record;
226
+ if (fileFlagIdx !== -1 && argv[fileFlagIdx + 1]) {
227
+ record = JSON.parse(readFileSync(argv[fileFlagIdx + 1], 'utf-8'));
228
+ } else if (jsonFlagIdx !== -1 && argv[jsonFlagIdx + 1]) {
229
+ record = JSON.parse(argv[jsonFlagIdx + 1]);
230
+ } else {
231
+ die('用法: loom verify write --json-file <path> | --json <json-string>');
232
+ }
233
+ const result = writeVerification(verificationsDir, record);
234
+ console.log(`验证记录已写入: ${result.filePath}`);
235
+ console.log(` 轮次: ${result.round}, verdict: ${record.verdict}`);
236
+ if (record.verdict === 'deviated') {
237
+ console.log(` deviated 累计: ${result.deviated_count} 轮`);
238
+ if (result.should_escalate) {
239
+ console.log(` ⚠ 达到 3 轮上限,应升级为 blocked——Keeper 应执行: loom intent update ${record.intent_id} --status blocked`);
240
+ }
241
+ }
242
+ break;
243
+ }
244
+ default:
245
+ die(`未知子命令: verify ${sub}\n用法: loom verify [contract <id>|history <id>|pending|list|write --json-file <path>|--json <string>]`);
246
+ }
247
+ break;
248
+ }
249
+
250
+ case 'version': {
251
+ const loomRoot = findLoomRoot();
252
+ switch (sub) {
253
+ case 'list': {
254
+ const { versions, current } = listVersions(loomRoot);
255
+ for (const v of versions) {
256
+ const mark = v === current ? ' *' : ' ';
257
+ console.log(`${mark}${v}`);
258
+ }
259
+ if (current) console.log(`\n当前版本: ${current}`);
260
+ break;
261
+ }
262
+ case 'current': {
263
+ const current = readCurrentPointer(loomRoot);
264
+ output(current ?? '没有版本目录');
265
+ break;
266
+ }
267
+ case 'new': {
268
+ const result = newVersion(cwd());
269
+ console.log(`已创建新版本: ${result.version}`);
270
+ console.log(` 创建: ${result.created.length} 项`);
271
+ for (const c of result.created) console.log(` + ${c}`);
272
+ if (result.skipped.length) {
273
+ console.log(` 跳过(已存在): ${result.skipped.length} 项`);
274
+ for (const s of result.skipped) console.log(` - ${s}`);
275
+ }
276
+ console.log(`\n当前版本已切换为 ${result.version}`);
277
+ console.log('下一步: loom activate weaver(参考上一版本哲学织造新哲学)');
278
+ break;
279
+ }
280
+ case 'use': {
281
+ const v = rest[0];
282
+ if (!v) die('用法: loom version use <v1|v2|...>');
283
+ const switched = useVersion(loomRoot, v);
284
+ console.log(`当前版本已切换为 ${switched}`);
285
+ break;
286
+ }
287
+ case 'diff': {
288
+ const v1 = rest[0];
289
+ const v2 = rest[1];
290
+ if (!v1 || !v2) die('用法: loom version diff <v1> <v2>');
291
+ output(diffVersions(loomRoot, v1, v2));
292
+ break;
293
+ }
294
+ default:
295
+ die(`未知子命令: version ${sub}\n用法: loom version [list|current|new|use <v>|diff <v1> <v2>]`);
296
+ }
297
+ break;
298
+ }
299
+
300
+ case 'doctor': {
301
+ const loomDir = findLoomDir();
302
+ const { issues, summary } = doctor(loomDir, getVerificationsDir(loomDir), getPhilosophyDir(loomDir));
303
+ if (summary.healthy) {
304
+ console.log('✓ 项目健康,未发现问题');
305
+ } else {
306
+ console.log(`发现 ${summary.total_issues} 个问题(fatal: ${summary.fatal}, high: ${summary.high}, medium: ${summary.medium})`);
307
+ for (const issue of issues) {
308
+ const icon = issue.severity === 'fatal' ? '☠' : issue.severity === 'high' ? '⚠' : '·';
309
+ console.log(` ${icon} [${issue.severity}] ${issue.type}: ${issue.msg}`);
310
+ }
311
+ }
312
+ break;
313
+ }
314
+
315
+ case 'context': {
316
+ const loomDir = findLoomDir();
317
+ output(contextSummary(loomDir, getVerificationsDir(loomDir), getPhilosophyDir(loomDir)));
318
+ break;
319
+ }
320
+
321
+ case 'help': {
322
+ // sub 是 topic(loom help workflow → sub='workflow')
323
+ // 过滤掉 --help/-h 这种被路由进来的情况
324
+ const topic = (sub && !sub.startsWith('-')) ? sub : null;
325
+ if (!topic) {
326
+ console.log('LOOM 指南 topics:');
327
+ for (const t of listHelpTopics()) {
328
+ console.log(` loom help ${t}`);
329
+ }
330
+ console.log('\n运行 loom --help 查看所有命令。');
331
+ } else {
332
+ const content = getHelpTopic(topic);
333
+ if (!content) {
334
+ die(`未知 topic: ${topic}\n可用 topics: ${listHelpTopics().join(', ')}`);
335
+ }
336
+ console.log(content);
337
+ }
338
+ break;
339
+ }
340
+
341
+ case 'guide': {
342
+ const result = guideProject(cwd());
343
+ console.log(`阶段 ${result.stage_num}: ${result.stage}`);
344
+ if (result.auto) {
345
+ console.log(`模式: AUTO(自动执行,不等确认)`);
346
+ } else {
347
+ console.log(`模式: 手动(每步需用户确认)`);
348
+ }
349
+ console.log(`\n${result.message}`);
350
+ console.log(`\n下一步: ${result.next_action}`);
351
+ console.log(` → ${result.next_command}`);
352
+ if (!result.auto && result.stage_num > 0 && result.stage_num < 6) {
353
+ console.log(`\n提示: 开启 AUTO 模式可自动连续执行 — loom auto on`);
354
+ }
355
+ break;
356
+ }
357
+
358
+ case 'auto': {
359
+ const loomRoot = findLoomRoot();
360
+ switch (sub) {
361
+ case 'on':
362
+ autoOn(loomRoot);
363
+ console.log('AUTO 模式已开启。Agent 将自动连续执行,不等用户确认。');
364
+ console.log('关闭: loom auto off');
365
+ break;
366
+ case 'off':
367
+ autoOff(loomRoot);
368
+ console.log('AUTO 模式已关闭。每步需要用户确认。');
369
+ break;
370
+ case 'status': {
371
+ const status = autoStatus(loomRoot);
372
+ if (status.on) {
373
+ console.log(`AUTO 模式: 开启(自 ${status.since})`);
374
+ } else {
375
+ console.log('AUTO 模式: 关闭');
376
+ }
377
+ break;
378
+ }
379
+ default:
380
+ die(`未知子命令: auto ${sub}\n用法: loom auto [on|off|status]`);
381
+ }
382
+ break;
383
+ }
384
+
385
+ case 'preview': {
386
+ // 输出提示词,让 AI 读 .loom/ 文件、重组信息、生成 HTML
387
+ const prompt = generatePreviewPrompt();
388
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
389
+ console.log('To Agent: 按以下提示词读 .loom/ 文件并生成 loom-preview.html');
390
+ console.log('To Human: 把以下内容给你的 AI agent');
391
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
392
+ console.log('');
393
+ console.log(prompt);
394
+ break;
395
+ }
396
+
397
+ case '--help':
398
+ case '-h':
399
+ case undefined:
400
+ console.log(`loom — LOOM 框架 CLI
401
+ 哲学驱动开发:哲学 → 愿景 → 架构 → Intent Loop
402
+
403
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
404
+ To Agent:
405
+ 你是 AI agent。本项目使用 LOOM 框架。
406
+ 第一步: 运行 loom guide 诊断当前阶段
407
+ 第二步: 按引导执行对应命令
408
+ 学习: loom help <topic> 获取详细指南
409
+ 状态: loom context 获取当前进度
410
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
411
+ To Human:
412
+ 请把以下指令给你的 AI agent:
413
+ "请用 LOOM 框架初始化并推进这个项目。
414
+ 先运行 loom --help 了解命令,再运行 loom guide 看当前阶段,
415
+ 然后按引导一步步执行。"
416
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
417
+
418
+ 用法:
419
+ loom init 初始化项目(创建 .loom/v1/ 骨架 + 模板)
420
+ loom guide 诊断当前阶段,输出下一步引导
421
+ loom auto on|off|status AUTO 模式开关(on 时 Agent 自动连续执行)
422
+ loom activate <role> 输出角色激活提示词(weaver|visionary|architect|forge|keeper)
423
+
424
+ loom version list 列出所有版本(* 标记当前)
425
+ loom version current 显示当前版本
426
+ loom version new 创建 v{N+1} + 自动切换为当前
427
+ loom version use <v> 切换当前版本
428
+ loom version diff <v1> <v2> 对比两个版本的文件差异
429
+
430
+ loom intent next 返回下一个可执行 Intent
431
+ loom intent status 返回进度概览
432
+ loom intent graph 输出 Mermaid 依赖图
433
+ loom intent get <id> 返回某 Intent 完整信息
434
+ loom intent narrative <id> 返回某 Intent 的意图叙事(解析 narrative_ref)
435
+ loom intent validate 校验 Intent Map 结构
436
+ loom intent trace <id> 返回某 Intent 的完整追溯链(依赖+验证+哲学+叙事)
437
+ loom intent reverse-dep <id> 返回依赖某 Intent 的所有 Intent(变更影响评估)
438
+ loom intent reverse-ref <anchor> 返回引用某哲学锚点的所有 Intent
439
+ loom intent update <id> --status <s> 更新 Intent 状态(Keeper 用)
440
+
441
+ loom doctor 项目健康检查(一致性+孤儿引用+循环依赖+僵尸)
442
+ loom context 上下文摘要(进度+下一步+待验证+风险)
443
+ loom preview 输出提示词+数据,让 AI 生成 HTML 可视化预览
444
+ loom help <topic> 分层指南(workflow|concepts|loop|version|doctor)
445
+
446
+ loom philosophy get <anchor> 按锚点加载哲学章节
447
+ loom philosophy list 列出哲学文档文件
448
+
449
+ loom verify contract <id> 返回某 Intent 的验收契约(解析引用)
450
+ loom verify history <id> 返回某 Intent 验证历史
451
+ loom verify pending 返回待验证的 Intent
452
+ loom verify list 列出所有验证记录
453
+ loom verify write --json-file <path> 从文件读入并写入验证记录
454
+ loom verify write --json <string> 从命令行字符串写入验证记录
455
+
456
+ 参数:
457
+ --loom-dir <path> 指定 .loom/v{N} 目录(默认读 .loom/current 指针)`);
458
+ break;
459
+
460
+ default:
461
+ die(`未知命令: ${cmd}\n运行 loom --help 查看用法`);
462
+ }
463
+ } catch (e) {
464
+ die(e.message);
465
+ }
@@ -0,0 +1,64 @@
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
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+
10
+ function getLoomRoot() {
11
+ return resolve(__dirname, '..', '..');
12
+ }
13
+
14
+ /** 合法角色名 */
15
+ const VALID_ROLES = ['weaver', 'visionary', 'architect', 'forge', 'keeper'];
16
+
17
+ /** 角色文件映射 */
18
+ const ROLE_FILES = {
19
+ weaver: 'meta/PHILOSOPHY_WEAVER.md',
20
+ visionary: 'roles/visionary.md',
21
+ architect: 'roles/architect.md',
22
+ forge: 'roles/forge.md',
23
+ keeper: 'roles/keeper.md',
24
+ };
25
+
26
+ /**
27
+ * 输出角色激活提示词。
28
+ * @param {string} role — 角色名
29
+ * @param {string} loomDir — .loom/v{N} 目录(可选,weaver 不需要)
30
+ * @returns {string} 激活提示词
31
+ */
32
+ export function activateRole(role, loomDir) {
33
+ if (!VALID_ROLES.includes(role)) {
34
+ throw new Error(`未知角色: ${role}\n合法角色: ${VALID_ROLES.join(', ')}`);
35
+ }
36
+
37
+ const loomRoot = getLoomRoot();
38
+ const parts = [];
39
+
40
+ // 1. 角色文件
41
+ const roleFile = join(loomRoot, ROLE_FILES[role]);
42
+ if (!existsSync(roleFile)) {
43
+ throw new Error(`角色文件不存在: ${roleFile}`);
44
+ }
45
+ parts.push(readFileSync(roleFile, 'utf-8'));
46
+
47
+ // 2. BASELINE(所有角色都需要)
48
+ const baselineFile = join(loomRoot, 'meta/BASELINE.md');
49
+ parts.push('\n---\n\n## 强制加载:BASELINE\n\n' + readFileSync(baselineFile, 'utf-8'));
50
+
51
+ // 3. 项目特定底线(如果有 loomDir)
52
+ if (loomDir) {
53
+ const projectBaseline = join(loomDir, '00_PHILOSOPHY/PROJECT_BASELINE.md');
54
+ if (existsSync(projectBaseline)) {
55
+ parts.push('\n---\n\n## 强制加载:项目特定底线\n\n' + readFileSync(projectBaseline, 'utf-8'));
56
+ }
57
+
58
+ // 4. 角色激活协议
59
+ const activationFile = join(loomRoot, 'meta/ROLE_ACTIVATION.md');
60
+ parts.push('\n---\n\n## 角色激活协议\n\n' + readFileSync(activationFile, 'utf-8'));
61
+ }
62
+
63
+ return parts.join('\n');
64
+ }
@@ -0,0 +1,44 @@
1
+ // auto — AUTO 模式开关
2
+ // 存储机制:.loom/auto 文件存在 = on,不存在 = off
3
+ // 影响 guide 输出语气和 Agent 行为:on 时直接跑,off 时等确认
4
+
5
+ import { existsSync, writeFileSync, unlinkSync, readFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+
8
+ /**
9
+ * 检查 AUTO 模式是否开启。
10
+ * @param {string} loomRoot — .loom 目录路径
11
+ * @returns {boolean}
12
+ */
13
+ export function isAutoOn(loomRoot) {
14
+ return existsSync(join(loomRoot, 'auto'));
15
+ }
16
+
17
+ /**
18
+ * 开启 AUTO 模式。
19
+ * @param {string} loomRoot — .loom 目录路径
20
+ */
21
+ export function autoOn(loomRoot) {
22
+ writeFileSync(join(loomRoot, 'auto'), new Date().toISOString(), 'utf-8');
23
+ }
24
+
25
+ /**
26
+ * 关闭 AUTO 模式。
27
+ * @param {string} loomRoot — .loom 目录路径
28
+ */
29
+ export function autoOff(loomRoot) {
30
+ const path = join(loomRoot, 'auto');
31
+ if (existsSync(path)) unlinkSync(path);
32
+ }
33
+
34
+ /**
35
+ * 获取 AUTO 状态描述。
36
+ * @param {string} loomRoot — .loom 目录路径
37
+ * @returns {{ on: boolean, since: string|null }}
38
+ */
39
+ export function autoStatus(loomRoot) {
40
+ const path = join(loomRoot, 'auto');
41
+ if (!existsSync(path)) return { on: false, since: null };
42
+ const since = readFileSync(path, 'utf-8').trim();
43
+ return { on: true, since };
44
+ }