@haaaiawd/loom 0.8.0 → 0.9.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.
@@ -2,102 +2,105 @@
2
2
  // 提供 doctor / context / trace / reverse-dep / reverse-ref 五个聚合命令。
3
3
  // 全部是只读的数据聚合,不做决策、不修改文件。
4
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, validateInspirationSources, validatePartDecomposition } from './philosophy.js';
9
- import { getVerificationHistory, getPendingVerifications, getVerificationContract } from './verify.js';
10
-
11
- function readIntentMapRaw(versionDir) {
12
- const filePath = join(versionDir, '04_INTENT_MAP.json');
13
- if (!existsSync(filePath)) return null;
14
- return JSON.parse(readFileSync(filePath, 'utf-8'));
15
- }
16
-
17
- function summarizeRawIntentMap(data) {
18
- const intents = data?.intents && typeof data.intents === 'object' ? data.intents : {};
19
- const ids = { pending: [], in_progress: [], completed: [], blocked: [], needs_review: [] };
20
- const titles = {};
21
-
22
- for (const [id, intent] of Object.entries(intents)) {
23
- const status = intent?.status;
24
- if (ids[status]) ids[status].push(id);
25
- titles[id] = intent?.title || '';
26
- }
27
-
28
- return {
29
- counts: {
30
- pending: ids.pending.length,
31
- in_progress: ids.in_progress.length,
32
- completed: ids.completed.length,
33
- blocked: ids.blocked.length,
34
- needs_review: ids.needs_review.length,
35
- total: Object.keys(intents).length,
36
- },
37
- ids,
38
- titles,
39
- };
40
- }
41
-
42
- function intentMapDiagnostics(versionDir) {
43
- const issues = [];
44
- let raw = null;
45
- let valid = null;
46
- let validMap = null;
47
-
48
- try {
49
- raw = readIntentMapRaw(versionDir);
50
- } catch (e) {
51
- issues.push({ id: 'intent_map', type: 'intent_map_unreadable', severity: 'fatal', msg: `Intent Map 无法读取或 JSON 损坏: ${e.message}` });
52
- return { raw, valid, issues, validMap: null };
53
- }
54
-
55
- if (!raw) {
56
- issues.push({ id: 'intent_map', type: 'intent_map_missing', severity: 'fatal', msg: '缺少 04_INTENT_MAP.json' });
57
- return { raw, valid, issues, validMap: null };
58
- }
59
-
60
- if (raw._meta?._template === true) {
61
- issues.push({ id: 'intent_map', type: 'intent_map_template', severity: 'high', msg: 'Intent Map 仍是模板,尚未由 Architect 产出真实意图图' });
62
- }
63
-
64
- try {
65
- validMap = loadIntentMap(versionDir);
66
- valid = true;
67
- } catch (e) {
68
- valid = false;
69
- issues.push({ id: 'intent_map', type: 'intent_map_invalid', severity: 'fatal', msg: e.message });
70
- }
71
-
72
- return { raw, valid, issues, validMap };
73
- }
74
-
75
- function getIntentVerificationMethod(intent) {
76
- return intent.verification_method || intent._optional?.verification_method || null;
77
- }
78
-
79
- function normalizeVerificationCommand(command) {
80
- return String(command || '')
81
- .replace(/^\s*run\s+/i, '')
82
- .replace(/^\s*exec\s+/i, '')
83
- .replace(/\s+/g, ' ')
84
- .trim();
85
- }
86
-
87
- function commandCoversMethod(actualCommand, expectedMethod) {
88
- const actual = normalizeVerificationCommand(actualCommand);
89
- const expected = normalizeVerificationCommand(expectedMethod);
90
- if (!actual || !expected) return false;
91
-
92
- return expected.split('&&').every((part) => {
93
- const expectedPart = normalizeVerificationCommand(part);
94
- if (!expectedPart) return true;
95
- if (actual.includes(expectedPart)) return true;
96
- // npm test is an acceptable broader reproduction for node --test based methods.
97
- if (expectedPart.startsWith('node --test') && actual.includes('npm test')) return true;
98
- return false;
99
- });
100
- }
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, validateInspirationSources, validatePartDecomposition } from './philosophy.js';
9
+ import { getVerificationHistory, getPendingVerifications, getVerificationContract } from './verify.js';
10
+
11
+ function readIntentMapRaw(versionDir) {
12
+ const filePath = join(versionDir, '04_INTENT_MAP.json');
13
+ if (!existsSync(filePath)) return null;
14
+ return JSON.parse(readFileSync(filePath, 'utf-8'));
15
+ }
16
+
17
+ function summarizeRawIntentMap(data) {
18
+ const intents = data?.intents && typeof data.intents === 'object' ? data.intents : {};
19
+ const ids = { pending: [], in_progress: [], completed: [], blocked: [], needs_review: [] };
20
+ const titles = {};
21
+
22
+ for (const [id, intent] of Object.entries(intents)) {
23
+ const status = intent?.status;
24
+ if (ids[status]) ids[status].push(id);
25
+ titles[id] = intent?.title || '';
26
+ }
27
+
28
+ return {
29
+ counts: {
30
+ pending: ids.pending.length,
31
+ in_progress: ids.in_progress.length,
32
+ completed: ids.completed.length,
33
+ blocked: ids.blocked.length,
34
+ needs_review: ids.needs_review.length,
35
+ total: Object.keys(intents).length,
36
+ },
37
+ ids,
38
+ titles,
39
+ };
40
+ }
41
+
42
+ function intentMapDiagnostics(versionDir) {
43
+ const issues = [];
44
+ let raw = null;
45
+ let valid = null;
46
+ let validMap = null;
47
+
48
+ try {
49
+ raw = readIntentMapRaw(versionDir);
50
+ } catch (e) {
51
+ issues.push({ id: 'intent_map', type: 'intent_map_unreadable', severity: 'fatal', msg: `Intent Map 无法读取或 JSON 损坏: ${e.message}` });
52
+ return { raw, valid, issues, validMap: null };
53
+ }
54
+
55
+ if (!raw) {
56
+ issues.push({ id: 'intent_map', type: 'intent_map_missing', severity: 'fatal', msg: '缺少 04_INTENT_MAP.json' });
57
+ return { raw, valid, issues, validMap: null };
58
+ }
59
+
60
+ const isTemplate = raw._meta?._template === true;
61
+ if (isTemplate) {
62
+ issues.push({ id: 'intent_map', type: 'intent_map_template', severity: 'high', msg: 'Intent Map 仍是模板,尚未由 Architect 产出真实意图图' });
63
+ }
64
+
65
+ try {
66
+ validMap = loadIntentMap(versionDir);
67
+ valid = true;
68
+ } catch (e) {
69
+ valid = false;
70
+ // 模板状态下字段缺失是预期的,降级为 high 而非 fatal
71
+ // 非模板状态下字段缺失是真正的损坏,保持 fatal
72
+ issues.push({ id: 'intent_map', type: 'intent_map_invalid', severity: isTemplate ? 'high' : 'fatal', msg: e.message });
73
+ }
74
+
75
+ return { raw, valid, issues, validMap };
76
+ }
77
+
78
+ function getIntentVerificationMethod(intent) {
79
+ return intent.verification_method || intent._optional?.verification_method || null;
80
+ }
81
+
82
+ function normalizeVerificationCommand(command) {
83
+ return String(command || '')
84
+ .replace(/^\s*run\s+/i, '')
85
+ .replace(/^\s*exec\s+/i, '')
86
+ .replace(/\s+/g, ' ')
87
+ .trim();
88
+ }
89
+
90
+ function commandCoversMethod(actualCommand, expectedMethod) {
91
+ const actual = normalizeVerificationCommand(actualCommand);
92
+ const expected = normalizeVerificationCommand(expectedMethod);
93
+ if (!actual || !expected) return false;
94
+
95
+ return expected.split('&&').every((part) => {
96
+ const expectedPart = normalizeVerificationCommand(part);
97
+ if (!expectedPart) return true;
98
+ if (actual.includes(expectedPart)) return true;
99
+ // npm test is an acceptable broader reproduction for node --test based methods.
100
+ if (expectedPart.startsWith('node --test') && actual.includes('npm test')) return true;
101
+ return false;
102
+ });
103
+ }
101
104
 
