@haaaiawd/loom 0.1.0 → 0.8.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 CHANGED
@@ -57,12 +57,14 @@ Keeper 验证(四维度:意图忠实度 / 哲学一致性 / 底线合规 /
57
57
 
58
58
  ### 步骤 0:诊断当前阶段
59
59
 
60
- ```bash
61
- loom guide
62
- ```
63
-
64
- guide 检测项目当前在哪个阶段,输出"你在阶段 X,下一步做 Y"。
65
- Agent 每完成一步都跑 guide 确认下一步。
60
+ ```bash
61
+ loom guide
62
+ loom guide --dry-run # 只读诊断,不写 heartbeat
63
+ ```
64
+
65
+ guide 检测项目当前在哪个阶段,输出"你在阶段 X,下一步做 Y"。
66
+ Agent 每完成一步都跑 guide 确认下一步。
67
+ 如果只是审计或探测,不希望产生任何状态写入,用 `loom guide --dry-run`。
66
68
 
67
69
  ### AUTO 模式
68
70
 
@@ -145,24 +147,34 @@ loom intent status
145
147
 
146
148
  ### 步骤 6:人类预览
147
149
 
148
- ```bash
149
- loom preview
150
- ```
151
-
152
- 输出提示词,Agent 按提示词读 `.loom/` 文件、拆解信息、生成 HTML 可视化预览。
153
- 人类用浏览器打开 `loom-preview.html` 看全局——哲学、愿景、架构、Intent 进度、验证历史。
154
- 这是只读投影,修改请编辑源文件后重新生成。
150
+ ```bash
151
+ loom preview status # 先检查 preview 是否新鲜
152
+ loom preview # 新鲜则打开;过期则提示重新生成
153
+ loom preview --regen # 输出生成提示词,让 Agent 重写 loom-preview.html
154
+ ```
155
+
156
+ preview 是人类总览用的只读投影:哲学、愿景、架构、Intent 进度、验证历史。
157
+ `loom preview` 会用 mtime 检查 `.loom/v{N}` 是否比 `loom-preview.html` 更新:
158
+ - 新鲜:直接打开 `loom-preview.html`
159
+ - 过期:不打开旧投影,提示 `loom preview --regen`
160
+ - 强行打开旧投影:`loom preview --stale`
161
+
162
+ Agent 在用户说"看看进度 / 打开 preview / 看全局"时,先跑 `loom preview status`。
155
163
 
156
164
  **CLI 命令一览**:
157
165
 
158
166
  | 命令 | 用途 |
159
167
  |---|---|
160
- | `loom init` | 初始化项目 |
161
- | `loom guide` | 诊断当前阶段,输出下一步引导 |
162
- | `loom auto on\|off\|status` | AUTO 模式开关 |
163
- | `loom activate <role>` | 输出角色激活提示词 |
164
- | `loom preview` | 输出提示词,AI 生成 HTML 可视化预览 |
165
- | `loom help <topic>` | 分层指南(workflow\|concepts\|loop\|version\|doctor) |
168
+ | `loom init` | 初始化项目 |
169
+ | `loom guide` | 诊断当前阶段,输出下一步引导 |
170
+ | `loom guide --dry-run` | 只读诊断当前阶段,不写 heartbeat |
171
+ | `loom auto on\|off\|status` | AUTO 模式开关 |
172
+ | `loom activate <role>` | 输出角色激活提示词 |
173
+ | `loom preview` | 打开新鲜 HTML 预览;过期时提示重新生成 |
174
+ | `loom preview status` | 检查 `loom-preview.html` 是否存在、是否新鲜 |
175
+ | `loom preview --regen` | 输出提示词,让 Agent 重写 HTML 预览 |
176
+ | `loom preview --stale` | 强行打开过期预览 |
177
+ | `loom help <topic>` | 分层指南(workflow\|concepts\|loop\|version\|doctor\|preview) |
166
178
  | `loom version list` | 列出所有版本(* 标记当前) |
167
179
  | `loom version new` | 创建新版本 + 自动切换(Major 升级) |
168
180
  | `loom version use <v>` | 切换当前版本 |
package/cli/bin/loom.js CHANGED
@@ -1,60 +1,37 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  // loom — LOOM 框架的 CLI 传感器层
3
3
  // Agent 通过这个 CLI 访问 Intent Map / 哲学 / 验证记录,不直接读文件。
4
4
 
5
5
  import { argv, cwd, exit } from 'node:process';
6
- import { resolve, join } from 'node:path';
6
+ import { resolve, join, dirname } from 'node:path';
7
7
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { findLoomRoot, findVersionDir, readCurrentPointer } from '../src/shared/paths.js';
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
12
 
9
13
  import { getNextIntent, getStatus, getDependencyGraph, getIntent, loadIntentMap, updateIntentStatus, getNarrative } from '../src/intent-map.js';
10
- import { getPhilosophy, listPhilosophyFiles } from '../src/philosophy.js';
14
+ import { getPhilosophy, listPhilosophyFiles, validateInspirationSources, validatePartDecomposition } from '../src/philosophy.js';
11
15
  import { writeVerification, getVerificationHistory, getPendingVerifications, listVerifications, getVerificationContract } from '../src/verify.js';
12
16
  import { initProject } from '../src/init.js';
13
17
  import { activateRole } from '../src/activate.js';
14
- import { listVersions, readCurrentPointer, newVersion, useVersion, diffVersions } from '../src/version.js';
18
+ import { listVersions, newVersion, useVersion, diffVersions } from '../src/version.js';
15
19
  import { doctor, contextSummary, traceIntent, reverseDep, reverseRef } from '../src/diagnostics.js';
16
20
  import { getHelpTopic, listHelpTopics } from '../src/help.js';
17
21
  import { guideProject } from '../src/guide.js';
18
22
  import { isAutoOn, autoOn, autoOff, autoStatus } from '../src/auto.js';
19
- import { generatePreviewPrompt } from '../src/preview.js';
23
+ import { generatePreviewPrompt, getPreviewStatus } from '../src/preview.js';
20
24
 
21
25
  // ─── 路径解析 ──────────────────────────────────────────
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
- }
26
+ // findLoomRoot / findVersionDir / readCurrentPointer 已提取到 shared/paths.js
27
+ // 这里只保留目录辅助函数。
35
28
 
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);
29
+ function getPhilosophyDir(versionDir) {
30
+ return join(versionDir, '00_PHILOSOPHY');
50
31
  }
