@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 +31 -19
- package/cli/bin/loom.js +182 -85
- package/cli/help/concepts.md +72 -0
- package/cli/help/doctor.md +121 -0
- package/cli/help/loop.md +135 -0
- package/cli/help/preview.md +59 -0
- package/cli/help/version.md +60 -0
- package/cli/help/workflow.md +100 -0
- package/cli/src/activate.js +21 -12
- package/cli/src/auto.js +54 -5
- package/cli/src/diagnostics.js +229 -48
- package/cli/src/guide.js +83 -12
- package/cli/src/help.js +29 -398
- package/cli/src/init.js +42 -8
- package/cli/src/intent-map.js +42 -85
- package/cli/src/philosophy.js +226 -69
- package/cli/src/preview-prompt.md +1 -0
- package/cli/src/preview.js +67 -10
- package/cli/src/shared/md-utils.js +125 -0
- package/cli/src/shared/paths.js +73 -0
- package/cli/src/verify.js +70 -74
- package/cli/src/version.js +1 -1
- package/dimensions/PART_DECOMPOSITION.md +203 -0
- package/dimensions/examples/AGENT_SYSTEM/README.md +219 -0
- package/dimensions/examples/CLI_TOOL/README.md +163 -0
- package/dimensions/universal/COLLABORATION_PHILOSOPHY.md +77 -0
- package/dimensions/universal/ENGINEERING_CREED.md +74 -0
- package/dimensions/universal/PRODUCT_PHILOSOPHY.md +70 -0
- package/meta/INTENT_LOOP.md +159 -18
- package/meta/PHILOSOPHY_WEAVER.md +104 -50
- package/package.json +5 -4
- package/roles/architect.md +12 -0
- package/roles/keeper.md +28 -1
- package/templates/INTENT_MAP_TEMPLATE.json +4 -1
- package/templates/VISION_TEMPLATE.md +4 -2
package/README.md
CHANGED
|
@@ -57,12 +57,14 @@ Keeper 验证(四维度:意图忠实度 / 哲学一致性 / 底线合规 /
|
|
|
57
57
|
|
|
58
58
|
### 步骤 0:诊断当前阶段
|
|
59
59
|
|
|
60
|
-
```bash
|
|
61
|
-
loom guide
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
|
163
|
-
| `loom
|
|
164
|
-
| `loom
|
|
165
|
-
| `loom
|
|
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,
|
|
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
|
-
//
|
|
23
|
-
//
|
|
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
|
|
37
|
-
|
|
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
|
|
53
|
-
return join(
|
|
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
|
|
68
|
+
const versionDir = findVersionDir();
|
|
83
69
|
switch (sub) {
|
|
84
70
|
case 'next':
|
|
85
|
-
output(getNextIntent(
|
|
71
|
+
output(getNextIntent(versionDir) ?? '没有可执行的 Intent');
|
|
86
72
|
break;
|
|
87
73
|
case 'status': {
|
|
88
|
-
const s = getStatus(
|
|
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
|
|
91
|
-
console.log(` in_progress: ${s.counts.in_progress} ${s.ids.in_progress
|
|
92
|
-
console.log(` completed: ${s.counts.completed} ${s.ids.completed
|
|
93
|
-
console.log(` blocked: ${s.counts.blocked} ${s.ids.blocked
|
|
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(
|
|
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(
|
|
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(
|
|
95
|
+
output(getNarrative(versionDir, id));
|
|
109
96
|
break;
|
|
110
97
|
}
|
|
111
98
|
case 'validate':
|
|
112
|
-
loadIntentMap(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 不需要
|
|
170
|
-
let
|
|
156
|
+
// weaver 不需要 versionDir(项目还没初始化时也能激活)
|
|
157
|
+
let versionDir = null;
|
|
171
158
|
if (role !== 'weaver') {
|
|
172
|
-
try {
|
|
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,
|
|
166
|
+
const prompt = activateRole(role, versionDir);
|
|
175
167
|
output(prompt);
|
|
176
168
|
break;
|
|
177
169
|
}
|
|
178
170
|
|
|
179
171
|
case 'philosophy': {
|
|
180
|
-
const
|
|
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(
|
|
177
|
+
output(getPhilosophy(getPhilosophyDir(versionDir), anchor));
|
|
186
178
|
break;
|
|
187
179
|
}
|
|
188
180
|
case 'list':
|
|
189
|
-
output(listPhilosophyFiles(getPhilosophyDir(
|
|
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
|
|
199
|
-
const verificationsDir = getVerificationsDir(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
302
|
-
const { issues, summary } = doctor(
|
|
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
|
|
317
|
-
output(contextSummary(
|
|
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
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
444
|
-
loom
|
|
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
|