@haaaiawd/loom 1.2.2 → 2.0.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/CHANGELOG.md +11 -58
- package/CONTRIBUTING.md +37 -0
- package/EVIL_EVAL.md +112 -0
- package/README.md +193 -446
- package/README.zh-CN.md +174 -0
- package/SECURITY.md +11 -0
- package/cli/bin/loom.js +170 -995
- package/cli/src/protocol.js +367 -0
- package/cli/src/store.js +626 -0
- package/design.md +194 -0
- package/docs/PROMPT_CATALOG.md +99 -0
- package/docs/RELEASE_CHECKLIST.md +53 -0
- package/docs/UX_FLOW.md +171 -0
- package/docs/brand/loom-mark.svg +18 -0
- package/docs/brand/loom-readme-header.svg +34 -0
- package/docs/brand/loom-readme-header.zh-CN.svg +29 -0
- package/docs/loom-eval-loop.drawio +21 -0
- package/docs/loom-eval-loop.svg +56 -0
- package/docs/loom-production-loop.drawio +41 -0
- package/docs/loom-production-loop.svg +92 -0
- package/package.json +43 -40
- package/EXTERNAL_ACQUISITION_DESIGN.md +0 -143
- package/cli/help/asset.md +0 -36
- package/cli/help/atelier.md +0 -37
- package/cli/help/capability.md +0 -73
- package/cli/help/concepts.md +0 -103
- package/cli/help/doctor.md +0 -73
- package/cli/help/expertise.md +0 -51
- package/cli/help/loop.md +0 -134
- package/cli/help/patch.md +0 -33
- package/cli/help/preview.md +0 -60
- package/cli/help/proposals.md +0 -21
- package/cli/help/version.md +0 -136
- package/cli/help/workflow.md +0 -114
- package/cli/src/activate.js +0 -473
- package/cli/src/asset-library.js +0 -384
- package/cli/src/atelier.js +0 -331
- package/cli/src/auto.js +0 -116
- package/cli/src/capability-graph.js +0 -430
- package/cli/src/capability-proposals.js +0 -225
- package/cli/src/diagnostics.js +0 -766
- package/cli/src/expertise-pack.js +0 -336
- package/cli/src/guide.js +0 -495
- package/cli/src/help.js +0 -41
- package/cli/src/init.js +0 -187
- package/cli/src/intent-draft.js +0 -303
- package/cli/src/intent-map.js +0 -747
- package/cli/src/patch.js +0 -214
- package/cli/src/philosophy.js +0 -331
- package/cli/src/preview-prompt.md +0 -337
- package/cli/src/preview.js +0 -73
- package/cli/src/shared/intent-ref.js +0 -38
- package/cli/src/shared/md-utils.js +0 -125
- package/cli/src/shared/paths.js +0 -73
- package/cli/src/shared/proof-reference.js +0 -19
- package/cli/src/shared/verification-method.js +0 -32
- package/cli/src/verify.js +0 -394
- package/cli/src/version.js +0 -134
- package/dimensions/AUTHORSHIP.md +0 -45
- package/dimensions/PART_DECOMPOSITION.md +0 -42
- package/dimensions/SEARCH_METHODOLOGY.md +0 -101
- package/dimensions/examples/AGENT_SYSTEM/README.md +0 -219
- package/dimensions/examples/CLI_TOOL/README.md +0 -163
- package/dimensions/universal/COLLABORATION_PHILOSOPHY.md +0 -28
- package/dimensions/universal/ENGINEERING_CREED.md +0 -30
- package/dimensions/universal/PRODUCT_PHILOSOPHY.md +0 -32
- package/meta/BASELINE.md +0 -91
- package/meta/INTENT_LOOP.md +0 -296
- package/meta/PHILOSOPHY_WEAVER.md +0 -110
- package/meta/ROLE_ACTIVATION.md +0 -114
- package/roles/architect.md +0 -86
- package/roles/forge.md +0 -112
- package/roles/keeper.md +0 -113
- package/roles/visionary.md +0 -57
- package/templates/ASSET_LIBRARY_MANIFEST_TEMPLATE.json +0 -10
- package/templates/ATELIER_RECORD_TEMPLATE.json +0 -48
- package/templates/CAPABILITY_BRIEF_TEMPLATE.md +0 -35
- package/templates/CAPABILITY_GRAPH_TEMPLATE.json +0 -11
- package/templates/EXPERTISE_PACK_TEMPLATE.json +0 -22
- package/templates/INTENT_MAP_TEMPLATE.json +0 -85
- package/templates/PHILOSOPHY_TEMPLATE.md +0 -44
- package/templates/VISION_TEMPLATE.md +0 -44
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export function getIntentVerificationMethod(intent) {
|
|
2
|
-
return intent?.verification_method || intent?._optional?.verification_method || null;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
function normalize(command) {
|
|
6
|
-
return String(command || '')
|
|
7
|
-
.replace(/^\s*(?:run|exec)\s+/i, '')
|
|
8
|
-
.replace(/\s+/g, ' ')
|
|
9
|
-
.trim();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function normalizePackageManager(command) {
|
|
13
|
-
return ['npm', 'pnpm', 'bun', 'yarn'].reduce(
|
|
14
|
-
(result, manager) => result.replace(new RegExp(`\\b${manager}\\b`, 'g'), '<PM>'),
|
|
15
|
-
command,
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Whether a recorded reproduction command covers the Architect-declared method. */
|
|
20
|
-
export function commandCoversVerificationMethod(actualCommand, expectedMethod) {
|
|
21
|
-
const actual = normalize(actualCommand);
|
|
22
|
-
const expected = normalize(expectedMethod);
|
|
23
|
-
if (!actual || !expected) return false;
|
|
24
|
-
return expected.split('&&').every((part) => {
|
|
25
|
-
const expectedPart = normalize(part);
|
|
26
|
-
if (!expectedPart || actual.includes(expectedPart)) return true;
|
|
27
|
-
const actualNorm = normalizePackageManager(actual);
|
|
28
|
-
const expectedNorm = normalizePackageManager(expectedPart);
|
|
29
|
-
if (actualNorm.includes(expectedNorm)) return true;
|
|
30
|
-
return expectedPart.startsWith('node --test') && actualNorm.includes('<PM> test');
|
|
31
|
-
});
|
|
32
|
-
}
|
package/cli/src/verify.js
DELETED
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
// verify.js — 验证记录的读写和查询
|
|
2
|
-
// 验证记录存放在 .loom/v{N}/verifications/ 下,每个 Intent 一份 JSON + 一份 MD。
|
|
3
|
-
|
|
4
|
-
import { readFileSync, writeFileSync, existsSync, readdirSync } from 'node:fs';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import { extractMdSection, readJsonFile } from './shared/md-utils.js';
|
|
7
|
-
import { getIntent, getEffectiveVerificationEpoch, hasLegacyIntentRevision } from './intent-map.js';
|
|
8
|
-
import { formatIntentRef, resolveIntentRef } from './shared/intent-ref.js';
|
|
9
|
-
import { resolveQualityProofReference } from './shared/proof-reference.js';
|
|
10
|
-
import { validateAtelierRecord } from './atelier.js';
|
|
11
|
-
import { assertExpertiseReady } from './expertise-pack.js';
|
|
12
|
-
|
|
13
|
-
/** 合法判定结果 */
|
|
14
|
-
const VALID_VERDICTS = ['passed', 'deviated', 'blocked', 'pending_human'];
|
|
15
|
-
const VALID_VERIFICATION_CONTEXTS = ['independent_thread', 'human_review'];
|
|
16
|
-
|
|
17
|
-
/** 每个 Intent 都必须覆盖的基础验证维度。 */
|
|
18
|
-
const BASE_DIMENSIONS = [
|
|
19
|
-
'intent_fidelity',
|
|
20
|
-
'philosophy_consistency',
|
|
21
|
-
'baseline_compliance',
|
|
22
|
-
'acceptance_achievement',
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
function getRequiredDimensions(intent) {
|
|
26
|
-
const dimensions = [...BASE_DIMENSIONS];
|
|
27
|
-
if (intent?.continuity_required) dimensions.push('preservation_achievement');
|
|
28
|
-
if (intent?.quality_contract) dimensions.push('quality_achievement');
|
|
29
|
-
return dimensions;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 写入一条验证记录(追加模式——同一 Intent 多次验证保留完整历史)。
|
|
34
|
-
* 文件格式: { intent_id, records: [{ round, verdict, timestamp, ... }] }
|
|
35
|
-
* @param {string} versionDir — 当前 .loom/v{N}/ 目录,用于可信读取 Intent revision
|
|
36
|
-
* @param {string} verificationsDir — verifications/ 目录路径
|
|
37
|
-
* @param {object} record — 验证记录
|
|
38
|
-
* @param {string} record.intent_id — 如 "INT-001"
|
|
39
|
-
* @param {string} record.verdict — passed | deviated | blocked
|
|
40
|
-
* @param {string} record.timestamp — ISO 8601
|
|
41
|
-
* @param {string} record.summary — 验证摘要
|
|
42
|
-
* @param {object} record.dimensions — 基础维度,以及质量契约存在时的 quality_achievement
|
|
43
|
-
* @param {string} [record.reproduction_command] — 复现验证的命令(如 "LLM_API_KEY=mock npm test")
|
|
44
|
-
* @param {string} [record.deviation_detail] — 偏离说明(deviated 时)
|
|
45
|
-
* @param {boolean} [record.reset_suggested] — 是否建议重置上下文
|
|
46
|
-
* @returns {{ filePath: string, round: number, deviated_count: number, should_escalate: boolean }}
|
|
47
|
-
*/
|
|
48
|
-
export function writeVerification(versionDir, verificationsDir, record) {
|
|
49
|
-
const errors = [];
|
|
50
|
-
let atelierEvidence = null;
|
|
51
|
-
let expertiseEvidence = null;
|
|
52
|
-
if (!record.intent_id) errors.push('缺少 intent_id');
|
|
53
|
-
if (!record.verdict || !VALID_VERDICTS.includes(record.verdict)) {
|
|
54
|
-
errors.push(`verdict 非法: "${record.verdict}" (合法: ${VALID_VERDICTS.join('|')})`);
|
|
55
|
-
}
|
|
56
|
-
if (!record.timestamp) errors.push('缺少 timestamp');
|
|
57
|
-
if (!record.dimensions) errors.push('缺少 dimensions(适用验证维度结果)');
|
|
58
|
-
const intent = record.intent_id ? getIntent(versionDir, record.intent_id) : null;
|
|
59
|
-
if (intent && !['in_progress', 'needs_review'].includes(intent.status)) {
|
|
60
|
-
errors.push(`Intent ${record.intent_id} 当前状态为 ${intent.status};只能为 in_progress 或 needs_review 的 Intent 写入验证记录`);
|
|
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
|
-
}
|
|
72
|
-
// dimensions 结构校验:每个维度必须是 { verdict, evidence } 对象
|
|
73
|
-
if (record.dimensions) {
|
|
74
|
-
for (const dim of requiredDimensions) {
|
|
75
|
-
const v = record.dimensions[dim];
|
|
76
|
-
if (v === undefined) {
|
|
77
|
-
errors.push(`dimensions.${dim} 缺失(当前 Intent 的适用维度必须全覆盖)`);
|
|
78
|
-
} else if (typeof v === 'string') {
|
|
79
|
-
errors.push(`dimensions.${dim} 是旧格式(枚举值),必须改成 { verdict, evidence } 对象`);
|
|
80
|
-
} else if (typeof v !== 'object' || v === null) {
|
|
81
|
-
errors.push(`dimensions.${dim} 必须是 { verdict, evidence } 对象`);
|
|
82
|
-
} else {
|
|
83
|
-
if (!VALID_VERDICTS.includes(v.verdict)) {
|
|
84
|
-
errors.push(`dimensions.${dim}.verdict 非法: "${v.verdict}" (合法: ${VALID_VERDICTS.join('|')})`);
|
|
85
|
-
}
|
|
86
|
-
if (record.verdict === 'passed' && v.verdict !== 'passed') {
|
|
87
|
-
errors.push(`整体 verdict 为 passed 时,dimensions.${dim}.verdict 也必须是 passed`);
|
|
88
|
-
}
|
|
89
|
-
if (!v.evidence || typeof v.evidence !== 'string' || v.evidence.trim() === '') {
|
|
90
|
-
errors.push(`dimensions.${dim}.evidence 缺失——必须给出具体证据,不能只写"合规"`);
|
|
91
|
-
} else {
|
|
92
|
-
// evidence 质量校验:长度 + 废话检测
|
|
93
|
-
const ev = v.evidence.trim();
|
|
94
|
-
if (ev.length < 10) {
|
|
95
|
-
errors.push(`dimensions.${dim}.evidence 太短(${ev.length}字符 < 10)——必须给出具体证据,不能只写"合规"`);
|
|
96
|
-
}
|
|
97
|
-
const NONSENSE = ['合规', '通过', 'OK', 'ok', '没问题', '符合要求', '已检查', 'pass', 'passed', 'done'];
|
|
98
|
-
if (NONSENSE.includes(ev)) {
|
|
99
|
-
errors.push(`dimensions.${dim}.evidence "${ev}" 是通用评价而非具体证据——必须写"对照了什么 + 在代码哪里看到/没看到"`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
const qualityProofRef = record.dimensions?.quality_achievement?.quality_proof_ref;
|
|
106
|
-
if (intent?.quality_contract && record.verdict === 'passed' && !qualityProofRef) {
|
|
107
|
-
errors.push('声明 quality_contract 的 Intent 通过时必须提供 dimensions.quality_achievement.quality_proof_ref');
|
|
108
|
-
}
|
|
109
|
-
if (qualityProofRef !== undefined
|
|
110
|
-
&& (typeof qualityProofRef !== 'string' || qualityProofRef.trim() === '')) {
|
|
111
|
-
errors.push('dimensions.quality_achievement.quality_proof_ref 必须是非空字符串');
|
|
112
|
-
} else if (qualityProofRef !== undefined) {
|
|
113
|
-
try {
|
|
114
|
-
resolveQualityProofReference(versionDir, qualityProofRef);
|
|
115
|
-
} catch (error) {
|
|
116
|
-
errors.push(error.message);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
if (record.verdict === 'passed' && intent) {
|
|
120
|
-
try {
|
|
121
|
-
expertiseEvidence = assertExpertiseReady(versionDir, record.intent_id);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
errors.push(`passed 前必须闭合外部能力获取强门: ${error.message}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (intent?.quality_strategy === 'atelier' && record.verdict === 'passed') {
|
|
127
|
-
try {
|
|
128
|
-
atelierEvidence = validateAtelierRecord(versionDir, record.intent_id);
|
|
129
|
-
if (!['selected', 'baseline_retained'].includes(atelierEvidence.status)) {
|
|
130
|
-
errors.push(`quality_strategy=atelier 通过前,Atelier Record 必须是 selected 或 baseline_retained(当前: ${atelierEvidence.status})`);
|
|
131
|
-
}
|
|
132
|
-
} catch (error) {
|
|
133
|
-
errors.push(`quality_strategy=atelier 通过前必须有当前且合法的 Atelier Record: ${error.message}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (errors.length > 0) {
|
|
137
|
-
throw new Error(`验证记录校验失败:\n - ${errors.join('\n - ')}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const intentRevision = getEffectiveIntentRevision(intent);
|
|
141
|
-
|
|
142
|
-
const filePath = join(verificationsDir, `${record.intent_id}.json`);
|
|
143
|
-
|
|
144
|
-
// 读取已有记录(如果有)
|
|
145
|
-
let data;
|
|
146
|
-
if (existsSync(filePath)) {
|
|
147
|
-
data = readJsonFile(filePath, '验证记录');
|
|
148
|
-
// 结构校验:已有文件必须是 { intent_id, records: [] } 格式
|
|
149
|
-
if (!data || typeof data !== 'object' || !Array.isArray(data.records)) {
|
|
150
|
-
throw new Error(
|
|
151
|
-
`已有验证记录格式错误: ${filePath}\n` +
|
|
152
|
-
`期望格式: { intent_id, records: [...] }\n` +
|
|
153
|
-
`实际格式: ${JSON.stringify(data).slice(0, 200)}\n` +
|
|
154
|
-
`修复: 删除或修正该文件后重试。`
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
data = { intent_id: record.intent_id, records: [] };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// 计算轮次和连续 deviated 计数。规范要求中间出现 passed/blocked 后重置。
|
|
162
|
-
const round = data.records.length + 1;
|
|
163
|
-
const recordsWithCurrent = [...data.records, record];
|
|
164
|
-
let deviatedCount = 0;
|
|
165
|
-
for (let i = recordsWithCurrent.length - 1; i >= 0; i--) {
|
|
166
|
-
if (recordsWithCurrent[i].verdict !== 'deviated') break;
|
|
167
|
-
deviatedCount++;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 追加新记录
|
|
171
|
-
data.records.push({
|
|
172
|
-
round,
|
|
173
|
-
intent_revision: intentRevision,
|
|
174
|
-
verification_epoch: getEffectiveVerificationEpoch(intent),
|
|
175
|
-
verdict: record.verdict,
|
|
176
|
-
timestamp: record.timestamp,
|
|
177
|
-
summary: record.summary,
|
|
178
|
-
verification_provenance: record.verification_provenance,
|
|
179
|
-
dimensions: record.dimensions,
|
|
180
|
-
atelier: atelierEvidence ? {
|
|
181
|
-
record_ref: `09_ATELIER/${record.intent_id}.json`,
|
|
182
|
-
stance_revision: atelierEvidence.stance_revision,
|
|
183
|
-
status: atelierEvidence.status,
|
|
184
|
-
} : undefined,
|
|
185
|
-
expertise: expertiseEvidence ? {
|
|
186
|
-
record_ref: `10_EXPERTISE_PACKS/${record.intent_id}.json`,
|
|
187
|
-
intent_revision: expertiseEvidence.intent_revision,
|
|
188
|
-
required_node_ids: expertiseEvidence.required_node_ids,
|
|
189
|
-
source_count: expertiseEvidence.source_count,
|
|
190
|
-
capsule_count: expertiseEvidence.capsule_count,
|
|
191
|
-
pack_digest: expertiseEvidence.pack_digest,
|
|
192
|
-
} : undefined,
|
|
193
|
-
reproduction_command: record.reproduction_command,
|
|
194
|
-
deviation_detail: record.deviation_detail,
|
|
195
|
-
reset_suggested: record.reset_suggested,
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
199
|
-
|
|
200
|
-
// 检查是否应该升级 blocked(连续 3 轮 deviated,默认值)
|
|
201
|
-
const DEVIATED_LIMIT = 3;
|
|
202
|
-
const shouldEscalate = record.verdict === 'deviated' && deviatedCount >= DEVIATED_LIMIT;
|
|
203
|
-
|
|
204
|
-
return { filePath, round, deviated_count: deviatedCount, should_escalate: shouldEscalate };
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* 读取某 Intent 的验证历史。
|
|
209
|
-
* @returns {{ intent_id: string, records: array } | null}
|
|
210
|
-
*/
|
|
211
|
-
export function getVerificationHistory(verificationsDir, intentId) {
|
|
212
|
-
const filePath = join(verificationsDir, `${intentId}.json`);
|
|
213
|
-
if (!existsSync(filePath)) {
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
return readJsonFile(filePath, '验证记录');
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/** Read each owning version's local records along the explicit predecessor graph. */
|
|
220
|
-
export function getAcrossVersionVerificationHistory(currentVersionDir, inputRef) {
|
|
221
|
-
const root = resolveIntentRef(currentVersionDir, inputRef);
|
|
222
|
-
const histories = [];
|
|
223
|
-
const visited = new Set();
|
|
224
|
-
const active = new Set();
|
|
225
|
-
|
|
226
|
-
function walk(resolved) {
|
|
227
|
-
if (active.has(resolved.ref)) throw new Error(`Intent lineage 存在循环: ${[...active, resolved.ref].join(' -> ')}`);
|
|
228
|
-
if (visited.has(resolved.ref)) return;
|
|
229
|
-
active.add(resolved.ref);
|
|
230
|
-
const intent = getIntent(resolved.versionDir, resolved.intentId);
|
|
231
|
-
const local = getVerificationHistory(join(resolved.versionDir, 'verifications'), resolved.intentId);
|
|
232
|
-
histories.push({
|
|
233
|
-
ref: resolved.ref,
|
|
234
|
-
source_version: resolved.version,
|
|
235
|
-
source_intent: resolved.intentId,
|
|
236
|
-
source_intent_id: resolved.intentId,
|
|
237
|
-
records: (local?.records || []).map((record) => ({
|
|
238
|
-
...record,
|
|
239
|
-
source_version: resolved.version,
|
|
240
|
-
source_intent: resolved.intentId,
|
|
241
|
-
source_intent_id: resolved.intentId,
|
|
242
|
-
})),
|
|
243
|
-
});
|
|
244
|
-
for (const predecessor of intent.lineage?.predecessors || []) {
|
|
245
|
-
walk(resolveIntentRef(currentVersionDir, formatIntentRef(predecessor.version, predecessor.intent_id)));
|
|
246
|
-
}
|
|
247
|
-
active.delete(resolved.ref);
|
|
248
|
-
visited.add(resolved.ref);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
walk(root);
|
|
252
|
-
return { intent_ref: root.ref, across_versions: true, histories };
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* 快捷创建验证记录——Agent 不用手动构造完整 JSON。
|
|
257
|
-
* 快捷记录只能用于低风险的结构化核验;passed 仍必须显式声明独立验证来源。
|
|
258
|
-
* @param {string} versionDir — 当前 .loom/v{N}/ 目录
|
|
259
|
-
* @param {string} verificationsDir — verifications/ 目录路径
|
|
260
|
-
* @param {string} intentId — 如 "INT-001"
|
|
261
|
-
* @param {string} verdict — 'passed' | 'deviated' | 'blocked'
|
|
262
|
-
* @param {string} summary — 验证摘要(也会作为所有适用维度的 evidence)
|
|
263
|
-
* @param {object} [extras]
|
|
264
|
-
* @param {string} [extras.reproduction_command] — 复现命令
|
|
265
|
-
* @param {string} [extras.quality_proof_ref] — Quality Proof 证据引用
|
|
266
|
-
* @param {string} [extras.preservation_evidence] — 对既有状态守恒的独立证据
|
|
267
|
-
* @param {string} [extras.deviation_detail] — 偏离说明(deviated 时)
|
|
268
|
-
* @returns {{ filePath: string, round: number, deviated_count: number, should_escalate: boolean }}
|
|
269
|
-
*/
|
|
270
|
-
export function createQuickVerification(versionDir, verificationsDir, intentId, verdict, summary, extras = {}) {
|
|
271
|
-
const timestamp = new Date().toISOString();
|
|
272
|
-
const intent = getIntent(versionDir, intentId);
|
|
273
|
-
// 用 summary 填充适用维度的 evidence——快捷命令不要求 Agent 逐维度写
|
|
274
|
-
const dimensions = {};
|
|
275
|
-
for (const dim of getRequiredDimensions(intent)) {
|
|
276
|
-
dimensions[dim] = {
|
|
277
|
-
verdict,
|
|
278
|
-
evidence: dim === 'preservation_achievement'
|
|
279
|
-
? (extras.preservation_evidence || summary)
|
|
280
|
-
: summary,
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
if (extras.quality_proof_ref && dimensions.quality_achievement) {
|
|
284
|
-
dimensions.quality_achievement.quality_proof_ref = extras.quality_proof_ref;
|
|
285
|
-
}
|
|
286
|
-
return writeVerification(versionDir, verificationsDir, {
|
|
287
|
-
intent_id: intentId,
|
|
288
|
-
verdict,
|
|
289
|
-
timestamp,
|
|
290
|
-
summary,
|
|
291
|
-
dimensions,
|
|
292
|
-
reproduction_command: extras.reproduction_command || null,
|
|
293
|
-
verification_provenance: extras.verification_provenance || null,
|
|
294
|
-
deviation_detail: extras.deviation_detail || null,
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* 返回所有待验证的 Intent(有实现产物但还没验证记录的)。
|
|
300
|
-
* 需要传入 Intent Map 来判断哪些 Intent 是 in_progress。
|
|
301
|
-
*/
|
|
302
|
-
export function getPendingVerifications(versionDir, verificationsDir) {
|
|
303
|
-
const intentMap = readJsonFile(join(versionDir, '04_INTENT_MAP.json'), 'Intent Map');
|
|
304
|
-
const pending = [];
|
|
305
|
-
for (const [id, intent] of Object.entries(intentMap.intents)) {
|
|
306
|
-
if (intent.status === 'in_progress' || intent.status === 'needs_review') {
|
|
307
|
-
const history = getVerificationHistory(verificationsDir, id);
|
|
308
|
-
if (!hasCurrentPassedVerification(intent, history)) pending.push(id);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return pending;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/** Missing Intent revisions are revision 1 without mutating the map. */
|
|
315
|
-
export function getEffectiveIntentRevision(intent) {
|
|
316
|
-
return intent.revision ?? 1;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Legacy records count as revision 1 only while the Intent itself is legacy.
|
|
321
|
-
* Once revision is explicit, an untagged record cannot prove freshness.
|
|
322
|
-
*/
|
|
323
|
-
export function getVerificationIntentRevision(intent, record) {
|
|
324
|
-
if (Number.isInteger(record?.intent_revision) && record.intent_revision >= 1) {
|
|
325
|
-
return record.intent_revision;
|
|
326
|
-
}
|
|
327
|
-
return hasLegacyIntentRevision(intent) ? 1 : null;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
export function isVerificationCurrent(intent, record) {
|
|
331
|
-
return getVerificationIntentRevision(intent, record) === getEffectiveIntentRevision(intent)
|
|
332
|
-
&& getVerificationEpoch(intent, record) === getEffectiveVerificationEpoch(intent);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
export function getVerificationEpoch(intent, record) {
|
|
336
|
-
if (Number.isInteger(record?.verification_epoch) && record.verification_epoch >= 1) {
|
|
337
|
-
return record.verification_epoch;
|
|
338
|
-
}
|
|
339
|
-
return intent?.verification_epoch === undefined ? 1 : null;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
export function getLatestPassedVerification(history) {
|
|
343
|
-
if (!Array.isArray(history?.records)) return null;
|
|
344
|
-
return [...history.records].reverse().find((record) => record.verdict === 'passed') ?? null;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export function hasCurrentPassedVerification(intent, history) {
|
|
348
|
-
const latest = history?.records?.[history.records.length - 1];
|
|
349
|
-
return latest?.verdict === 'passed' && isVerificationCurrent(intent, latest);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* 列出所有验证记录文件。
|
|
354
|
-
* 只列出正式验证记录——文件名匹配 INT-XXX 格式且内容含 records 字段。
|
|
355
|
-
* 过滤掉用户写入的临时输入文件(如 INT-001.verify.json、_tmp_*.json)。
|
|
356
|
-
*/
|
|
357
|
-
export function listVerifications(verificationsDir) {
|
|
358
|
-
if (!existsSync(verificationsDir)) return [];
|
|
359
|
-
return readdirSync(verificationsDir)
|
|
360
|
-
.filter((f) => f.endsWith('.json'))
|
|
361
|
-
.filter((f) => /^INT-\d+\.json$/.test(f))
|
|
362
|
-
.map((f) => f.replace('.json', ''));
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* 获取某 Intent 的验证契约(acceptance 字段的解析结果)。
|
|
367
|
-
* 如果 acceptance 是内联定义,直接返回。
|
|
368
|
-
* 如果是引用(如 "see 05_VERIFICATION.md#int-001"),解析引用并返回对应章节内容。
|
|
369
|
-
* @param {string} versionDir — .loom/v{N}/ 目录
|
|
370
|
-
* @param {string} intentId — Intent ID
|
|
371
|
-
* @returns {string} 验收契约内容
|
|
372
|
-
*/
|
|
373
|
-
export function getVerificationContract(versionDir, intentId) {
|
|
374
|
-
const intentMap = readJsonFile(join(versionDir, '04_INTENT_MAP.json'), 'Intent Map');
|
|
375
|
-
if (!(intentId in intentMap.intents)) {
|
|
376
|
-
throw new Error(`Intent 不存在: ${intentId}`);
|
|
377
|
-
}
|
|
378
|
-
const acceptance = intentMap.intents[intentId].acceptance;
|
|
379
|
-
|
|
380
|
-
// 检测是否是引用格式: "see 05_VERIFICATION.md#section" 或 "05_VERIFICATION.md#section"
|
|
381
|
-
const refMatch = acceptance.match(/(?:see\s+)?(\w+\.md)#([\w-]+)/i);
|
|
382
|
-
if (refMatch) {
|
|
383
|
-
const [, file, section] = refMatch;
|
|
384
|
-
const filePath = join(versionDir, file);
|
|
385
|
-
if (!existsSync(filePath)) {
|
|
386
|
-
throw new Error(`验证契约引用的文件不存在: ${filePath}`);
|
|
387
|
-
}
|
|
388
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
389
|
-
return extractMdSection(content, section, '验证契约');
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// 内联定义,直接返回
|
|
393
|
-
return acceptance;
|
|
394
|
-
}
|
package/cli/src/version.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
// version — LOOM 版本管理
|
|
2
|
-
// 提供 list / current / new / use / diff 五个原子操作。
|
|
3
|
-
// CLI 只做数据操作,演进决策(Minor/Major)由 Agent + 用户对话完成。
|
|
4
|
-
|
|
5
|
-
import { existsSync, readdirSync, readFileSync, writeFileSync, statSync } from 'node:fs';
|
|
6
|
-
import { join, resolve } from 'node:path';
|
|
7
|
-
import { createVersionStructure } from './init.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 列出所有版本目录。
|
|
11
|
-
* @param {string} loomRoot — .loom 目录路径
|
|
12
|
-
* @returns {{ versions: string[], current: string|null }}
|
|
13
|
-
*/
|
|
14
|
-
export function listVersions(loomRoot) {
|
|
15
|
-
if (!existsSync(loomRoot)) {
|
|
16
|
-
throw new Error(`找不到 .loom 目录: ${loomRoot}`);
|
|
17
|
-
}
|
|
18
|
-
const versions = readdirSync(loomRoot)
|
|
19
|
-
.filter((d) => /^v\d+$/.test(d) && statSync(join(loomRoot, d)).isDirectory())
|
|
20
|
-
.sort((a, b) => parseInt(a.slice(1)) - parseInt(b.slice(1)));
|
|
21
|
-
const current = readCurrentPointer(loomRoot);
|
|
22
|
-
return { versions, current };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 读取当前版本指针。
|
|
27
|
-
* 优先读 .loom/current 文件;不存在则回退到自动探测最新版本。
|
|
28
|
-
* @param {string} loomRoot — .loom 目录路径
|
|
29
|
-
* @returns {string|null} 版本号如 'v1',或 null(无版本)
|
|
30
|
-
*/
|
|
31
|
-
export function readCurrentPointer(loomRoot) {
|
|
32
|
-
const pointerPath = join(loomRoot, 'current');
|
|
33
|
-
if (existsSync(pointerPath)) {
|
|
34
|
-
const v = readFileSync(pointerPath, 'utf-8').trim();
|
|
35
|
-
if (/^v\d+$/.test(v) && existsSync(join(loomRoot, v))) return v;
|
|
36
|
-
}
|
|
37
|
-
// 回退:自动探测最新版本
|
|
38
|
-
if (!existsSync(loomRoot)) return null;
|
|
39
|
-
const versions = readdirSync(loomRoot)
|
|
40
|
-
.filter((d) => /^v\d+$/.test(d) && statSync(join(loomRoot, d)).isDirectory())
|
|
41
|
-
.sort((a, b) => parseInt(b.slice(1)) - parseInt(a.slice(1)));
|
|
42
|
-
return versions[0] ?? null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* 创建新版本 v{N+1},自动切换为当前版本。
|
|
47
|
-
* 不复制旧版本内容——空目录 + 模板强制 Agent 重新思考。
|
|
48
|
-
* @param {string} projectDir — 项目根目录
|
|
49
|
-
* @returns {{ version: string, created: string[], skipped: string[] }}
|
|
50
|
-
*/
|
|
51
|
-
export function newVersion(projectDir) {
|
|
52
|
-
const loomRoot = join(projectDir, '.loom');
|
|
53
|
-
const { versions } = listVersions(loomRoot);
|
|
54
|
-
const parentVersion = readCurrentPointer(loomRoot);
|
|
55
|
-
const nextNum = versions.length === 0
|
|
56
|
-
? 1
|
|
57
|
-
: parseInt(versions[versions.length - 1].slice(1)) + 1;
|
|
58
|
-
const nextV = `v${nextNum}`;
|
|
59
|
-
const result = createVersionStructure(projectDir, nextV, parentVersion);
|
|
60
|
-
// 自动切换为当前版本
|
|
61
|
-
writeFileSync(join(loomRoot, 'current'), nextV, 'utf-8');
|
|
62
|
-
result.created.push('.loom/current');
|
|
63
|
-
return { version: nextV, created: result.created, skipped: result.skipped };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* 切换当前版本指针。
|
|
68
|
-
* @param {string} loomRoot — .loom 目录路径
|
|
69
|
-
* @param {string} version — 目标版本号如 'v1'
|
|
70
|
-
*/
|
|
71
|
-
export function useVersion(loomRoot, version) {
|
|
72
|
-
const v = version.startsWith('v') ? version : `v${version}`;
|
|
73
|
-
if (!existsSync(join(loomRoot, v))) {
|
|
74
|
-
throw new Error(`版本不存在: ${v}`);
|
|
75
|
-
}
|
|
76
|
-
writeFileSync(join(loomRoot, 'current'), v, 'utf-8');
|
|
77
|
-
return v;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* 对比两个版本的文件差异。
|
|
82
|
-
* 只对比文件存在性和大小,不做内容 diff(内容 diff 用 Git)。
|
|
83
|
-
* @param {string} loomRoot — .loom 目录路径
|
|
84
|
-
* @param {string} v1 — 版本 A
|
|
85
|
-
* @param {string} v2 — 版本 B
|
|
86
|
-
* @returns {{ only_in_a: string[], only_in_b: string[], different_size: string[], same: string[] }}
|
|
87
|
-
*/
|
|
88
|
-
export function diffVersions(loomRoot, v1, v2) {
|
|
89
|
-
const a = v1.startsWith('v') ? v1 : `v${v1}`;
|
|
90
|
-
const b = v2.startsWith('v') ? v2 : `v${v2}`;
|
|
91
|
-
const dirA = join(loomRoot, a);
|
|
92
|
-
const dirB = join(loomRoot, b);
|
|
93
|
-
if (!existsSync(dirA)) throw new Error(`版本不存在: ${a}`);
|
|
94
|
-
if (!existsSync(dirB)) throw new Error(`版本不存在: ${b}`);
|
|
95
|
-
|
|
96
|
-
const filesA = listFilesRelative(dirA);
|
|
97
|
-
const filesB = listFilesRelative(dirB);
|
|
98
|
-
const setA = new Set(filesA);
|
|
99
|
-
const setB = new Set(filesB);
|
|
100
|
-
|
|
101
|
-
const onlyInA = filesA.filter((f) => !setB.has(f));
|
|
102
|
-
const onlyInB = filesB.filter((f) => !setA.has(f));
|
|
103
|
-
const common = filesA.filter((f) => setB.has(f));
|
|
104
|
-
const differentSize = common.filter((f) => {
|
|
105
|
-
const sa = statSync(join(dirA, f)).size;
|
|
106
|
-
const sb = statSync(join(dirB, f)).size;
|
|
107
|
-
return sa !== sb;
|
|
108
|
-
});
|
|
109
|
-
const same = common.filter((f) => {
|
|
110
|
-
const sa = statSync(join(dirA, f)).size;
|
|
111
|
-
const sb = statSync(join(dirB, f)).size;
|
|
112
|
-
return sa === sb;
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
return { only_in_a: onlyInA, only_in_b: onlyInB, different_size: differentSize, same };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* 递归列出目录下所有文件的相对路径。
|
|
120
|
-
*/
|
|
121
|
-
function listFilesRelative(dir, base = '') {
|
|
122
|
-
const result = [];
|
|
123
|
-
if (!existsSync(dir)) return result;
|
|
124
|
-
for (const entry of readdirSync(dir)) {
|
|
125
|
-
const full = join(dir, entry);
|
|
126
|
-
const rel = base ? join(base, entry) : entry;
|
|
127
|
-
if (statSync(full).isDirectory()) {
|
|
128
|
-
result.push(...listFilesRelative(full, rel));
|
|
129
|
-
} else {
|
|
130
|
-
result.push(rel);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return result.sort();
|
|
134
|
-
}
|
package/dimensions/AUTHORSHIP.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# Authorship — Identity Compiler 与 Atelier Method
|
|
2
|
-
|
|
3
|
-
本维度只在 `quality_strategy=atelier` 时加载。目标不是扮演某位大师,而是迫使本次创作
|
|
4
|
-
形成可反驳的命题、明确的选择与可观察的作品差异。
|
|
5
|
-
|
|
6
|
-
## Identity Compiler
|
|
7
|
-
|
|
8
|
-
先读取当前 Intent、Doctrine anchors、Capability Graph / Brief、quality contract、
|
|
9
|
-
creative scope、真实媒介约束与参考机制,再形成 Authorial Stance:
|
|
10
|
-
|
|
11
|
-
1. `creative_thesis`:作品要让用户以什么不同方式理解或感受问题。
|
|
12
|
-
2. `gaze`:这次优先看见什么。
|
|
13
|
-
3. `tension`:哪两个价值必须同时成立。
|
|
14
|
-
4. `signature_bet`:主张、实现机制与主要代价。
|
|
15
|
-
5. `refusals`:拒绝哪些安全但平庸的默认解。
|
|
16
|
-
6. `medium_grammar`:构图、节奏、动效、材质、语言或声音如何承载命题。
|
|
17
|
-
7. `surprise_budget`:允许陌生到什么程度,哪些边界不可牺牲。
|
|
18
|
-
8. `anti_fixation`:至少一个主动打破首个构想的约束。
|
|
19
|
-
9. `verification_lens`:不看阐述时,怎样从作品与用户行为判断命题成立。
|
|
20
|
-
|
|
21
|
-
如果这些内容不会改变任何构图、交互、资产、语言或验证动作,Stance 无效。
|
|
22
|
-
|
|
23
|
-
## Atelier
|
|
24
|
-
|
|
25
|
-
1. 在修改前冻结真实基线。
|
|
26
|
-
2. 定义至少两个会改变用户体验机制的差异轴。
|
|
27
|
-
3. 独立产生媒介原型;换色、换皮、同义改写不算不同候选。
|
|
28
|
-
4. 每个候选先过 Reliability Floor,再进入质量比较。
|
|
29
|
-
5. 交换顺序或隐藏来源进行比较;没有候选胜过基线时保留基线。
|
|
30
|
-
6. 完整实现胜出机制,观察真实宿主并修正。
|
|
31
|
-
|
|
32
|
-
唯一记录位于 `.loom/vN/09_ATELIER/<intent-id>.json`。每个候选必须绑定
|
|
33
|
-
`stance_revision`;Stance 改变后,旧候选要重新资格检查或归档。
|
|
34
|
-
|
|
35
|
-
## Correction Triage
|
|
36
|
-
|
|
37
|
-
- 当前命题、机制、媒介语法或候选选择失效:写 `corrections[]`,递增
|
|
38
|
-
`stance_revision`。
|
|
39
|
-
- 新用户结果、约束、能力缺口、风险或项目证据:提交带 provenance 的 Capability Graph
|
|
40
|
-
proposal,由 Architect 裁决。
|
|
41
|
-
- Intent、契约或 Doctrine 错误:按 LOOM reflow 回到对应上层。
|
|
42
|
-
- 多个任务经 Quality Proof 重复验证的方法:作为 learning candidate,人工晋升为 Skill;
|
|
43
|
-
只有跨 Intent 的长期创作判断才考虑 Creative Lineage。
|
|
44
|
-
|
|
45
|
-
Author 不能修改 Graph、Intent 或验收标准,也不能裁决自己的 proposal。
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# Responsibility and Intent Slicing
|
|
2
|
-
|
|
3
|
-
> 这是 Architect 的按需方法,不是 Weaver 的必填清单,也不是 CLI 通过条件。
|
|
4
|
-
|
|
5
|
-
## 何时使用
|
|
6
|
-
|
|
7
|
-
只有在以下情况使用拆分:
|
|
8
|
-
|
|
9
|
-
- 一个目标跨越多个独立系统责任。
|
|
10
|
-
- 不同部分可以单独验证或具有明确依赖。
|
|
11
|
-
- 一次实现会产生过大风险、上下文或回滚成本。
|
|
12
|
-
|
|
13
|
-
如果拆分不会改善边界、验证或交付顺序,就保持一个 Intent。
|
|
14
|
-
|
|
15
|
-
## 三种不能混淆的结构
|
|
16
|
-
|
|
17
|
-
1. **Doctrine 领域**:会反复影响未来决策的长期判断,由 Weaver 负责。
|
|
18
|
-
2. **系统责任**:模块、数据、接口与依赖边界,由 Architect 负责。
|
|
19
|
-
3. **Intent 切片**:能够独立保护和验证的用户结果,由 Architect 负责。
|
|
20
|
-
|
|
21
|
-
不要从技术目录直接推导 Intent,也不要让 Doctrine 文档变成模块清单。
|
|
22
|
-
|
|
23
|
-
## 最小拆分法
|
|
24
|
-
|
|
25
|
-
对候选切片逐一询问:
|
|
26
|
-
|
|
27
|
-
- 它保护的用户结果能否独立描述。
|
|
28
|
-
- 它是否有独立的完成契约。
|
|
29
|
-
- 它失败时能否独立回流或回滚。
|
|
30
|
-
- 它与其他切片的依赖是否单向且必要。
|
|
31
|
-
- 合并后是否更简单,且不会模糊验证。
|
|
32
|
-
|
|
33
|
-
只有前三项明确、依赖可解释时才创建独立 Intent。
|
|
34
|
-
|
|
35
|
-
## 输出
|
|
36
|
-
|
|
37
|
-
拆分结果只进入:
|
|
38
|
-
|
|
39
|
-
- `02_ARCHITECTURE.md` 的系统责任与依赖说明。
|
|
40
|
-
- `04_INTENT_MAP.json` 的 Intent DAG。
|
|
41
|
-
|
|
42
|
-
它不进入 Project Doctrine,也不以“数量足够多”作为质量信号。
|