@haaaiawd/loom 0.10.0 → 1.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +147 -74
  4. package/cli/bin/loom.js +428 -105
  5. package/cli/help/asset.md +36 -0
  6. package/cli/help/atelier.md +37 -0
  7. package/cli/help/capability.md +68 -0
  8. package/cli/help/concepts.md +100 -72
  9. package/cli/help/doctor.md +71 -121
  10. package/cli/help/loop.md +120 -135
  11. package/cli/help/patch.md +33 -0
  12. package/cli/help/preview.md +2 -1
  13. package/cli/help/proposals.md +21 -0
  14. package/cli/help/version.md +92 -16
  15. package/cli/help/workflow.md +101 -100
  16. package/cli/src/activate.js +349 -73
  17. package/cli/src/asset-library.js +384 -0
  18. package/cli/src/atelier.js +331 -0
  19. package/cli/src/capability-graph.js +351 -0
  20. package/cli/src/capability-proposals.js +225 -0
  21. package/cli/src/diagnostics.js +240 -44
  22. package/cli/src/guide.js +155 -40
  23. package/cli/src/init.js +58 -32
  24. package/cli/src/intent-draft.js +303 -0
  25. package/cli/src/intent-map.js +560 -54
  26. package/cli/src/patch.js +214 -0
  27. package/cli/src/philosophy.js +177 -154
  28. package/cli/src/preview-prompt.md +13 -6
  29. package/cli/src/preview.js +1 -0
  30. package/cli/src/shared/intent-ref.js +38 -0
  31. package/cli/src/shared/proof-reference.js +19 -0
  32. package/cli/src/shared/verification-method.js +32 -0
  33. package/cli/src/verify.js +202 -62
  34. package/cli/src/version.js +5 -4
  35. package/dimensions/AUTHORSHIP.md +45 -0
  36. package/dimensions/PART_DECOMPOSITION.md +42 -203
  37. package/dimensions/SEARCH_METHODOLOGY.md +101 -97
  38. package/dimensions/examples/AGENT_SYSTEM/README.md +1 -1
  39. package/dimensions/examples/CLI_TOOL/README.md +1 -1
  40. package/dimensions/universal/COLLABORATION_PHILOSOPHY.md +28 -77
  41. package/dimensions/universal/ENGINEERING_CREED.md +30 -74
  42. package/dimensions/universal/PRODUCT_PHILOSOPHY.md +32 -70
  43. package/meta/BASELINE.md +91 -276
  44. package/meta/INTENT_LOOP.md +289 -737
  45. package/meta/PHILOSOPHY_WEAVER.md +110 -343
  46. package/meta/ROLE_ACTIVATION.md +109 -267
  47. package/package.json +13 -7
  48. package/roles/architect.md +84 -111
  49. package/roles/forge.md +105 -126
  50. package/roles/keeper.md +109 -223
  51. package/roles/visionary.md +57 -86
  52. package/templates/ASSET_LIBRARY_MANIFEST_TEMPLATE.json +10 -0
  53. package/templates/ATELIER_RECORD_TEMPLATE.json +48 -0
  54. package/templates/CAPABILITY_BRIEF_TEMPLATE.md +34 -0
  55. package/templates/CAPABILITY_GRAPH_TEMPLATE.json +11 -0
  56. package/templates/INTENT_MAP_TEMPLATE.json +26 -10
  57. package/templates/PHILOSOPHY_TEMPLATE.md +44 -75
  58. package/templates/VISION_TEMPLATE.md +44 -67