51
32
 
52
- function getPhilosophyDir(loomDir) {
53
- return join(loomDir, '00_PHILOSOPHY');
54
- }
55
-
56
- function getVerificationsDir(loomDir) {
57
- return join(loomDir, 'verifications');
33
+ function getVerificationsDir(versionDir) {
34
+ return join(versionDir, 'verifications');
58
35
  }
59
36
 
60
37
  // ─── 输出工具 ──────────────────────────────────────────
@@ -78,56 +55,66 @@ const [cmd, sub, ...rest] = argv.slice(2);
78
55
 
79
56
  try {
80
57
  switch (cmd) {
58
+ case '--version':
59
+ case '-v': {
60
+ // 从根 package.json 读版本号(cli/bin -> cli -> LOOM root)
61
+ const pkgPath = resolve(__dirname, '..', '..', 'package.json');
62
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
63
+ console.log(`loom ${pkg.version}`);
64
+ break;
65
+ }
66
+
81
67
  case 'intent': {
82
- const loomDir = findLoomDir();
68
+ const versionDir = findVersionDir();
83
69
  switch (sub) {
84
70
  case 'next':
85
- output(getNextIntent(loomDir) ?? '没有可执行的 Intent');
71
+ output(getNextIntent(versionDir) ?? '没有可执行的 Intent');
86
72
  break;
87
73
  case 'status': {
88
- const s = getStatus(loomDir);
74
+ const s = getStatus(versionDir);
75
+ const fmt = (ids) => ids.map((id) => s.titles[id] ? `${id}(${s.titles[id]})` : id).join(', ') || '-';
89
76
  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(', ') || '-'}`);
77
+ console.log(` pending: ${s.counts.pending} ${fmt(s.ids.pending)}`);
78
+ console.log(` in_progress: ${s.counts.in_progress} ${fmt(s.ids.in_progress)}`);
79
+ console.log(` completed: ${s.counts.completed} ${fmt(s.ids.completed)}`);
80
+ console.log(` blocked: ${s.counts.blocked} ${fmt(s.ids.blocked)}`);
94
81
  break;
95
82
  }
96
83
  case 'graph':
97
- output(getDependencyGraph(loomDir));
84
+ output(getDependencyGraph(versionDir));
98
85
  break;
99
86
  case 'get': {
100
87
  const id = rest[0];
101
88
  if (!id) die('用法: loom intent get <id>');
102
- output(getIntent(loomDir, id));
89
+ output(getIntent(versionDir, id));
103
90
  break;
104
91
  }
105
92
  case 'narrative': {
106
93
  const id = rest[0];
107
94
  if (!id) die('用法: loom intent narrative <id>');
108
- output(getNarrative(loomDir, id));
95
+ output(getNarrative(versionDir, id));
109
96
  break;
110
97
  }
111
98
  case 'validate':
112
- loadIntentMap(loomDir);
99
+ loadIntentMap(versionDir);
113
100
  console.log('Intent Map 校验通过');
114
101
  break;
115
102
  case 'trace': {
116
103
  const id = rest[0];
117
104
  if (!id) die('用法: loom intent trace <id>');
118
- output(traceIntent(loomDir, getVerificationsDir(loomDir), getPhilosophyDir(loomDir), id));
105
+ output(traceIntent(versionDir, getVerificationsDir(versionDir), getPhilosophyDir(versionDir), id));
119
106
  break;
120
107
  }
121
108
  case 'reverse-dep': {
122
109
  const id = rest[0];
123
110
  if (!id) die('用法: loom intent reverse-dep <id>');
124
- output(reverseDep(loomDir, id));
111
+ output(reverseDep(versionDir, id));
125
112
  break;
126
113
  }
127
114
  case 'reverse-ref': {
128
115
  const anchor = rest[0];
129
116
  if (!anchor) die('用法: loom intent reverse-ref <anchor>\n例: loom intent reverse-ref PRODUCT_PHILOSOPHY.md#core-belief');
130
- output(reverseRef(loomDir, anchor));
117
+ output(reverseRef(versionDir, anchor));
131
118
  break;
132
119
  }
133
120
  case 'update': {
@@ -135,7 +122,7 @@ try {
135
122
  const statusFlagIdx = argv.indexOf('--status');
136
123
  const newStatus = statusFlagIdx !== -1 ? argv[statusFlagIdx + 1] : null;
137
124
  if (!id || !newStatus) die('用法: loom intent update <id> --status <pending|in_progress|completed|blocked|needs_review>');
138
- updateIntentStatus(loomDir, id, newStatus);
125
+ updateIntentStatus(versionDir, id, newStatus);
139
126
  console.log(`${id} status 已更新为 ${newStatus}`);
140
127
  break;
141
128
  }
@@ -166,42 +153,77 @@ try {
166
153
  case 'activate': {
167
154
  const role = sub;
168
155
  if (!role) die('用法: loom activate <role>\n角色: weaver | visionary | architect | forge | keeper');
169
- // weaver 不需要 loomDir(项目还没初始化时也能激活)
170
- let loomDir = null;
156
+ // weaver 不需要 versionDir(项目还没初始化时也能激活)
157
+ let versionDir = null;
171
158
  if (role !== 'weaver') {
172
- try { loomDir = findLoomDir(); } catch { /* 项目还没初始化,weaver 之外的角色会缺少项目上下文 */ }
159
+ try {
160
+ versionDir = findVersionDir();
161
+ } catch (e) {
162
+ // 只吞"找不到 .loom 目录"——其他错误(权限、磁盘)向上抛
163
+ if (!String(e.message).includes('找不到 .loom')) throw e;
164
+ }
173
165
  }
174
- const prompt = activateRole(role, loomDir);
166
+ const prompt = activateRole(role, versionDir);
175
167
  output(prompt);
176
168
  break;
177
169
  }
178
170
 
179
171
  case 'philosophy': {
180
- const loomDir = findLoomDir();
172
+ const versionDir = findVersionDir();
181
173
  switch (sub) {
182
174
  case 'get': {
183
175
  const anchor = rest[0];
184
176
  if (!anchor) die('用法: loom philosophy get <anchor>\n例: loom philosophy get PRODUCT_PHILOSOPHY.md#core-belief');
185
- output(getPhilosophy(getPhilosophyDir(loomDir), anchor));
177
+ output(getPhilosophy(getPhilosophyDir(versionDir), anchor));
186
178
  break;
187
179
  }
188
180
  case 'list':
189
- output(listPhilosophyFiles(getPhilosophyDir(loomDir)));
181
+ output(listPhilosophyFiles(getPhilosophyDir(versionDir)));
190
182
  break;
183
+ case 'check': {
184
+ const philDir = getPhilosophyDir(versionDir);
185
+ const inspiration = validateInspirationSources(philDir);
186
+ const decomposition = validatePartDecomposition(philDir);
187
+ const allIssues = [...inspiration.issues, ...decomposition.issues];
188
+ const allPassed = inspiration.passed && decomposition.passed;
189
+
190
+ if (allPassed) {
191
+ console.log('✓ 哲学文档校验通过');
192
+ console.log(' 灵感来源:');
193
+ for (const { file, sources } of inspiration.sources) {
194
+ console.log(` ${file}: ${sources.length} 个源`);
195
+ }
196
+ console.log(' 实现部分拆解:');
197
+ for (const part of decomposition.parts) {
198
+ console.log(` - ${part}`);
199
+ }
200
+ } else {
201
+ const high = allIssues.filter((i) => i.severity === 'high').length;
202
+ const medium = allIssues.filter((i) => i.severity === 'medium').length;
203
+ console.log(`✗ 哲学文档校验未通过(${allIssues.length} 个问题: ${high} high, ${medium} medium)`);
204
+ for (const issue of allIssues) {
205
+ const icon = issue.severity === 'high' ? '⚠' : '·';
206
+ console.log(` ${icon} [${issue.severity}] ${issue.msg}`);
207
+ }
208
+ console.log('\n参见 meta/PHILOSOPHY_WEAVER.md + dimensions/PART_DECOMPOSITION.md + dimensions/SEARCH_METHODOLOGY.md。');
209
+ exit(1);
210
+ }
211
+ break;
212
+ }
191
213
  default:
192
- die(`未知子命令: philosophy ${sub}\n用法: loom philosophy [get <anchor>|list]`);
214
+ die(`未知子命令: philosophy ${sub}\n用法: loom philosophy [get <anchor>|list|check]`);
193
215
  }
194
216
  break;
195
217
  }
196
218
 
197
219
  case 'verify': {
198
- const loomDir = findLoomDir();
199
- const verificationsDir = getVerificationsDir(loomDir);
220
+ const versionDir = findVersionDir();
221
+ const verificationsDir = getVerificationsDir(versionDir);
200
222
  switch (sub) {
201
223
  case 'contract': {
202
224
  const id = rest[0];
203
225
  if (!id) die('用法: loom verify contract <id>');
204
- output(getVerificationContract(loomDir, id));
226
+ output(getVerificationContract(versionDir, id));
205
227
  break;
206
228
  }
207
229
  case 'history': {
@@ -212,7 +234,7 @@ try {
212
234
  break;
213
235
  }
214
236
  case 'pending':
215
- output(getPendingVerifications(loomDir, verificationsDir));
237
+ output(getPendingVerifications(versionDir, verificationsDir));
216
238
  break;
217
239
  case 'list':
218
240
  output(listVerifications(verificationsDir));
@@ -224,9 +246,17 @@ try {
224
246
  const jsonFlagIdx = argv.indexOf('--json');
225
247
  let record;
226
248
  if (fileFlagIdx !== -1 && argv[fileFlagIdx + 1]) {
227
- record = JSON.parse(readFileSync(argv[fileFlagIdx + 1], 'utf-8'));
249
+ try {
250
+ record = JSON.parse(readFileSync(argv[fileFlagIdx + 1], 'utf-8'));
251
+ } catch (e) {
252
+ die(`JSON 文件解析失败: ${argv[fileFlagIdx + 1]}\n原因: ${e.message}`);
253
+ }
228
254
  } else if (jsonFlagIdx !== -1 && argv[jsonFlagIdx + 1]) {
229
- record = JSON.parse(argv[jsonFlagIdx + 1]);
255
+ try {
256
+ record = JSON.parse(argv[jsonFlagIdx + 1]);
257
+ } catch (e) {
258
+ die(`JSON 字符串解析失败: ${e.message}`);
259
+ }
230
260
  } else {
231
261
  die('用法: loom verify write --json-file <path> | --json <json-string>');
232
262
  }
@@ -298,8 +328,8 @@ try {
298
328
  }
299
329
 
300
330
  case 'doctor': {
301
- const loomDir = findLoomDir();
302
- const { issues, summary } = doctor(loomDir, getVerificationsDir(loomDir), getPhilosophyDir(loomDir));
331
+ const versionDir = findVersionDir();
332
+ const { issues, summary } = doctor(versionDir, getVerificationsDir(versionDir), getPhilosophyDir(versionDir));
303
333
  if (summary.healthy) {
304
334
  console.log('✓ 项目健康,未发现问题');
305
335
  } else {
@@ -313,8 +343,8 @@ try {
313
343
  }
314
344
 
315
345
  case 'context': {
316
- const loomDir = findLoomDir();
317
- output(contextSummary(loomDir, getVerificationsDir(loomDir), getPhilosophyDir(loomDir)));
346
+ const versionDir = findVersionDir();
347
+ output(contextSummary(versionDir, getVerificationsDir(versionDir), getPhilosophyDir(versionDir)));
318
348
  break;
319
349
  }
320
350
 
@@ -338,12 +368,16 @@ try {
338
368
  break;
339
369
  }
340
370
 
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 {
371
+ case 'guide': {
372
+ const dryRun = argv.includes('--dry-run');
373
+ const result = guideProject(cwd(), { dryRun });
374
+ console.log(`阶段 ${result.stage_num}: ${result.stage}`);
375
+ if (dryRun) {
376
+ console.log('诊断: dry-run(不写 heartbeat)');
377
+ }
378
+ if (result.auto) {
379
+ console.log(`模式: AUTO(自动执行,不等确认)`);
380
+ } else {
347
381
  console.log(`模式: 手动(每步需用户确认)`);
348
382
  }
349
383
  console.log(`\n${result.message}`);
@@ -361,6 +395,9 @@ try {
361
395
  case 'on':
362
396
  autoOn(loomRoot);
363
397
  console.log('AUTO 模式已开启。Agent 将自动连续执行,不等用户确认。');
398
+ console.log('核心契约: 持续运行,除非出意外否则不允许私自停止。');
399
+ console.log(' - L3 human_review 由 Keeper 自主判定,不停下等人类');
400
+ console.log(' - 唯一允许停下的情况: blocked(依赖阻塞/契约无法判定/连续 3 轮 deviated 升级)');
364
401
  console.log('关闭: loom auto off');
365
402
  break;
366
403
  case 'off':
@@ -371,8 +408,18 @@ try {
371
408
  const status = autoStatus(loomRoot);
372
409
  if (status.on) {
373
410
  console.log(`AUTO 模式: 开启(自 ${status.since})`);
411
+ console.log(' 规则: stage 1-3(哲学/愿景/架构)需人类 review,stage 4+(Intent Loop)自动执行');
412
+ if (status.heartbeat) {
413
+ const hb = status.heartbeat;
414
+ console.log(` 心跳: ${hb.timestamp}`);
415
+ console.log(` 阶段: ${hb.stage} (stage ${hb.stage_num})`);
416
+ console.log(` 下一步: ${hb.next_action}`);
417
+ console.log(` 命令: ${hb.next_command}`);
418
+ } else {
419
+ console.log(' 心跳: 尚未记录(运行 loom guide 后生成)');
420
+ }
374
421
  } else {
375
- console.log('AUTO 模式: 关闭');
422
+ console.log('AUTO 模式: 关闭(所有阶段都需人类确认)');
376
423
  }
377
424
  break;
378
425
  }
@@ -382,11 +429,56 @@ try {
382
429
  break;
383
430
  }
384
431
 
385
- case 'preview': {
386
- // 输出提示词,让 AI .loom/ 文件、重组信息、生成 HTML
432
+ case 'preview': {
433
+ const previewFile = join(cwd(), 'loom-preview.html');
434
+ if (argv.includes('--help') || argv.includes('-h')) {
435
+ console.log(`用法:
436
+ loom preview 打开新鲜 preview;过期时提示重新生成
437
+ loom preview --regen 输出生成提示词,让 Agent 重写 loom-preview.html
438
+ loom preview status 检查 preview 是否存在、是否新鲜
439
+ loom preview --stale 强行打开过期 preview
440
+ loom preview --help 显示本帮助`);
441
+ break;
442
+ }
443
+ const status = getPreviewStatus(cwd());
444
+ const hasPreview = status.exists;
445
+ const regenOnly = argv.includes('--regen') || argv.includes('-r');
446
+ const openStale = argv.includes('--stale');
447
+
448
+ if (sub === 'status') {
449
+ output(status);
450
+ break;
451
+ }
452
+
453
+ // 已有 HTML 且没指定 --regen:直接打开浏览器
454
+ if (hasPreview && !regenOnly) {
455
+ if (!status.fresh && !openStale) {
456
+ console.log('preview 已过期:.loom 源文件比 loom-preview.html 更新。');
457
+ console.log(` preview: ${status.preview_mtime || '未知'}`);
458
+ console.log(` 最新源: ${status.source_latest_mtime || '未知'} ${status.latest_source_file ? `(${status.latest_source_file})` : ''}`);
459
+ console.log('\n下一步: loom preview --regen');
460
+ console.log('强行打开旧 preview: loom preview --stale');
461
+ break;
462
+ }
463
+ const { spawn } = await import('node:child_process');
464
+ const target = previewFile.replace(/\\/g, '/');
465
+ if (process.platform === 'win32') {
466
+ spawn('cmd', ['/c', 'start', target], { detached: true, stdio: 'ignore' }).unref();
467
+ } else if (process.platform === 'darwin') {
468
+ spawn('open', [target], { detached: true, stdio: 'ignore' }).unref();
469
+ } else {
470
+ spawn('xdg-open', [target], { detached: true, stdio: 'ignore' }).unref();
471
+ }
472
+ console.log(`已打开浏览器: ${previewFile}`);
473
+ console.log(status.fresh ? `重新生成: loom preview --regen` : `已打开旧 preview。重新生成: loom preview --regen`);
474
+ break;
475
+ }
476
+
477
+ // 没有 HTML 或指定 --regen:输出提示词让 AI 生成
387
478
  const prompt = generatePreviewPrompt();
388
479
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
389
480
  console.log('To Agent: 按以下提示词读 .loom/ 文件并生成 loom-preview.html');
481
+ console.log(' 生成完成后再次运行 loom preview 会自动打开浏览器');
390
482
  console.log('To Human: 把以下内容给你的 AI agent');
391
483
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
392
484
  console.log('');
@@ -416,9 +508,10 @@ To Human:
416
508
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
417
509
 
418
510
  用法:
419
- loom init 初始化项目(创建 .loom/v1/ 骨架 + 模板)
420
- loom guide 诊断当前阶段,输出下一步引导
421
- loom auto on|off|status AUTO 模式开关(on 时 Agent 自动连续执行)
511
+ loom init 初始化项目(创建 .loom/v1/ 骨架 + 模板)
512
+ loom guide 诊断当前阶段,输出下一步引导
513
+ loom guide --dry-run 只读诊断当前阶段,不写 heartbeat
514
+ loom auto on|off|status AUTO 模式开关(on 时 Agent 自动连续执行)
422
515
  loom activate <role> 输出角色激活提示词(weaver|visionary|architect|forge|keeper)
423
516
 
424
517
  loom version list 列出所有版本(* 标记当前)
@@ -440,11 +533,15 @@ To Human:
440
533
 
441
534
  loom doctor 项目健康检查(一致性+孤儿引用+循环依赖+僵尸)
442
535
  loom context 上下文摘要(进度+下一步+待验证+风险)
443
- loom preview 输出提示词+数据,让 AI 生成 HTML 可视化预览
444
- loom help <topic> 分层指南(workflow|concepts|loop|version|doctor)
536
+ loom preview 打开新鲜 HTML;过期则提示重新生成
537
+ loom preview status 检查 preview 是否存在、是否新鲜
538
+ loom preview --regen 强制重新输出提示词(让 AI 重新生成 HTML)
539
+ loom preview --stale 强行打开过期 preview
540
+ loom help <topic> 分层指南(workflow|concepts|loop|version|doctor|preview)
445
541
 
446
542
  loom philosophy get <anchor> 按锚点加载哲学章节
447
543
  loom philosophy list 列出哲学文档文件
544
+ loom philosophy check 校验灵感来源质量(源数量/多样性/理由)
448
545
 
449
546
  loom verify contract <id> 返回某 Intent 的验收契约(解析引用)
450
547
  loom verify history <id> 返回某 Intent 验证历史
@@ -0,0 +1,72 @@
1
+ ## LOOM 核心概念
2
+
3
+ ## 哲学
4
+
5
+ 项目的价值观和工程原则——为什么存在、什么不做、冲突时谁优先。
6
+ 由 Philosophy Weaver 从真实思想体系织造,不是模板填空。
7
+ 所有角色激活时强制加载哲学作为共同锚点。
8
+
9
+ 相关命令:
10
+ - \`loom philosophy get <anchor>\` — 加载哲学章节
11
+ - \`loom philosophy list\` — 列出哲学文档
12
+ - \`loom intent reverse-ref <anchor>\` — 哪些 Intent 引用了这个哲学锚点
13
+
14
+ ## Intent
15
+
16
+ 一个意图单元——不是"做什么"(任务),是"为什么做"(意图)。
17
+ 每个 Intent 携带:
18
+ - narrative_ref — 意图叙事引用(为什么存在)
19
+ - depends_on — 依赖的 Intent(拓扑序)
20
+ - acceptance — 验收契约(Keeper 据此判定)
21
+ - philosophy_anchors — 哲学锚点(引用哪些哲学原则)
22
+ - status — 状态(pending|in_progress|completed|blocked|needs_review)
23
+ - verification_method — 验证方式(L1 静态|L2 运行时|L3 人类反馈,可选)
24
+
25
+ 相关命令:
26
+ - \`loom intent next\` — 下一个可执行 Intent
27
+ - \`loom intent get <id>\` — Intent 详情
28
+ - \`loom intent narrative <id>\` — 意图叙事
29
+ - \`loom intent trace <id>\` — 完整追溯链
30
+ - \`loom intent reverse-dep <id>\` — 谁依赖这个 Intent
31
+
32
+ ## Intent Map
33
+
34
+ 所有 Intent 的依赖图(JSON)。Architect 绘制,定义拓扑序和依赖关系。
35
+ 必须是 DAG(有向无环图),不能有循环依赖。
36
+
37
+ 相关命令:
38
+ - \`loom intent validate\` — 校验结构 + 依赖一致性
39
+ - \`loom intent graph\` — Mermaid 依赖图
40
+ - \`loom intent status\` — 进度概览
41
+
42
+ ## Intent Loop
43
+
44
+ 核心循环:Keeper 选 Intent → Forge 实现 → Keeper 验证 → 闭合或修正。
45
+ 每个 Intent 独立走一圈。详细流程见 \`loom help loop\`。
46
+
47
+ ## Keeper
48
+
49
+ 独立验证子代理——不继承 Forge 的实现上下文,从磁盘重新加载意图和契约。
50
+ 判定四维度:意图忠实度 / 哲学一致性 / 底线合规 / 验收达成。
51
+ 判定结果:passed / deviated / blocked / pending_human。
52
+
53
+ ## 底线
54
+
55
+ 不可妥协的约束(BASELINE.md 5 条 + 项目特定底线)。
56
+ 角色激活时强制加载,哲学不能覆盖。违反底线必须立即停止。
57
+
58
+ ## 验证记录
59
+
60
+ Keeper 每次验证写入一条记录(追加模式),包含:
61
+ - verdict(passed/deviated/blocked/pending_human)
62
+ - 四维度判定
63
+ - 证据
64
+ - 偏离说明(如果 deviated)
65
+
66
+ deviated 连续 3 轮升级 blocked。pending_human 默认 7 天超时升级 blocked。
67
+
68
+ 相关命令:
69
+ - \`loom verify contract <id>\` — 获取验收契约
70
+ - \`loom verify write --json-file <path>\` — 写入验证记录
71
+ - \`loom verify history <id>\` — 验证历史
72
+ - \`loom verify pending\` — 待验证的 Intent