soloforge 1.4.11 → 1.4.13

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 (60) hide show
  1. package/dist/adapters/claude_code/tools.d.ts +3 -0
  2. package/dist/adapters/claude_code/tools.d.ts.map +1 -1
  3. package/dist/adapters/claude_code/tools.js +321 -374
  4. package/dist/adapters/claude_code/tools.js.map +1 -1
  5. package/dist/engine/brainstorm_contract.d.ts +2 -0
  6. package/dist/engine/brainstorm_contract.d.ts.map +1 -1
  7. package/dist/engine/brainstorm_contract.js +11 -1
  8. package/dist/engine/brainstorm_contract.js.map +1 -1
  9. package/dist/engine/expand_pipeline.d.ts +121 -0
  10. package/dist/engine/expand_pipeline.d.ts.map +1 -0
  11. package/dist/engine/expand_pipeline.js +141 -0
  12. package/dist/engine/expand_pipeline.js.map +1 -0
  13. package/dist/engine/first_principles.d.ts +2 -0
  14. package/dist/engine/first_principles.d.ts.map +1 -1
  15. package/dist/engine/first_principles.js +14 -1
  16. package/dist/engine/first_principles.js.map +1 -1
  17. package/dist/engine/intent_expander.d.ts +3 -0
  18. package/dist/engine/intent_expander.d.ts.map +1 -1
  19. package/dist/engine/intent_expander.js +252 -306
  20. package/dist/engine/intent_expander.js.map +1 -1
  21. package/dist/engine/intent_route_scorer.d.ts +1 -1
  22. package/dist/engine/intent_route_scorer.d.ts.map +1 -1
  23. package/dist/engine/intent_route_scorer.js +18 -3
  24. package/dist/engine/intent_route_scorer.js.map +1 -1
  25. package/dist/engine/intent_router.d.ts +2 -2
  26. package/dist/engine/intent_router.d.ts.map +1 -1
  27. package/dist/engine/intent_router.js +28 -6
  28. package/dist/engine/intent_router.js.map +1 -1
  29. package/dist/engine/intent_signal_extractor.d.ts +1 -1
  30. package/dist/engine/intent_signal_extractor.d.ts.map +1 -1
  31. package/dist/engine/intent_signal_extractor.js +12 -0
  32. package/dist/engine/intent_signal_extractor.js.map +1 -1
  33. package/dist/engine/release_readiness_gate.d.ts.map +1 -1
  34. package/dist/engine/release_readiness_gate.js +38 -37
  35. package/dist/engine/release_readiness_gate.js.map +1 -1
  36. package/dist/engine/source_code_matcher.d.ts +53 -0
  37. package/dist/engine/source_code_matcher.d.ts.map +1 -0
  38. package/dist/engine/source_code_matcher.js +165 -0
  39. package/dist/engine/source_code_matcher.js.map +1 -0
  40. package/dist/engine/task_context.d.ts +5 -0
  41. package/dist/engine/task_context.d.ts.map +1 -1
  42. package/dist/engine/task_context.js +37 -5
  43. package/dist/engine/task_context.js.map +1 -1
  44. package/dist/engine/technology_decision.d.ts +2 -0
  45. package/dist/engine/technology_decision.d.ts.map +1 -1
  46. package/dist/engine/technology_decision.js +18 -1
  47. package/dist/engine/technology_decision.js.map +1 -1
  48. package/dist/engine/traceability.d.ts.map +1 -1
  49. package/dist/engine/traceability.js +4 -0
  50. package/dist/engine/traceability.js.map +1 -1
  51. package/dist/engine/workflow_contract_registry.d.ts.map +1 -1
  52. package/dist/engine/workflow_contract_registry.js +24 -0
  53. package/dist/engine/workflow_contract_registry.js.map +1 -1
  54. package/dist/knowledge/index_manager.d.ts +1 -0
  55. package/dist/knowledge/index_manager.d.ts.map +1 -1
  56. package/dist/knowledge/index_manager.js +15 -0
  57. package/dist/knowledge/index_manager.js.map +1 -1
  58. package/dist/types.d.ts +2 -0
  59. package/dist/types.d.ts.map +1 -1
  60. package/package.json +1 -1
@@ -125,21 +125,38 @@ function decodeIndexedJsonString(value) {
125
125
  function parseContractObject(value, fieldName) {
126
126
  if (value === undefined || value === null)
127
127
  return {};
128
- const candidate = decodeIndexedJsonString(typeof value === "string"
129
- ? (() => {
130
- try {
131
- return JSON.parse(value);
132
- }
133
- catch {
134
- return undefined;
135
- }
136
- })()
137
- : value);
128
+ // 字符串 尝试 JSON 解析(可能被 MCP 客户端序列化为 JSON 字符串)
129
+ let parsed;
130
+ if (typeof value === "string") {
131
+ try {
132
+ parsed = JSON.parse(value);
133
+ }
134
+ catch (parseErr) {
135
+ return { error: `${fieldName} 的 JSON 解析失败: ${parseErr.message}。请检查 JSON 格式是否正确` };
136
+ }
137
+ }
138
+ else {
139
+ parsed = value;
140
+ }
141
+ const candidate = decodeIndexedJsonString(parsed);
138
142
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
139
- return { error: `${fieldName} 必须是对象或可解析为对象的 JSON 字符串` };
143
+ return { error: `${fieldName} 必须是 JSON 对象(当前类型: ${Array.isArray(candidate) ? "array" : typeof candidate})。期望格式请参考 expected_schema` };
140
144
  }
141
145
  return { value: candidate };
142
146
  }
147
+ /** 需要通过 parseContractObject 校验的契约字段列表 */
148
+ const CONTRACT_OBJECT_FIELDS = [
149
+ "architecture_decision_workshop", "decision_workshop",
150
+ "technology_decision_contract", "technology_decision_record",
151
+ "detail_discipline_contract", "provided_details",
152
+ "first_principles_frame", "brainstorm_session",
153
+ "design_artifact_pack", "ood_solid_summary",
154
+ "backend_implementation_work_package", "code_observability_work_package",
155
+ ];
156
+ /** 需要规范化的字段 */
157
+ const NORMALIZED_FIELDS = {
158
+ decision_workshop: "decision_workshop",
159
+ };
143
160
  // ── Zod Schema 定义 ──