@@ -0,0 +1,38 @@
1
+ // intent-ref.js — Resolve current and version-qualified Intent read references.
2
+
3
+ import { existsSync } from 'node:fs';
4
+ import { basename, dirname, join } from 'node:path';
5
+
6
+ export const VERSION_PATTERN = /^v\d+$/;
7
+ export const INTENT_ID_PATTERN = /^INT-\d+$/;
8
+
9
+ export function formatIntentRef(version, intentId) {
10
+ return `${version}:${intentId}`;
11
+ }
12
+
13
+ /** Resolve INT-003 against the current version or v1:INT-003 against .loom/v1. */
14
+ export function resolveIntentRef(currentVersionDir, input) {
15
+ const currentVersion = basename(currentVersionDir);
16
+ if (!VERSION_PATTERN.test(currentVersion)) {
17
+ throw new Error(`无法从目录确定当前 LOOM 版本: ${currentVersionDir}`);
18
+ }
19
+
20
+ const parts = String(input || '').split(':');
21
+ if (parts.length > 2) throw new Error(`Intent 引用格式非法: ${input}`);
22
+ const qualified = parts.length === 2;
23
+ const version = qualified ? parts[0] : currentVersion;
24
+ const intentId = qualified ? parts[1] : parts[0];
25
+ if (!VERSION_PATTERN.test(version) || !INTENT_ID_PATTERN.test(intentId)) {
26
+ throw new Error(`Intent 引用格式非法: ${input}(应为 INT-003 或 v1:INT-003)`);
27
+ }
28
+
29
+ const versionDir = qualified ? join(dirname(currentVersionDir), version) : currentVersionDir;
30
+ if (!existsSync(versionDir)) throw new Error(`版本不存在: ${version}`);
31
+ return {
32
+ ref: formatIntentRef(version, intentId),
33
+ version,
34
+ intentId,
35
+ versionDir,
36
+ historical: version !== currentVersion,
37
+ };
38
+ }
@@ -0,0 +1,19 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { isAbsolute, relative, resolve } from 'node:path';
3
+ import { extractMdSection } from './md-utils.js';
4
+
5
+ /** Resolve a root-relative Markdown proof reference without allowing path escape. */
6
+ export function resolveQualityProofReference(versionDir, ref) {
7
+ if (typeof ref !== 'string' || !ref.trim()) throw new Error('quality_proof_ref 必须是非空字符串');
8
+ const match = ref.trim().match(/^([^#]+)#([\w-]+)$/);
9
+ if (!match) throw new Error('quality_proof_ref 必须是项目相对路径加 Markdown 锚点,例如 verifications/INT-001-quality-proof.md#int-001');
10
+ const [, file, anchor] = match;
11
+ if (isAbsolute(file)) throw new Error('quality_proof_ref 不得使用绝对路径');
12
+ const projectDir = resolve(versionDir, '..', '..');
13
+ const filePath = resolve(projectDir, file);
14
+ const relation = relative(projectDir, filePath);
15
+ if (relation.startsWith('..') || isAbsolute(relation)) throw new Error('quality_proof_ref 不得越出项目目录');
16
+ if (!existsSync(filePath)) throw new Error(`quality_proof_ref 指向的文件不存在: ${file}`);
17
+ extractMdSection(readFileSync(filePath, 'utf-8'), anchor, 'Quality Proof');
18
+ return { filePath, anchor, ref: ref.trim() };
19
+ }
@@ -0,0 +1,32 @@
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 CHANGED
@@ -3,56 +3,77 @@
3
3
 
4
4
  import { readFileSync, writeFileSync, existsSync, readdirSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
- import { extractMdSection, readJsonFile } from './shared/md-utils.js';
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';
7
11
 
8
12
  /** 合法判定结果 */
9
13
  const VALID_VERDICTS = ['passed', 'deviated', 'blocked', 'pending_human'];
10
14
 
11
- /** 四个必须覆盖的验证维度 */
12
- const REQUIRED_DIMENSIONS = [
13
- 'intent_fidelity',
14
- 'philosophy_consistency',
15
- 'baseline_compliance',
16
- 'acceptance_achievement',
17
- ];
15
+ /** 每个 Intent 都必须覆盖的基础验证维度。 */
16
+ const BASE_DIMENSIONS = [
17
+ 'intent_fidelity',
18
+ 'philosophy_consistency',
19
+ 'baseline_compliance',
20
+ 'acceptance_achievement',
21
+ ];
22
+
23
+ function getRequiredDimensions(intent) {
24
+ const dimensions = [...BASE_DIMENSIONS];
25
+ if (intent?.continuity_required) dimensions.push('preservation_achievement');
26
+ if (intent?.quality_contract) dimensions.push('quality_achievement');
27
+ return dimensions;
28
+ }
18
29
 
19
30
  /**
20
31
  * 写入一条验证记录(追加模式——同一 Intent 多次验证保留完整历史)。
21
32
  * 文件格式: { intent_id, records: [{ round, verdict, timestamp, ... }] }
22
- * @param {string} verificationsDirverifications/ 目录路径
33
+ * @param {string} versionDir当前 .loom/v{N}/ 目录,用于可信读取 Intent revision
34
+ * @param {string} verificationsDir — verifications/ 目录路径
23
35
  * @param {object} record — 验证记录
24
36
  * @param {string} record.intent_id — 如 "INT-001"
25
37
  * @param {string} record.verdict — passed | deviated | blocked
26
38
  * @param {string} record.timestamp — ISO 8601
27
39
  * @param {string} record.summary — 验证摘要
28
- * @param {object} record.dimensions — 四个维度的验证结果
40
+ * @param {object} record.dimensions — 基础维度,以及质量契约存在时的 quality_achievement
29
41
  * @param {string} [record.reproduction_command] — 复现验证的命令(如 "LLM_API_KEY=mock npm test")
30
42
  * @param {string} [record.deviation_detail] — 偏离说明(deviated 时)
31
43
  * @param {boolean} [record.reset_suggested] — 是否建议重置上下文
32
44
  * @returns {{ filePath: string, round: number, deviated_count: number, should_escalate: boolean }}
33
45
  */
34
- export function writeVerification(verificationsDir, record) {
35
- const errors = [];
36
- if (!record.intent_id) errors.push('缺少 intent_id');
46
+ export function writeVerification(versionDir, verificationsDir, record) {
47
+ const errors = [];
48
+ let atelierEvidence = null;
49
+ if (!record.intent_id) errors.push('缺少 intent_id');
37
50
  if (!record.verdict || !VALID_VERDICTS.includes(record.verdict)) {
38
51
  errors.push(`verdict 非法: "${record.verdict}" (合法: ${VALID_VERDICTS.join('|')})`);
39
52
  }
40
- if (!record.timestamp) errors.push('缺少 timestamp');
41
- if (!record.dimensions) errors.push('缺少 dimensions(四个维度结果)');
42
- // dimensions 结构校验:每个维度必须是 { verdict, evidence } 对象
43
- if (record.dimensions) {
44
- for (const dim of REQUIRED_DIMENSIONS) {
45
- const v = record.dimensions[dim];
46
- if (v === undefined) {
47
- errors.push(`dimensions.${dim} 缺失(四个维度必须全覆盖)`);
53
+ if (!record.timestamp) errors.push('缺少 timestamp');
54
+ if (!record.dimensions) errors.push('缺少 dimensions(适用验证维度结果)');
55
+ const intent = record.intent_id ? getIntent(versionDir, record.intent_id) : null;
56
+ if (intent && !['in_progress', 'needs_review'].includes(intent.status)) {
57
+ errors.push(`Intent ${record.intent_id} 当前状态为 ${intent.status};只能为 in_progress 或 needs_review 的 Intent 写入验证记录`);
58
+ }
59
+ const requiredDimensions = getRequiredDimensions(intent);
60
+ // dimensions 结构校验:每个维度必须是 { verdict, evidence } 对象
61
+ if (record.dimensions) {
62
+ for (const dim of requiredDimensions) {
63
+ const v = record.dimensions[dim];
64
+ if (v === undefined) {
65
+ errors.push(`dimensions.${dim} 缺失(当前 Intent 的适用维度必须全覆盖)`);
48
66
  } else if (typeof v === 'string') {
49
67
  errors.push(`dimensions.${dim} 是旧格式(枚举值),必须改成 { verdict, evidence } 对象`);
50
68
  } else if (typeof v !== 'object' || v === null) {
51
69
  errors.push(`dimensions.${dim} 必须是 { verdict, evidence } 对象`);
52
70
  } else {
53
- if (!VALID_VERDICTS.includes(v.verdict)) {
54
- errors.push(`dimensions.${dim}.verdict 非法: "${v.verdict}" (合法: ${VALID_VERDICTS.join('|')})`);
55
- }
71
+ if (!VALID_VERDICTS.includes(v.verdict)) {
72
+ errors.push(`dimensions.${dim}.verdict 非法: "${v.verdict}" (合法: ${VALID_VERDICTS.join('|')})`);
73
+ }
74
+ if (record.verdict === 'passed' && v.verdict !== 'passed') {
75
+ errors.push(`整体 verdict 为 passed 时,dimensions.${dim}.verdict 也必须是 passed`);
76
+ }
56
77
  if (!v.evidence || typeof v.evidence !== 'string' || v.evidence.trim() === '') {
57
78
  errors.push(`dimensions.${dim}.evidence 缺失——必须给出具体证据,不能只写"合规"`);
58
79
  } else {
@@ -66,12 +87,38 @@ export function writeVerification(verificationsDir, record) {
66
87
  errors.push(`dimensions.${dim}.evidence "${ev}" 是通用评价而非具体证据——必须写"对照了什么 + 在代码哪里看到/没看到"`);
67
88
  }
68
89
  }
69
- }
70
- }
71
- }
72
- if (errors.length > 0) {
73
- throw new Error(`验证记录校验失败:\n - ${errors.join('\n - ')}`);
74
- }
90
+ }
91
+ }
92
+ }
93
+ const qualityProofRef = record.dimensions?.quality_achievement?.quality_proof_ref;
94
+ if (intent?.quality_contract && record.verdict === 'passed' && !qualityProofRef) {
95
+ errors.push('声明 quality_contract 的 Intent 通过时必须提供 dimensions.quality_achievement.quality_proof_ref');
96
+ }
97
+ if (qualityProofRef !== undefined
98
+ && (typeof qualityProofRef !== 'string' || qualityProofRef.trim() === '')) {
99
+ errors.push('dimensions.quality_achievement.quality_proof_ref 必须是非空字符串');
100
+ } else if (qualityProofRef !== undefined) {
101
+ try {
102
+ resolveQualityProofReference(versionDir, qualityProofRef);
103
+ } catch (error) {
104
+ errors.push(error.message);
105
+ }
106
+ }
107
+ if (intent?.quality_strategy === 'atelier' && record.verdict === 'passed') {
108
+ try {
109
+ atelierEvidence = validateAtelierRecord(versionDir, record.intent_id);
110
+ if (!['selected', 'baseline_retained'].includes(atelierEvidence.status)) {
111
+ errors.push(`quality_strategy=atelier 通过前,Atelier Record 必须是 selected 或 baseline_retained(当前: ${atelierEvidence.status})`);
112
+ }
113
+ } catch (error) {
114
+ errors.push(`quality_strategy=atelier 通过前必须有当前且合法的 Atelier Record: ${error.message}`);
115
+ }
116
+ }
117
+ if (errors.length > 0) {
118
+ throw new Error(`验证记录校验失败:\n - ${errors.join('\n - ')}`);
119
+ }
120
+
121
+ const intentRevision = getEffectiveIntentRevision(intent);
75
122
 
76
123
  const filePath = join(verificationsDir, `${record.intent_id}.json`);
77
124
 
@@ -102,14 +149,21 @@ export function writeVerification(verificationsDir, record) {
102
149
  }
103
150
 
104
151
  // 追加新记录
105
- data.records.push({
106
- round,
152
+ data.records.push({
153
+ round,
154
+ intent_revision: intentRevision,
155
+ verification_epoch: getEffectiveVerificationEpoch(intent),
107
156
  verdict: record.verdict,
108
157
  timestamp: record.timestamp,
109
158
  summary: record.summary,
110
- dimensions: record.dimensions,
111
- reproduction_command: record.reproduction_command,
112
- deviation_detail: record.deviation_detail,
159
+ dimensions: record.dimensions,
160
+ atelier: atelierEvidence ? {
161
+ record_ref: `09_ATELIER/${record.intent_id}.json`,
162
+ stance_revision: atelierEvidence.stance_revision,
163
+ status: atelierEvidence.status,
164
+ } : undefined,
165
+ reproduction_command: record.reproduction_command,
166
+ deviation_detail: record.deviation_detail,
113
167
  reset_suggested: record.reset_suggested,
114
168
  });
115
169
 
@@ -126,59 +180,145 @@ export function writeVerification(verificationsDir, record) {
126
180
  * 读取某 Intent 的验证历史。
127
181
  * @returns {{ intent_id: string, records: array } | null}
128
182
  */
129
- export function getVerificationHistory(verificationsDir, intentId) {
183
+ export function getVerificationHistory(verificationsDir, intentId) {
130
184
  const filePath = join(verificationsDir, `${intentId}.json`);
131
185
  if (!existsSync(filePath)) {
132
186
  return null;
133
187
  }
134
- return readJsonFile(filePath, '验证记录');
135
- }
188
+ return readJsonFile(filePath, '验证记录');
189
+ }
190
+
191
+ /** Read each owning version's local records along the explicit predecessor graph. */
192
+ export function getAcrossVersionVerificationHistory(currentVersionDir, inputRef) {
193
+ const root = resolveIntentRef(currentVersionDir, inputRef);
194
+ const histories = [];
195
+ const visited = new Set();
196
+ const active = new Set();
197
+
198
+ function walk(resolved) {
199
+ if (active.has(resolved.ref)) throw new Error(`Intent lineage 存在循环: ${[...active, resolved.ref].join(' -> ')}`);
200
+ if (visited.has(resolved.ref)) return;
201
+ active.add(resolved.ref);
202
+ const intent = getIntent(resolved.versionDir, resolved.intentId);
203
+ const local = getVerificationHistory(join(resolved.versionDir, 'verifications'), resolved.intentId);
204
+ histories.push({
205
+ ref: resolved.ref,
206
+ source_version: resolved.version,
207
+ source_intent: resolved.intentId,
208
+ source_intent_id: resolved.intentId,
209
+ records: (local?.records || []).map((record) => ({
210
+ ...record,
211
+ source_version: resolved.version,
212
+ source_intent: resolved.intentId,
213
+ source_intent_id: resolved.intentId,
214
+ })),
215
+ });
216
+ for (const predecessor of intent.lineage?.predecessors || []) {
217
+ walk(resolveIntentRef(currentVersionDir, formatIntentRef(predecessor.version, predecessor.intent_id)));
218
+ }
219
+ active.delete(resolved.ref);
220
+ visited.add(resolved.ref);
221
+ }
222
+
223
+ walk(root);
224
+ return { intent_ref: root.ref, across_versions: true, histories };
225
+ }
136
226
 
137
227
  /**
138
228
  * 快捷创建验证记录——Agent 不用手动构造完整 JSON。
139
- * 内部用 summary 填充四个维度的 evidence,生成标准记录格式。
140
- * @param {string} verificationsDirverifications/ 目录路径
229
+ * 内部用 summary 填充适用维度的 evidence,生成标准记录格式。
230
+ * @param {string} versionDir当前 .loom/v{N}/ 目录
231
+ * @param {string} verificationsDir — verifications/ 目录路径
141
232
  * @param {string} intentId — 如 "INT-001"
142
233
  * @param {string} verdict — 'passed' | 'deviated' | 'blocked'
143
- * @param {string} summary — 验证摘要(也会作为四个维度的 evidence)
144
- * @param {object} [extras]
145
- * @param {string} [extras.reproduction_command] — 复现命令
234
+ * @param {string} summary — 验证摘要(也会作为所有适用维度的 evidence)
235
+ * @param {object} [extras]
236
+ * @param {string} [extras.reproduction_command] — 复现命令
237
+ * @param {string} [extras.quality_proof_ref] — Quality Proof 证据引用
238
+ * @param {string} [extras.preservation_evidence] — 对既有状态守恒的独立证据
146
239
  * @param {string} [extras.deviation_detail] — 偏离说明(deviated 时)
147
240
  * @returns {{ filePath: string, round: number, deviated_count: number, should_escalate: boolean }}
148
241
  */
149
- export function createQuickVerification(verificationsDir, intentId, verdict, summary, extras = {}) {
150
- const timestamp = new Date().toISOString();
151
- // summary 填充四个维度的 evidence——快捷命令不要求 Agent 逐维度写
152
- const dimensions = {};
153
- for (const dim of REQUIRED_DIMENSIONS) {
154
- dimensions[dim] = { verdict, evidence: summary };
155
- }
156
- return writeVerification(verificationsDir, {
242
+ export function createQuickVerification(versionDir, verificationsDir, intentId, verdict, summary, extras = {}) {
243
+ const timestamp = new Date().toISOString();
244
+ const intent = getIntent(versionDir, intentId);
245
+ // summary 填充适用维度的 evidence——快捷命令不要求 Agent 逐维度写
246
+ const dimensions = {};
247
+ for (const dim of getRequiredDimensions(intent)) {
248
+ dimensions[dim] = {
249
+ verdict,
250
+ evidence: dim === 'preservation_achievement'
251
+ ? (extras.preservation_evidence || summary)
252
+ : summary,
253
+ };
254
+ }
255
+ if (extras.quality_proof_ref && dimensions.quality_achievement) {
256
+ dimensions.quality_achievement.quality_proof_ref = extras.quality_proof_ref;
257
+ }
258
+ return writeVerification(versionDir, verificationsDir, {
157
259
  intent_id: intentId,
158
260
  verdict,
159
261
  timestamp,
160
262
  summary,
161
- dimensions,
162
- reproduction_command: extras.reproduction_command || null,
163
- deviation_detail: extras.deviation_detail || null,
164
- });
165
- }
263
+ dimensions,
264
+ reproduction_command: extras.reproduction_command || null,
265
+ deviation_detail: extras.deviation_detail || null,
266
+ });
267
+ }
166
268
 
167
269
  /**
168
270
  * 返回所有待验证的 Intent(有实现产物但还没验证记录的)。
169
271
  * 需要传入 Intent Map 来判断哪些 Intent 是 in_progress。
170
272
  */
171
- export function getPendingVerifications(versionDir, verificationsDir) {
273
+ export function getPendingVerifications(versionDir, verificationsDir) {
172
274
  const intentMap = readJsonFile(join(versionDir, '04_INTENT_MAP.json'), 'Intent Map');
173
275
  const pending = [];
174
276
  for (const [id, intent] of Object.entries(intentMap.intents)) {
175
- if (intent.status === 'in_progress') {
176
- const hasRecord = existsSync(join(verificationsDir, `${id}.json`));
177
- if (!hasRecord) pending.push(id);
178
- }
277
+ if (intent.status === 'in_progress' || intent.status === 'needs_review') {
278
+ const history = getVerificationHistory(verificationsDir, id);
279
+ if (!hasCurrentPassedVerification(intent, history)) pending.push(id);
280
+ }
179
281
  }
180
282
  return pending;
181
- }
283
+ }
284
+
285
+ /** Missing Intent revisions are revision 1 without mutating the map. */
286
+ export function getEffectiveIntentRevision(intent) {
287
+ return intent.revision ?? 1;
288
+ }
289
+
290
+ /**
291
+ * Legacy records count as revision 1 only while the Intent itself is legacy.
292
+ * Once revision is explicit, an untagged record cannot prove freshness.
293
+ */
294
+ export function getVerificationIntentRevision(intent, record) {
295
+ if (Number.isInteger(record?.intent_revision) && record.intent_revision >= 1) {
296
+ return record.intent_revision;
297
+ }
298
+ return hasLegacyIntentRevision(intent) ? 1 : null;
299
+ }
300
+
301
+ export function isVerificationCurrent(intent, record) {
302
+ return getVerificationIntentRevision(intent, record) === getEffectiveIntentRevision(intent)
303
+ && getVerificationEpoch(intent, record) === getEffectiveVerificationEpoch(intent);
304
+ }
305
+
306
+ export function getVerificationEpoch(intent, record) {
307
+ if (Number.isInteger(record?.verification_epoch) && record.verification_epoch >= 1) {
308
+ return record.verification_epoch;
309
+ }
310
+ return intent?.verification_epoch === undefined ? 1 : null;
311
+ }
312
+
313
+ export function getLatestPassedVerification(history) {
314
+ if (!Array.isArray(history?.records)) return null;
315
+ return [...history.records].reverse().find((record) => record.verdict === 'passed') ?? null;
316
+ }
317
+
318
+ export function hasCurrentPassedVerification(intent, history) {
319
+ const latest = history?.records?.[history.records.length - 1];
320
+ return latest?.verdict === 'passed' && isVerificationCurrent(intent, latest);
321
+ }
182
322
 
183
323
  /**
184
324
  * 列出所有验证记录文件。
@@ -48,14 +48,15 @@ export function readCurrentPointer(loomRoot) {
48
48
  * @param {string} projectDir — 项目根目录
49
49
  * @returns {{ version: string, created: string[], skipped: string[] }}
50
50
  */
51
- export function newVersion(projectDir) {
52
- const loomRoot = join(projectDir, '.loom');
53
- const { versions } = listVersions(loomRoot);
51
+ export function newVersion(projectDir) {
52
+ const loomRoot = join(projectDir, '.loom');
53
+ const { versions } = listVersions(loomRoot);
54
+ const parentVersion = readCurrentPointer(loomRoot);
54
55
  const nextNum = versions.length === 0
55
56
  ? 1
56
57
  : parseInt(versions[versions.length - 1].slice(1)) + 1;
57
58
  const nextV = `v${nextNum}`;
58
- const result = createVersionStructure(projectDir, nextV);
59
+ const result = createVersionStructure(projectDir, nextV, parentVersion);
59
60
  // 自动切换为当前版本
60
61
  writeFileSync(join(loomRoot, 'current'), nextV, 'utf-8');
61
62
  result.created.push('.loom/current');
@@ -0,0 +1,45 @@
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。