@tea-agent/loop-agent 0.44.0-next.10 → 0.44.0-next.11

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.
@@ -0,0 +1,8 @@
1
+ /** DAG hybrid 模板族统一入口。 */
2
+ export { buildAnalyzeInputsNode, buildBackendTestAnalysisContractGateNode, buildBackendTestEnvironmentScoutNode, buildBackendTestExecutionContractGateNode, buildGenerateBackendFunctionalCasesNode, buildEmitBackendCaseManifestNode, buildBackendTestCaseManifestGateNode, buildBackendTestTraceabilityGateNode, buildReviewBackendCasesNode, buildExecuteBackendPytestNode, buildClassifyBackendTestResultNode, buildTestRetrospectNode, buildBackendTestOutcomeGateNode, buildL5MetricsNode, applyBackendTestLayoutToText, buildBackendTestModuleManifestShellCommand, buildBackendTestAnalysisHybridDag, buildBackendTestGapFillDag, buildBackendTestHybridDag, applyBackendTestLayoutToDagSpec, BACKEND_TEST_CASE_MANIFEST_OUTPUT_INSTRUCTIONS, collectBackendTestShellEnvAllowlist, taskAllowsBackendTestReportWrite, BACKEND_TEST_DEFAULTS, BACKEND_TEST_SKILLS_BY_ROLE, applyBackendTestWorkspaceControl } from "./backend-test.js";
3
+ export { KNOWLEDGE_SYNC_DEFAULTS, KNOWLEDGE_SYNC_SKILLS_BY_ROLE, buildMultiPerspectiveReviewAggregateScript, buildMultiPerspectiveReviewNodes, KNOWLEDGE_SYNC_FEATURE_ID_RE, assertSafeKnowledgeSyncFeatureId, resolveKnowledgeSyncFeatureId, knowledgeSyncWriteSet, knowledgeSyncPendingWriteSet, knowledgeSyncPointerWriteSet, knowledgeSyncDraftRelPath, buildKnowledgeSyncCollectNode, buildKnowledgeSyncDraftNode, buildKnowledgeSyncValidateNode, KNOWLEDGE_SYNC_MULTI_REVIEW_PERSPECTIVES, buildKnowledgeSyncMultiReviewNodes, buildKnowledgeSyncApplyNode, buildKnowledgeSyncPointerNode, buildKnowledgeSyncHybridDag } from "./knowledge-sync.js";
4
+ export { KG_BOOTSTRAP_DEFAULTS, KG_BOOTSTRAP_SKILLS_BY_ROLE, buildKgBootstrapInlineNodeScript, buildKgBootstrapPreflightNode, buildKgBootstrapInventoryNode, buildKgBootstrapProposeNode, buildKgBootstrapValidateNode, KG_BOOTSTRAP_MULTI_REVIEW_PERSPECTIVES, buildKgBootstrapMultiReviewNodes, buildKgBootstrapPromoteNode, buildKgBootstrapMaterializeNode, buildKnowledgeGraphBootstrapHybridDag } from "./kg-bootstrap.js";
5
+ export { buildFrontendTestHybridDag, applyFrontendTestLayoutToDagSpec, allowedPathsCoverFrontendTestRoot } from "./frontend-test.js";
6
+ export { buildFrontendMockAssessNode, buildFrontendMockContractGateNode, buildFrontendMockVerifyNode, buildBlockedFrontendMockDag, resolveFrontendMockContextBlock, buildFrontendHybridDagFromTask, loadFrontendDagTemplate, frontendMockStrategyMustBeNotNeeded, frontendImplementationNodeId, resolveFrontendCapabilityContextBlock, resolveFrontendShapeCapsuleGenerationInput, deriveFrontendBehaviorPaths, FRONTEND_NO_STATIC_VERIFICATION_MARKER, chooseFrontendVerifyCommands, resolveFrontendOpenspecGateConfig, filterRelevantOpenspecCandidates, frontendComponentConformanceInstruction, buildFrontendWriterNodeDefaults, pruneFrontendTasksForSplitRequired, pruneFrontendTasksForRisk, pruneFrontendTasksForMicro, buildFrontendSpecCandidateSummaries, openspecCandidateMatchesSource, extractOpenspecSourceTokens } from "./frontend.js";
7
+ export { buildStandardHybridDagFromTask, deriveParallelScoutPaths, implementationNodeId, stampTargetTemplateTransientRetryProfile } from "./standard.js";
8
+ export { commonReadOnlyPaths, commonForbiddenPaths, applyDefaultReadOnlyRetryPolicy, stampGeneratedArtifactBindings, mapTaskComplexity, isFrontendTestPathPattern, replaceSourceSegmentWithTestSegment } from "./shared.js";
@@ -0,0 +1,449 @@
1
+ import { assertValidDagSpec } from "../../validate.js";
2
+ import { DEFAULT_DAG_EXECUTOR_MODELS, DEFAULT_DAG_OUTPUT_LANGUAGE, parseDagSpec } from "../../types.js";
3
+ import { HYBRID_DEFAULTS, STANDARD_GLOBAL_CONSTRAINTS, buildSourceContextBlock, buildVerifyEvidence, extractObjective, extractSuccessCriteria } from "../sources.js";
4
+ import { buildMultiPerspectiveReviewNodes } from "./knowledge-sync.js";
5
+ import { commonForbiddenPaths, stampGeneratedArtifactBindings, } from "./shared.js";
6
+ export const KG_BOOTSTRAP_DEFAULTS = {
7
+ ...HYBRID_DEFAULTS,
8
+ writePolicy: "read-only",
9
+ };
10
+ export const KG_BOOTSTRAP_SKILLS_BY_ROLE = {
11
+ planner: ["loop-agent"],
12
+ scout: ["codebase-scout"],
13
+ implementer: ["verification-before-completion"],
14
+ reviewer: ["requesting-code-review"],
15
+ verifier: ["verification-before-completion", "systematic-debugging"],
16
+ closeout: ["loop-agent", "verification-before-completion"],
17
+ };
18
+ export function buildKgBootstrapInlineNodeScript(lines) {
19
+ return ["node", "-e", JSON.stringify(lines.join(""))].join(" ");
20
+ }
21
+ export function buildKgBootstrapPreflightNode(sources) {
22
+ const script = buildKgBootstrapInlineNodeScript([
23
+ "const fs=require('fs');",
24
+ "const path=require('path');",
25
+ "const root=process.cwd();",
26
+ "const scope=path.join(root,'knowledge','bootstrap','scope.yaml');",
27
+ "const status=path.join(root,'knowledge','bootstrap','status.yaml');",
28
+ "const staging=path.join(root,'knowledge','bootstrap','staging');",
29
+ "function ensureDir(p){fs.mkdirSync(p,{recursive:true});}",
30
+ "ensureDir(path.join(root,'knowledge','bootstrap','runs'));",
31
+ "ensureDir(staging);",
32
+ "ensureDir(path.join(root,'knowledge','graph'));",
33
+ "if(!fs.existsSync(scope)){",
34
+ " console.error('kg-bootstrap preflight: missing knowledge/bootstrap/scope.yaml — complete B0/B1 skeleton first');",
35
+ " process.exit(1);",
36
+ "}",
37
+ "if(!fs.existsSync(status)){",
38
+ " console.error('kg-bootstrap preflight: missing knowledge/bootstrap/status.yaml');",
39
+ " process.exit(1);",
40
+ "}",
41
+ "console.log('kg-bootstrap preflight ok');",
42
+ ]);
43
+ return {
44
+ id: "kg-bootstrap-preflight-shell",
45
+ depends_on: [],
46
+ role: "verifier",
47
+ executor: "shell",
48
+ complexity: "LOW",
49
+ writePolicy: "read-only",
50
+ allowedPaths: ["knowledge/bootstrap/**", "knowledge/graph/**"],
51
+ forbiddenPaths: commonForbiddenPaths(sources),
52
+ outputContract: "Preflight: require knowledge/bootstrap/scope.yaml and status.yaml; ensure staging/runs/graph dirs exist.",
53
+ subtask_prompt: "Fail if bootstrap skeleton is missing. Do not invent business entities.",
54
+ shell: {
55
+ commands: [script],
56
+ verifyEvidence: buildVerifyEvidence({
57
+ phase: "intermediate",
58
+ quota: "full",
59
+ commandSource: "inline",
60
+ fallbackCommands: [script],
61
+ commandTimeoutMs: 60_000,
62
+ }),
63
+ cwd: ".",
64
+ timeoutMs: 60000,
65
+ },
66
+ };
67
+ }
68
+ export function buildKgBootstrapInventoryNode(sources) {
69
+ const script = buildKgBootstrapInlineNodeScript([
70
+ "const fs=require('fs');",
71
+ "const path=require('path');",
72
+ "const root=process.cwd();",
73
+ "const out=path.join(root,'knowledge','bootstrap','inventory.json');",
74
+ "function listDir(rel){const p=path.join(root,rel);if(!fs.existsSync(p))return[];return fs.readdirSync(p,{withFileTypes:true}).map(e=>({name:e.name,dir:e.isDirectory()}));}",
75
+ "const top=listDir('.').filter(e=>e.dir).map(e=>e.name).filter(n=>!['node_modules','.git','dist','.harness'].includes(n));",
76
+ "const features=listDir('features').filter(e=>e.dir&&/^F-/.test(e.name)).map(e=>e.name);",
77
+ "const services=[];",
78
+ "for (const base of ['services','apps','packages']) {",
79
+ " for (const e of listDir(base)) if(e.dir) services.push({candidateName:e.name,paths:[base+'/'+e.name+'/**'],evidence:[base+'/'+e.name]});",
80
+ "}",
81
+ "const manifests=['package.json','go.mod','pyproject.toml','pom.xml','Cargo.toml'].filter(f=>fs.existsSync(path.join(root,f)));",
82
+ "const inventory={schemaVersion:1,generatedAt:new Date().toISOString(),signals:{packageManifests:manifests,topLevelDirs:top,featureIds:features,likelyServices:services,docHits:[],openApiOrProto:[],existingKnowledge:{hasDomains:fs.existsSync(path.join(root,'knowledge','domains')),hasServices:fs.existsSync(path.join(root,'knowledge','services')),hasGraph:fs.existsSync(path.join(root,'knowledge','graph','edges.yaml'))}},limits:{maxFilesScanned:0,truncated:false}};",
83
+ "fs.mkdirSync(path.dirname(out),{recursive:true});",
84
+ "fs.writeFileSync(out,JSON.stringify(inventory,null,2)+'\\n');",
85
+ "console.log('wrote '+out+' features='+features.length+' serviceCandidates='+services.length);",
86
+ ]);
87
+ return {
88
+ id: "kg-bootstrap-inventory-shell",
89
+ depends_on: ["kg-bootstrap-preflight-shell"],
90
+ role: "verifier",
91
+ executor: "shell",
92
+ complexity: "LOW",
93
+ writePolicy: "exclusive",
94
+ writeSet: [
95
+ "knowledge/bootstrap/inventory.json",
96
+ "knowledge/bootstrap/status.yaml",
97
+ ],
98
+ allowedPaths: ["knowledge/bootstrap/**", "./**"],
99
+ forbiddenPaths: commonForbiddenPaths(sources),
100
+ outputContract: "Deterministic inventory.json under knowledge/bootstrap/ from directory and feature signals.",
101
+ subtask_prompt: "Scan repository structure into knowledge/bootstrap/inventory.json (B2).",
102
+ shell: {
103
+ commands: [script],
104
+ verifyEvidence: buildVerifyEvidence({
105
+ phase: "intermediate",
106
+ quota: "full",
107
+ commandSource: "inline",
108
+ fallbackCommands: [script],
109
+ commandTimeoutMs: 120_000,
110
+ }),
111
+ cwd: ".",
112
+ timeoutMs: 120000,
113
+ },
114
+ };
115
+ }
116
+ export function buildKgBootstrapProposeNode(sources) {
117
+ return {
118
+ id: "kg-bootstrap-propose-pi",
119
+ depends_on: ["kg-bootstrap-inventory-shell"],
120
+ role: "implementer",
121
+ executor: "pi",
122
+ toolProfile: "write",
123
+ complexity: "HIGH",
124
+ writePolicy: "exclusive",
125
+ writeSet: ["knowledge/bootstrap/staging/**", "knowledge/bootstrap/runs/**"],
126
+ allowedPaths: [
127
+ "knowledge/bootstrap/**",
128
+ "knowledge/**",
129
+ "features/**",
130
+ "docs/**",
131
+ "README.md",
132
+ "services/**",
133
+ "apps/**",
134
+ "packages/**",
135
+ "src/**",
136
+ ],
137
+ forbiddenPaths: [
138
+ ...commonForbiddenPaths(sources),
139
+ "knowledge/domains/**",
140
+ "knowledge/services/**",
141
+ "knowledge/modules/**",
142
+ "knowledge/graph/edges.yaml",
143
+ "knowledge/graph/entities-index.yaml",
144
+ ],
145
+ subtask_prompt: [
146
+ "B3: Generate AI proposals for the business knowledge graph ONLY under knowledge/bootstrap/staging/**.",
147
+ "Read knowledge/bootstrap/scope.yaml and knowledge/bootstrap/inventory.json first.",
148
+ "If scope.yaml has update_mode: incremental (or status.yaml update_mode: incremental), treat this as a narrow incremental update:",
149
+ "- Prefer seed_features / seed_services / seed_domains and include_paths only; do not re-propose the entire monorepo.",
150
+ "- Do not invent entities outside the incremental scope; note skipped inventory candidates in coverage-notes.md.",
151
+ "Create domain/service/module drafts with meta.yaml + overview.md (and interfaces/dependencies/pitfalls when evidence exists).",
152
+ "Write staging/edges.proposed.yaml and optional staging/features-links.proposed/<F-id>.yaml for seed features.",
153
+ "Write staging/coverage-notes.md explaining which inventory candidates were covered or skipped.",
154
+ "Every entity/edge MUST include evidence paths. confidence may be inferred|proposed only — NEVER asserted.",
155
+ "Do not invent production API details without file evidence; use unknown/TODO instead.",
156
+ "Do NOT write knowledge/domains|services formal trees or graph indexes — promotion is a later node.",
157
+ "Stay within writeSet.",
158
+ buildSourceContextBlock(sources),
159
+ ].join("\n\n"),
160
+ };
161
+ }
162
+ export function buildKgBootstrapValidateNode(sources) {
163
+ const script = buildKgBootstrapInlineNodeScript([
164
+ "const fs=require('fs');",
165
+ "const path=require('path');",
166
+ "const staging=path.join(process.cwd(),'knowledge','bootstrap','staging');",
167
+ "if(!fs.existsSync(staging)){console.error('missing staging');process.exit(1);}",
168
+ "function walk(dir,acc=[]){if(!fs.existsSync(dir))return acc;for(const ent of fs.readdirSync(dir,{withFileTypes:true})){const p=path.join(dir,ent.name);if(ent.isDirectory())walk(p,acc);else acc.push(p);}return acc;}",
169
+ "const files=walk(staging);",
170
+ "if(files.length===0){console.error('staging empty');process.exit(1);}",
171
+ "const ids=new Set(); let failed=false;",
172
+ "for(const f of files){",
173
+ " if(!f.endsWith('meta.yaml')&&!f.endsWith('edges.proposed.yaml')) continue;",
174
+ " const text=fs.readFileSync(f,'utf8');",
175
+ " if(/confidence:\\s*asserted/i.test(text)&&f.includes('staging')){console.error('agent must not self-assert: '+f);failed=true;}",
176
+ " const m=text.match(/^id:\\s*([A-Za-z0-9_-]+)/m);",
177
+ " if(m){ if(ids.has(m[1])){console.error('duplicate id '+m[1]);failed=true;} ids.add(m[1]); }",
178
+ "}",
179
+ "const edgesPath=path.join(staging,'edges.proposed.yaml');",
180
+ "if(!fs.existsSync(edgesPath)){console.error('missing staging/edges.proposed.yaml');failed=true;}",
181
+ "if(!fs.existsSync(path.join(staging,'coverage-notes.md'))){console.error('missing staging/coverage-notes.md');failed=true;}",
182
+ "if(failed) process.exit(1);",
183
+ "console.log('kg-bootstrap validate ok files='+files.length+' ids='+ids.size);",
184
+ ]);
185
+ return {
186
+ id: "kg-bootstrap-validate-shell",
187
+ depends_on: ["kg-bootstrap-propose-pi"],
188
+ role: "verifier",
189
+ executor: "shell",
190
+ complexity: "LOW",
191
+ writePolicy: "read-only",
192
+ allowedPaths: ["knowledge/bootstrap/staging/**"],
193
+ forbiddenPaths: commonForbiddenPaths(sources),
194
+ outputContract: "Validate staging proposals: non-empty, edges.proposed + coverage-notes present, no self-asserted confidence, unique ids.",
195
+ subtask_prompt: "B4 deterministic validation of knowledge/bootstrap/staging.",
196
+ shell: {
197
+ commands: [script],
198
+ verifyEvidence: buildVerifyEvidence({
199
+ phase: "final",
200
+ quota: "full",
201
+ commandSource: "inline",
202
+ fallbackCommands: [script],
203
+ finalFullRequired: true,
204
+ commandTimeoutMs: 120_000,
205
+ }),
206
+ cwd: ".",
207
+ timeoutMs: 120000,
208
+ },
209
+ };
210
+ }
211
+ export const KG_BOOTSTRAP_MULTI_REVIEW_PERSPECTIVES = [
212
+ {
213
+ id: "structure",
214
+ perspective: "architecture / structure",
215
+ focus: [
216
+ "domain/service/module partition vs inventory candidates",
217
+ "id uniqueness and naming conventions",
218
+ "edges that create impossible or circular dependencies",
219
+ ],
220
+ },
221
+ {
222
+ id: "evidence",
223
+ perspective: "evidence / anti-hallucination",
224
+ focus: [
225
+ "every entity/edge has concrete evidence paths",
226
+ "no confidence: asserted in staging",
227
+ "no invented APIs or production details without files",
228
+ ],
229
+ },
230
+ {
231
+ id: "safety",
232
+ perspective: "write-boundary / promote safety",
233
+ focus: [
234
+ "writes stayed in knowledge/bootstrap/staging/** (and runs/**)",
235
+ "no formal knowledge/domains|services trees or graph indexes written by AI",
236
+ "incremental scope respected when update_mode: incremental",
237
+ ],
238
+ },
239
+ ];
240
+ export function buildKgBootstrapMultiReviewNodes(sources) {
241
+ return buildMultiPerspectiveReviewNodes({
242
+ sources,
243
+ dependsOn: ["kg-bootstrap-validate-shell"],
244
+ nodePrefix: "kg-bootstrap-review-",
245
+ gateId: "kg-bootstrap-multi-review-gate-shell",
246
+ gateLabel: "kg-bootstrap multi-perspective review",
247
+ perspectives: KG_BOOTSTRAP_MULTI_REVIEW_PERSPECTIVES,
248
+ allowedPaths: [
249
+ "knowledge/bootstrap/**",
250
+ "knowledge/**",
251
+ "features/**",
252
+ "docs/**",
253
+ ],
254
+ sharedBrief: [
255
+ "Review staging knowledge-graph proposals before promote.",
256
+ "pass means ready for promote consideration — it does NOT mark entities asserted.",
257
+ "request-revision if critical entities lack evidence, ids collide, or formal trees were written outside staging.",
258
+ ],
259
+ });
260
+ }
261
+ export function buildKgBootstrapPromoteNode(sources) {
262
+ const script = buildKgBootstrapInlineNodeScript([
263
+ "const fs=require('fs');",
264
+ "const path=require('path');",
265
+ "const root=process.cwd();",
266
+ "const staging=path.join(root,'knowledge','bootstrap','staging');",
267
+ "function copyDir(src,dest){",
268
+ " if(!fs.existsSync(src)) return 0;",
269
+ " let n=0;",
270
+ " fs.mkdirSync(dest,{recursive:true});",
271
+ " for(const ent of fs.readdirSync(src,{withFileTypes:true})){",
272
+ " const s=path.join(src,ent.name), d=path.join(dest,ent.name);",
273
+ " if(ent.isDirectory()) n+=copyDir(s,d);",
274
+ " else { if(!fs.existsSync(d)){ fs.mkdirSync(path.dirname(d),{recursive:true}); fs.copyFileSync(s,d); n++; } }",
275
+ " }",
276
+ " return n;",
277
+ "}",
278
+ "let copied=0;",
279
+ "copied+=copyDir(path.join(staging,'domains'), path.join(root,'knowledge','domains'));",
280
+ "copied+=copyDir(path.join(staging,'services'), path.join(root,'knowledge','services'));",
281
+ "copied+=copyDir(path.join(staging,'modules'), path.join(root,'knowledge','modules'));",
282
+ "const edgesSrc=path.join(staging,'edges.proposed.yaml');",
283
+ "const edgesManual=path.join(root,'knowledge','graph','edges.manual.yaml');",
284
+ "if(fs.existsSync(edgesSrc)){ fs.mkdirSync(path.dirname(edgesManual),{recursive:true}); if(!fs.existsSync(edgesManual)) fs.copyFileSync(edgesSrc,edgesManual); }",
285
+ "const linksDir=path.join(staging,'features-links.proposed');",
286
+ "if(fs.existsSync(linksDir)){",
287
+ " for(const f of fs.readdirSync(linksDir)){",
288
+ " if(!f.endsWith('.yaml')&&!f.endsWith('.yml')) continue;",
289
+ " if(!/^F-[A-Za-z0-9][A-Za-z0-9._-]*\\.ya?ml$/.test(f)){ console.error('invalid feature link proposal filename: '+f); process.exit(1); }",
290
+ " const id=f.replace(/\\.ya?ml$/,'');",
291
+ " if(id.includes('..')){ console.error('invalid feature link proposal filename: '+f); process.exit(1); }",
292
+ " const dest=path.join(root,'features',id,'knowledge-links.yaml');",
293
+ " if(!fs.existsSync(path.join(root,'features',id))) continue;",
294
+ " if(!fs.existsSync(dest)){ fs.mkdirSync(path.dirname(dest),{recursive:true}); fs.copyFileSync(path.join(linksDir,f),dest); copied++; }",
295
+ " }",
296
+ "}",
297
+ "console.log('kg-bootstrap promote copied_new_files='+copied+' (existing asserted targets skipped)');",
298
+ ]);
299
+ return {
300
+ id: "kg-bootstrap-promote-shell",
301
+ depends_on: ["kg-bootstrap-multi-review-gate-shell"],
302
+ role: "verifier",
303
+ executor: "shell",
304
+ complexity: "LOW",
305
+ writePolicy: "exclusive",
306
+ writeSet: [
307
+ "knowledge/domains/**",
308
+ "knowledge/services/**",
309
+ "knowledge/modules/**",
310
+ "knowledge/graph/edges.manual.yaml",
311
+ "features/**/knowledge-links.yaml",
312
+ ],
313
+ allowedPaths: [
314
+ "knowledge/bootstrap/staging/**",
315
+ "knowledge/domains/**",
316
+ "knowledge/services/**",
317
+ "knowledge/modules/**",
318
+ "knowledge/graph/**",
319
+ "features/**",
320
+ ],
321
+ forbiddenPaths: [...commonForbiddenPaths(sources), "src/**", "testcase/**"],
322
+ outputContract: "Promote staging → formal knowledge trees without overwriting existing files; copy edges.manual.yaml if absent.",
323
+ subtask_prompt: "B5 promote: merge new files only (no overwrite of existing asserted content).",
324
+ shell: {
325
+ commands: [script],
326
+ verifyEvidence: buildVerifyEvidence({
327
+ phase: "final",
328
+ quota: "full",
329
+ commandSource: "inline",
330
+ fallbackCommands: [script],
331
+ finalFullRequired: true,
332
+ commandTimeoutMs: 120_000,
333
+ }),
334
+ cwd: ".",
335
+ timeoutMs: 120000,
336
+ },
337
+ };
338
+ }
339
+ export function buildKgBootstrapMaterializeNode(sources) {
340
+ const script = buildKgBootstrapInlineNodeScript([
341
+ "const fs=require('fs');",
342
+ "const path=require('path');",
343
+ "const root=process.cwd();",
344
+ "const entities=[];",
345
+ "function walk(dir,acc=[]){if(!fs.existsSync(dir))return acc;for(const ent of fs.readdirSync(dir,{withFileTypes:true})){const p=path.join(dir,ent.name);if(ent.isDirectory())walk(p,acc);else acc.push(p);}return acc;}",
346
+ "for (const base of ['knowledge/domains','knowledge/services','knowledge/modules']) {",
347
+ " for (const f of walk(path.join(root,base))) {",
348
+ " if(!f.endsWith('meta.yaml')&&!f.endsWith('.md')) continue;",
349
+ " const rel=path.relative(root,f).split(path.sep).join('/');",
350
+ " const text=fs.existsSync(f)&&f.endsWith('meta.yaml')?fs.readFileSync(f,'utf8'):'';",
351
+ " const id=(text.match(/^id:\\s*([A-Za-z0-9_-]+)/m)||[])[1];",
352
+ " const kind=(text.match(/^kind:\\s*([A-Za-z0-9_-]+)/m)||[])[1];",
353
+ " if(id) entities.push({kind:kind||'unknown',id,path:rel});",
354
+ " }",
355
+ "}",
356
+ "const featRoot=path.join(root,'features');",
357
+ "if(fs.existsSync(featRoot)){",
358
+ " for(const name of fs.readdirSync(featRoot)){",
359
+ " if(!/^F-/.test(name)) continue;",
360
+ " entities.push({kind:'feature',id:name,path:'features/'+name+'/'} );",
361
+ " const acc=path.join(featRoot,name,'acceptance.yaml');",
362
+ " if(fs.existsSync(acc)) entities.push({kind:'ac-file',id:name+':acceptance',path:'features/'+name+'/acceptance.yaml',feature_id:name});",
363
+ " }",
364
+ "}",
365
+ "const graphDir=path.join(root,'knowledge','graph');",
366
+ "fs.mkdirSync(graphDir,{recursive:true});",
367
+ "const index={schema_version:1,generated_at:new Date().toISOString(),entities};",
368
+ "fs.writeFileSync(path.join(graphDir,'entities-index.yaml'),'# generated by kg-bootstrap-materialize\\nschema_version: 1\\ngenerated_at: '+index.generated_at+'\\nentities:\\n'+entities.map(e=>' - kind: '+e.kind+'\\n id: '+e.id+'\\n path: '+e.path+'\\n').join(''));",
369
+ "const manual=path.join(graphDir,'edges.manual.yaml');",
370
+ "const edgesOut=path.join(graphDir,'edges.yaml');",
371
+ "if(fs.existsSync(manual)) fs.copyFileSync(manual,edgesOut);",
372
+ "else fs.writeFileSync(edgesOut,'schema_version: 1\\nupdated_at: '+index.generated_at+'\\nedges: []\\n');",
373
+ "console.log('materialized entities='+entities.length);",
374
+ ]);
375
+ return {
376
+ id: "kg-bootstrap-materialize-shell",
377
+ depends_on: ["kg-bootstrap-promote-shell"],
378
+ role: "closeout",
379
+ executor: "shell",
380
+ complexity: "LOW",
381
+ writePolicy: "exclusive",
382
+ writeSet: [
383
+ "knowledge/graph/entities-index.yaml",
384
+ "knowledge/graph/edges.yaml",
385
+ ],
386
+ allowedPaths: ["knowledge/**", "features/**"],
387
+ forbiddenPaths: commonForbiddenPaths(sources),
388
+ outputContract: "Write knowledge/graph/entities-index.yaml and edges.yaml from formal trees + edges.manual.yaml.",
389
+ subtask_prompt: "B6 materialize graph indexes for kb-query.",
390
+ shell: {
391
+ commands: [script],
392
+ verifyEvidence: buildVerifyEvidence({
393
+ phase: "final",
394
+ quota: "full",
395
+ commandSource: "inline",
396
+ fallbackCommands: [script],
397
+ finalFullRequired: true,
398
+ commandTimeoutMs: 120_000,
399
+ }),
400
+ cwd: ".",
401
+ timeoutMs: 120000,
402
+ },
403
+ };
404
+ }
405
+ export function buildKnowledgeGraphBootstrapHybridDag(sources) {
406
+ const { taskConfig } = sources;
407
+ const globalConstraints = [
408
+ ...taskConfig.hardConstraints,
409
+ ...(sources.constraintMarkdown
410
+ ? [`See 执行约束.md in task source (${sources.taskId})`]
411
+ : []),
412
+ ...STANDARD_GLOBAL_CONSTRAINTS,
413
+ "knowledge-graph-bootstrap-dag: AI may write only knowledge/bootstrap/staging/** and runs/** until promote.",
414
+ "AI must never set confidence: asserted; only human promotion marks formal knowledge.",
415
+ "Do not modify src/**, testcase/**, or Feature testing verdict/cases via bootstrap.",
416
+ "Promote must not overwrite existing formal files (merge-new-only).",
417
+ "Require knowledge/bootstrap/scope.yaml before propose (B0/B1 skeleton).",
418
+ "Graph indexes are written only by materialize-shell, not by propose-pi.",
419
+ "Multi-perspective review (structure, evidence, safety) must all VERDICT: pass before promote.",
420
+ ];
421
+ const multiReview = buildKgBootstrapMultiReviewNodes(sources);
422
+ const spec = {
423
+ version: 2,
424
+ title: `Knowledge-graph bootstrap DAG: ${taskConfig.title}`,
425
+ outputLanguage: sources.outputLanguage ?? DEFAULT_DAG_OUTPUT_LANGUAGE,
426
+ objective: extractObjective(sources.requirementMarkdown, taskConfig.title),
427
+ successCriteria: extractSuccessCriteria(sources.requirementMarkdown, sources.taskId),
428
+ globalConstraints,
429
+ defaults: {
430
+ ...KG_BOOTSTRAP_DEFAULTS,
431
+ contextProfile: taskConfig.contextProfile,
432
+ },
433
+ skillsByRole: KG_BOOTSTRAP_SKILLS_BY_ROLE,
434
+ executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS,
435
+ tasks: [
436
+ buildKgBootstrapPreflightNode(sources),
437
+ buildKgBootstrapInventoryNode(sources),
438
+ buildKgBootstrapProposeNode(sources),
439
+ buildKgBootstrapValidateNode(sources),
440
+ ...multiReview,
441
+ buildKgBootstrapPromoteNode(sources),
442
+ buildKgBootstrapMaterializeNode(sources),
443
+ ],
444
+ };
445
+ stampGeneratedArtifactBindings(spec);
446
+ parseDagSpec(spec);
447
+ assertValidDagSpec(spec);
448
+ return spec;
449
+ }