@haaaiawd/loom 1.2.0 → 1.2.2
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/CHANGELOG.md +21 -0
- package/README.md +4 -1
- package/cli/bin/loom.js +28 -17
- package/cli/help/doctor.md +3 -1
- package/cli/help/loop.md +13 -1
- package/cli/help/workflow.md +3 -0
- package/cli/src/activate.js +93 -5
- package/cli/src/capability-graph.js +14 -0
- package/cli/src/diagnostics.js +3 -0
- package/cli/src/guide.js +24 -17
- package/cli/src/init.js +1 -0
- package/cli/src/verify.js +15 -3
- package/package.json +1 -1
- package/roles/architect.md +3 -1
- package/roles/keeper.md +2 -0
- package/templates/INTENT_MAP_TEMPLATE.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.2 — 2026-07-31
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `guide` 只输出下一步命令、Context Pack 提示与完成校验,不再展示文件输入/输出清单;当下一步是角色激活时,由该命令装配 Context Pack。
|
|
8
|
+
- `activate weaver` 直接注入检索方法与哲学维度目录;`activate architect` 在无 Intent 的设计阶段直接注入愿景、当前 Capability Graph、Intent Map 与未闭合 proposal。
|
|
9
|
+
- Capability Graph 未闭合或 proposal 待裁决时,`guide` 重新指向可推进工作的 Architect Context Pack,而不是重复运行只读检查。
|
|
10
|
+
- `loom intent next` 返回开始该 Intent 和获取 Forge Context Pack 的明确后续命令。
|
|
11
|
+
|
|
12
|
+
## 1.2.1 — 2026-07-31
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- `verify pass` 现在要求声明独立 Keeper 或人类复核来源,并将该来源写入验证记录;普通自检不能再被快捷命令包装成 `passed`。
|
|
17
|
+
- 高影响 capability 若选择 `adaptive`,必须留下为何不启用外部获取强门的理由;无理由的 `adaptive` 不再是静默绕过路径。
|
|
18
|
+
- Intent 模板不再默认引用不存在的质量契约章节,并增加叙事语义守恒与反例验证的提示。
|
|
19
|
+
|
|
20
|
+
### Compatibility
|
|
21
|
+
|
|
22
|
+
- 旧版结构化验证记录仍可读取;新的 passed 记录(包括 `verify write`)必须声明验证来源。旧记录可供历史追溯,但不自动获得独立验证声明。
|
|
23
|
+
|
|
3
24
|
## 1.2.0 — 2026-07-31
|
|
4
25
|
|
|
5
26
|
### Added
|
package/README.md
CHANGED
|
@@ -87,11 +87,14 @@ loom guide --dry-run # 只读诊断,不写 heartbeat
|
|
|
87
87
|
guide 检测项目当前在哪个阶段,输出"你在阶段 X,下一步做 Y"。
|
|
88
88
|
Agent 每完成一步都跑 guide 确认下一步。
|
|
89
89
|
如果只是审计或探测,不希望产生任何状态写入,用 `loom guide --dry-run`。
|
|
90
|
+
|
|
91
|
+
当 `guide` 的下一步是 `loom activate <role>` 时,先运行这条命令:它会把该阶段需要的
|
|
92
|
+
规范与版本化输入编译为 Context Pack。`guide` 只负责导航,不输出待阅读文件清单。
|
|
90
93
|
|
|
91
94
|
### AUTO 模式
|
|
92
95
|
|
|
93
96
|
```bash
|
|
94
|
-
loom auto on # Agent
|
|
97
|
+
loom auto on # Agent 在允许阶段连续推进;不会跳过契约、证据或 Keeper 门禁
|
|
95
98
|
loom auto off # 每步需要用户确认
|
|
96
99
|
```
|
|
97
100
|
|
package/cli/bin/loom.js
CHANGED
|
@@ -274,9 +274,19 @@ try {
|
|
|
274
274
|
}));
|
|
275
275
|
break;
|
|
276
276
|
}
|
|
277
|
-
case 'next':
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
case 'next':
|
|
278
|
+
{
|
|
279
|
+
const next = getNextIntent(versionDir);
|
|
280
|
+
output(next ? {
|
|
281
|
+
...next,
|
|
282
|
+
suggested_flow: {
|
|
283
|
+
start: `loom intent update ${next.id} --status in_progress`,
|
|
284
|
+
forge_context: `loom activate forge --intent ${next.id}`,
|
|
285
|
+
note: '先显式开始 Intent,再由 activate 命令装配该 Intent 的叙事、契约、图谱路由与专业输入。',
|
|
286
|
+
},
|
|
287
|
+
} : '没有可执行的 Intent');
|
|
288
|
+
}
|
|
289
|
+
break;
|
|
280
290
|
case 'status': {
|
|
281
291
|
const s = getStatus(versionDir);
|
|
282
292
|
const fmt = (ids) => ids.map((id) => s.titles[id] ? `${id}(${s.titles[id]})` : id).join(', ') || '-';
|
|
@@ -518,7 +528,7 @@ try {
|
|
|
518
528
|
const icon = issue.severity === 'high' ? '⚠' : '·';
|
|
519
529
|
console.log(` ${icon} [${issue.severity}] ${issue.msg}`);
|
|
520
530
|
}
|
|
521
|
-
console.log('\n
|
|
531
|
+
console.log('\n下一步: 运行 loom activate weaver;命令会重新输出哲学阶段所需的 Context Pack。');
|
|
522
532
|
exit(1);
|
|
523
533
|
}
|
|
524
534
|
break;
|
|
@@ -592,7 +602,7 @@ try {
|
|
|
592
602
|
}
|
|
593
603
|
case 'pass':
|
|
594
604
|
case 'fail': {
|
|
595
|
-
// loom verify pass <id> --summary "..."
|
|
605
|
+
// loom verify pass <id> --summary "..." --verified-by <id> --verification-context <independent_thread|human_review>
|
|
596
606
|
// loom verify fail <id> --summary "..." [--deviation "..."] [--reproduction-command "..."]
|
|
597
607
|
const id = rest[0];
|
|
598
608
|
if (!id) die(`用法: loom verify ${sub} <id> --summary "..." [--reproduction-command "..."]${sub === 'pass' ? ' [--preservation-evidence "..."] [--quality-proof "..."]' : ' [--deviation "..."]'}`);
|
|
@@ -601,6 +611,8 @@ try {
|
|
|
601
611
|
const deviationIdx = argv.indexOf('--deviation');
|
|
602
612
|
const qualityProofIdx = argv.indexOf('--quality-proof');
|
|
603
613
|
const preservationIdx = argv.indexOf('--preservation-evidence');
|
|
614
|
+
const verifiedByIdx = argv.indexOf('--verified-by');
|
|
615
|
+
const verificationContextIdx = argv.indexOf('--verification-context');
|
|
604
616
|
const summary = summaryIdx !== -1 ? argv[summaryIdx + 1] : null;
|
|
605
617
|
if (!summary) die(`缺少 --summary: loom verify ${sub} ${id} --summary "..."`);
|
|
606
618
|
const intent = getIntent(versionDir, id);
|
|
@@ -610,11 +622,15 @@ try {
|
|
|
610
622
|
if (sub === 'pass' && intent.quality_contract && !(qualityProofIdx !== -1 && argv[qualityProofIdx + 1])) {
|
|
611
623
|
die(`Intent ${id} 声明了 quality_contract;通过前必须提供 --quality-proof,指向项目内真实的 Quality Proof Markdown 锚点。`);
|
|
612
624
|
}
|
|
625
|
+
if (sub === 'pass' && !(verifiedByIdx !== -1 && argv[verifiedByIdx + 1] && verificationContextIdx !== -1 && argv[verificationContextIdx + 1])) {
|
|
626
|
+
die(`Intent ${id} 通过前必须声明独立验证来源:--verified-by <thread/run/人类标识> --verification-context <independent_thread|human_review>。同一会话自检请记录为自检,不得写 passed。`);
|
|
627
|
+
}
|
|
613
628
|
const extras = {};
|
|
614
629
|
if (reproIdx !== -1 && argv[reproIdx + 1]) extras.reproduction_command = argv[reproIdx + 1];
|
|
615
630
|
if (sub === 'fail' && deviationIdx !== -1 && argv[deviationIdx + 1]) extras.deviation_detail = argv[deviationIdx + 1];
|
|
616
631
|
if (sub === 'pass' && qualityProofIdx !== -1 && argv[qualityProofIdx + 1]) extras.quality_proof_ref = argv[qualityProofIdx + 1];
|
|
617
632
|
if (sub === 'pass' && preservationIdx !== -1 && argv[preservationIdx + 1]) extras.preservation_evidence = argv[preservationIdx + 1];
|
|
633
|
+
if (sub === 'pass') extras.verification_provenance = { verified_by: argv[verifiedByIdx + 1], context: argv[verificationContextIdx + 1] };
|
|
618
634
|
const verdict = sub === 'pass' ? 'passed' : 'deviated';
|
|
619
635
|
const result = createQuickVerification(versionDir, verificationsDir, id, verdict, summary, extras);
|
|
620
636
|
console.log(`验证记录已写入: ${result.filePath}`);
|
|
@@ -732,7 +748,7 @@ try {
|
|
|
732
748
|
console.log(` → 修复: ${issue.fix_hint}`);
|
|
733
749
|
}
|
|
734
750
|
}
|
|
735
|
-
console.log(
|
|
751
|
+
console.log('\n下一步: 运行 loom guide;它会根据当前状态给出可执行动作与所需 Context Pack。');
|
|
736
752
|
}
|
|
737
753
|
break;
|
|
738
754
|
}
|
|
@@ -782,17 +798,12 @@ try {
|
|
|
782
798
|
};
|
|
783
799
|
console.log(`模式: ${modeDesc[result.auto_mode] || result.auto_mode}`);
|
|
784
800
|
console.log(`\n${result.message}`);
|
|
785
|
-
console.log(`\n下一步: ${result.next_action}`);
|
|
786
|
-
console.log(` → ${result.next_command}`);
|
|
787
|
-
if (result.
|
|
788
|
-
console.log(
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
if (result.outputs && result.outputs.length > 0) {
|
|
792
|
-
console.log(`\n需要产出:`);
|
|
793
|
-
for (const f of result.outputs) console.log(` - ${f}`);
|
|
794
|
-
}
|
|
795
|
-
if (result.verify_command) {
|
|
801
|
+
console.log(`\n下一步: ${result.next_action}`);
|
|
802
|
+
console.log(` → ${result.next_command}`);
|
|
803
|
+
if (result.input_delivery === 'context_pack') {
|
|
804
|
+
console.log('\n上下文: 直接运行这条命令;它会输出当前阶段所需的 Context Pack。');
|
|
805
|
+
}
|
|
806
|
+
if (result.verify_command) {
|
|
796
807
|
console.log(`\n完成后校验: ${result.verify_command}`);
|
|
797
808
|
}
|
|
798
809
|
if (result.auto_mode === 'manual' && result.stage_num > 0 && result.stage_num < 6) {
|
package/cli/help/doctor.md
CHANGED
|
@@ -49,7 +49,9 @@ loom philosophy check
|
|
|
49
49
|
快速命令:
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
loom verify pass <id> --summary "<证据>" --quality-proof "<ref>"
|
|
52
|
+
loom verify pass <id> --summary "<证据>" --quality-proof "<ref>" \
|
|
53
|
+
--verified-by "<keeper-thread-or-human>" \
|
|
54
|
+
--verification-context independent_thread
|
|
53
55
|
```
|
|
54
56
|
|
|
55
57
|
若只达到完成契约,写 `deviated` 或完整验证记录,不要伪造质量通过。
|
package/cli/help/loop.md
CHANGED
|
@@ -28,7 +28,7 @@ Forge 先形成任务级 Expertise Pack:
|
|
|
28
28
|
- 已实际加载的技能、资料、工具及其用途。
|
|
29
29
|
- Critic 视角与验证方法。
|
|
30
30
|
|
|
31
|
-
Pack
|
|
31
|
+
Pack 是任务级认知配置,不写成新的长期规范。高影响 capability 默认进入 External Acquisition Gate;若明确选择 `adaptive`,也必须写明为何此处不启用外部获取。Pack
|
|
32
32
|
必须落盘到 `10_EXPERTISE_PACKS/<intent-id>.json`:Search Plan 可由 AI 派生,但 Capsule 内容
|
|
33
33
|
必须来自实际打开的外部来源,并写出判断门、失败模式和验证信号。明显的机械任务可以保持
|
|
34
34
|
`adaptive`;高质量任务必须足以解释为什么某个专业手法适合这个项目。
|
|
@@ -60,6 +60,10 @@ Keeper 独立检查基础四维;有质量契约时增加第五维:
|
|
|
60
60
|
"verdict": "passed",
|
|
61
61
|
"timestamp": "2026-07-28T12:00:00.000Z",
|
|
62
62
|
"summary": "具体、可定位、可复现的判定摘要",
|
|
63
|
+
"verification_provenance": {
|
|
64
|
+
"verified_by": "keeper thread 或人类复核标识",
|
|
65
|
+
"context": "independent_thread"
|
|
66
|
+
},
|
|
63
67
|
"reproduction_command": "npm test",
|
|
64
68
|
"dimensions": {
|
|
65
69
|
"intent_fidelity": {
|
|
@@ -87,6 +91,14 @@ Keeper 独立检查基础四维;有质量契约时增加第五维:
|
|
|
87
91
|
}
|
|
88
92
|
```
|
|
89
93
|
|
|
94
|
+
快捷写入 `passed` 时,也必须声明这一来源:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
loom verify pass INT-001 --summary "..." \
|
|
98
|
+
--verified-by "keeper-run-123" \
|
|
99
|
+
--verification-context independent_thread
|
|
100
|
+
```
|
|
101
|
+
|
|
90
102
|
若 Intent 声明 `continuity_required: true`,Keeper 还必须写入并通过:
|
|
91
103
|
|
|
92
104
|
```json
|
package/cli/help/workflow.md
CHANGED
package/cli/src/activate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// activate — compile a role- and intent-scoped Context Pack.
|
|
2
2
|
|
|
3
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
3
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
4
|
+
import { join, relative } from 'node:path';
|
|
5
5
|
import { getLoomRoot } from './shared/paths.js';
|
|
6
6
|
import { getIntent, getNarrative } from './intent-map.js';
|
|
7
7
|
import { getIntentDraft } from './intent-draft.js';
|
|
@@ -11,6 +11,7 @@ import { extractMdSection } from './shared/md-utils.js';
|
|
|
11
11
|
import { compileCapabilityInputs } from './capability-graph.js';
|
|
12
12
|
import { getAtelierRecord } from './atelier.js';
|
|
13
13
|
import { formatExpertisePackForPrompt, getExpertisePack, getExpertisePackState } from './expertise-pack.js';
|
|
14
|
+
import { listCapabilityProposals } from './capability-proposals.js';
|
|
14
15
|
|
|
15
16
|
const VALID_ROLES = ['weaver', 'visionary', 'architect', 'forge', 'keeper'];
|
|
16
17
|
|
|
@@ -354,6 +355,92 @@ function compileWorkingFacts(versionDir, objective) {
|
|
|
354
355
|
return refs.join('\n');
|
|
355
356
|
}
|
|
356
357
|
|
|
358
|
+
function readStageFile(path, label) {
|
|
359
|
+
if (!existsSync(path)) return `> ${label} 当前不存在;不要猜测其内容。按角色契约创建或回流。`;
|
|
360
|
+
return readFileSync(path, 'utf-8').trim();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function listMarkdownFiles(root, current = root) {
|
|
364
|
+
if (!existsSync(current)) return [];
|
|
365
|
+
return readdirSync(current, { withFileTypes: true })
|
|
366
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
367
|
+
.flatMap((entry) => {
|
|
368
|
+
const path = join(current, entry.name);
|
|
369
|
+
if (entry.isDirectory()) return listMarkdownFiles(root, path);
|
|
370
|
+
return entry.isFile() && entry.name.endsWith('.md')
|
|
371
|
+
? [relative(root, path).replaceAll('\\', '/')]
|
|
372
|
+
: [];
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Compile the stage inputs that used to be merely named by `loom guide`.
|
|
378
|
+
* These are intentionally separate from Working Facts: they are the source
|
|
379
|
+
* material for a role's immediate decision, not a request to hunt for paths.
|
|
380
|
+
*/
|
|
381
|
+
function compileStageInputs(role, versionDir, objective) {
|
|
382
|
+
if (role === 'weaver') {
|
|
383
|
+
const dimensionsRoot = join(getLoomRoot(), 'dimensions');
|
|
384
|
+
const catalog = listMarkdownFiles(dimensionsRoot)
|
|
385
|
+
.filter((path) => path !== 'SEARCH_METHODOLOGY.md')
|
|
386
|
+
.map((path) => `- dimensions/${path}`);
|
|
387
|
+
return [
|
|
388
|
+
'本节由 `loom activate weaver` 直接装配。不要为了取得这些输入而搜索 CLI 安装目录。',
|
|
389
|
+
'',
|
|
390
|
+
'### Search Methodology',
|
|
391
|
+
'',
|
|
392
|
+
readStageFile(join(dimensionsRoot, 'SEARCH_METHODOLOGY.md'), 'SEARCH_METHODOLOGY.md'),
|
|
393
|
+
'',
|
|
394
|
+
'### Available Dimension Catalog',
|
|
395
|
+
'',
|
|
396
|
+
catalog.length
|
|
397
|
+
? `${catalog.join('\n')}\n\n只打开会实质改变当前项目原则或取舍的维度;目录本身不是要求逐个阅读。`
|
|
398
|
+
: '> 当前没有可用的 dimensions 文档。',
|
|
399
|
+
].join('\n');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (role === 'architect' && versionDir && !objective.intent && !objective.draft) {
|
|
403
|
+
let proposals = [];
|
|
404
|
+
let proposalsError = null;
|
|
405
|
+
try {
|
|
406
|
+
proposals = listCapabilityProposals(versionDir, { unresolvedOnly: true });
|
|
407
|
+
} catch (error) {
|
|
408
|
+
proposalsError = error.message;
|
|
409
|
+
}
|
|
410
|
+
return [
|
|
411
|
+
'本节由 `loom activate architect` 直接装配。以下是当前设计决策所需的版本化输入,不要先手动查找路径。',
|
|
412
|
+
'',
|
|
413
|
+
'### Vision Input (01_VISION.md)',
|
|
414
|
+
'',
|
|
415
|
+
readStageFile(join(versionDir, '01_VISION.md'), '01_VISION.md'),
|
|
416
|
+
'',
|
|
417
|
+
'### Capability Graph Input (07_CAPABILITY_GRAPH.json)',
|
|
418
|
+
'',
|
|
419
|
+
readStageFile(join(versionDir, '07_CAPABILITY_GRAPH.json'), '07_CAPABILITY_GRAPH.json'),
|
|
420
|
+
'',
|
|
421
|
+
'### Intent Map Input (04_INTENT_MAP.json)',
|
|
422
|
+
'',
|
|
423
|
+
readStageFile(join(versionDir, '04_INTENT_MAP.json'), '04_INTENT_MAP.json'),
|
|
424
|
+
'',
|
|
425
|
+
'### Open Capability Graph Proposals',
|
|
426
|
+
'',
|
|
427
|
+
proposalsError
|
|
428
|
+
? `> Proposal 记录无法读取:${proposalsError}。先修复这份记录,不要猜测其状态。`
|
|
429
|
+
: proposals.length
|
|
430
|
+
? `\`\`\`json\n${JSON.stringify(proposals, null, 2)}\n\`\`\``
|
|
431
|
+
: '无。',
|
|
432
|
+
].join('\n');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (role === 'visionary') {
|
|
436
|
+
return '产品哲学与决策准则已在上方 Project Judgment 中装配;不要重新搜索 `.loom` 目录。';
|
|
437
|
+
}
|
|
438
|
+
if (role === 'forge' || role === 'keeper') {
|
|
439
|
+
return '当前 Intent 的叙事、契约、图谱路由与已就绪的 Expertise 输入已在上方装配;只有产物本身需要按 Working Facts 检查。';
|
|
440
|
+
}
|
|
441
|
+
return '当前角色没有额外的阶段输入。';
|
|
442
|
+
}
|
|
443
|
+
|
|
357
444
|
/**
|
|
358
445
|
* Compile a role-scoped Context Pack.
|
|
359
446
|
* @param {string} role
|
|
@@ -377,9 +464,10 @@ export function activateRole(role, versionDir, intentId = null) {
|
|
|
377
464
|
section('3. Hard Invariants', compileInvariants(role, versionDir)),
|
|
378
465
|
section('4. Success Contracts', compileContracts(role, versionDir, objective)),
|
|
379
466
|
section('5. Project Judgment', compileProjectJudgment(role, versionDir, objective)),
|
|
380
|
-
section('6.
|
|
381
|
-
section('7.
|
|
382
|
-
section('8.
|
|
467
|
+
section('6. Stage Inputs (command-assembled)', compileStageInputs(role, versionDir, objective)),
|
|
468
|
+
section('7. Expertise Inputs', compileExpertiseInputs(role, versionDir, objective)),
|
|
469
|
+
section('8. Working Facts', compileWorkingFacts(versionDir, objective)),
|
|
470
|
+
section('9. Role Contract / Output / Reflow / Stop', readRole(role)),
|
|
383
471
|
];
|
|
384
472
|
return `${parts.join('\n\n---\n\n')}\n`;
|
|
385
473
|
}
|
|
@@ -78,6 +78,10 @@ function validateNode(id, node, allIds, errors) {
|
|
|
78
78
|
&& (typeof node.acquisition_rationale !== 'string' || !node.acquisition_rationale.trim())) {
|
|
79
79
|
errors.push(`nodes["${id}"].acquisition_mode=project_only 必须声明 acquisition_rationale`);
|
|
80
80
|
}
|
|
81
|
+
if (node.impact === 'high' && node.acquisition_mode === 'adaptive'
|
|
82
|
+
&& (typeof node.acquisition_rationale !== 'string' || !node.acquisition_rationale.trim())) {
|
|
83
|
+
errors.push(`nodes["${id}"] 为高影响 capability 且选择 adaptive 时必须声明 acquisition_rationale;说明为何此处不启用 external_required`);
|
|
84
|
+
}
|
|
81
85
|
if (node.verification !== undefined) {
|
|
82
86
|
if (node.kind !== 'evidence') {
|
|
83
87
|
errors.push(`nodes["${id}"].verification 只允许写在 evidence 节点`);
|
|
@@ -267,6 +271,7 @@ export function getCapabilityCoverage(versionDir) {
|
|
|
267
271
|
const capabilitiesWithoutPlan = [];
|
|
268
272
|
const routingGaps = [];
|
|
269
273
|
const outcomesWithoutConcern = [];
|
|
274
|
+
const evidenceArtifactGaps = [];
|
|
270
275
|
|
|
271
276
|
for (const node of nodes) {
|
|
272
277
|
for (const intentId of node.intent_refs || []) {
|
|
@@ -305,6 +310,12 @@ export function getCapabilityCoverage(versionDir) {
|
|
|
305
310
|
if (node.kind === 'outcome' && !(node.relationships || []).some((relation) => graph.nodes[relation.target]?.kind === 'concern')) {
|
|
306
311
|
outcomesWithoutConcern.push({ node_id: node.id, reason: 'outcome 没有连接到 concern,项目初衷尚未展开为问题面' });
|
|
307
312
|
}
|
|
313
|
+
if (node.kind === 'evidence' && node.verification?.artifact) {
|
|
314
|
+
const ownedCompletedIntent = (node.intent_refs || []).some((intentId) => intentMap.intents[intentId]?.status === 'completed');
|
|
315
|
+
if (ownedCompletedIntent && !resolveEvidenceArtifact(versionDir, node.verification.artifact)) {
|
|
316
|
+
evidenceArtifactGaps.push({ node_id: node.id, reason: `已完成 Intent 的 evidence artifact 不存在或不可读: ${node.verification.artifact}` });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
308
319
|
if (node.brief_ref) {
|
|
309
320
|
try { resolveBrief(versionDir, node.brief_ref); } catch (error) {
|
|
310
321
|
capabilitiesWithoutPlan.push({ node_id: node.id, reason: error.message });
|
|
@@ -326,6 +337,7 @@ export function getCapabilityCoverage(versionDir) {
|
|
|
326
337
|
routing_gaps: routingGaps.length,
|
|
327
338
|
outcomes_without_concern: outcomesWithoutConcern.length,
|
|
328
339
|
high_outcomes_without_observable_evidence: highOutcomesWithoutObservableEvidence.length,
|
|
340
|
+
evidence_artifact_gaps: evidenceArtifactGaps.length,
|
|
329
341
|
intent_mapping_required: intentMappingRequired,
|
|
330
342
|
unmapped_intents: unmappedIntents.length,
|
|
331
343
|
ready: highUnrouted.length === 0
|
|
@@ -334,6 +346,7 @@ export function getCapabilityCoverage(versionDir) {
|
|
|
334
346
|
&& routingGaps.length === 0
|
|
335
347
|
&& outcomesWithoutConcern.length === 0
|
|
336
348
|
&& highOutcomesWithoutObservableEvidence.length === 0
|
|
349
|
+
&& evidenceArtifactGaps.length === 0
|
|
337
350
|
&& unmappedIntents.length === 0,
|
|
338
351
|
},
|
|
339
352
|
high_unrouted: highUnrouted,
|
|
@@ -342,6 +355,7 @@ export function getCapabilityCoverage(versionDir) {
|
|
|
342
355
|
routing_gaps: routingGaps,
|
|
343
356
|
outcomes_without_concern: outcomesWithoutConcern,
|
|
344
357
|
high_outcomes_without_observable_evidence: highOutcomesWithoutObservableEvidence,
|
|
358
|
+
evidence_artifact_gaps: evidenceArtifactGaps,
|
|
345
359
|
unmapped_intents: unmappedIntents,
|
|
346
360
|
};
|
|
347
361
|
}
|
package/cli/src/diagnostics.js
CHANGED
|
@@ -274,6 +274,9 @@ export function doctor(versionDir, verificationsDir, philosophyDir) {
|
|
|
274
274
|
for (const item of coverage.high_outcomes_without_observable_evidence) {
|
|
275
275
|
issues.push({ id: item.node_id, type: 'capability_outcome_unobservable', severity: 'high', msg: `${item.node_id} 缺少真实呈现或交付的验证入口: ${item.reason}` });
|
|
276
276
|
}
|
|
277
|
+
for (const item of coverage.evidence_artifact_gaps || []) {
|
|
278
|
+
issues.push({ id: item.node_id, type: 'capability_evidence_artifact_missing', severity: 'high', msg: `${item.node_id} 的完成证据不可用: ${item.reason}` });
|
|
279
|
+
}
|
|
277
280
|
for (const item of coverage.orphan_intent_refs) {
|
|
278
281
|
issues.push({ id: item.node_id, type: 'intent_graph_unmapped', severity: 'high', msg: `${item.node_id} 引用了不存在的 Intent: ${item.intent_id}` });
|
|
279
282
|
}
|
package/cli/src/guide.js
CHANGED
|
@@ -63,35 +63,41 @@ export function guideProject(projectDir, options = {}) {
|
|
|
63
63
|
outputs: ['.loom/v1/'],
|
|
64
64
|
verify_command: 'loom guide',
|
|
65
65
|
},
|
|
66
|
-
need_philosophy: {
|
|
66
|
+
need_philosophy: {
|
|
67
67
|
inputs: ['meta/PHILOSOPHY_WEAVER.md', 'meta/BASELINE.md', 'dimensions/SEARCH_METHODOLOGY.md'],
|
|
68
|
-
outputs: [`.loom/${current}/00_PHILOSOPHY/PRODUCT_PHILOSOPHY.md`, `.loom/${current}/00_PHILOSOPHY/ENGINEERING_CREED.md`, `.loom/${current}/00_PHILOSOPHY/DECISION_RUBRIC.md`],
|
|
69
|
-
verify_command: 'loom philosophy check',
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
outputs: [`.loom/${current}/00_PHILOSOPHY/PRODUCT_PHILOSOPHY.md`, `.loom/${current}/00_PHILOSOPHY/ENGINEERING_CREED.md`, `.loom/${current}/00_PHILOSOPHY/DECISION_RUBRIC.md`],
|
|
69
|
+
verify_command: 'loom philosophy check',
|
|
70
|
+
input_delivery: 'context_pack',
|
|
71
|
+
},
|
|
72
|
+
need_vision: {
|
|
73
|
+
inputs: ['roles/visionary.md', `.loom/${current}/00_PHILOSOPHY/`],
|
|
74
|
+
outputs: [`.loom/${current}/01_VISION.md`],
|
|
75
|
+
verify_command: 'loom guide',
|
|
76
|
+
input_delivery: 'context_pack',
|
|
77
|
+
},
|
|
76
78
|
need_architecture: {
|
|
77
79
|
inputs: ['roles/architect.md', `.loom/${current}/01_VISION.md`],
|
|
78
80
|
outputs: [`.loom/${current}/02_ARCHITECTURE.md`, `.loom/${current}/04_INTENT_MAP.json`],
|
|
79
81
|
verify_command: 'loom doctor',
|
|
82
|
+
input_delivery: 'context_pack',
|
|
80
83
|
},
|
|
81
84
|
need_capability_graph: {
|
|
82
85
|
inputs: ['roles/architect.md', `.loom/${current}/01_VISION.md`],
|
|
83
86
|
outputs: [`.loom/${current}/07_CAPABILITY_GRAPH.json`, `.loom/${current}/07_CAPABILITY_BRIEFS/`],
|
|
84
87
|
verify_command: 'loom capability coverage',
|
|
88
|
+
input_delivery: 'context_pack',
|
|
85
89
|
},
|
|
86
90
|
capability_graph_incomplete: {
|
|
87
91
|
inputs: ['roles/architect.md', `.loom/${current}/07_CAPABILITY_GRAPH.json`, `.loom/${current}/04_INTENT_MAP.json`],
|
|
88
92
|
outputs: [`.loom/${current}/07_CAPABILITY_GRAPH.json`, `.loom/${current}/07_CAPABILITY_BRIEFS/`],
|
|
89
93
|
verify_command: 'loom capability coverage',
|
|
94
|
+
input_delivery: 'context_pack',
|
|
90
95
|
},
|
|
91
96
|
capability_graph_proposals_pending: {
|
|
92
97
|
inputs: ['roles/architect.md', `.loom/${current}/07_GRAPH_PROPOSALS/`, `.loom/${current}/07_CAPABILITY_GRAPH.json`],
|
|
93
98
|
outputs: [`.loom/${current}/07_GRAPH_PROPOSALS/`, `.loom/${current}/07_CAPABILITY_GRAPH.json`, `.loom/${current}/04_INTENT_MAP.json`],
|
|
94
99
|
verify_command: 'loom capability proposal list',
|
|
100
|
+
input_delivery: 'context_pack',
|
|
95
101
|
},
|
|
96
102
|
intent_map_broken: {
|
|
97
103
|
inputs: [`.loom/${current}/04_INTENT_MAP.json`],
|
|
@@ -120,9 +126,10 @@ export function guideProject(projectDir, options = {}) {
|
|
|
120
126
|
},
|
|
121
127
|
};
|
|
122
128
|
const meta = stageMeta[result.stage] || {};
|
|
123
|
-
result.inputs = meta.inputs || [];
|
|
124
|
-
result.outputs = meta.outputs || [];
|
|
125
|
-
result.verify_command = meta.verify_command || null;
|
|
129
|
+
result.inputs = meta.inputs || [];
|
|
130
|
+
result.outputs = meta.outputs || [];
|
|
131
|
+
result.verify_command = meta.verify_command || null;
|
|
132
|
+
result.input_delivery = meta.input_delivery || null;
|
|
126
133
|
// 统一后处理:写心跳 + 加 AUTO 提示词 + 判断是否需要人类 review
|
|
127
134
|
if (existsSync(loomRoot) && !options.dryRun) {
|
|
128
135
|
try {
|
|
@@ -146,7 +153,7 @@ export function guideProject(projectDir, options = {}) {
|
|
|
146
153
|
}
|
|
147
154
|
} else if (result.stage_num >= 4) {
|
|
148
155
|
if (auto) {
|
|
149
|
-
result.message += '\n\n> AUTO
|
|
156
|
+
result.message += '\n\n> AUTO 模式开启——可继续进入下一阶段;契约、证据与 Keeper 门禁仍不会被跳过。';
|
|
150
157
|
} else {
|
|
151
158
|
result.message += '\n\n> ⚠ AUTO 模式关闭——执行 next_command 后等人类确认再继续。';
|
|
152
159
|
}
|
|
@@ -242,14 +249,14 @@ function diagnoseStage(cwd, loomRoot, auto) {
|
|
|
242
249
|
details: { version: current, proposals: pendingProposals.map((proposal) => proposal.id) },
|
|
243
250
|
auto,
|
|
244
251
|
next_action: '审计新信息对 Capability Graph、Intent 和契约的影响',
|
|
245
|
-
next_command: 'loom
|
|
252
|
+
next_command: 'loom activate architect',
|
|
246
253
|
message: `当前版本 ${current} 有 ${pendingProposals.length} 个未闭合的 Capability Graph proposal。它们是新要求、研究或实现发现,不得由 Forge 静默变成当前 Intent 范围。`,
|
|
247
254
|
};
|
|
248
255
|
}
|
|
249
256
|
} catch (error) {
|
|
250
257
|
return {
|
|
251
258
|
stage: 'capability_graph_proposals_pending', stage_num: 3, details: { version: current, error: error.message }, auto,
|
|
252
|
-
next_action: '修复 Capability Graph proposal', next_command: 'loom
|
|
259
|
+
next_action: '修复 Capability Graph proposal', next_command: 'loom activate architect',
|
|
253
260
|
message: `Capability Graph proposal 无法审计: ${error.message}`,
|
|
254
261
|
};
|
|
255
262
|
}
|
|
@@ -262,7 +269,7 @@ function diagnoseStage(cwd, loomRoot, auto) {
|
|
|
262
269
|
details: { version: current, coverage: coverage.summary },
|
|
263
270
|
auto,
|
|
264
271
|
next_action: '补齐 Capability Graph 的路由、可观察验证入口与 Intent 回链',
|
|
265
|
-
next_command: 'loom
|
|
272
|
+
next_command: 'loom activate architect',
|
|
266
273
|
message: `当前版本 ${current}:Capability Graph 尚未闭合(高影响前沿 ${coverage.summary.high_unrouted}、路由缺口 ${coverage.summary.routing_gaps}、不可观察 outcome ${coverage.summary.high_outcomes_without_observable_evidence}、未映射 Intent ${coverage.summary.unmapped_intents})。先由 Architect 补图谱,再继续设计 Intent。`,
|
|
267
274
|
};
|
|
268
275
|
}
|
|
@@ -273,7 +280,7 @@ function diagnoseStage(cwd, loomRoot, auto) {
|
|
|
273
280
|
details: { version: current, error: error.message },
|
|
274
281
|
auto,
|
|
275
282
|
next_action: '修复 Capability Graph',
|
|
276
|
-
next_command: 'loom
|
|
283
|
+
next_command: 'loom activate architect',
|
|
277
284
|
message: `Capability Graph 无法通过校验: ${error.message}`,
|
|
278
285
|
};
|
|
279
286
|
}
|
package/cli/src/init.js
CHANGED
|
@@ -169,6 +169,7 @@ export function initProject(projectDir) {
|
|
|
169
169
|
'3. 只在当前角色的权限内行动;发现目标、契约或架构需要改变时,按 LOOM 回流,不要静默扩展范围。',
|
|
170
170
|
'4. 完成前运行当前 Intent 的验证方法与 `loom doctor`;声称质量提升时必须提供基线相对 Quality Proof,以磁盘证据而非会话记忆判断状态。',
|
|
171
171
|
'5. Keeper 验证必须运行在新的 Agent thread 中;同一会话切换角色不构成独立验证。',
|
|
172
|
+
'6. 协作节奏默认是手动;需要让 Agent 在已允许阶段连续推进时,显式运行 `loom auto on`。AUTO 不会跳过契约、证据或 Keeper 门禁。',
|
|
172
173
|
'',
|
|
173
174
|
'常用入口:',
|
|
174
175
|
'- `loom --help`',
|
package/cli/src/verify.js
CHANGED
|
@@ -11,7 +11,8 @@ import { validateAtelierRecord } from './atelier.js';
|
|
|
11
11
|
import { assertExpertiseReady } from './expertise-pack.js';
|
|
12
12
|
|
|
13
13
|
/** 合法判定结果 */
|
|
14
|
-
const VALID_VERDICTS = ['passed', 'deviated', 'blocked', 'pending_human'];
|
|
14
|
+
const VALID_VERDICTS = ['passed', 'deviated', 'blocked', 'pending_human'];
|
|
15
|
+
const VALID_VERIFICATION_CONTEXTS = ['independent_thread', 'human_review'];
|
|
15
16
|
|
|
16
17
|
/** 每个 Intent 都必须覆盖的基础验证维度。 */
|
|
17
18
|
const BASE_DIMENSIONS = [
|
|
@@ -59,6 +60,15 @@ export function writeVerification(versionDir, verificationsDir, record) {
|
|
|
59
60
|
errors.push(`Intent ${record.intent_id} 当前状态为 ${intent.status};只能为 in_progress 或 needs_review 的 Intent 写入验证记录`);
|
|
60
61
|
}
|
|
61
62
|
const requiredDimensions = getRequiredDimensions(intent);
|
|
63
|
+
if (record.verdict === 'passed') {
|
|
64
|
+
const provenance = record.verification_provenance;
|
|
65
|
+
if (!provenance || typeof provenance !== 'object') {
|
|
66
|
+
errors.push('passed 必须声明 verification_provenance(verified_by + context);实现者自检不能单独闭合 Intent');
|
|
67
|
+
} else {
|
|
68
|
+
if (typeof provenance.verified_by !== 'string' || !provenance.verified_by.trim()) errors.push('verification_provenance.verified_by 必须是非空验证者标识');
|
|
69
|
+
if (!VALID_VERIFICATION_CONTEXTS.includes(provenance.context)) errors.push(`verification_provenance.context 非法: ${provenance.context}(合法: ${VALID_VERIFICATION_CONTEXTS.join('|')})`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
62
72
|
// dimensions 结构校验:每个维度必须是 { verdict, evidence } 对象
|
|
63
73
|
if (record.dimensions) {
|
|
64
74
|
for (const dim of requiredDimensions) {
|
|
@@ -164,7 +174,8 @@ export function writeVerification(versionDir, verificationsDir, record) {
|
|
|
164
174
|
verification_epoch: getEffectiveVerificationEpoch(intent),
|
|
165
175
|
verdict: record.verdict,
|
|
166
176
|
timestamp: record.timestamp,
|
|
167
|
-
summary: record.summary,
|
|
177
|
+
summary: record.summary,
|
|
178
|
+
verification_provenance: record.verification_provenance,
|
|
168
179
|
dimensions: record.dimensions,
|
|
169
180
|
atelier: atelierEvidence ? {
|
|
170
181
|
record_ref: `09_ATELIER/${record.intent_id}.json`,
|
|
@@ -243,7 +254,7 @@ export function getAcrossVersionVerificationHistory(currentVersionDir, inputRef)
|
|
|
243
254
|
|
|
244
255
|
/**
|
|
245
256
|
* 快捷创建验证记录——Agent 不用手动构造完整 JSON。
|
|
246
|
-
*
|
|
257
|
+
* 快捷记录只能用于低风险的结构化核验;passed 仍必须显式声明独立验证来源。
|
|
247
258
|
* @param {string} versionDir — 当前 .loom/v{N}/ 目录
|
|
248
259
|
* @param {string} verificationsDir — verifications/ 目录路径
|
|
249
260
|
* @param {string} intentId — 如 "INT-001"
|
|
@@ -279,6 +290,7 @@ export function createQuickVerification(versionDir, verificationsDir, intentId,
|
|
|
279
290
|
summary,
|
|
280
291
|
dimensions,
|
|
281
292
|
reproduction_command: extras.reproduction_command || null,
|
|
293
|
+
verification_provenance: extras.verification_provenance || null,
|
|
282
294
|
deviation_detail: extras.deviation_detail || null,
|
|
283
295
|
});
|
|
284
296
|
}
|
package/package.json
CHANGED
package/roles/architect.md
CHANGED
|
@@ -41,7 +41,9 @@
|
|
|
41
41
|
12. `creative_scope` 说明 Forge 可以大胆改变什么、必须保持什么。
|
|
42
42
|
13. 每个高影响 `outcome` 必须用 `validated_by` 连接到一个 `evidence` 节点。该节点必须写出 `verification.method`、`target`、`procedure`、`pass_criteria` 和 `artifact`,并回链负责把证据真正产出的 Intent。`artifact` 必须是当前版本 `verifications/` 或 `08_ASSET_LIBRARY/files/` 中实际存在的文件。`target` 是结果实际被接收、呈现或消费的位置:用户界面、目标宿主、外部系统、交付物或人工验收现场。不能用“接口返回成功”“URL 可访问”替代目标宿主中的可观察结果。
|
|
43
43
|
14. 对每个重要 Intent 做简短 Pre-Mortem:最可能出现什么“表面完成”,并将其转成
|
|
44
|
-
acceptance 或 verification_method
|
|
44
|
+
acceptance 或 verification_method。当 narrative 的核心现象可能被一个更容易实现、但
|
|
45
|
+
语义不同的代理替换时,写 `semantic_guard`:明确哪种替代不算完成,以及 Keeper 应
|
|
46
|
+
使用什么反例验证它。它不把 Intent 切成技术碎片,只防止“看似相近”偷换用户结果。
|
|
45
47
|
15. 新要求、论文/资料发现、Keeper 或 Forge 发现先进入 `07_GRAPH_PROPOSALS/`,带来源、观察证据和候选类型。Architect 明确判定它已被覆盖、需要改 Graph、生成/修订 Intent、改变 acceptance,还是 Minor/Major;不得把候选静默写入正式 Graph。
|
|
46
48
|
16. 当项目使用素材时,`08_ASSET_LIBRARY/manifest.json` 是唯一可用素材与来源/作者/许可/哈希的真相源。只允许已批准、可验证的本地资产进入交付;若资产构成结果证据,让 evidence 节点与资产记录双向回链。
|
|
47
49
|
17. `quality_strategy` 缺失等价于 `adaptive`。只有结果确实需要作者命题、媒介原型和独立候选比较时才设为 `atelier`,且必须同时声明 `quality_contract` 与 `creative_scope`。
|
package/roles/keeper.md
CHANGED
|
@@ -52,6 +52,8 @@ Keeper 默认运行在新的 Agent thread 中。只接收:
|
|
|
52
52
|
| quality_achievement | 仅在存在质量契约时,目标水准是否有证据成立? |
|
|
53
53
|
|
|
54
54
|
每个维度都必须记录“对照了什么、观察到什么、如何复现”。“合规”“没问题”不是证据。
|
|
55
|
+
若 Intent 声明 `semantic_guard`,Keeper 必须执行其中的反例检查;实现看起来拥有相近
|
|
56
|
+
控件或数据,不能代替叙事要求的真实现象。
|
|
55
57
|
|
|
56
58
|
若 `continuity_required` 为 true,缺少明确的旧状态、操作和新状态证据时,`preservation_achievement` 不得通过;“页面目前看起来正常”不构成守恒证据。
|
|
57
59
|
实现方式与 Architect 设想不同不构成偏差,只要公共契约和意图仍成立。
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"depends_on": [],
|
|
19
19
|
"acceptance": "[必须] 完成契约:什么算可观察地完成,包含功能承诺、关键失败边界和防御承诺。可内联或引用 05_VERIFICATION.md。",
|
|
20
20
|
"continuity_required": "[可选] true:本 Intent 改动既有用户/系统状态,必须在 acceptance 写明保留项与时序验证;验证时额外要求 preservation_achievement。一次性、无既有状态任务省略。",
|
|
21
|
-
"quality_contract": "
|
|
21
|
+
"quality_contract": "[可选] 只有声明高于功能正确性的质量目标时才填写;若引用 05_VERIFICATION.md,必须先创建对应章节。",
|
|
22
22
|
"quality_strategy": "adaptive",
|
|
23
23
|
"capability_needs": ["visual hierarchy", "responsive interaction"],
|
|
24
24
|
"creative_scope": "可以改变布局与动效;不得改变业务流程、数据结构和公开接口。",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"quality_strategy": "[可选] adaptive | atelier,缺失等价于 adaptive。atelier 只用于明确需要作者命题、媒介原型和独立候选比较的 Intent,并要求 quality_contract 与 creative_scope。",
|
|
59
59
|
"capability_needs": "[可选] Expertise Compiler 需要补齐的专业领域字符串数组。",
|
|
60
60
|
"creative_scope": "[可选] Forge 可以大胆改变什么、必须保持什么。",
|
|
61
|
+
"semantic_guard": "[可选] 当叙事核心容易被相似实现偷换时,写明不算完成的替代物与 Keeper 的反例验证方式。",
|
|
61
62
|
"philosophy_anchors": "[必须] 哲学文档引用列表。Forge 加载哲学的指引。",
|
|
62
63
|
"status": "[必须] pending | in_progress | completed | blocked | needs_review",
|
|
63
64
|
"lifecycle.deprecation": "[可选] { deprecated_at, reason, replacement }。与 status 分离;弃用后 status 保持 completed。",
|