@haaaiawd/loom 1.3.1 → 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 -86
- package/CONTRIBUTING.md +37 -0
- package/EVIL_EVAL.md +112 -0
- package/README.md +193 -445
- package/README.zh-CN.md +174 -0
- package/SECURITY.md +11 -0
- package/cli/bin/loom.js +171 -998
- 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/atlas.md +0 -48
- package/cli/help/capability.md +0 -118
- package/cli/help/concepts.md +0 -105
- package/cli/help/doctor.md +0 -80
- package/cli/help/expertise.md +0 -52
- package/cli/help/loop.md +0 -134
- package/cli/help/patch.md +0 -33
- package/cli/help/proposals.md +0 -21
- package/cli/help/version.md +0 -136
- package/cli/help/workflow.md +0 -116
- package/cli/src/activate.js +0 -505
- package/cli/src/asset-library.js +0 -384
- package/cli/src/atelier.js +0 -331
- package/cli/src/atlas.js +0 -282
- package/cli/src/auto.js +0 -116
- package/cli/src/capability-graph.js +0 -724
- package/cli/src/capability-proposals.js +0 -225
- package/cli/src/diagnostics.js +0 -859
- package/cli/src/expertise-pack.js +0 -336
- package/cli/src/guide.js +0 -548
- 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/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 -92
- package/roles/forge.md +0 -110
- package/roles/impact-reviewer.md +0 -37
- 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/ATLAS_TEMPLATE.html +0 -104
- package/templates/CAPABILITY_BRIEF_TEMPLATE.md +0 -36
- package/templates/CAPABILITY_GRAPH_EXAMPLE.json +0 -188
- package/templates/CAPABILITY_GRAPH_TEMPLATE.json +0 -78
- 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,336 +0,0 @@
|
|
|
1
|
-
// expertise-pack.js — provenance-backed external capability acquisition for one Intent.
|
|
2
|
-
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { basename, join } from 'node:path';
|
|
5
|
-
import { createHash } from 'node:crypto';
|
|
6
|
-
import { getIntent } from './intent-map.js';
|
|
7
|
-
import { compileCapabilityInputs } from './capability-graph.js';
|
|
8
|
-
import { getLoomRoot } from './shared/paths.js';
|
|
9
|
-
import { readJsonFile } from './shared/md-utils.js';
|
|
10
|
-
|
|
11
|
-
const PACKS_DIR = '10_EXPERTISE_PACKS';
|
|
12
|
-
const VALID_STATUS = ['draft', 'ready', 'blocked'];
|
|
13
|
-
const QUERY_CHANNELS = ['skill_registry', 'web', 'official_docs', 'research', 'tool', 'asset'];
|
|
14
|
-
const SOURCE_KINDS = ['skill', 'web', 'official_docs', 'research', 'tool', 'human'];
|
|
15
|
-
const SOURCE_AUTHORITIES = ['official', 'primary', 'expert', 'community', 'secondary'];
|
|
16
|
-
const EXTERNAL_KNOWLEDGE_KINDS = new Set(['skill', 'web', 'official_docs', 'research']);
|
|
17
|
-
|
|
18
|
-
export function getExpertisePacksDir(versionDir) {
|
|
19
|
-
return join(versionDir, PACKS_DIR);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function getExpertisePackPath(versionDir, intentId) {
|
|
23
|
-
return join(getExpertisePacksDir(versionDir), `${intentId}.json`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function pushText(value, label, errors) {
|
|
27
|
-
if (typeof value !== 'string' || value.trim() === '') errors.push(`${label} 必须是非空字符串`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function pushTextArray(value, label, errors, { min = 1 } = {}) {
|
|
31
|
-
if (!Array.isArray(value)) {
|
|
32
|
-
errors.push(`${label} 必须是字符串数组`);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
if (value.length < min) errors.push(`${label} 至少需要 ${min} 项`);
|
|
36
|
-
value.forEach((item, index) => pushText(item, `${label}[${index}]`, errors));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function isHttpsLocator(value) {
|
|
40
|
-
try {
|
|
41
|
-
return new URL(value).protocol === 'https:';
|
|
42
|
-
} catch {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function getExpertiseRequirement(versionDir, intentId) {
|
|
48
|
-
const intent = getIntent(versionDir, intentId);
|
|
49
|
-
const compiled = compileCapabilityInputs(versionDir, intentId);
|
|
50
|
-
const acquisition = compiled.acquisition || {
|
|
51
|
-
required: false,
|
|
52
|
-
required_node_ids: [],
|
|
53
|
-
nodes: [],
|
|
54
|
-
};
|
|
55
|
-
return {
|
|
56
|
-
intent,
|
|
57
|
-
compiled,
|
|
58
|
-
required: acquisition.required === true,
|
|
59
|
-
required_node_ids: acquisition.required_node_ids || [],
|
|
60
|
-
acquisition_nodes: acquisition.nodes || [],
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function validateSearchPlan(plan, strict, errors) {
|
|
65
|
-
if (!plan || typeof plan !== 'object' || Array.isArray(plan)) {
|
|
66
|
-
errors.push('search_plan 必须是对象');
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
if (!strict) return;
|
|
70
|
-
pushText(plan.decision_question, 'search_plan.decision_question', errors);
|
|
71
|
-
pushTextArray(plan.project_signals, 'search_plan.project_signals', errors);
|
|
72
|
-
pushTextArray(plan.constraints, 'search_plan.constraints', errors);
|
|
73
|
-
pushText(plan.stop_condition, 'search_plan.stop_condition', errors);
|
|
74
|
-
if (!Array.isArray(plan.derived_queries) || plan.derived_queries.length === 0) {
|
|
75
|
-
errors.push('search_plan.derived_queries 至少需要一条运行时派生查询');
|
|
76
|
-
} else {
|
|
77
|
-
plan.derived_queries.forEach((query, index) => {
|
|
78
|
-
const prefix = `search_plan.derived_queries[${index}]`;
|
|
79
|
-
if (!query || typeof query !== 'object' || Array.isArray(query)) {
|
|
80
|
-
errors.push(`${prefix} 必须是对象`);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if (!QUERY_CHANNELS.includes(query.channel)) {
|
|
84
|
-
errors.push(`${prefix}.channel 必须是 ${QUERY_CHANNELS.join('|')}`);
|
|
85
|
-
}
|
|
86
|
-
pushText(query.query, `${prefix}.query`, errors);
|
|
87
|
-
pushText(query.rationale, `${prefix}.rationale`, errors);
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function validateSources(sources, strict, errors) {
|
|
93
|
-
const ids = new Set();
|
|
94
|
-
const valid = new Map();
|
|
95
|
-
if (!Array.isArray(sources)) {
|
|
96
|
-
errors.push('sources 必须是数组');
|
|
97
|
-
return { ids, valid, externalCount: 0 };
|
|
98
|
-
}
|
|
99
|
-
if (strict && sources.length === 0) errors.push('ready Expertise Pack 必须包含外部来源');
|
|
100
|
-
let externalCount = 0;
|
|
101
|
-
sources.forEach((source, index) => {
|
|
102
|
-
const prefix = `sources[${index}]`;
|
|
103
|
-
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
104
|
-
errors.push(`${prefix} 必须是对象`);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
pushText(source.id, `${prefix}.id`, errors);
|
|
108
|
-
if (typeof source.id === 'string') {
|
|
109
|
-
if (ids.has(source.id)) errors.push(`${prefix}.id 重复: ${source.id}`);
|
|
110
|
-
ids.add(source.id);
|
|
111
|
-
}
|
|
112
|
-
if (!SOURCE_KINDS.includes(source.kind)) {
|
|
113
|
-
errors.push(`${prefix}.kind 必须是 ${SOURCE_KINDS.join('|')}`);
|
|
114
|
-
}
|
|
115
|
-
if (!SOURCE_AUTHORITIES.includes(source.authority)) {
|
|
116
|
-
errors.push(`${prefix}.authority 必须是 ${SOURCE_AUTHORITIES.join('|')}`);
|
|
117
|
-
}
|
|
118
|
-
pushText(source.title, `${prefix}.title`, errors);
|
|
119
|
-
pushText(source.locator, `${prefix}.locator`, errors);
|
|
120
|
-
if (source.kind !== 'human' && typeof source.locator === 'string' && !isHttpsLocator(source.locator)) {
|
|
121
|
-
errors.push(`${prefix}.locator 必须是可追溯的 https URL`);
|
|
122
|
-
}
|
|
123
|
-
pushText(source.retrieved_at, `${prefix}.retrieved_at`, errors);
|
|
124
|
-
if (typeof source.retrieved_at === 'string' && Number.isNaN(Date.parse(source.retrieved_at))) {
|
|
125
|
-
errors.push(`${prefix}.retrieved_at 必须是合法日期时间`);
|
|
126
|
-
}
|
|
127
|
-
pushText(source.why_selected, `${prefix}.why_selected`, errors);
|
|
128
|
-
pushText(source.retrieval_evidence, `${prefix}.retrieval_evidence`, errors);
|
|
129
|
-
if (EXTERNAL_KNOWLEDGE_KINDS.has(source.kind)) externalCount += 1;
|
|
130
|
-
if (typeof source.id === 'string' && source.id.trim()) valid.set(source.id, source);
|
|
131
|
-
});
|
|
132
|
-
if (strict && externalCount === 0) {
|
|
133
|
-
errors.push('ready Expertise Pack 至少需要一个 skill|web|official_docs|research 外部知识来源');
|
|
134
|
-
}
|
|
135
|
-
return { ids, valid, externalCount };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function validateCapsules(capsules, requiredNodeIds, sourceMap, strict, errors) {
|
|
139
|
-
const covered = new Set();
|
|
140
|
-
if (!Array.isArray(capsules)) {
|
|
141
|
-
errors.push('capsules 必须是数组');
|
|
142
|
-
return covered;
|
|
143
|
-
}
|
|
144
|
-
if (strict && capsules.length === 0) errors.push('ready Expertise Pack 必须包含 Capability Capsule');
|
|
145
|
-
capsules.forEach((capsule, index) => {
|
|
146
|
-
const prefix = `capsules[${index}]`;
|
|
147
|
-
if (!capsule || typeof capsule !== 'object' || Array.isArray(capsule)) {
|
|
148
|
-
errors.push(`${prefix} 必须是对象`);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
pushText(capsule.capability_ref, `${prefix}.capability_ref`, errors);
|
|
152
|
-
if (typeof capsule.capability_ref === 'string') covered.add(capsule.capability_ref);
|
|
153
|
-
if (strict && requiredNodeIds.length > 0 && !requiredNodeIds.includes(capsule.capability_ref)) {
|
|
154
|
-
errors.push(`${prefix}.capability_ref 未引用当前 Intent 的外部获取能力节点: ${capsule.capability_ref}`);
|
|
155
|
-
}
|
|
156
|
-
if (!strict) return;
|
|
157
|
-
pushText(capsule.professional_problem, `${prefix}.professional_problem`, errors);
|
|
158
|
-
pushText(capsule.when_to_use, `${prefix}.when_to_use`, errors);
|
|
159
|
-
pushTextArray(capsule.rules, `${prefix}.rules`, errors);
|
|
160
|
-
pushTextArray(capsule.workflow, `${prefix}.workflow`, errors);
|
|
161
|
-
pushTextArray(capsule.decision_gates, `${prefix}.decision_gates`, errors);
|
|
162
|
-
pushTextArray(capsule.failure_modes, `${prefix}.failure_modes`, errors);
|
|
163
|
-
pushTextArray(capsule.verification_signals, `${prefix}.verification_signals`, errors);
|
|
164
|
-
if (!Array.isArray(capsule.source_refs) || capsule.source_refs.length === 0) {
|
|
165
|
-
errors.push(`${prefix}.source_refs 至少需要一个来源`);
|
|
166
|
-
} else {
|
|
167
|
-
let externalRefCount = 0;
|
|
168
|
-
capsule.source_refs.forEach((ref, refIndex) => {
|
|
169
|
-
pushText(ref, `${prefix}.source_refs[${refIndex}]`, errors);
|
|
170
|
-
if (typeof ref === 'string' && !sourceMap.has(ref)) {
|
|
171
|
-
errors.push(`${prefix}.source_refs[${refIndex}] 引用不存在的来源: ${ref}`);
|
|
172
|
-
} else if (EXTERNAL_KNOWLEDGE_KINDS.has(sourceMap.get(ref)?.kind)) {
|
|
173
|
-
externalRefCount += 1;
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
if (externalRefCount === 0) {
|
|
177
|
-
errors.push(`${prefix} 必须直接引用至少一个 skill|web|official_docs|research 外部知识来源`);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
if (strict) {
|
|
182
|
-
for (const nodeId of requiredNodeIds) {
|
|
183
|
-
if (!covered.has(nodeId)) errors.push(`缺少外部必需能力 ${nodeId} 的 Capability Capsule`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return covered;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export function validateExpertisePack(versionDir, intentId, pack = null, { requireReady = true } = {}) {
|
|
190
|
-
const requirement = getExpertiseRequirement(versionDir, intentId);
|
|
191
|
-
const path = getExpertisePackPath(versionDir, intentId);
|
|
192
|
-
const data = pack ?? readJsonFile(path, 'Expertise Pack');
|
|
193
|
-
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
194
|
-
throw new Error('Expertise Pack 校验失败:\n - 根节点必须是对象');
|
|
195
|
-
}
|
|
196
|
-
const errors = [];
|
|
197
|
-
if (!data._meta || typeof data._meta !== 'object' || Array.isArray(data._meta)) {
|
|
198
|
-
errors.push('_meta 必须是对象');
|
|
199
|
-
} else {
|
|
200
|
-
if (data._meta._version !== '1.0') errors.push('_meta._version 必须是 1.0');
|
|
201
|
-
if (data._meta._loom_version !== basename(versionDir)) {
|
|
202
|
-
errors.push(`_meta._loom_version 必须是 ${basename(versionDir)}`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
if (data.intent_id !== intentId) errors.push(`intent_id 必须是 ${intentId}`);
|
|
206
|
-
if (data.intent_revision !== (requirement.intent.revision ?? 1)) {
|
|
207
|
-
errors.push(`intent_revision=${data.intent_revision} 已过期;当前为 ${requirement.intent.revision ?? 1}`);
|
|
208
|
-
}
|
|
209
|
-
if (!Array.isArray(data.required_capability_refs)) {
|
|
210
|
-
errors.push('required_capability_refs 必须是数组');
|
|
211
|
-
} else {
|
|
212
|
-
data.required_capability_refs.forEach((ref, index) => {
|
|
213
|
-
pushText(ref, `required_capability_refs[${index}]`, errors);
|
|
214
|
-
});
|
|
215
|
-
const declared = [...new Set(data.required_capability_refs)].sort();
|
|
216
|
-
const expected = [...new Set(requirement.required_node_ids)].sort();
|
|
217
|
-
if (JSON.stringify(declared) !== JSON.stringify(expected)) {
|
|
218
|
-
errors.push(`required_capability_refs 必须精确匹配当前强门节点: ${expected.join(', ') || '无'}`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
if (!VALID_STATUS.includes(data.status)) errors.push(`status 必须是 ${VALID_STATUS.join('|')}`);
|
|
222
|
-
const strict = data.status === 'ready';
|
|
223
|
-
validateSearchPlan(data.search_plan, data.status === 'ready' || data.status === 'blocked', errors);
|
|
224
|
-
const sourceResult = validateSources(data.sources, strict, errors);
|
|
225
|
-
validateCapsules(data.capsules, requirement.required_node_ids, sourceResult.valid, strict, errors);
|
|
226
|
-
if (data.status === 'blocked') {
|
|
227
|
-
if (!data.blocker || typeof data.blocker !== 'object' || Array.isArray(data.blocker)) {
|
|
228
|
-
errors.push('blocked 状态必须提供 blocker 对象');
|
|
229
|
-
} else {
|
|
230
|
-
pushText(data.blocker.reason, 'blocker.reason', errors);
|
|
231
|
-
pushText(data.blocker.recovery_condition, 'blocker.recovery_condition', errors);
|
|
232
|
-
}
|
|
233
|
-
} else if (data.blocker !== null && data.blocker !== undefined) {
|
|
234
|
-
errors.push('非 blocked 状态的 blocker 必须为 null 或省略');
|
|
235
|
-
}
|
|
236
|
-
if (requireReady && requirement.required && data.status !== 'ready') {
|
|
237
|
-
errors.push(`当前 Intent 需要外部能力获取,Expertise Pack 必须为 ready(当前: ${data.status})`);
|
|
238
|
-
}
|
|
239
|
-
if (errors.length) throw new Error(`Expertise Pack 校验失败:\n - ${errors.join('\n - ')}`);
|
|
240
|
-
return {
|
|
241
|
-
valid: true,
|
|
242
|
-
required: requirement.required,
|
|
243
|
-
intent_id: intentId,
|
|
244
|
-
intent_revision: data.intent_revision,
|
|
245
|
-
status: data.status,
|
|
246
|
-
required_node_ids: requirement.required_node_ids,
|
|
247
|
-
source_count: data.sources.length,
|
|
248
|
-
external_source_count: sourceResult.externalCount,
|
|
249
|
-
capsule_count: data.capsules.length,
|
|
250
|
-
pack_digest: createHash('sha256').update(JSON.stringify(data)).digest('hex'),
|
|
251
|
-
path,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
export function getExpertisePack(versionDir, intentId) {
|
|
256
|
-
const path = getExpertisePackPath(versionDir, intentId);
|
|
257
|
-
if (!existsSync(path)) throw new Error(`Expertise Pack 不存在: ${path}`);
|
|
258
|
-
const pack = readJsonFile(path, 'Expertise Pack');
|
|
259
|
-
validateExpertisePack(versionDir, intentId, pack, { requireReady: false });
|
|
260
|
-
return pack;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
export function initExpertisePack(versionDir, intentId) {
|
|
264
|
-
const requirement = getExpertiseRequirement(versionDir, intentId);
|
|
265
|
-
if (!requirement.required) {
|
|
266
|
-
throw new Error(`${intentId} 当前没有外部能力获取强门;只有显式 external_required 或未显式豁免的高影响 capability 才需要持久化 Expertise Pack`);
|
|
267
|
-
}
|
|
268
|
-
const path = getExpertisePackPath(versionDir, intentId);
|
|
269
|
-
if (existsSync(path)) throw new Error(`Expertise Pack 已存在,不会覆盖: ${path}`);
|
|
270
|
-
const templatePath = join(getLoomRoot(), 'templates', 'EXPERTISE_PACK_TEMPLATE.json');
|
|
271
|
-
const pack = JSON.parse(readFileSync(templatePath, 'utf-8'));
|
|
272
|
-
pack._meta._loom_version = basename(versionDir);
|
|
273
|
-
pack.intent_id = intentId;
|
|
274
|
-
pack.intent_revision = requirement.intent.revision ?? 1;
|
|
275
|
-
pack.required_capability_refs = requirement.required_node_ids;
|
|
276
|
-
mkdirSync(getExpertisePacksDir(versionDir), { recursive: true });
|
|
277
|
-
writeFileSync(path, `${JSON.stringify(pack, null, 2)}\n`, { encoding: 'utf-8', flag: 'wx' });
|
|
278
|
-
return pack;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
export function getExpertisePackState(versionDir, intentId) {
|
|
282
|
-
const requirement = getExpertiseRequirement(versionDir, intentId);
|
|
283
|
-
const path = getExpertisePackPath(versionDir, intentId);
|
|
284
|
-
if (!requirement.required) {
|
|
285
|
-
return { required: false, ready: true, path, required_node_ids: [] };
|
|
286
|
-
}
|
|
287
|
-
if (!existsSync(path)) {
|
|
288
|
-
return { required: true, ready: false, path, required_node_ids: requirement.required_node_ids, reason: 'missing' };
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
const validation = validateExpertisePack(versionDir, intentId, null, { requireReady: false });
|
|
292
|
-
const ready = validation.status === 'ready';
|
|
293
|
-
const pack = ready ? null : readJsonFile(path, 'Expertise Pack');
|
|
294
|
-
const reason = ready
|
|
295
|
-
? undefined
|
|
296
|
-
: validation.status === 'blocked'
|
|
297
|
-
? `blocked: ${pack.blocker.reason}`
|
|
298
|
-
: `status=${validation.status}`;
|
|
299
|
-
return { required: true, ready, path, required_node_ids: requirement.required_node_ids, validation, reason };
|
|
300
|
-
} catch (error) {
|
|
301
|
-
return { required: true, ready: false, path, required_node_ids: requirement.required_node_ids, reason: error.message };
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export function assertExpertiseReady(versionDir, intentId) {
|
|
306
|
-
const state = getExpertisePackState(versionDir, intentId);
|
|
307
|
-
if (!state.required) return null;
|
|
308
|
-
if (!state.ready) {
|
|
309
|
-
const action = state.reason === 'missing'
|
|
310
|
-
? `先运行 loom expertise init ${intentId},通过 find skill 与网络搜索获取外部信息,完成后运行 loom expertise validate ${intentId}`
|
|
311
|
-
: `修正 10_EXPERTISE_PACKS/${intentId}.json 后运行 loom expertise validate ${intentId}`;
|
|
312
|
-
throw new Error(`${intentId} 的外部能力获取强门尚未闭合: ${state.reason || 'Expertise Pack not ready'}\n${action}`);
|
|
313
|
-
}
|
|
314
|
-
return state.validation;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export function formatExpertisePackForPrompt(pack) {
|
|
318
|
-
const lines = [
|
|
319
|
-
`- decision_question: ${pack.search_plan.decision_question}`,
|
|
320
|
-
`- sources: ${pack.sources.map((source) => `${source.id} ${source.title} (${source.locator})`).join('; ')}`,
|
|
321
|
-
];
|
|
322
|
-
for (const capsule of pack.capsules) {
|
|
323
|
-
lines.push(
|
|
324
|
-
`\n### Capability Capsule: ${capsule.capability_ref}`,
|
|
325
|
-
`- professional_problem: ${capsule.professional_problem}`,
|
|
326
|
-
`- when_to_use: ${capsule.when_to_use}`,
|
|
327
|
-
`- rules:\n${capsule.rules.map((item) => ` - ${item}`).join('\n')}`,
|
|
328
|
-
`- workflow:\n${capsule.workflow.map((item) => ` - ${item}`).join('\n')}`,
|
|
329
|
-
`- decision_gates:\n${capsule.decision_gates.map((item) => ` - ${item}`).join('\n')}`,
|
|
330
|
-
`- failure_modes:\n${capsule.failure_modes.map((item) => ` - ${item}`).join('\n')}`,
|
|
331
|
-
`- verification_signals:\n${capsule.verification_signals.map((item) => ` - ${item}`).join('\n')}`,
|
|
332
|
-
`- source_refs: ${capsule.source_refs.join(', ')}`,
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
return lines.join('\n');
|
|
336
|
-
}
|