144
161
  const ClassifySchema = {
145
162
  intent: z.string().describe("开发者意图描述"),
@@ -149,18 +166,18 @@ const ExpandSchema = {
149
166
  task_id: z.string().describe("sf_classify 返回的任务 ID"),
150
167
  clarification_answers: z.array(z.string()).optional().describe("对澄清问题的回答"),
151
168
  input_material_confirmations: z.array(z.string()).optional().describe("已确认安全的输入材料路径列表"),
152
- architecture_decision_workshop: z.unknown().optional().describe("架构设计前已讨论并确认的六域决策记录"),
153
- decision_workshop: z.unknown().optional().describe("通用决策研讨合同(技术选型/数据迁移/安全策略/部署/重构/第三方集成等)"),
154
- technology_decision_contract: z.unknown().optional().describe("技术决策主权契约,含选项、推荐、证据、回滚计划和 human_gate_evidence"),
155
- technology_decision_record: z.unknown().optional().describe("技术决策执行记录,记录已批准/已实现等显式决策状态"),
156
- detail_discipline_contract: z.unknown().optional().describe("细节纪律契约,声明必须覆盖的边界、失败路径、回滚和证据维度"),
157
- provided_details: z.unknown().optional().describe("细节纪律已提供证据,按 required_dimensions 映射为字符串数组"),
158
- first_principles_frame: z.unknown().optional().describe("高影响任务的第一性原理框架"),
159
- brainstorm_session: z.unknown().optional().describe("不确定项/方案选择的脑暴与方案探索会话"),
160
- design_artifact_pack: z.unknown().optional().describe("设计产物包路径映射或已复验状态"),
161
- ood_solid_summary: z.unknown().optional().describe("复杂编码任务的 OOD/SOLID 设计摘要"),
162
- backend_implementation_work_package: z.unknown().optional().describe("后端接口实现的工程工作包"),
163
- code_observability_work_package: z.unknown().optional().describe("代码可维护性与可观测性工作包"),
169
+ architecture_decision_workshop: z.record(z.unknown()).optional().describe("架构设计前六域决策记录。示例 {domains:[{domain:'backend',options:[{id:'opt-a',title:'...'}],recommended_option_id:'opt-a',user_confirmation_ref:'confirm:...'}],status:'confirmed'}"),
170
+ decision_workshop: z.record(z.unknown()).optional().describe("通用决策研讨合同。示例 {activated_packs:['delivery_validation'],domains:[{domain:'delivery_validation',options:[{id:'opt-a',title:'...'}],recommended_option_id:'opt-a',user_confirmation_ref:'confirm:...'}],status:'confirmed'}"),
171
+ technology_decision_contract: z.record(z.unknown()).optional().describe("技术决策主权契约。示例 {decision_id:'TECH-001',decision_scope:'architecture',options:[{option_id:'opt-a',title:'A',description:'...'}],recommended_option:'opt-a',rejected_options:[{option:'opt-b',reason:'...'}],evidence:[{claim:'...',supporting_facts:['...']}],falsification:['...'],failure_conditions:['...'],validation_plan:['...'],rollback_or_exit_plan:['...'],human_gate_required:true,human_gate_evidence:'confirm:...',executable_without_human:false}"),
172
+ technology_decision_record: z.record(z.unknown()).optional().describe("技术决策执行记录。示例 {decision_id:'TECH-001',selected_option:'opt-a',status:'approved',approved_by:'user',evidence_refs:['confirm:...']}"),
173
+ detail_discipline_contract: z.record(z.unknown()).optional().describe("细节纪律契约。示例 {contract_id:'detail-001',required_dimensions:['boundary','failure_path','rollback'],blocking_dimensions:['boundary'],status:'ready'}"),
174
+ provided_details: z.record(z.unknown()).optional().describe("细节纪律已提供证据。示例 {boundary:['只检查 readiness,不改代码'],failure_path:['unknown 时标记证据不足'],rollback:['无写入,无需回滚']}"),
175
+ first_principles_frame: z.record(z.unknown()).optional().describe("高影响任务第一性原理框架。示例 {fundamental_need:'...',known_facts:['...'],assumptions:['...'],candidate_solutions:[{solution_id:'s1',description:'...',complexity:'low'}],simplest_viable_solution:'s1',chosen_solution:'s1',falsification_questions:['...'],why_not_legacy_path:'...'}"),
176
+ brainstorm_session: z.record(z.unknown()).optional().describe("不确定项/方案选择会话。示例 {trigger:'uncertainty',options:[{option_id:'opt-a',title:'...',description:'...',pros:['...'],cons:['...']}],recommended_option_id:'opt-a',rejected_option_ids:['opt-b'],failure_conditions:['...'],brainstorm_status:'options_ready'}"),
177
+ design_artifact_pack: z.record(z.unknown()).optional().describe("设计产物包路径映射或复验状态。示例 {status:'implementation_ready',paths:{architecture_document:'docs/architecture/01-架构设计文档.md'},verification_refs:['verification:...']}"),
178
+ ood_solid_summary: z.record(z.unknown()).optional().describe("复杂编码任务 OOD/SOLID 摘要。示例 {status:'confirmed',responsibilities:['...'],interfaces:['...'],risks:['...'],evidence_refs:['...']}"),
179
+ backend_implementation_work_package: z.record(z.unknown()).optional().describe("后端接口实现工程工作包。示例 {status:'confirmed',api_boundary:['...'],transaction_boundary:['...'],error_contract:['...'],evidence_refs:['...']}"),
180
+ code_observability_work_package: z.record(z.unknown()).optional().describe("代码可维护性与可观测性工作包。示例 {status:'confirmed',logging_plan:['...'],comment_plan:['...'],sensitive_logging_policy:'redact',evidence_refs:['...']}"),
164
181
  };
165
182
  const VerifySchema = {
166
183
  task_id: z.string().describe("任务 ID"),
@@ -178,7 +195,7 @@ const LearnSchema = {
178
195
  };
179
196
  const StatusSchema = {
180
197
  task_id: z.string().optional().describe("任务 ID(不传则查当前任务)"),
181
- action: z.enum(["current", "recent", "resume", "cancel", "archive_stale"]).optional().describe("操作类型"),
198
+ action: z.enum(["current", "recent", "resume", "cancel", "archive_stale", "retry_expand"]).optional().describe("操作类型"),
182
199
  confirm: z.boolean().optional().describe("确认执行会写入状态的操作,例如 cancel/archive_stale/job_cancel"),
183
200
  authorized: z.boolean().optional().describe("confirm 的兼容别名"),
184
201
  job_id: z.string().optional().describe("Job ID(用于 job_status / job_resume / job_cancel)"),
@@ -511,7 +528,7 @@ export async function checkLocalAcceptanceGate(params) {
511
528
  const HIGH_IMPACT_KEYWORDS = /架构|技术选型|数据库|框架|多端|跨模块|高影响|高风险|migration|技术路线|选型|rework/i;
512
529
  const UNCERTAINTY_KEYWORDS = /不确定|方案选择|多选项|权衡|脑暴|brainstorm|多方案|选型|比较/i;
513
530
  const DETAIL_INTENSIVE_KEYWORDS = /多步骤|复杂|跨模块|高风险|多端|分布式|微服务/i;
514
- const LOW_RISK_ROUTES = new Set(["skip_chat", "direct_answer", "operation", "planning", "review", "source_extraction"]);
531
+ const LOW_RISK_ROUTES = new Set(["skip_chat", "direct_answer", "operation", "planning", "review", "source_extraction", "acceptance"]);
515
532
  /** 技术选型/架构/数据库/框架/高影响决策 → 需 decision sovereignty gate */
516
533
  export function requiresDecisionSovereignty(ctx, route) {
517
534
  if (!ctx)
@@ -607,7 +624,7 @@ export async function checkDecisionSovereigntyGate(params) {
607
624
  const decisionModule = await lazyTechnologyDecision();
608
625
  const validation = decisionModule.validateTechnologyDecision(decisionContract);
609
626
  if (!validation.valid) {
610
- return { allowed: false, reason_zh: `技术选型决策校验失败: ${validation.violations.join("; ")}` };
627
+ return { allowed: false, reason_zh: `技术选型决策校验失败: ${validation.violations.join("; ")}`, expected_schema: validation.expected_schema };
611
628
  }
612
629
  // 有决策记录时评估执行状态
613
630
  const decisionRecord = params.ctx.technology_decision_record;
@@ -672,7 +689,7 @@ export async function checkFirstPrinciplesGate(params) {
672
689
  const fpModule = await lazyFirstPrinciples();
673
690
  const validation = fpModule.validateFirstPrinciplesForHighImpact(fpFrame);
674
691
  if (!validation.passed) {
675
- return { allowed: false, reason_zh: `高影响任务缺第一性原理推理: ${validation.violations.join("; ")}`, failures: validation.violations };
692
+ return { allowed: false, reason_zh: `高影响任务缺第一性原理推理: ${validation.violations.join("; ")}`, failures: validation.violations, expected_schema: validation.expected_schema };
676
693
  }
677
694
  return { allowed: true };
678
695
  }
@@ -695,7 +712,7 @@ export async function checkBrainstormGate(params) {
695
712
  const brainstormModule = await lazyBrainstormContract();
696
713
  const validation = brainstormModule.validateBrainstormSession(brainstormSession);
697
714
  if (!validation.valid) {
698
- return { allowed: false, reason_zh: `方案探索不完整: ${validation.violations.join("; ")}`, violations: validation.violations };
715
+ return { allowed: false, reason_zh: `方案探索不完整: ${validation.violations.join("; ")}`, violations: validation.violations, expected_schema: validation.expected_schema };
699
716
  }
700
717
  // 脑暴推荐永远不能自动执行 — 需 Decision Sovereignty human gate
701
718
  const canAutoExec = brainstormModule.canBrainstormAutoExecute(brainstormSession);
@@ -1329,7 +1346,7 @@ export async function registerTools(server, deps) {
1329
1346
  }
1330
1347
  catch (err) {
1331
1348
  const errorNextTools = name === "sf_expand"
1332
- ? ["sf_status", "sf_debug", "sf_governance_report"]
1349
+ ? ["sf_expand", "sf_status", "sf_debug", "sf_governance_report"]
1333
1350
  : contract.default_next_tools;
1334
1351
  const errorForbiddenTools = name === "sf_expand"
1335
1352
  ? ["sf_verify", "sf_review", "sf_deliver", "sf_scaffold"]
@@ -1484,161 +1501,27 @@ export async function registerTools(server, deps) {
1484
1501
  if (!ctx || !ctx.classification) {
1485
1502
  return { result: { error: "任务不存在或尚未分类,请先调用 sf_classify" } };
1486
1503
  }
1487
- const existingArchitectureWorkshop = parseContractObject(ctx.architecture_decision_workshop, "architecture_decision_workshop");
1488
- if (existingArchitectureWorkshop.error) {
1489
- return { result: { error: existingArchitectureWorkshop.error, status: "invalid_input" } };
1490
- }
1491
- const existingDecisionWorkshop = parseContractObject(ctx.decision_workshop, "decision_workshop");
1492
- if (existingDecisionWorkshop.error) {
1493
- return { result: { error: existingDecisionWorkshop.error, status: "invalid_input" } };
1494
- }
1495
- const existingTechnologyDecisionContract = parseContractObject(ctx.technology_decision_contract, "technology_decision_contract");
1496
- if (existingTechnologyDecisionContract.error) {
1497
- return { result: { error: existingTechnologyDecisionContract.error, status: "invalid_input" } };
1498
- }
1499
- const existingTechnologyDecisionRecord = parseContractObject(ctx.technology_decision_record, "technology_decision_record");
1500
- if (existingTechnologyDecisionRecord.error) {
1501
- return { result: { error: existingTechnologyDecisionRecord.error, status: "invalid_input" } };
1502
- }
1503
- const existingDetailContract = parseContractObject(ctx.detail_discipline_contract, "detail_discipline_contract");
1504
- if (existingDetailContract.error) {
1505
- return { result: { error: existingDetailContract.error, status: "invalid_input" } };
1506
- }
1507
- const existingProvidedDetails = parseContractObject(ctx.provided_details, "provided_details");
1508
- if (existingProvidedDetails.error) {
1509
- return { result: { error: existingProvidedDetails.error, status: "invalid_input" } };
1510
- }
1511
- const existingFirstPrinciplesFrame = parseContractObject(ctx.first_principles_frame, "first_principles_frame");
1512
- if (existingFirstPrinciplesFrame.error) {
1513
- return { result: { error: existingFirstPrinciplesFrame.error, status: "invalid_input" } };
1514
- }
1515
- const existingBrainstormSession = parseContractObject(ctx.brainstorm_session, "brainstorm_session");
1516
- if (existingBrainstormSession.error) {
1517
- return { result: { error: existingBrainstormSession.error, status: "invalid_input" } };
1518
- }
1519
- const existingDesignArtifactPack = parseContractObject(ctx.design_artifact_pack, "design_artifact_pack");
1520
- if (existingDesignArtifactPack.error) {
1521
- return { result: { error: existingDesignArtifactPack.error, status: "invalid_input" } };
1522
- }
1523
- const existingOodSolidSummary = parseContractObject(ctx.ood_solid_summary, "ood_solid_summary");
1524
- if (existingOodSolidSummary.error) {
1525
- return { result: { error: existingOodSolidSummary.error, status: "invalid_input" } };
1526
- }
1527
- const existingBackendImplementationWorkPackage = parseContractObject(ctx.backend_implementation_work_package, "backend_implementation_work_package");
1528
- if (existingBackendImplementationWorkPackage.error) {
1529
- return { result: { error: existingBackendImplementationWorkPackage.error, status: "invalid_input" } };
1530
- }
1531
- const existingCodeObservabilityWorkPackage = parseContractObject(ctx.code_observability_work_package, "code_observability_work_package");
1532
- if (existingCodeObservabilityWorkPackage.error) {
1533
- return { result: { error: existingCodeObservabilityWorkPackage.error, status: "invalid_input" } };
1534
- }
1535
- if (existingArchitectureWorkshop.value)
1536
- ctx.architecture_decision_workshop = existingArchitectureWorkshop.value;
1537
- if (existingDecisionWorkshop.value) {
1538
- ctx.decision_workshop = (await lazyDecisionWorkshop()).normalizeDecisionWorkshopContract(existingDecisionWorkshop.value, args.task_id) ?? existingDecisionWorkshop.value;
1539
- }
1540
- if (existingTechnologyDecisionContract.value)
1541
- ctx.technology_decision_contract = existingTechnologyDecisionContract.value;
1542
- if (existingTechnologyDecisionRecord.value)
1543
- ctx.technology_decision_record = existingTechnologyDecisionRecord.value;
1544
- if (existingDetailContract.value)
1545
- ctx.detail_discipline_contract = existingDetailContract.value;
1546
- if (existingProvidedDetails.value)
1547
- ctx.provided_details = existingProvidedDetails.value;
1548
- if (existingFirstPrinciplesFrame.value)
1549
- ctx.first_principles_frame = existingFirstPrinciplesFrame.value;
1550
- if (existingBrainstormSession.value)
1551
- ctx.brainstorm_session = existingBrainstormSession.value;
1552
- if (existingDesignArtifactPack.value)
1553
- ctx.design_artifact_pack = existingDesignArtifactPack.value;
1554
- if (existingOodSolidSummary.value)
1555
- ctx.ood_solid_summary = existingOodSolidSummary.value;
1556
- if (existingBackendImplementationWorkPackage.value)
1557
- ctx.backend_implementation_work_package = existingBackendImplementationWorkPackage.value;
1558
- if (existingCodeObservabilityWorkPackage.value)
1559
- ctx.code_observability_work_package = existingCodeObservabilityWorkPackage.value;
1560
- if (args.architecture_decision_workshop) {
1561
- const parsed = parseContractObject(args.architecture_decision_workshop, "architecture_decision_workshop");
1562
- if (parsed.error)
1563
- return { result: { error: parsed.error, status: "invalid_input" } };
1564
- ctx.architecture_decision_workshop = parsed.value;
1565
- }
1566
- if (args.decision_workshop) {
1567
- const parsed = parseContractObject(args.decision_workshop, "decision_workshop");
1568
- if (parsed.error)
1569
- return { result: { error: parsed.error, status: "invalid_input" } };
1570
- ctx.decision_workshop = (await lazyDecisionWorkshop()).normalizeDecisionWorkshopContract(parsed.value, args.task_id) ?? parsed.value;
1571
- }
1572
- if (args.technology_decision_contract) {
1573
- const parsed = parseContractObject(args.technology_decision_contract, "technology_decision_contract");
1574
- if (parsed.error)
1575
- return { result: { error: parsed.error, status: "invalid_input" } };
1576
- ctx.technology_decision_contract = parsed.value;
1577
- }
1578
- if (args.technology_decision_record) {
1579
- const parsed = parseContractObject(args.technology_decision_record, "technology_decision_record");
1580
- if (parsed.error)
1581
- return { result: { error: parsed.error, status: "invalid_input" } };
1582
- ctx.technology_decision_record = parsed.value;
1583
- }
1584
- if (args.detail_discipline_contract) {
1585
- const parsed = parseContractObject(args.detail_discipline_contract, "detail_discipline_contract");
1586
- if (parsed.error)
1587
- return { result: { error: parsed.error, status: "invalid_input" } };
1588
- ctx.detail_discipline_contract = parsed.value;
1589
- }
1590
- if (args.provided_details) {
1591
- const parsed = parseContractObject(args.provided_details, "provided_details");
1592
- if (parsed.error)
1593
- return { result: { error: parsed.error, status: "invalid_input" } };
1594
- ctx.provided_details = parsed.value;
1595
- }
1596
- if (args.first_principles_frame) {
1597
- const parsed = parseContractObject(args.first_principles_frame, "first_principles_frame");
1598
- if (parsed.error)
1599
- return { result: { error: parsed.error, status: "invalid_input" } };
1600
- ctx.first_principles_frame = parsed.value;
1601
- }
1602
- if (args.brainstorm_session) {
1603
- const parsed = parseContractObject(args.brainstorm_session, "brainstorm_session");
1604
- if (parsed.error)
1605
- return { result: { error: parsed.error, status: "invalid_input" } };
1606
- ctx.brainstorm_session = parsed.value;
1607
- }
1608
- if (args.design_artifact_pack) {
1609
- const parsed = parseContractObject(args.design_artifact_pack, "design_artifact_pack");
1610
- if (parsed.error)
1611
- return { result: { error: parsed.error, status: "invalid_input" } };
1612
- ctx.design_artifact_pack = parsed.value;
1613
- }
1614
- if (args.ood_solid_summary) {
1615
- const parsed = parseContractObject(args.ood_solid_summary, "ood_solid_summary");
1616
- if (parsed.error)
1617
- return { result: { error: parsed.error, status: "invalid_input" } };
1618
- ctx.ood_solid_summary = parsed.value;
1619
- }
1620
- if (args.backend_implementation_work_package) {
1621
- const parsed = parseContractObject(args.backend_implementation_work_package, "backend_implementation_work_package");
1622
- if (parsed.error)
1623
- return { result: { error: parsed.error, status: "invalid_input" } };
1624
- ctx.backend_implementation_work_package = parsed.value;
1625
- }
1626
- if (args.code_observability_work_package) {
1627
- const parsed = parseContractObject(args.code_observability_work_package, "code_observability_work_package");
1504
+ // 合并校验 + 应用:ctx 已存储值 → args 覆盖值,统一解析和规范化
1505
+ let needsSave = false;
1506
+ for (const field of CONTRACT_OBJECT_FIELDS) {
1507
+ // args 优先,其次 ctx 已存储值
1508
+ const raw = args[field] ?? ctx[field];
1509
+ if (!raw)
1510
+ continue;
1511
+ const parsed = parseContractObject(raw, field);
1628
1512
  if (parsed.error)
1629
1513
  return { result: { error: parsed.error, status: "invalid_input" } };
1630
- ctx.code_observability_work_package = parsed.value;
1514
+ if (parsed.value) {
1515
+ ctx[field] = field === "decision_workshop"
1516
+ ? ((await lazyDecisionWorkshop()).normalizeDecisionWorkshopContract(parsed.value, args.task_id) ?? parsed.value)
1517
+ : parsed.value;
1518
+ needsSave = true;
1519
+ }
1631
1520
  }
1632
- if (args.architecture_decision_workshop || args.decision_workshop
1633
- || args.technology_decision_contract || args.technology_decision_record
1634
- || args.detail_discipline_contract || args.provided_details
1635
- || args.first_principles_frame || args.brainstorm_session
1636
- || args.design_artifact_pack
1637
- || args.ood_solid_summary || args.backend_implementation_work_package || args.code_observability_work_package) {
1521
+ if (needsSave)
1638
1522
  await taskContext.save(ctx);
1639
- }
1640
- // 状态守卫:classifying/expanding/clarifying expanding
1641
- if (ctx.status === "classifying" || ctx.status === "clarifying") {
1523
+ // 状态守卫:classifying/expanding/clarifying/failed → expanding
1524
+ if (ctx.status === "classifying" || ctx.status === "clarifying" || ctx.status === "failed") {
1642
1525
  await taskContext.updateStatus(args.task_id, "expanding");
1643
1526
  ctx.status = "expanding";
1644
1527
  }
@@ -1699,6 +1582,7 @@ export async function registerTools(server, deps) {
1699
1582
  plan_context: planContext,
1700
1583
  input_material_confirmations: args.input_material_confirmations,
1701
1584
  task_id: args.task_id,
1585
+ brainstorm_session: ctx.brainstorm_session ?? undefined,
1702
1586
  });
1703
1587
  }
1704
1588
  catch (expandErr) {
@@ -1951,223 +1835,231 @@ export async function registerTools(server, deps) {
1951
1835
  }
1952
1836
  }
1953
1837
  }
1954
- // ── S4 gates: 独立于 hasInstructionContract,由 requires* 触发判定 ──
1955
- // 问题十六: 技术选型决策主权 gate 高影响技术决策缺证据时 blocked
1956
- {
1957
- const decisionGate = await checkDecisionSovereigntyGate({ ctx, route: expansionRoute });
1958
- if (!decisionGate.allowed) {
1959
- return {
1960
- result: {
1961
- error: decisionGate.reason_zh,
1962
- status: "blocked",
1963
- recovery: decisionGate.requires_human_gate ? "请提供技术决策确认证据(human_gate_evidence)" : "技术选型决策校验失败,请提供 technology_decision_contract",
1964
- },
1965
- };
1966
- }
1967
- }
1968
- // 问题二十六: 细节纪律 gate 复杂任务缺关键细节时 blocked
1969
- {
1970
- const detailGate = await checkDetailDisciplineGate({ ctx, route: expansionRoute });
1971
- if (!detailGate.allowed) {
1972
- return {
1973
- result: {
1974
- error: detailGate.reason_zh,
1975
- missing_details: detailGate.missing_details,
1976
- status: "blocked",
1977
- recovery: "多步骤/复杂任务缺 detail_discipline_contract,请提供后重新 sf_expand",
1978
- },
1979
- };
1838
+ // ── S4 gates: 轻量路径(acceptance/review/read_only)整体跳过 ──
1839
+ const isLightweightRoute = expansionRoute === "acceptance" || expansionRoute === "review"
1840
+ || expansionRoute === "direct_answer" || expansionRoute === "analysis";
1841
+ if (!isLightweightRoute) {
1842
+ // ── S4 gates: 独立于 hasInstructionContract,由 requires* 触发判定 ──
1843
+ // 问题十六: 技术选型决策主权 gate — 高影响技术决策缺证据时 blocked
1844
+ {
1845
+ const decisionGate = await checkDecisionSovereigntyGate({ ctx, route: expansionRoute });
1846
+ if (!decisionGate.allowed) {
1847
+ return {
1848
+ result: {
1849
+ error: decisionGate.reason_zh,
1850
+ status: "blocked",
1851
+ expected_schema: decisionGate.expected_schema,
1852
+ recovery: decisionGate.requires_human_gate ? "请提供技术决策确认证据(human_gate_evidence)" : "技术选型决策校验失败,请按照 expected_schema 格式提供 technology_decision_contract",
1853
+ },
1854
+ };
1855
+ }
1980
1856
  }
1981
- }
1982
- // 问题二十七: 第一性原理 gate — 高影响任务缺推理时 blocked
1983
- {
1984
- const fpGate = await checkFirstPrinciplesGate({ ctx, route: expansionRoute });
1985
- if (!fpGate.allowed) {
1986
- return {
1987
- result: {
1988
- error: fpGate.reason_zh,
1989
- failures: fpGate.failures,
1990
- status: "blocked",
1991
- recovery: "高影响任务缺 first_principles_frame,请提供后重新 sf_expand",
1992
- },
1993
- };
1857
+ // 问题二十六: 细节纪律 gate — 复杂任务缺关键细节时 blocked
1858
+ {
1859
+ const detailGate = await checkDetailDisciplineGate({ ctx, route: expansionRoute });
1860
+ if (!detailGate.allowed) {
1861
+ return {
1862
+ result: {
1863
+ error: detailGate.reason_zh,
1864
+ missing_details: detailGate.missing_details,
1865
+ status: "blocked",
1866
+ recovery: "多步骤/复杂任务缺 detail_discipline_contract,请提供后重新 sf_expand",
1867
+ },
1868
+ };
1869
+ }
1994
1870
  }
1995
- }
1996
- // 问题四十二(brainstorm): 脑暴契约 gate — 不确定项缺方案探索时 blocked
1997
- {
1998
- const brainstormGate = await checkBrainstormGate({ ctx, route: expansionRoute });
1999
- if (!brainstormGate.allowed) {
2000
- return {
2001
- result: {
2002
- error: brainstormGate.reason_zh,
2003
- violations: brainstormGate.violations,
2004
- status: "blocked",
2005
- recovery: "不确定项/方案选择缺 brainstorm_session,请提供后重新 sf_expand",
2006
- },
2007
- };
1871
+ // 问题二十七: 第一性原理 gate — 高影响任务缺推理时 blocked
1872
+ {
1873
+ const fpGate = await checkFirstPrinciplesGate({ ctx, route: expansionRoute });
1874
+ if (!fpGate.allowed) {
1875
+ return {
1876
+ result: {
1877
+ error: fpGate.reason_zh,
1878
+ failures: fpGate.failures,
1879
+ expected_schema: fpGate.expected_schema,
1880
+ status: "blocked",
1881
+ recovery: "高影响任务缺 first_principles_frame,请按照 expected_schema 格式提供后重新 sf_expand",
1882
+ },
1883
+ };
1884
+ }
2008
1885
  }
2009
- }
2010
- // 问题六十六/六十七/六十八: 编码前工程约束必须聚合返回。
2011
- // 不能让 OOD 或后端工程门禁抢先返回,遮蔽用户项目的注释/日志契约。
2012
- {
2013
- const preImplementationBlocks = [];
2014
- const preImplementationPayload = {};
2015
- const artifactDesignIntent = expansionRoute === "artifact_generation"
2016
- || ["architecture_design", "database_design", "api_design", "detailed_design"].includes(String(workflowIntent))
2017
- || /(?:架构|数据库|API|接口|OpenAPI).{0,12}(?:设计|规格|文档)|(?:设计|规格|文档).{0,12}(?:API|接口|OpenAPI)/i.test(ctx.intent);
2018
- const isImplementationRoute = !artifactDesignIntent && (expansionRoute === "code_change"
2019
- || String(workflowIntent) === "backend_api_implementation"
2020
- || String(workflowIntent) === "frontend_business_logic"
2021
- || /编码|实现|开发|controller|service|component/i.test(ctx.intent));
2022
- const traceabilityModule = await lazyTraceability();
2023
- const traceReport = isImplementationRoute
2024
- ? traceabilityModule.auditDesignImplementationTraceability(projectPath)
2025
- : {
2026
- passed: true,
2027
- matrix_exists: false,
2028
- findings: [],
2029
- checked_files: [],
2030
- matrix_path: traceabilityModule.DEFAULT_DESIGN_IMPLEMENTATION_TRACEABILITY_PATH,
2031
- };
2032
- if (isImplementationRoute && !traceReport.passed) {
2033
- ctx.traceability_binding = {
2034
- requirement_ids: [],
2035
- prototype_ids: [],
2036
- architecture_ids: [],
2037
- detail_design_ids: [],
2038
- phase_ids: [],
2039
- slice_ids: [],
2040
- acceptance_ids: [],
2041
- status: "needs_backfill",
2042
- findings: traceReport.findings.map((finding) => `${finding.code}: ${finding.message_zh}`),
2043
- };
2044
- await taskContext.save(ctx);
2045
- return {
2046
- result: {
2047
- error: "需求/原型/设计/切片/验收追踪链路未闭合,不得进入编码实现",
2048
- status: "blocked",
2049
- diagnostic_code: TOOL_DIAGNOSTIC_CODES.traceabilityMissing,
2050
- traceability_findings: traceReport.findings,
2051
- checked_files: traceReport.checked_files,
2052
- matrix_path: traceReport.matrix_path,
2053
- template_path: traceabilityModule.DESIGN_IMPLEMENTATION_TRACEABILITY_TEMPLATE_PATH,
2054
- recovery_command: traceabilityModule.DESIGN_IMPLEMENTATION_TRACEABILITY_RECOVERY_COMMAND,
2055
- recovery: `${traceabilityModule.DESIGN_IMPLEMENTATION_TRACEABILITY_RECOVERY_GUIDANCE}。不得取消任务后直接写代码,不得改用 Bash 绕过追踪矩阵。`,
2056
- },
2057
- };
1886
+ // 问题四十二(brainstorm): 脑暴契约 gate — 不确定项缺方案探索时 blocked
1887
+ {
1888
+ const brainstormGate = await checkBrainstormGate({ ctx, route: expansionRoute });
1889
+ if (!brainstormGate.allowed) {
1890
+ return {
1891
+ result: {
1892
+ error: brainstormGate.reason_zh,
1893
+ violations: brainstormGate.violations,
1894
+ expected_schema: brainstormGate.expected_schema,
1895
+ status: "blocked",
1896
+ recovery: "不确定项/方案选择缺 brainstorm_session,请按照 expected_schema 格式提供后重新 sf_expand",
1897
+ },
1898
+ };
1899
+ }
2058
1900
  }
2059
- const traceBindingIds = traceabilityModule.extractTraceabilityBindingIds(ctx);
2060
- if (traceReport.matrix_exists && isImplementationRoute) {
2061
- const missingPrefixes = ["PHASE", "SLICE", "DD", "AC"].filter((prefix) => !traceBindingIds.some((id) => id.startsWith(`${prefix}-`)));
2062
- if (missingPrefixes.length > 0) {
1901
+ // 问题六十六/六十七/六十八: 编码前工程约束必须聚合返回。
1902
+ // 不能让 OOD 或后端工程门禁抢先返回,遮蔽用户项目的注释/日志契约。
1903
+ {
1904
+ const preImplementationBlocks = [];
1905
+ const preImplementationPayload = {};
1906
+ const artifactDesignIntent = expansionRoute === "artifact_generation"
1907
+ || ["architecture_design", "database_design", "api_design", "detailed_design"].includes(String(workflowIntent))
1908
+ || /(?:架构|数据库|API|接口|OpenAPI).{0,12}(?:设计|规格|文档)|(?:设计|规格|文档).{0,12}(?:API|接口|OpenAPI)/i.test(ctx.intent);
1909
+ const isImplementationRoute = !artifactDesignIntent && (expansionRoute === "code_change"
1910
+ || String(workflowIntent) === "backend_api_implementation"
1911
+ || String(workflowIntent) === "frontend_business_logic"
1912
+ || /编码|实现|开发|controller|service|component/i.test(ctx.intent));
1913
+ const traceabilityModule = await lazyTraceability();
1914
+ const traceReport = isImplementationRoute
1915
+ ? traceabilityModule.auditDesignImplementationTraceability(projectPath)
1916
+ : {
1917
+ passed: true,
1918
+ matrix_exists: false,
1919
+ findings: [],
1920
+ checked_files: [],
1921
+ matrix_path: traceabilityModule.DEFAULT_DESIGN_IMPLEMENTATION_TRACEABILITY_PATH,
1922
+ };
1923
+ if (isImplementationRoute && !traceReport.passed) {
2063
1924
  ctx.traceability_binding = {
2064
- requirement_ids: traceBindingIds.filter((id) => id.startsWith("REQ-")),
2065
- prototype_ids: traceBindingIds.filter((id) => id.startsWith("PROTO-")),
2066
- architecture_ids: traceBindingIds.filter((id) => id.startsWith("ARCH-")),
2067
- detail_design_ids: traceBindingIds.filter((id) => id.startsWith("DD-")),
2068
- phase_ids: traceBindingIds.filter((id) => id.startsWith("PHASE-")),
2069
- slice_ids: traceBindingIds.filter((id) => id.startsWith("SLICE-")),
2070
- acceptance_ids: traceBindingIds.filter((id) => id.startsWith("AC-")),
2071
- status: "missing",
2072
- findings: missingPrefixes.map((prefix) => `编码前缺少 ${prefix}-* 绑定`),
1925
+ requirement_ids: [],
1926
+ prototype_ids: [],
1927
+ architecture_ids: [],
1928
+ detail_design_ids: [],
1929
+ phase_ids: [],
1930
+ slice_ids: [],
1931
+ acceptance_ids: [],
1932
+ status: "needs_backfill",
1933
+ findings: traceReport.findings.map((finding) => `${finding.code}: ${finding.message_zh}`),
2073
1934
  };
2074
1935
  await taskContext.save(ctx);
2075
1936
  return {
2076
1937
  result: {
2077
- error: "编码任务必须先绑定阶段、切片、详细设计和验收 ID",
2078
- status: "awaiting_traceability_binding",
2079
- diagnostic_code: TOOL_DIAGNOSTIC_CODES.traceabilityBindingMissing,
2080
- missing_prefixes: missingPrefixes,
1938
+ error: "需求/原型/设计/切片/验收追踪链路未闭合,不得进入编码实现",
1939
+ status: "blocked",
1940
+ diagnostic_code: TOOL_DIAGNOSTIC_CODES.traceabilityMissing,
1941
+ traceability_findings: traceReport.findings,
1942
+ checked_files: traceReport.checked_files,
2081
1943
  matrix_path: traceReport.matrix_path,
2082
1944
  template_path: traceabilityModule.DESIGN_IMPLEMENTATION_TRACEABILITY_TEMPLATE_PATH,
2083
- recovery_command: "sf_expand",
2084
- recovery: "在任务上下文或指令中明确本次实现对应的 PHASE-*、SLICE-*、DD-*、AC-*,再重新调用 sf_expand;不得脱离已确认设计直接编码,不得取消任务后改用 Bash/Edit/Write 直接实现",
1945
+ recovery_command: traceabilityModule.DESIGN_IMPLEMENTATION_TRACEABILITY_RECOVERY_COMMAND,
1946
+ recovery: `${traceabilityModule.DESIGN_IMPLEMENTATION_TRACEABILITY_RECOVERY_GUIDANCE}。不得取消任务后直接写代码,不得改用 Bash 绕过追踪矩阵。`,
2085
1947
  },
2086
1948
  };
2087
1949
  }
2088
- ctx.traceability_binding = {
2089
- requirement_ids: traceBindingIds.filter((id) => id.startsWith("REQ-")),
2090
- prototype_ids: traceBindingIds.filter((id) => id.startsWith("PROTO-")),
2091
- architecture_ids: traceBindingIds.filter((id) => id.startsWith("ARCH-")),
2092
- detail_design_ids: traceBindingIds.filter((id) => id.startsWith("DD-")),
2093
- phase_ids: traceBindingIds.filter((id) => id.startsWith("PHASE-")),
2094
- slice_ids: traceBindingIds.filter((id) => id.startsWith("SLICE-")),
2095
- acceptance_ids: traceBindingIds.filter((id) => id.startsWith("AC-")),
2096
- status: "bound",
2097
- findings: [],
2098
- };
2099
- preImplementationPayload.traceability_binding = ctx.traceability_binding;
2100
- }
2101
- const oodModule = await lazyOodSolid();
2102
- const oodGate = oodModule.evaluateOodDesignGate({
2103
- task_id: args.task_id,
2104
- intent: ctx.intent,
2105
- route: expansionRoute,
2106
- summary: ctx.ood_solid_summary,
2107
- });
2108
- if (oodGate.applicable) {
2109
- ctx.ood_solid_summary = oodGate.required_summary;
2110
- preImplementationPayload.ood_solid_summary = oodGate.required_summary;
2111
- }
2112
- if (!oodGate.allowed) {
2113
- preImplementationBlocks.push({
2114
- code: TOOL_DIAGNOSTIC_CODES.oodMissingSummary,
2115
- reason_zh: oodGate.reason_zh,
2116
- });
2117
- }
2118
- const backendModule = await lazyBackendImplementation();
2119
- const backendGate = backendModule.evaluateBackendImplementationGate({
2120
- task_id: args.task_id,
2121
- intent: ctx.intent,
2122
- route: expansionRoute,
2123
- work_package: ctx.backend_implementation_work_package,
2124
- });
2125
- if (backendGate.applicable) {
2126
- ctx.backend_implementation_work_package = backendGate.work_package;
2127
- preImplementationPayload.backend_implementation_work_package = backendGate.work_package;
2128
- }
2129
- if (!backendGate.allowed) {
2130
- preImplementationBlocks.push({
2131
- code: TOOL_DIAGNOSTIC_CODES.backendMissingSummary,
2132
- reason_zh: backendGate.reason_zh,
1950
+ const traceBindingIds = traceabilityModule.extractTraceabilityBindingIds(ctx);
1951
+ if (traceReport.matrix_exists && isImplementationRoute) {
1952
+ const missingPrefixes = ["PHASE", "SLICE", "DD", "AC"].filter((prefix) => !traceBindingIds.some((id) => id.startsWith(`${prefix}-`)));
1953
+ if (missingPrefixes.length > 0) {
1954
+ ctx.traceability_binding = {
1955
+ requirement_ids: traceBindingIds.filter((id) => id.startsWith("REQ-")),
1956
+ prototype_ids: traceBindingIds.filter((id) => id.startsWith("PROTO-")),
1957
+ architecture_ids: traceBindingIds.filter((id) => id.startsWith("ARCH-")),
1958
+ detail_design_ids: traceBindingIds.filter((id) => id.startsWith("DD-")),
1959
+ phase_ids: traceBindingIds.filter((id) => id.startsWith("PHASE-")),
1960
+ slice_ids: traceBindingIds.filter((id) => id.startsWith("SLICE-")),
1961
+ acceptance_ids: traceBindingIds.filter((id) => id.startsWith("AC-")),
1962
+ status: "missing",
1963
+ findings: missingPrefixes.map((prefix) => `编码前缺少 ${prefix}-* 绑定`),
1964
+ };
1965
+ await taskContext.save(ctx);
1966
+ return {
1967
+ result: {
1968
+ error: "编码任务必须先绑定阶段、切片、详细设计和验收 ID",
1969
+ status: "awaiting_traceability_binding",
1970
+ diagnostic_code: TOOL_DIAGNOSTIC_CODES.traceabilityBindingMissing,
1971
+ missing_prefixes: missingPrefixes,
1972
+ matrix_path: traceReport.matrix_path,
1973
+ template_path: traceabilityModule.DESIGN_IMPLEMENTATION_TRACEABILITY_TEMPLATE_PATH,
1974
+ recovery_command: "sf_expand",
1975
+ recovery: "在任务上下文或指令中明确本次实现对应的 PHASE-*、SLICE-*、DD-*、AC-*,再重新调用 sf_expand;不得脱离已确认设计直接编码,不得取消任务后改用 Bash/Edit/Write 直接实现",
1976
+ },
1977
+ };
1978
+ }
1979
+ ctx.traceability_binding = {
1980
+ requirement_ids: traceBindingIds.filter((id) => id.startsWith("REQ-")),
1981
+ prototype_ids: traceBindingIds.filter((id) => id.startsWith("PROTO-")),
1982
+ architecture_ids: traceBindingIds.filter((id) => id.startsWith("ARCH-")),
1983
+ detail_design_ids: traceBindingIds.filter((id) => id.startsWith("DD-")),
1984
+ phase_ids: traceBindingIds.filter((id) => id.startsWith("PHASE-")),
1985
+ slice_ids: traceBindingIds.filter((id) => id.startsWith("SLICE-")),
1986
+ acceptance_ids: traceBindingIds.filter((id) => id.startsWith("AC-")),
1987
+ status: "bound",
1988
+ findings: [],
1989
+ };
1990
+ preImplementationPayload.traceability_binding = ctx.traceability_binding;
1991
+ }
1992
+ const oodModule = await lazyOodSolid();
1993
+ const oodGate = oodModule.evaluateOodDesignGate({
1994
+ task_id: args.task_id,
1995
+ intent: ctx.intent,
1996
+ route: expansionRoute,
1997
+ summary: ctx.ood_solid_summary,
2133
1998
  });
2134
- }
2135
- if (ctx.classification.task_type !== "scaffold") {
2136
- const obsModule = await lazyCodeObservability();
2137
- const obsGate = obsModule.evaluateCodeObservabilityGate({
1999
+ if (oodGate.applicable) {
2000
+ ctx.ood_solid_summary = oodGate.required_summary;
2001
+ preImplementationPayload.ood_solid_summary = oodGate.required_summary;
2002
+ }
2003
+ if (!oodGate.allowed) {
2004
+ preImplementationBlocks.push({
2005
+ code: TOOL_DIAGNOSTIC_CODES.oodMissingSummary,
2006
+ reason_zh: oodGate.reason_zh,
2007
+ });
2008
+ }
2009
+ const backendModule = await lazyBackendImplementation();
2010
+ const backendGate = backendModule.evaluateBackendImplementationGate({
2138
2011
  task_id: args.task_id,
2139
2012
  intent: ctx.intent,
2140
2013
  route: expansionRoute,
2141
- changed_files: ctx.execution?.changed_files,
2142
- work_package: ctx.code_observability_work_package,
2014
+ work_package: ctx.backend_implementation_work_package,
2143
2015
  });
2144
- if (obsGate.applicable) {
2145
- ctx.code_observability_work_package = obsGate.work_package;
2146
- preImplementationPayload.code_observability_work_package = obsGate.work_package;
2016
+ if (backendGate.applicable) {
2017
+ ctx.backend_implementation_work_package = backendGate.work_package;
2018
+ preImplementationPayload.backend_implementation_work_package = backendGate.work_package;
2147
2019
  }
2148
- if (!obsGate.allowed) {
2020
+ if (!backendGate.allowed) {
2149
2021
  preImplementationBlocks.push({
2150
- code: TOOL_DIAGNOSTIC_CODES.codeObservabilityWorkPackage,
2151
- reason_zh: obsGate.reason_zh,
2022
+ code: TOOL_DIAGNOSTIC_CODES.backendMissingSummary,
2023
+ reason_zh: backendGate.reason_zh,
2152
2024
  });
2153
2025
  }
2026
+ if (ctx.classification.task_type !== "scaffold") {
2027
+ const obsModule = await lazyCodeObservability();
2028
+ const obsGate = obsModule.evaluateCodeObservabilityGate({
2029
+ task_id: args.task_id,
2030
+ intent: ctx.intent,
2031
+ route: expansionRoute,
2032
+ changed_files: ctx.execution?.changed_files,
2033
+ work_package: ctx.code_observability_work_package,
2034
+ });
2035
+ if (obsGate.applicable) {
2036
+ ctx.code_observability_work_package = obsGate.work_package;
2037
+ preImplementationPayload.code_observability_work_package = obsGate.work_package;
2038
+ }
2039
+ if (!obsGate.allowed) {
2040
+ preImplementationBlocks.push({
2041
+ code: TOOL_DIAGNOSTIC_CODES.codeObservabilityWorkPackage,
2042
+ reason_zh: obsGate.reason_zh,
2043
+ });
2044
+ }
2045
+ }
2046
+ if (Object.keys(preImplementationPayload).length > 0) {
2047
+ await taskContext.save(ctx);
2048
+ }
2049
+ if (preImplementationBlocks.length > 0) {
2050
+ return {
2051
+ result: {
2052
+ error: "编码前工程契约未全部确认,不得开始实现",
2053
+ status: "awaiting_implementation_contracts",
2054
+ diagnostic_codes: preImplementationBlocks.map((b) => b.code),
2055
+ blocking_findings: preImplementationBlocks,
2056
+ ...preImplementationPayload,
2057
+ recovery: "请同时确认 OOD/SOLID、后端工程边界、代码注释与日志契约后重新调用 sf_expand",
2058
+ },
2059
+ };
2060
+ }
2154
2061
  }
2155
- if (Object.keys(preImplementationPayload).length > 0) {
2156
- await taskContext.save(ctx);
2157
- }
2158
- if (preImplementationBlocks.length > 0) {
2159
- return {
2160
- result: {
2161
- error: "编码前工程契约未全部确认,不得开始实现",
2162
- status: "awaiting_implementation_contracts",
2163
- diagnostic_codes: preImplementationBlocks.map((b) => b.code),
2164
- blocking_findings: preImplementationBlocks,
2165
- ...preImplementationPayload,
2166
- recovery: "请同时确认 OOD/SOLID、后端工程边界、代码注释与日志契约后重新调用 sf_expand",
2167
- },
2168
- };
2169
- }
2170
- }
2062
+ } // end if (!isLightweightRoute) 轻量路径跳过 S4 gates + 编码前工程约束
2171
2063
  // Input Material Contract: 处理 hard-blocking 结果
2172
2064
  // Privacy Gate: 隐私/敏感信息策略阻断
2173
2065
  const isBlocked = expansion.prompt.startsWith("## 阻塞:输入材料禁止读取")
@@ -2321,7 +2213,6 @@ export async function registerTools(server, deps) {
2321
2213
  // 存储膨胀结果
2322
2214
  try {
2323
2215
  await taskContext.setExpansion(args.task_id, expansion);
2324
- // 存储产物记录
2325
2216
  if (expansion.output_artifact_record) {
2326
2217
  await taskContext.setArtifact(args.task_id, expansion.output_artifact_record);
2327
2218
  }
@@ -2356,6 +2247,46 @@ export async function registerTools(server, deps) {
2356
2247
  }
2357
2248
  throw updateErr;
2358
2249
  }
2250
+ // 设置 plan_proposal_gate — 必须在 setExpansion/setArtifact/updateStatus 之后
2251
+ // 这些操作各自 load-save 会覆盖之前的写入,所以 plan_proposal_gate 必须最后设置
2252
+ {
2253
+ const { evaluatePlanProposalGate, inferPlanGateTaskType } = await import("../../engine/plan_proposal_gate.js");
2254
+ const routeForGate = expansion.workflow_trace?.route ?? expansionRoute ?? "code_change";
2255
+ const taskTypeForGate = inferPlanGateTaskType(ctx.intent);
2256
+ const isLightweight = routeForGate === "acceptance" || routeForGate === "review"
2257
+ || routeForGate === "direct_answer" || routeForGate === "analysis";
2258
+ try {
2259
+ if (isLightweight) {
2260
+ await taskContext.setPlanProposalGate(args.task_id, {
2261
+ passed: true,
2262
+ plan_gate_status: "passed",
2263
+ required_level: "brief_plan",
2264
+ reason_zh: "轻量路径自动通过",
2265
+ violations: [],
2266
+ evidence_refs: [],
2267
+ blocked_actions: [],
2268
+ });
2269
+ }
2270
+ else {
2271
+ const gateResult = evaluatePlanProposalGate({
2272
+ task_id: args.task_id,
2273
+ task_type: taskTypeForGate,
2274
+ user_intent: ctx.intent,
2275
+ plan_level: "execution_plan",
2276
+ plan_summary_zh: ctx.intent.slice(0, 200),
2277
+ proposed_steps: [expansionRoute ?? "execute"],
2278
+ verification_plan: (expansion.acceptance?.automated ?? []).map((a) => a.description ?? String(a)),
2279
+ risks: [],
2280
+ evidence_refs: expansion.matched_knowledge ?? [],
2281
+ });
2282
+ await taskContext.setPlanProposalGate(args.task_id, gateResult);
2283
+ }
2284
+ }
2285
+ catch (gateErr) {
2286
+ // gate 设置失败不阻断主流程,后续写工具会重新检查
2287
+ console.error(`[sf_expand] plan_proposal_gate 设置失败: ${gateErr.message}`);
2288
+ }
2289
+ }
2359
2290
  const expandPayload = Object.keys(advisories).length > 0
2360
2291
  ? { ...expansion, advisories }
2361
2292
  : expansion;
@@ -3160,6 +3091,22 @@ export async function registerTools(server, deps) {
3160
3091
  result: { task_id: args.task_id, cancelled },
3161
3092
  };
3162
3093
  }
3094
+ case "retry_expand": {
3095
+ if (!args.task_id) {
3096
+ return {
3097
+ result: { error: "重试 expand 需要 task_id" },
3098
+ };
3099
+ }
3100
+ const retried = await taskContext.retryExpand(args.task_id);
3101
+ if (!retried) {
3102
+ return {
3103
+ result: { error: "任务不存在、非 failed 状态或缺少 classification,无法重试 expand", task_id: args.task_id },
3104
+ };
3105
+ }
3106
+ return {
3107
+ result: { task_id: args.task_id, retried: true, hint: "任务已恢复到 expanding 状态,可重新调用 sf_expand" },
3108
+ };
3109
+ }
3163
3110
  case "archive_stale": {
3164
3111
  const stale = await detectStaleCurrentTask(taskContext.getStateDir());
3165
3112
  const taskId = args.task_id ?? stale.task_id;