102
105
  // ─── doctor ────────────────────────────────────────────
103
106
  // 全面健康检查:一致性 + 孤儿引用 + 循环依赖 + 僵尸 Intent
@@ -109,16 +112,16 @@ function commandCoversMethod(actualCommand, expectedMethod) {
109
112
  * @param {string} philosophyDir — 哲学目录
110
113
  * @returns {{ issues: object[], summary: object }}
111
114
  */
112
- export function doctor(versionDir, verificationsDir, philosophyDir) {
113
- const mapState = intentMapDiagnostics(versionDir);
114
- const issues = [...mapState.issues];
115
-
116
- if (!mapState.validMap) {
117
- appendPhilosophyDiagnostics(issues, philosophyDir);
118
- return { issues, summary: summarizeIssues(issues) };
119
- }
120
-
121
- const { intents } = mapState.validMap;
115
+ export function doctor(versionDir, verificationsDir, philosophyDir) {
116
+ const mapState = intentMapDiagnostics(versionDir);
117
+ const issues = [...mapState.issues];
118
+
119
+ if (!mapState.validMap) {
120
+ appendPhilosophyDiagnostics(issues, philosophyDir);
121
+ return { issues, summary: summarizeIssues(issues) };
122
+ }
123
+
124
+ const { intents } = mapState.validMap;
122
125
 
123
126
  // 1. 状态一致性:in_progress/completed 但无验证记录
124
127
  for (const [id, intent] of Object.entries(intents)) {
@@ -188,13 +191,13 @@ export function doctor(versionDir, verificationsDir, philosophyDir) {
188
191
  }
189
192
  }
190
193
 
191
- // 7. 验证脚本可执行性:检查 verification_method 引用的脚本/目录是否存在
192
- const projectDir = join(versionDir, '..', '..');
193
- for (const [id, intent] of Object.entries(intents)) {
194
- const vm = getIntentVerificationMethod(intent);
195
- if (!vm) continue;
196
- // 检测 npm test 引用
197
- if (vm.includes('npm test') || vm.includes('npm run test')) {
194
+ // 7. 验证脚本可执行性:检查 verification_method 引用的脚本/目录是否存在
195
+ const projectDir = join(versionDir, '..', '..');
196
+ for (const [id, intent] of Object.entries(intents)) {
197
+ const vm = getIntentVerificationMethod(intent);
198
+ if (!vm) continue;
199
+ // 检测 npm test 引用
200
+ if (vm.includes('npm test') || vm.includes('npm run test')) {
198
201
  const pkgPath = join(projectDir, 'package.json');
199
202
  if (!existsSync(pkgPath)) {
200
203
  issues.push({ id, type: 'test_script_missing', severity: 'medium', msg: `${id} verification_method 要求 npm test 但项目根没有 package.json` });
@@ -218,61 +221,61 @@ export function doctor(versionDir, verificationsDir, philosophyDir) {
218
221
  } catch {
219
222
  // package.json 解析失败,不报——不是 doctor 的职责
220
223
  }
221
- }
222
- }
223
- }
224
-
225
- // 8. completed Intent 的 verification_method 必须被最新验证记录覆盖,防止契约命令漂移。
226
- for (const [id, intent] of Object.entries(intents)) {
227
- if (intent.status !== 'completed') continue;
228
- const method = getIntentVerificationMethod(intent);
229
- if (!method) continue;
230
-
231
- const expected = normalizeVerificationCommand(method);
232
- if (!expected || expected === 'human_review') continue;
233
-
234
- const history = getVerificationHistory(verificationsDir, id);
235
- const latest = history?.records?.[history.records.length - 1];
236
- const actual = normalizeVerificationCommand(latest?.reproduction_command);
237
-
238
- if (!latest) {
239
- issues.push({ id, type: 'verification_method_unverified', severity: 'high', msg: `${id} 声明了 verification_method 但没有验证记录覆盖: ${method}` });
240
- } else if (!actual) {
241
- issues.push({ id, type: 'verification_method_unverified', severity: 'high', msg: `${id} 最新验证记录缺少 reproduction_command,无法复现 verification_method: ${method}` });
242
- } else if (!commandCoversMethod(actual, expected)) {
243
- issues.push({ id, type: 'verification_method_drift', severity: 'high', msg: `${id} verification_method 未被最新 reproduction_command 覆盖。method="${method}" reproduction_command="${latest.reproduction_command}"` });
244
- }
245
- }
246
-
247
- appendPhilosophyDiagnostics(issues, philosophyDir);
248
- return { issues, summary: summarizeIssues(issues) };
249
- }
250
-
251
- function appendPhilosophyDiagnostics(issues, philosophyDir) {
252
- // 哲学灵感来源校验(防止 Weaver 从训练数据"背"几个名字就交差)
253
- if (!existsSync(philosophyDir)) return;
254
-
255
- const inspirationCheck = validateInspirationSources(philosophyDir);
256
- for (const issue of inspirationCheck.issues) {
257
- issues.push({ id: 'philosophy', type: 'inspiration_source', severity: issue.severity, msg: issue.msg });
258
- }
259
-
260
- // 实现部分拆解校验(防止 Weaver 跳过拆解步骤)
261
- const decompositionCheck = validatePartDecomposition(philosophyDir);
262
- for (const issue of decompositionCheck.issues) {
263
- issues.push({ id: 'philosophy', type: 'part_decomposition', severity: issue.severity, msg: issue.msg });
264
- }
265
- }
266
-
267
- function summarizeIssues(issues) {
268
- return {
269
- total_issues: issues.length,
270
- fatal: issues.filter((i) => i.severity === 'fatal').length,
271
- high: issues.filter((i) => i.severity === 'high').length,
272
- medium: issues.filter((i) => i.severity === 'medium').length,
273
- healthy: issues.length === 0,
274
- };
275
- }
224
+ }
225
+ }
226
+ }
227
+
228
+ // 8. completed Intent 的 verification_method 必须被最新验证记录覆盖,防止契约命令漂移。
229
+ for (const [id, intent] of Object.entries(intents)) {
230
+ if (intent.status !== 'completed') continue;
231
+ const method = getIntentVerificationMethod(intent);
232
+ if (!method) continue;
233
+
234
+ const expected = normalizeVerificationCommand(method);
235
+ if (!expected || expected === 'human_review') continue;
236
+
237
+ const history = getVerificationHistory(verificationsDir, id);
238
+ const latest = history?.records?.[history.records.length - 1];
239
+ const actual = normalizeVerificationCommand(latest?.reproduction_command);
240
+
241
+ if (!latest) {
242
+ issues.push({ id, type: 'verification_method_unverified', severity: 'high', msg: `${id} 声明了 verification_method 但没有验证记录覆盖: ${method}` });
243
+ } else if (!actual) {
244
+ issues.push({ id, type: 'verification_method_unverified', severity: 'high', msg: `${id} 最新验证记录缺少 reproduction_command,无法复现 verification_method: ${method}` });
245
+ } else if (!commandCoversMethod(actual, expected)) {
246
+ issues.push({ id, type: 'verification_method_drift', severity: 'high', msg: `${id} verification_method 未被最新 reproduction_command 覆盖。method="${method}" reproduction_command="${latest.reproduction_command}"` });
247
+ }
248
+ }
249
+
250
+ appendPhilosophyDiagnostics(issues, philosophyDir);
251
+ return { issues, summary: summarizeIssues(issues) };
252
+ }
253
+
254
+ function appendPhilosophyDiagnostics(issues, philosophyDir) {
255
+ // 哲学灵感来源校验(防止 Weaver 从训练数据"背"几个名字就交差)
256
+ if (!existsSync(philosophyDir)) return;
257
+
258
+ const inspirationCheck = validateInspirationSources(philosophyDir);
259
+ for (const issue of inspirationCheck.issues) {
260
+ issues.push({ id: 'philosophy', type: 'inspiration_source', severity: issue.severity, msg: issue.msg });
261
+ }
262
+
263
+ // 实现部分拆解校验(防止 Weaver 跳过拆解步骤)
264
+ const decompositionCheck = validatePartDecomposition(philosophyDir);
265
+ for (const issue of decompositionCheck.issues) {
266
+ issues.push({ id: 'philosophy', type: 'part_decomposition', severity: issue.severity, msg: issue.msg });
267
+ }
268
+ }
269
+
270
+ function summarizeIssues(issues) {
271
+ return {
272
+ total_issues: issues.length,
273
+ fatal: issues.filter((i) => i.severity === 'fatal').length,
274
+ high: issues.filter((i) => i.severity === 'high').length,
275
+ medium: issues.filter((i) => i.severity === 'medium').length,
276
+ healthy: issues.length === 0,
277
+ };
278
+ }
276
279
 
