@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.
- package/README.md +371 -0
- package/cli/bin/loom.js +465 -0
- package/cli/src/activate.js +64 -0
- package/cli/src/auto.js +44 -0
- package/cli/src/diagnostics.js +277 -0
- package/cli/src/guide.js +201 -0
- package/cli/src/help.js +410 -0
- package/cli/src/init.js +125 -0
- package/cli/src/intent-map.js +284 -0
- package/cli/src/philosophy.js +117 -0
- package/cli/src/preview-prompt.md +329 -0
- package/cli/src/preview.js +15 -0
- package/cli/src/verify.js +199 -0
- package/cli/src/version.js +133 -0
- package/dimensions/SEARCH_METHODOLOGY.md +97 -0
- package/meta/BASELINE.md +276 -0
- package/meta/INTENT_LOOP.md +596 -0
- package/meta/PHILOSOPHY_WEAVER.md +289 -0
- package/meta/ROLE_ACTIVATION.md +267 -0
- package/package.json +41 -0
- package/roles/architect.md +99 -0
- package/roles/forge.md +126 -0
- package/roles/keeper.md +196 -0
- package/roles/visionary.md +86 -0
- package/templates/INTENT_MAP_TEMPLATE.json +65 -0
- package/templates/PHILOSOPHY_TEMPLATE.md +75 -0
- package/templates/VISION_TEMPLATE.md +65 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// diagnostics — LOOM 诊断与追溯工具集
|
|
2
|
+
// 提供 doctor / context / trace / reverse-dep / reverse-ref 五个聚合命令。
|
|
3
|
+
// 全部是只读的数据聚合,不做决策、不修改文件。
|
|
4
|
+
|
|
5
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { loadIntentMap, getStatus, getNextIntent, getNarrative, getIntent } from './intent-map.js';
|
|
8
|
+
import { getPhilosophy } from './philosophy.js';
|
|
9
|
+
import { getVerificationHistory, getPendingVerifications, getVerificationContract } from './verify.js';
|
|
10
|
+
|
|
11
|
+
// ─── doctor ────────────────────────────────────────────
|
|
12
|
+
// 全面健康检查:一致性 + 孤儿引用 + 循环依赖 + 僵尸 Intent
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 项目健康检查。
|
|
16
|
+
* @param {string} loomDir — 当前版本目录
|
|
17
|
+
* @param {string} verificationsDir — 验证记录目录
|
|
18
|
+
* @param {string} philosophyDir — 哲学目录
|
|
19
|
+
* @returns {{ issues: object[], summary: object }}
|
|
20
|
+
*/
|
|
21
|
+
export function doctor(loomDir, verificationsDir, philosophyDir) {
|
|
22
|
+
const { intents, topo_order } = loadIntentMap(loomDir);
|
|
23
|
+
const issues = [];
|
|
24
|
+
|
|
25
|
+
// 1. 状态一致性:in_progress/completed 但无验证记录
|
|
26
|
+
for (const [id, intent] of Object.entries(intents)) {
|
|
27
|
+
const hasRecord = existsSync(join(verificationsDir, `${id}.json`));
|
|
28
|
+
if (intent.status === 'completed' && !hasRecord) {
|
|
29
|
+
issues.push({ id, type: 'completed_no_record', severity: 'high', msg: `${id} 状态为 completed 但无验证记录` });
|
|
30
|
+
}
|
|
31
|
+
if (intent.status === 'in_progress' && !hasRecord) {
|
|
32
|
+
issues.push({ id, type: 'in_progress_no_record', severity: 'medium', msg: `${id} 状态为 in_progress 但无验证记录(可能上次中断)` });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. 孤儿引用:哲学锚点指向不存在的文件
|
|
37
|
+
for (const [id, intent] of Object.entries(intents)) {
|
|
38
|
+
if (!intent.philosophy_anchors) continue;
|
|
39
|
+
for (const anchor of intent.philosophy_anchors) {
|
|
40
|
+
const [file] = anchor.split('#');
|
|
41
|
+
const filePath = join(philosophyDir, file);
|
|
42
|
+
if (!existsSync(filePath)) {
|
|
43
|
+
issues.push({ id, type: 'orphan_philosophy_ref', severity: 'high', msg: `${id} 引用不存在的哲学文件: ${file}` });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. 孤儿引用:depends_on 指向不存在的 Intent
|
|
49
|
+
for (const [id, intent] of Object.entries(intents)) {
|
|
50
|
+
if (!intent.depends_on) continue;
|
|
51
|
+
for (const dep of intent.depends_on) {
|
|
52
|
+
if (!(dep in intents)) {
|
|
53
|
+
issues.push({ id, type: 'orphan_dependency', severity: 'high', msg: `${id} 依赖不存在的 Intent: ${dep}` });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 4. 循环依赖检测(DFS)
|
|
59
|
+
const cycles = detectCycles(intents);
|
|
60
|
+
for (const cycle of cycles) {
|
|
61
|
+
issues.push({ id: cycle.join('→'), type: 'cycle', severity: 'fatal', msg: `循环依赖: ${cycle.join(' → ')}` });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 5. 僵尸 Intent:in_progress/blocked 超过 N 天无活动(按验证记录最后修改时间)
|
|
65
|
+
const ZOMBIE_DAYS = 7;
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
for (const [id, intent] of Object.entries(intents)) {
|
|
68
|
+
if (intent.status !== 'in_progress' && intent.status !== 'blocked') continue;
|
|
69
|
+
const recordPath = join(verificationsDir, `${id}.json`);
|
|
70
|
+
let lastActivity = existsSync(join(loomDir, '04_INTENT_MAP.json'))
|
|
71
|
+
? statSync(join(loomDir, '04_INTENT_MAP.json')).mtimeMs
|
|
72
|
+
: now;
|
|
73
|
+
if (existsSync(recordPath)) {
|
|
74
|
+
lastActivity = Math.max(lastActivity, statSync(recordPath).mtimeMs);
|
|
75
|
+
}
|
|
76
|
+
const daysIdle = (now - lastActivity) / (1000 * 60 * 60 * 24);
|
|
77
|
+
if (daysIdle > ZOMBIE_DAYS) {
|
|
78
|
+
issues.push({ id, type: 'zombie', severity: 'medium', msg: `${id} 状态为 ${intent.status} 已 ${Math.floor(daysIdle)} 天无活动` });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 6. 依赖状态一致性:completed 不能依赖 blocked
|
|
83
|
+
for (const [id, intent] of Object.entries(intents)) {
|
|
84
|
+
if (intent.status !== 'completed' || !intent.depends_on) continue;
|
|
85
|
+
for (const dep of intent.depends_on) {
|
|
86
|
+
const depIntent = intents[dep];
|
|
87
|
+
if (depIntent && depIntent.status === 'blocked') {
|
|
88
|
+
issues.push({ id, type: 'completed_depends_blocked', severity: 'high', msg: `${id} 状态为 completed 但依赖 blocked 的 ${dep}` });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const summary = {
|
|
94
|
+
total_issues: issues.length,
|
|
95
|
+
fatal: issues.filter((i) => i.severity === 'fatal').length,
|
|
96
|
+
high: issues.filter((i) => i.severity === 'high').length,
|
|
97
|
+
medium: issues.filter((i) => i.severity === 'medium').length,
|
|
98
|
+
healthy: issues.length === 0,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return { issues, summary };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* DFS 检测循环依赖。
|
|
106
|
+
* 返回循环路径数组,每个是 [A, B, C, A] 形式。
|
|
107
|
+
*/
|
|
108
|
+
function detectCycles(intents) {
|
|
109
|
+
const cycles = [];
|
|
110
|
+
const visited = new Set();
|
|
111
|
+
const stack = new Set();
|
|
112
|
+
const path = [];
|
|
113
|
+
|
|
114
|
+
function dfs(id) {
|
|
115
|
+
if (stack.has(id)) {
|
|
116
|
+
// 找到环
|
|
117
|
+
const cycleStart = path.indexOf(id);
|
|
118
|
+
cycles.push([...path.slice(cycleStart), id]);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (visited.has(id)) return;
|
|
122
|
+
visited.add(id);
|
|
123
|
+
stack.add(id);
|
|
124
|
+
path.push(id);
|
|
125
|
+
const intent = intents[id];
|
|
126
|
+
if (intent?.depends_on) {
|
|
127
|
+
for (const dep of intent.depends_on) {
|
|
128
|
+
if (dep in intents) dfs(dep);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
path.pop();
|
|
132
|
+
stack.delete(id);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const id of Object.keys(intents)) {
|
|
136
|
+
if (!visited.has(id)) dfs(id);
|
|
137
|
+
}
|
|
138
|
+
return cycles;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─── context ───────────────────────────────────────────
|
|
142
|
+
// 一条命令拿到:进度 + 下一个 Intent + 待验证 + 不一致项 + 风险
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 项目上下文摘要——Agent 重启后一条命令获取"我在哪"。
|
|
146
|
+
* @param {string} loomDir
|
|
147
|
+
* @param {string} verificationsDir
|
|
148
|
+
* @param {string} philosophyDir
|
|
149
|
+
* @returns {object}
|
|
150
|
+
*/
|
|
151
|
+
export function contextSummary(loomDir, verificationsDir, philosophyDir) {
|
|
152
|
+
const status = getStatus(loomDir);
|
|
153
|
+
const next = getNextIntent(loomDir);
|
|
154
|
+
const pending = getPendingVerifications(loomDir, verificationsDir);
|
|
155
|
+
const { issues } = doctor(loomDir, verificationsDir, philosophyDir);
|
|
156
|
+
|
|
157
|
+
const risks = [];
|
|
158
|
+
const fatalCount = issues.filter((i) => i.severity === 'fatal').length;
|
|
159
|
+
const highCount = issues.filter((i) => i.severity === 'high').length;
|
|
160
|
+
if (fatalCount > 0) risks.push(`${fatalCount} 个致命问题(循环依赖)`);
|
|
161
|
+
if (highCount > 0) risks.push(`${highCount} 个高严重度问题(状态不一致/孤儿引用)`);
|
|
162
|
+
if (status.counts.blocked > 0) risks.push(`${status.counts.blocked} 个阻塞 Intent`);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
progress: {
|
|
166
|
+
completed: status.counts.completed,
|
|
167
|
+
total: status.counts.total,
|
|
168
|
+
rate: `${status.counts.completed}/${status.counts.total}`,
|
|
169
|
+
},
|
|
170
|
+
next_intent: next ? next.id : null,
|
|
171
|
+
pending_verifications: pending,
|
|
172
|
+
inconsistent_states: issues.filter((i) => i.type === 'in_progress_no_record' || i.type === 'completed_no_record').map((i) => i.id),
|
|
173
|
+
risks,
|
|
174
|
+
healthy: issues.length === 0,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ─── trace ─────────────────────────────────────────────
|
|
179
|
+
// Intent 完整追溯链:依赖链 + 验证历史 + 哲学锚点内容 + 意图叙事
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 返回某个 Intent 的完整追溯链。
|
|
183
|
+
* @param {string} loomDir
|
|
184
|
+
* @param {string} verificationsDir
|
|
185
|
+
* @param {string} philosophyDir
|
|
186
|
+
* @param {string} intentId
|
|
187
|
+
* @returns {object}
|
|
188
|
+
*/
|
|
189
|
+
export function traceIntent(loomDir, verificationsDir, philosophyDir, intentId) {
|
|
190
|
+
const intent = getIntent(loomDir, intentId);
|
|
191
|
+
if (!intent) throw new Error(`Intent 不存在: ${intentId}`);
|
|
192
|
+
|
|
193
|
+
// 意图叙事
|
|
194
|
+
let narrative = null;
|
|
195
|
+
try { narrative = getNarrative(loomDir, intentId); } catch { /* narrative_ref 可能缺失 */ }
|
|
196
|
+
|
|
197
|
+
// 验收契约
|
|
198
|
+
let acceptance = null;
|
|
199
|
+
try { acceptance = getVerificationContract(loomDir, intentId); } catch { /* 可能缺失 */ }
|
|
200
|
+
|
|
201
|
+
// 验证历史
|
|
202
|
+
const verificationHistory = getVerificationHistory(verificationsDir, intentId);
|
|
203
|
+
|
|
204
|
+
// 哲学锚点内容
|
|
205
|
+
const philosophyContent = {};
|
|
206
|
+
if (intent.philosophy_anchors) {
|
|
207
|
+
for (const anchor of intent.philosophy_anchors) {
|
|
208
|
+
try {
|
|
209
|
+
philosophyContent[anchor] = getPhilosophy(philosophyDir, anchor);
|
|
210
|
+
} catch (e) {
|
|
211
|
+
philosophyContent[anchor] = null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 依赖链(递归向上)
|
|
217
|
+
const { intents } = loadIntentMap(loomDir);
|
|
218
|
+
const dependencyChain = [];
|
|
219
|
+
function walkDeps(id, depth) {
|
|
220
|
+
const node = intents[id];
|
|
221
|
+
if (!node?.depends_on || node.depends_on.length === 0) return;
|
|
222
|
+
for (const dep of node.depends_on) {
|
|
223
|
+
dependencyChain.push({ id: dep, depth, status: intents[dep]?.status });
|
|
224
|
+
walkDeps(dep, depth + 1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
walkDeps(intentId, 0);
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
intent,
|
|
231
|
+
narrative,
|
|
232
|
+
acceptance,
|
|
233
|
+
verification_history: verificationHistory,
|
|
234
|
+
philosophy_anchors_content: philosophyContent,
|
|
235
|
+
dependency_chain: dependencyChain,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ─── reverse-dep ───────────────────────────────────────
|
|
240
|
+
// 反向依赖:哪些 Intent 依赖这个 Intent
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 返回依赖指定 Intent 的所有 Intent。
|
|
244
|
+
* @param {string} loomDir
|
|
245
|
+
* @param {string} intentId
|
|
246
|
+
* @returns {string[]}
|
|
247
|
+
*/
|
|
248
|
+
export function reverseDep(loomDir, intentId) {
|
|
249
|
+
const { intents } = loadIntentMap(loomDir);
|
|
250
|
+
const result = [];
|
|
251
|
+
for (const [id, intent] of Object.entries(intents)) {
|
|
252
|
+
if (intent.depends_on?.includes(intentId)) {
|
|
253
|
+
result.push(id);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ─── reverse-ref ───────────────────────────────────────
|
|
260
|
+
// 反向哲学引用:哪些 Intent 引用了这个哲学锚点
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 返回引用指定哲学锚点的所有 Intent。
|
|
264
|
+
* @param {string} loomDir
|
|
265
|
+
* @param {string} anchor — 如 "PRODUCT_PHILOSOPHY.md#core-belief"
|
|
266
|
+
* @returns {string[]}
|
|
267
|
+
*/
|
|
268
|
+
export function reverseRef(loomDir, anchor) {
|
|
269
|
+
const { intents } = loadIntentMap(loomDir);
|
|
270
|
+
const result = [];
|
|
271
|
+
for (const [id, intent] of Object.entries(intents)) {
|
|
272
|
+
if (intent.philosophy_anchors?.includes(anchor)) {
|
|
273
|
+
result.push(id);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
}
|
package/cli/src/guide.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// guide — 状态感知动态引导
|
|
2
|
+
// 检测项目当前在哪个阶段,输出"你在阶段 X,下一步做 Y"。
|
|
3
|
+
// 面向 Agent(主)和人类(辅)。比 help workflow(静态)智能。
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { readCurrentPointer } from './version.js';
|
|
8
|
+
import { loadIntentMap } from './intent-map.js';
|
|
9
|
+
import { isAutoOn } from './auto.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 检测文件是否还是模板(未填充真实内容)。
|
|
13
|
+
* MD 文件检查 <!-- LOOM_TEMPLATE --> 标记。
|
|
14
|
+
* JSON 文件检查 _meta._template 字段。
|
|
15
|
+
*/
|
|
16
|
+
function isTemplate(filePath) {
|
|
17
|
+
if (!existsSync(filePath)) return true;
|
|
18
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
19
|
+
if (filePath.endsWith('.json')) {
|
|
20
|
+
try {
|
|
21
|
+
const data = JSON.parse(content);
|
|
22
|
+
return data._meta?._template === true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false; // 损坏文件不算模板
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return content.includes('<!-- LOOM_TEMPLATE -->');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 诊断项目当前阶段。
|
|
32
|
+
* @param {string} projectDir — 项目根目录
|
|
33
|
+
* @returns {{ stage: string, stage_num: number, details: object, auto: boolean, next_action: string, next_command: string, message: string }}
|
|
34
|
+
*/
|
|
35
|
+
export function guideProject(projectDir) {
|
|
36
|
+
const cwd = projectDir || process.cwd();
|
|
37
|
+
const loomRoot = join(cwd, '.loom');
|
|
38
|
+
const auto = isAutoOn(loomRoot);
|
|
39
|
+
|
|
40
|
+
// 状态 0: 没有 .loom/
|
|
41
|
+
if (!existsSync(loomRoot)) {
|
|
42
|
+
return {
|
|
43
|
+
stage: 'not_initialized',
|
|
44
|
+
stage_num: 0,
|
|
45
|
+
details: {},
|
|
46
|
+
auto,
|
|
47
|
+
next_action: '初始化 LOOM 项目',
|
|
48
|
+
next_command: 'loom init',
|
|
49
|
+
message: '项目尚未初始化。运行 loom init 创建 .loom/v1/ 骨架。',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const current = readCurrentPointer(loomRoot);
|
|
54
|
+
if (!current) {
|
|
55
|
+
return {
|
|
56
|
+
stage: 'no_version',
|
|
57
|
+
stage_num: 0,
|
|
58
|
+
details: {},
|
|
59
|
+
auto,
|
|
60
|
+
next_action: '初始化第一个版本',
|
|
61
|
+
next_command: 'loom init',
|
|
62
|
+
message: '.loom/ 存在但没有版本目录。运行 loom init 创建 v1。',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const versionDir = join(loomRoot, current);
|
|
67
|
+
const philosophyDir = join(versionDir, '00_PHILOSOPHY');
|
|
68
|
+
const visionPath = join(versionDir, '01_VISION.md');
|
|
69
|
+
const intentMapPath = join(versionDir, '04_INTENT_MAP.json');
|
|
70
|
+
|
|
71
|
+
// 状态 1: 哲学未织造
|
|
72
|
+
const philosophyFile = join(philosophyDir, 'PRODUCT_PHILOSOPHY.md');
|
|
73
|
+
if (isTemplate(philosophyFile)) {
|
|
74
|
+
return {
|
|
75
|
+
stage: 'need_philosophy',
|
|
76
|
+
stage_num: 1,
|
|
77
|
+
details: { version: current },
|
|
78
|
+
auto,
|
|
79
|
+
next_action: '织造产品哲学',
|
|
80
|
+
next_command: 'loom activate weaver',
|
|
81
|
+
message: `当前版本 ${current}:哲学还是模板,需要 Weaver 织造。`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 状态 2: 哲学已织造,愿景未定义
|
|
86
|
+
if (isTemplate(visionPath)) {
|
|
87
|
+
return {
|
|
88
|
+
stage: 'need_vision',
|
|
89
|
+
stage_num: 2,
|
|
90
|
+
details: { version: current },
|
|
91
|
+
auto,
|
|
92
|
+
next_action: '定义产品愿景',
|
|
93
|
+
next_command: 'loom activate visionary',
|
|
94
|
+
message: `当前版本 ${current}:哲学已织造,愿景还是模板,需要 Visionary 定义。`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 状态 3: 愿景已定义,Intent Map 未设计
|
|
99
|
+
if (isTemplate(intentMapPath)) {
|
|
100
|
+
return {
|
|
101
|
+
stage: 'need_architecture',
|
|
102
|
+
stage_num: 3,
|
|
103
|
+
details: { version: current },
|
|
104
|
+
auto,
|
|
105
|
+
next_action: '设计系统架构 + Intent Map',
|
|
106
|
+
next_command: 'loom activate architect',
|
|
107
|
+
message: `当前版本 ${current}:愿景已定义,Intent Map 还是模板,需要 Architect 设计。`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 状态 4-7: Intent Map 已设计,根据 Intent 状态判断
|
|
112
|
+
let intents;
|
|
113
|
+
try {
|
|
114
|
+
intents = loadIntentMap(versionDir).intents;
|
|
115
|
+
} catch (e) {
|
|
116
|
+
return {
|
|
117
|
+
stage: 'intent_map_broken',
|
|
118
|
+
stage_num: 3,
|
|
119
|
+
details: { version: current, error: e.message },
|
|
120
|
+
auto,
|
|
121
|
+
next_action: '修复 Intent Map',
|
|
122
|
+
next_command: 'loom intent validate',
|
|
123
|
+
message: `Intent Map 格式错误: ${e.message}`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const allIntents = Object.values(intents);
|
|
128
|
+
const counts = {
|
|
129
|
+
pending: allIntents.filter((i) => i.status === 'pending').length,
|
|
130
|
+
in_progress: allIntents.filter((i) => i.status === 'in_progress').length,
|
|
131
|
+
completed: allIntents.filter((i) => i.status === 'completed').length,
|
|
132
|
+
blocked: allIntents.filter((i) => i.status === 'blocked').length,
|
|
133
|
+
needs_review: allIntents.filter((i) => i.status === 'needs_review').length,
|
|
134
|
+
};
|
|
135
|
+
const total = allIntents.length;
|
|
136
|
+
|
|
137
|
+
// 状态 7: 有 blocked(优先报告)
|
|
138
|
+
if (counts.blocked > 0) {
|
|
139
|
+
const blockedIds = allIntents.filter((i) => i.status === 'blocked').map((i) => i.id);
|
|
140
|
+
return {
|
|
141
|
+
stage: 'blocked',
|
|
142
|
+
stage_num: 7,
|
|
143
|
+
details: { version: current, counts, blocked_ids: blockedIds },
|
|
144
|
+
auto,
|
|
145
|
+
next_action: '人工介入解决阻塞',
|
|
146
|
+
next_command: 'loom intent get ' + blockedIds[0],
|
|
147
|
+
message: `有 ${counts.blocked} 个 Intent 阻塞: ${blockedIds.join(', ')}。需要人工介入。`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 状态 6: 全部 completed
|
|
152
|
+
if (counts.completed === total && total > 0) {
|
|
153
|
+
return {
|
|
154
|
+
stage: 'done',
|
|
155
|
+
stage_num: 6,
|
|
156
|
+
details: { version: current, counts, total },
|
|
157
|
+
auto,
|
|
158
|
+
next_action: '项目阶段完成,考虑版本演进',
|
|
159
|
+
next_command: 'loom version new',
|
|
160
|
+
message: `当前版本 ${current}:全部 ${total} 个 Intent 已完成。可考虑 loom version new 开始新版本。`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 状态 5: 有 in_progress
|
|
165
|
+
if (counts.in_progress > 0) {
|
|
166
|
+
const inProgressIds = allIntents.filter((i) => i.status === 'in_progress').map((i) => i.id);
|
|
167
|
+
return {
|
|
168
|
+
stage: 'in_loop',
|
|
169
|
+
stage_num: 5,
|
|
170
|
+
details: { version: current, counts, in_progress_ids: inProgressIds },
|
|
171
|
+
auto,
|
|
172
|
+
next_action: '继续 Intent Loop',
|
|
173
|
+
next_command: 'loom context',
|
|
174
|
+
message: `当前版本 ${current}:Intent Loop 进行中(${inProgressIds.join(', ')})。运行 loom context 查看状态。`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 状态 4: 有 pending,进入 Intent Loop
|
|
179
|
+
if (counts.pending > 0) {
|
|
180
|
+
return {
|
|
181
|
+
stage: 'ready_for_loop',
|
|
182
|
+
stage_num: 4,
|
|
183
|
+
details: { version: current, counts, total },
|
|
184
|
+
auto,
|
|
185
|
+
next_action: '进入 Intent Loop',
|
|
186
|
+
next_command: 'loom intent next',
|
|
187
|
+
message: `当前版本 ${current}:${counts.pending} 个 Intent 待执行。运行 loom intent next 开始。`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 兜底
|
|
192
|
+
return {
|
|
193
|
+
stage: 'unknown',
|
|
194
|
+
stage_num: -1,
|
|
195
|
+
details: { version: current, counts },
|
|
196
|
+
auto,
|
|
197
|
+
next_action: '运行健康检查',
|
|
198
|
+
next_command: 'loom doctor',
|
|
199
|
+
message: '项目状态不明确,运行 loom doctor 诊断。',
|
|
200
|
+
};
|
|
201
|
+
}
|