277
280
  /**
278
281
  * DFS 检测循环依赖。
@@ -321,26 +324,26 @@ function detectCycles(intents) {
321
324
  * @param {string} philosophyDir
322
325
  * @returns {object}
323
326
  */
324
- export function contextSummary(versionDir, verificationsDir, philosophyDir) {
325
- const mapState = intentMapDiagnostics(versionDir);
326
- const status = mapState.valid ? getStatus(versionDir) : summarizeRawIntentMap(mapState.raw);
327
- const next = mapState.valid ? getNextIntent(versionDir) : null;
328
- const pending = mapState.valid ? getPendingVerifications(versionDir, verificationsDir) : [];
329
- const { issues } = doctor(versionDir, verificationsDir, philosophyDir);
327
+ export function contextSummary(versionDir, verificationsDir, philosophyDir) {
328
+ const mapState = intentMapDiagnostics(versionDir);
329
+ const status = mapState.valid ? getStatus(versionDir) : summarizeRawIntentMap(mapState.raw);
330
+ const next = mapState.valid ? getNextIntent(versionDir) : null;
331
+ const pending = mapState.valid ? getPendingVerifications(versionDir, verificationsDir) : [];
332
+ const { issues } = doctor(versionDir, verificationsDir, philosophyDir);
330
333
 
331
334
  const risks = [];
332
335
  const fatalCount = issues.filter((i) => i.severity === 'fatal').length;
333
336
  const highCount = issues.filter((i) => i.severity === 'high').length;
334
- if (fatalCount > 0) risks.push(`${fatalCount} 个致命问题(Intent Map 损坏/循环依赖)`);
335
- if (highCount > 0) risks.push(`${highCount} 个高严重度问题(状态不一致/孤儿引用)`);
336
- if (status.counts.blocked > 0) risks.push(`${status.counts.blocked} 个阻塞 Intent`);
337
-
338
- return {
339
- intent_map_valid: mapState.valid === true,
340
- progress: {
341
- completed: status.counts.completed,
342
- total: status.counts.total,
343
- rate: `${status.counts.completed}/${status.counts.total}`,
337
+ if (fatalCount > 0) risks.push(`${fatalCount} 个致命问题(Intent Map 损坏/循环依赖)`);
338
+ if (highCount > 0) risks.push(`${highCount} 个高严重度问题(状态不一致/孤儿引用)`);
339
+ if (status.counts.blocked > 0) risks.push(`${status.counts.blocked} 个阻塞 Intent`);
340
+
341
+ return {
342
+ intent_map_valid: mapState.valid === true,
343
+ progress: {
344
+ completed: status.counts.completed,
345
+ total: status.counts.total,
346
+ rate: `${status.counts.completed}/${status.counts.total}`,
344
347
  },
345
348
  next_intent: next ? next.id : null,
346
349
  pending_verifications: pending,
@@ -62,10 +62,13 @@ const REASON_KEYWORDS = ['萃取', '理由', '为什么', '因为', '启发', '
62
62
  * 从哲学文档内容中提取"灵感来源"章节的条目。
63
63
  * @param {string} content — MD 全文
64
64
  * @returns {Array<{ raw: string, name: string, urls: string[], hasReason: boolean }>}
65
+ * 如果返回空数组,调用方需区分"没有章节"和"有章节但没条目"——
66
+ * 用 hasInspirationSection() 单独判断。
65
67
  */
66
68
  function parseInspirationSources(content) {
67
- // 匹配 "## 灵感来源" 或 "## Inspiration" 章节
68
- const sectionMatch = content.match(/^##\s+(?:灵感来源|Inspiration|参考来源|References)/m);
69
+ // 匹配 "## 灵感来源" 或 "## Inspiration Sources" 等章节
70
+ // 支持 {#anchor} 后缀和多种标题变体
71
+ const sectionMatch = content.match(/^##\s+(?:灵感来源|Inspiration|参考来源|References?|参考文献|参考资料|Sources|Bibliography)/m);
69
72
  if (!sectionMatch) return [];
70
73
 
71
74
  const startIdx = sectionMatch.index + sectionMatch[0].length;
@@ -75,16 +78,19 @@ function parseInspirationSources(content) {
75
78
  ? content.slice(startIdx, startIdx + nextSection.index)
76
79
  : content.slice(startIdx);
77
80
 
78
- // 解析每个 list item(- * 开头)
81
+ // 解析每个 list item(- / * / 1. / 2. 等开头的无序或有序列表)
79
82
  const items = [];
80
83
  const lines = sectionText.split('\n');
81
84
  let currentItem = null;
82
85
 
86
+ // 匹配 - xxx / * xxx / 1. xxx / 2. xxx 等
87
+ const ITEM_RE = /^\s*(?:[-*]|\d+\.)\s+/;
88
+
83
89
  for (const line of lines) {
84
- if (/^\s*[-*]\s/.test(line)) {
90
+ if (ITEM_RE.test(line)) {
85
91
  // 新条目
86
92
  if (currentItem) items.push(currentItem);
87
- const raw = line.replace(/^\s*[-*]\s/, '').trim();
93
+ const raw = line.replace(ITEM_RE, '').trim();
88
94
  const urls = [...raw.matchAll(/https?:\/\/[^\s))]+/g)].map((m) => m[0]);
89
95
  const name = raw.replace(/\*\*/g, '').split(/[((——]/)[0].trim();
90
96
  const hasReason = REASON_KEYWORDS.some((kw) => raw.includes(kw));
@@ -103,6 +109,14 @@ function parseInspirationSources(content) {
103
109
  return items;
104
110
  }
105
111
 
112
+ /**
113
+ * 检查哲学文档是否有"灵感来源"章节(不管有没有条目)。
114
+ * 用来区分"没有章节"和"有章节但没条目"两种情况。
115
+ */
116
+ function hasInspirationSection(content) {
117
+ return /^##\s+(?:灵感来源|Inspiration|参考来源|References?|参考文献|参考资料|Sources|Bibliography)/m.test(content);
118
+ }
119
+
106
120
  /**
107
121
  * 判断 URL 是否为 Wikipedia 链接。
108
122
  */
@@ -173,12 +187,26 @@ export function validateInspirationSources(philosophyDir) {
173
187
  }
174
188
  }
175
189
 
176
- // 如果没有任何文件包含灵感来源章节
190
+ // 如果没有任何文件提取到灵感来源条目
177
191
  if (allSources.length === 0) {
178
- issues.push({
179
- severity: 'high',
180
- msg: '所有哲学文档都没有"灵感来源"章节。PHILOSOPHY_WEAVER.md 要求哲学文档必须包含灵感来源(参考了哪些机构、人物、流派——附 URL 和理由)。',
181
- });
192
+ // 区分两种情况:完全没有章节 vs 有章节但没条目
193
+ const filesWithSection = [];
194
+ for (const file of files) {
195
+ const content = readFileSync(join(philosophyDir, file), 'utf-8');
196
+ if (hasInspirationSection(content)) filesWithSection.push(file);
197
+ }
198
+
199
+ if (filesWithSection.length > 0) {
200
+ issues.push({
201
+ severity: 'high',
202
+ msg: `${filesWithSection.join(', ')} 有"灵感来源"章节但没有可识别的条目。章节里可能是模板占位符。需要 Weaver 真正走搜索漏斗,填入至少 ${MIN_SOURCES} 个源(- **源名** — 理由。来源:URL 格式)。`,
203
+ });
204
+ } else {
205
+ issues.push({
206
+ severity: 'high',
207
+ msg: '所有哲学文档都没有"灵感来源"章节。PHILOSOPHY_WEAVER.md 要求哲学文档必须包含灵感来源(参考了哪些机构、人物、流派——附 URL 和理由)。支持的标题:灵感来源 / Inspiration / 参考来源 / References / 参考文献 / 参考资料 / Sources / Bibliography。',
208
+ });
209
+ }
182
210
  }
183
211
 
184
212
  return {
@@ -204,7 +232,7 @@ export function validatePartDecomposition(philosophyDir) {
204
232
 
205
233
  const files = listPhilosophyFiles(philosophyDir);
206
234
 
207
- // 搜索"实现部分"相关章节——可能叫"实现部分清单""部分拆解""Part Decomposition"等
235
+ // 搜索"实现部分"相关章节——支持 {#anchor} 后缀和多种标题变体
208
236
  const PART_SECTION_PATTERNS = [
209
237
  /^##\s+实现部分清单/m,
210
238
  /^##\s+部分拆解/m,
@@ -212,11 +240,15 @@ export function validatePartDecomposition(philosophyDir) {
212
240
  /^##\s+Part Decomposition/m,
213
241
  /^##\s+Implementation Parts/m,
214
242
  /^##\s+拆解出的部分/m,
243
+ /^##\s+Implementation Decomposition/m,
244
+ /^##\s+Parts? /m,
215
245
  ];
216
246
 
217
- // 搜索部分条目——通常是 "- **部分名**" 或 "├── 部分名" 或 "| 部分名 |"
247
+ // 搜索部分条目——支持无序列表、有序列表、树形符号
218
248
  const PART_ITEM_PATTERNS = [
219
- /^\s*[-*]\s+\*\*(.+?)\*\*/gm, // - **CLI 交互设计**
249
+ /^\s*[-*]\s+\*\*(.+?)\*\*/gm, // - **CLI 交互设计**
250
+ /^\s*\d+\.\s+\*\*(.+?)\*\*/gm, // 1. **CLI 交互设计**
251
+ /^\s*\d+\.\s+(.+)/gm, // 1. CLI 交互设计
220
252
  /^\s*├──\s+(.+)/gm, // ├── CLI 交互设计
221
253
  /^\s*└──\s+(.+)/gm, // └── 产物设计
222
254
  ];
@@ -257,12 +289,12 @@ export function validatePartDecomposition(philosophyDir) {
257
289
  if (!foundSection) {
258
290
  issues.push({
259
291
  severity: 'high',
260
- msg: '哲学文档没有"实现部分清单"章节。PHILOSOPHY_WEAVER.md Step 2 要求按 PART_DECOMPOSITION.md 拆解实现部分,并在哲学文档中显式列出。可能 Weaver 跳过了拆解步骤。',
292
+ msg: '哲学文档没有"实现部分清单"章节。PHILOSOPHY_WEAVER.md Step 2 要求按 PART_DECOMPOSITION.md 拆解实现部分,并在哲学文档中显式列出。支持的标题:实现部分清单 / 部分拆解 / 实现部分 / Part Decomposition / Implementation Parts / 拆解出的部分。',
261
293
  });
262
294
  } else if (parts.length < 2) {
263
295
  issues.push({
264
296
  severity: 'medium',
265
- msg: `实现部分清单仅识别到 ${parts.length} 个部分。PART_DECOMPOSITION.md 建议小项目 3-5 个部分,大项目 6-10 个。可能拆解不充分。`,
297
+ msg: `找到"实现部分清单"章节但仅识别到 ${parts.length} 个部分。可能章节里是模板占位符,或条目格式不被识别(用 - **部分名** 或 ├── 部分名 格式)。PART_DECOMPOSITION.md 建议小项目 3-5 个部分,大项目 6-10 个。`,
266
298
  });
267
299
  }
268
300
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haaaiawd/loom",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "LOOM — 哲学驱动开发框架。Agent 通过 CLI 访问 Intent Map / 哲学 / 验证记录,不直接读文件",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,8 +35,8 @@
35
35
  "license": "MIT",
36
36
  "repository": {
37
37
  "type": "git",
38
- "url": "git+https://github.com/Haaaiawd/loom.git"
39
- },
40
- "homepage": "https://github.com/Haaaiawd/loom#readme",
38
+ "url": "git+https://github.com/Haaaiawd/loom.git"
39
+ },
40
+ "homepage": "https://github.com/Haaaiawd/loom#readme",
41
41
  "author": "haaaiawd"
42
42
  }