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
@@ -33,6 +33,7 @@ import { validateBrainstormSession } from "./brainstorm_contract.js";
33
33
  import { resolveTaskScope } from "./scope_resolver.js";
34
34
  import { evaluateLifecycleKnowledgeDecision } from "./lifecycle_knowledge_contract.js";
35
35
  import { extractTraceabilityIds } from "./traceability.js";
36
+ import { createExpandRoutePhasePolicy } from "./expand_pipeline.js";
36
37
  // 配置优先级:委托给 resolveCurrentProjectConfigReports (shared function)
37
38
  // ── 产物类型映射 ──
38
39
  function mapToArtifactKind(kind) {
@@ -246,7 +247,42 @@ const READ_ONLY_ANALYSIS_TEMPLATE = Handlebars.compile(`## 任务
246
247
  - 标注风险和假设
247
248
  - 给出建议(如有)
248
249
  `);
249
- // 5. verification_only_prompt: 验证命令生成,不修改源码
250
+ // 5. acceptance_prompt: 验收/就绪检查,不修改源码
251
+ const ACCEPTANCE_TEMPLATE = Handlebars.compile(`## 任务
252
+ {{intent}}
253
+
254
+ ## 工作流: 验收/就绪检查
255
+ - 这是只读检查路径,不修改业务源码,不生成脚手架,不创建实现文件
256
+ - 目标是判断列出的事项是否就绪、通过、满足条件或仍需处理
257
+ - 只输出检查结论、证据、阻塞项和下一步建议
258
+
259
+ ## 项目上下文
260
+ - 技术栈: {{tech_stack}}
261
+ - 产品类型: {{product_profile}}
262
+
263
+ {{#if matched_knowledge}}
264
+ ## 适用知识
265
+ {{#each matched_knowledge}}
266
+ ### {{this.name}}
267
+ {{this.content}}
268
+
269
+ {{/each}}
270
+ {{/if}}
271
+
272
+ ## 检查要求
273
+ - 对每一项给出: passed / blocked / unknown
274
+ - blocked 必须说明阻塞原因和可执行恢复动作
275
+ - unknown 必须说明缺少哪些证据
276
+ - 不要求技术决策契约、架构决策研讨、OOD/SOLID 摘要或代码可观测性工作包
277
+
278
+ {{#if acceptance.automated}}
279
+ ## 验收项
280
+ {{#each acceptance.automated}}
281
+ - [{{this.id}}] {{this.description}}
282
+ {{/each}}
283
+ {{/if}}
284
+ `);
285
+ // 6. verification_only_prompt: 验证命令生成,不修改源码
250
286
  const VERIFICATION_ONLY_TEMPLATE = Handlebars.compile(`## 任务
251
287
  {{intent}}
252
288
 
@@ -506,6 +542,35 @@ function renderPromptInjectionPlan(plan) {
506
542
  }
507
543
  return sections.length > 0 ? "---\n\n" + sections.join("\n\n") : "";
508
544
  }
545
+ function buildBlockedResult(prompt, safeGoal, options) {
546
+ return {
547
+ task_id: "",
548
+ prompt,
549
+ scope: { allowed_paths: [], readonly_paths: [], new_files_allowed: false },
550
+ acceptance: { automated: [], manual: [] },
551
+ matched_patterns: [],
552
+ matched_knowledge: [],
553
+ is_legacy_code: false,
554
+ degraded: true,
555
+ ...(options.sensitivityLabels?.length ? { sensitivity_labels: options.sensitivityLabels } : {}),
556
+ ...(options.redactionRecords?.length ? { redaction_records: options.redactionRecords } : {}),
557
+ ...(options.privacyFindings?.length ? { privacy_findings: options.privacyFindings } : {}),
558
+ ...(options.inputMaterials ? { input_materials: options.inputMaterials } : {}),
559
+ ...(options.ingestionEvidences ? { ingestion_evidences: options.ingestionEvidences } : {}),
560
+ ...(options.promptInjectionPlan ? { prompt_injection_plan: options.promptInjectionPlan } : {}),
561
+ ...(options.outputArtifactRecord ? { output_artifact_record: options.outputArtifactRecord } : {}),
562
+ ...(options.injectionReport ? { injection_report: options.injectionReport } : {}),
563
+ ...(options.projectKnowledge ? { project_knowledge: options.projectKnowledge } : {}),
564
+ ...(options.workflowTrace ? { workflow_trace: options.workflowTrace } : {}),
565
+ contract: {
566
+ goal: safeGoal,
567
+ scope: [],
568
+ constraints: options.constraints,
569
+ verification: [],
570
+ stop_conditions: options.stopConditions,
571
+ },
572
+ };
573
+ }
509
574
  /**
510
575
  * 根据执行形态选择 prompt 模板。
511
576
  * 模板使用独立工作流专属模板,包含对应约束注入。
@@ -515,6 +580,7 @@ function chooseTemplate(templateName) {
515
580
  case "single_artifact_prompt": return SINGLE_ARTIFACT_TEMPLATE;
516
581
  case "source_extraction_prompt": return SOURCE_EXTRACTION_TEMPLATE;
517
582
  case "read_only_analysis_prompt": return READ_ONLY_ANALYSIS_TEMPLATE;
583
+ case "acceptance_prompt": return ACCEPTANCE_TEMPLATE;
518
584
  case "verification_only_prompt": return VERIFICATION_ONLY_TEMPLATE;
519
585
  case "direct_answer_prompt": return DIRECT_ANSWER_TEMPLATE;
520
586
  case "architecture_design_prompt": return ARCHITECTURE_DESIGN_TEMPLATE;
@@ -593,6 +659,12 @@ export async function expand(input) {
593
659
  const { intent, classification, projectPath, config, knowledgeIndex } = input;
594
660
  let routeDecision = input.route_decision ?? classification.route_decision;
595
661
  const templateName = routeDecision ? selectPromptTemplate(routeDecision) : "code_execution_prompt";
662
+ const phasePolicy = createExpandRoutePhasePolicy(routeDecision);
663
+ const skipWorkflowContractStrict = phasePolicy.skip_workflow_contract_strict;
664
+ const skipInputMaterialHardBlock = phasePolicy.skip_input_material_hard_block;
665
+ const skipProjectKnowledgeHardBlock = phasePolicy.skip_project_knowledge_hard_block;
666
+ const skipHeavyContractGates = phasePolicy.skip_heavy_contract_gates;
667
+ const skipPreImplementationGates = phasePolicy.skip_pre_implementation_gates;
596
668
  // 配置优先级解析 — 使用共享函数
597
669
  let configResolution;
598
670
  try {
@@ -608,36 +680,30 @@ export async function expand(input) {
608
680
  routeDecision = resolveClarificationAnswers(routeDecision, intent, input.clarificationAnswers);
609
681
  }
610
682
  // ── Privacy gate— 提前到所有 early-return 之前 ──
611
- const privacyGate = evaluatePrivacyGate({
612
- intent,
613
- input_materials: input.route_decision?.input_materials?.map((m) => ({ path_or_ref: m.path ?? "" })) ?? [],
614
- });
615
- const safeIntent = privacyGate.redacted_text ?? redactSensitiveText(intent, "intent").redacted;
616
- const safeGoal = redactSensitiveText(intent.slice(0, 200), "intent_goal").redacted;
683
+ // 轻量路径(acceptance/review/read_only)只做基础脱敏,跳过深度隐私扫描
684
+ let safeIntent;
685
+ let safeGoal;
686
+ let privacyGate;
687
+ if (phasePolicy.lightweight) {
688
+ safeIntent = redactSensitiveText(intent, "intent").redacted;
689
+ safeGoal = redactSensitiveText(intent.slice(0, 200), "intent_goal").redacted;
690
+ privacyGate = { allowed: true, labels: [], redaction_records: [], findings: [], blocked_sources: [], hard_fail: false };
691
+ }
692
+ else {
693
+ privacyGate = evaluatePrivacyGate({
694
+ intent,
695
+ input_materials: input.route_decision?.input_materials?.map((m) => ({ path_or_ref: m.path ?? "" })) ?? [],
696
+ });
697
+ safeIntent = privacyGate.redacted_text ?? redactSensitiveText(intent, "intent").redacted;
698
+ safeGoal = redactSensitiveText(intent.slice(0, 200), "intent_goal").redacted;
699
+ }
617
700
  // 提前创建产物记录 (在所有 early-return 之前),确保所有路径都携带它
618
701
  // task_id 为必需 — block if missing
619
702
  let outputArtifactRecord;
620
703
  if (routeDecision?.output_artifact && routeDecision.execution_shape !== "none" && routeDecision.execution_shape !== "code_execution") {
621
704
  const artifactKind = mapToArtifactKind(routeDecision.output_artifact.kind);
622
705
  if (!input.task_id) {
623
- // task_id 缺失时始终阻止 for artifact routes
624
- return {
625
- task_id: "",
626
- prompt: `## 阻塞:产物缺少 task_id\n\n任务: ${safeIntent}\n\nexpand() 收到 task_id 为空,无法创建产物记录。`,
627
- scope: { allowed_paths: [], readonly_paths: [], new_files_allowed: false },
628
- acceptance: { automated: [], manual: [] },
629
- matched_patterns: [],
630
- matched_knowledge: [],
631
- is_legacy_code: false,
632
- degraded: true,
633
- contract: {
634
- goal: safeGoal,
635
- scope: [],
636
- constraints: ["task_id 缺失"],
637
- verification: [],
638
- stop_conditions: ["task_id 缺失"],
639
- },
640
- };
706
+ return buildBlockedResult(`## 阻塞:产物缺少 task_id\n\n任务: ${safeIntent}\n\nexpand() 收到 task_id 为空,无法创建产物记录。`, safeGoal, { constraints: ["task_id 缺失"], stopConditions: ["task_id 缺失"] });
641
707
  }
642
708
  // 收集源材料引用 from routeDecision.input_materials
643
709
  const sourceMaterialRefs = routeDecision.input_materials
@@ -645,15 +711,7 @@ export async function expand(input) {
645
711
  .map((m) => m.path);
646
712
  // 冲突时拒绝 with input.task_id
647
713
  if (routeDecision.output_artifact.taskId && routeDecision.output_artifact.taskId !== input.task_id) {
648
- return {
649
- task_id: input.task_id,
650
- prompt: `## Contract Error: task_id 不一致\n\nrouteDecision.taskId=${routeDecision.output_artifact.taskId} !== input.task_id=${input.task_id}`,
651
- scope: { allowed_paths: [], readonly_paths: [], new_files_allowed: false },
652
- acceptance: { automated: [], manual: [] },
653
- matched_patterns: [], matched_knowledge: [],
654
- is_legacy_code: false, degraded: true,
655
- contract: { goal: safeGoal, scope: [], constraints: ["task_id 不一致"], verification: [], stop_conditions: ["task_id 不一致"] },
656
- };
714
+ return buildBlockedResult(`## Contract Error: task_id 不一致\n\nrouteDecision.taskId=${routeDecision.output_artifact.taskId} !== input.task_id=${input.task_id}`, safeGoal, { constraints: ["task_id 不一致"], stopConditions: ["task_id 不一致"] });
657
715
  }
658
716
  // 路径始终使用 input.task_id
659
717
  outputArtifactRecord = createOutputArtifact({
@@ -688,95 +746,44 @@ export async function expand(input) {
688
746
  });
689
747
  recordCepIntegration("workflow_resolve", cepWfResult);
690
748
  if (!workflowContract) {
691
- return {
692
- task_id: "",
693
- prompt: `## 阻塞:无工作流契约\n\n任务: ${safeIntent}\n\nroute="${routeDecision.route}" + execution_shape="${routeDecision.execution_shape}" 没有匹配的工作流契约。\n\n请检查意图路由配置。`,
694
- scope: { allowed_paths: [], readonly_paths: [], new_files_allowed: false },
695
- acceptance: { automated: [], manual: [] },
696
- matched_patterns: [],
697
- matched_knowledge: [],
698
- is_legacy_code: false,
699
- degraded: true,
700
- workflow_trace: {
701
- workflow_id: "none",
702
- route: routeDecision.route,
703
- execution_shape: routeDecision.execution_shape,
704
- mutation_allowed: routeDecision.mutation_allowed,
749
+ return buildBlockedResult(`## 阻塞:无工作流契约\n\n任务: ${safeIntent}\n\nroute="${routeDecision.route}" + execution_shape="${routeDecision.execution_shape}" 没有匹配的工作流契约。\n\n请检查意图路由配置。`, safeGoal, {
750
+ constraints: ["无匹配工作流契约"], stopConditions: ["修复意图路由配置"],
751
+ outputArtifactRecord: outputArtifactRecord,
752
+ workflowTrace: {
753
+ workflow_id: "none", route: routeDecision.route,
754
+ execution_shape: routeDecision.execution_shape, mutation_allowed: routeDecision.mutation_allowed,
705
755
  required_mechanisms: [],
706
756
  governance_findings: [{ severity: "hard_fail", rule: "gc-no-workflow", message: `no workflow contract for ${routeDecision.route}+${routeDecision.execution_shape}` }],
707
- degraded: true,
708
- state_transition_target: "none",
709
- },
710
- contract: {
711
- goal: safeGoal,
712
- scope: [],
713
- constraints: ["无工作流契约"],
714
- verification: [],
715
- stop_conditions: ["无工作流契约"],
757
+ degraded: true, state_transition_target: "none",
716
758
  },
717
- output_artifact_record: outputArtifactRecord,
718
- };
759
+ });
719
760
  }
720
- // mutation_allowed 一致性检查
721
- if (workflowContract.mutation_allowed !== routeDecision.mutation_allowed) {
722
- return {
723
- task_id: "",
724
- prompt: `## 阻塞:mutation_allowed 不一致\n\n任务: ${safeIntent}\n\n工作流 "${workflowContract.id}" 的 mutation_allowed=${workflowContract.mutation_allowed} 与 route_decision 的 mutation_allowed=${routeDecision.mutation_allowed} 不一致。`,
725
- scope: { allowed_paths: [], readonly_paths: [], new_files_allowed: false },
726
- acceptance: { automated: [], manual: [] },
727
- matched_patterns: [],
728
- matched_knowledge: [],
729
- is_legacy_code: false,
730
- degraded: true,
731
- workflow_trace: {
732
- workflow_id: workflowContract.id,
733
- route: routeDecision.route,
734
- execution_shape: routeDecision.execution_shape,
735
- mutation_allowed: workflowContract.mutation_allowed,
761
+ // mutation_allowed 一致性检查(轻量路径跳过)
762
+ if (!skipWorkflowContractStrict && workflowContract.mutation_allowed !== routeDecision.mutation_allowed) {
763
+ return buildBlockedResult(`## 阻塞:mutation_allowed 不一致\n\n任务: ${safeIntent}\n\n工作流 "${workflowContract.id}" 的 mutation_allowed=${workflowContract.mutation_allowed} 与 route_decision 的 mutation_allowed=${routeDecision.mutation_allowed} 不一致。`, safeGoal, {
764
+ constraints: ["mutation_allowed 不一致"], stopConditions: ["mutation_allowed 不一致"],
765
+ outputArtifactRecord: outputArtifactRecord,
766
+ workflowTrace: {
767
+ workflow_id: workflowContract.id, route: routeDecision.route,
768
+ execution_shape: routeDecision.execution_shape, mutation_allowed: workflowContract.mutation_allowed,
736
769
  required_mechanisms: workflowContract.required_mechanisms,
737
770
  governance_findings: [{ severity: "hard_fail", rule: "gc-mutation-mismatch", message: `mutation_allowed mismatch: workflow=${workflowContract.mutation_allowed} route=${routeDecision.mutation_allowed}` }],
738
- degraded: true,
739
- state_transition_target: "none",
771
+ degraded: true, state_transition_target: "none",
740
772
  },
741
- contract: {
742
- goal: safeGoal,
743
- scope: [],
744
- constraints: ["mutation_allowed 不一致"],
745
- verification: [],
746
- stop_conditions: ["mutation_allowed 不一致"],
747
- },
748
- output_artifact_record: outputArtifactRecord,
749
- };
773
+ });
750
774
  }
751
- // prompt_template 一致性检查
752
- if (workflowContract.prompt_template !== templateName) {
753
- return {
754
- task_id: "",
755
- prompt: `## 阻塞:prompt_template 不一致\n\n任务: ${safeIntent}\n\n工作流 "${workflowContract.id}" 要求 prompt_template="${workflowContract.prompt_template}",但 selectPromptTemplate 返回 "${templateName}"。`,
756
- scope: { allowed_paths: [], readonly_paths: [], new_files_allowed: false },
757
- acceptance: { automated: [], manual: [] },
758
- matched_patterns: [],
759
- matched_knowledge: [],
760
- is_legacy_code: false,
761
- degraded: true,
762
- workflow_trace: {
763
- workflow_id: workflowContract.id,
764
- route: routeDecision.route,
765
- execution_shape: routeDecision.execution_shape,
766
- mutation_allowed: workflowContract.mutation_allowed,
775
+ // prompt_template 一致性检查(轻量路径跳过)
776
+ if (!skipWorkflowContractStrict && workflowContract.prompt_template !== templateName) {
777
+ return buildBlockedResult(`## 阻塞:prompt_template 不一致\n\n任务: ${safeIntent}\n\n工作流 "${workflowContract.id}" 要求 prompt_template="${workflowContract.prompt_template}",但 selectPromptTemplate 返回 "${templateName}"。`, safeGoal, {
778
+ constraints: ["prompt_template 不一致"], stopConditions: ["prompt_template 不一致"],
779
+ workflowTrace: {
780
+ workflow_id: workflowContract.id, route: routeDecision.route,
781
+ execution_shape: routeDecision.execution_shape, mutation_allowed: workflowContract.mutation_allowed,
767
782
  required_mechanisms: workflowContract.required_mechanisms,
768
783
  governance_findings: [{ severity: "hard_fail", rule: "gc-prompt-template-mismatch", message: `prompt_template mismatch: workflow="${workflowContract.prompt_template}" selected="${templateName}"` }],
769
- degraded: true,
770
- state_transition_target: "none",
771
- },
772
- contract: {
773
- goal: safeGoal,
774
- scope: [],
775
- constraints: ["prompt_template 不一致"],
776
- verification: [],
777
- stop_conditions: ["prompt_template 不一致"],
784
+ degraded: true, state_transition_target: "none",
778
785
  },
779
- };
786
+ });
780
787
  }
781
788
  }
782
789
  // 缺少必需输入时返回澄清,不生成执行 prompt(在 workflow resolution 之后)
@@ -809,40 +816,21 @@ export async function expand(input) {
809
816
  // ── 输入材料硬阻断──
810
817
  if (inputMaterials) {
811
818
  const forbiddenMaterials = inputMaterials.filter((m) => m.access_mode === "forbidden");
812
- if (forbiddenMaterials.length > 0) {
819
+ if (!skipInputMaterialHardBlock && forbiddenMaterials.length > 0) {
813
820
  const forbiddenPaths = forbiddenMaterials.map((m) => m.path_or_ref).join(", ");
814
821
  debugLog("意图膨胀: 输入材料禁止读取: %s", forbiddenPaths);
815
- return {
816
- task_id: "",
817
- prompt: `## 阻塞:输入材料禁止读取\n\n任务: ${safeIntent}\n\n以下材料路径触发禁止规则,不得读取:\n${forbiddenMaterials.map((m) => " - " + m.path_or_ref).join("\\n")}\n\n请移除这些材料后重新调用。`,
818
- scope: { allowed_paths: [], readonly_paths: [], new_files_allowed: false },
819
- acceptance: { automated: [], manual: [] },
820
- matched_patterns: [],
821
- matched_knowledge: [],
822
- is_legacy_code: false,
823
- degraded: true,
824
- input_materials: inputMaterials,
825
- ingestion_evidences: ingestionEvidences,
826
- prompt_injection_plan: promptInjectionPlan,
827
- contract: {
828
- goal: safeGoal,
829
- scope: [],
830
- constraints: ["输入材料包含禁止路径"],
831
- verification: [],
832
- stop_conditions: [],
833
- },
834
- output_artifact_record: outputArtifactRecord,
835
- workflow_trace: workflowContract ? {
836
- workflow_id: workflowContract.id,
837
- route: routeDecision.route,
838
- execution_shape: routeDecision.execution_shape,
839
- mutation_allowed: workflowContract.mutation_allowed,
822
+ return buildBlockedResult(`## 阻塞:输入材料禁止读取\n\n任务: ${safeIntent}\n\n以下材料路径触发禁止规则,不得读取:\n${forbiddenMaterials.map((m) => " - " + m.path_or_ref).join("\\n")}\n\n请移除这些材料后重新调用。`, safeGoal, {
823
+ constraints: ["输入材料包含禁止路径"], stopConditions: [],
824
+ inputMaterials: inputMaterials, ingestionEvidences: ingestionEvidences,
825
+ promptInjectionPlan: promptInjectionPlan, outputArtifactRecord: outputArtifactRecord,
826
+ workflowTrace: workflowContract ? {
827
+ workflow_id: workflowContract.id, route: routeDecision.route,
828
+ execution_shape: routeDecision.execution_shape, mutation_allowed: workflowContract.mutation_allowed,
840
829
  required_mechanisms: workflowContract.required_mechanisms,
841
830
  governance_findings: [{ severity: "hard_fail", rule: "gc-forbidden-material", message: `材料禁止读取: ${forbiddenPaths}` }],
842
- degraded: true,
843
- state_transition_target: "none",
831
+ degraded: true, state_transition_target: "none",
844
832
  } : undefined,
845
- };
833
+ });
846
834
  }
847
835
  const confirmations = input.input_material_confirmations ?? [];
848
836
  const requiresConfirmationMaterials = inputMaterials.filter((m) => m.access_mode !== "forbidden"
@@ -850,37 +838,17 @@ export async function expand(input) {
850
838
  && !confirmations.includes(m.path_or_ref));
851
839
  if (requiresConfirmationMaterials.length > 0) {
852
840
  const confirmPaths = requiresConfirmationMaterials.map((m) => m.path_or_ref);
853
- return {
854
- task_id: "",
855
- prompt: `## 澄清请求\n\n任务: ${safeIntent}\n\n以下输入材料需要人工确认后才能使用:\n${confirmPaths.map((p) => " - " + p).join("\\n")}\n\n请确认以上材料安全性后,通过 input_material_confirmations 参数重新调用。`,
856
- scope: { allowed_paths: [], readonly_paths: [], new_files_allowed: false },
857
- acceptance: { automated: [], manual: [] },
858
- matched_patterns: [],
859
- matched_knowledge: [],
860
- is_legacy_code: false,
861
- degraded: true,
862
- input_materials: inputMaterials,
863
- ingestion_evidences: ingestionEvidences,
864
- prompt_injection_plan: promptInjectionPlan,
865
- contract: {
866
- goal: safeGoal,
867
- scope: [],
868
- constraints: ["输入材料需要确认"],
869
- verification: [],
870
- stop_conditions: [],
871
- },
872
- output_artifact_record: outputArtifactRecord,
873
- workflow_trace: workflowContract ? {
874
- workflow_id: workflowContract.id,
875
- route: routeDecision.route,
876
- execution_shape: routeDecision.execution_shape,
877
- mutation_allowed: workflowContract.mutation_allowed,
841
+ return buildBlockedResult(`## 澄清请求\n\n任务: ${safeIntent}\n\n以下输入材料需要人工确认后才能使用:\n${confirmPaths.map((p) => " - " + p).join("\\n")}\n\n请确认以上材料安全性后,通过 input_material_confirmations 参数重新调用。`, safeGoal, {
842
+ constraints: ["输入材料需要确认"], stopConditions: [],
843
+ inputMaterials: inputMaterials, ingestionEvidences: ingestionEvidences,
844
+ promptInjectionPlan: promptInjectionPlan, outputArtifactRecord: outputArtifactRecord,
845
+ workflowTrace: workflowContract ? {
846
+ workflow_id: workflowContract.id, route: routeDecision.route,
847
+ execution_shape: routeDecision.execution_shape, mutation_allowed: workflowContract.mutation_allowed,
878
848
  required_mechanisms: workflowContract.required_mechanisms,
879
- governance_findings: [],
880
- degraded: true,
881
- state_transition_target: "clarifying",
849
+ governance_findings: [], degraded: true, state_transition_target: "clarifying",
882
850
  } : undefined,
883
- };
851
+ });
884
852
  }
885
853
  }
886
854
  // ── 隐私硬阻断门禁 (privacyGate 已在顶部计算) ──
@@ -890,52 +858,31 @@ export async function expand(input) {
890
858
  // 硬阻断: 意图或输入材料中包含密钥/凭证
891
859
  if (privacyGate.hard_fail) {
892
860
  debugLog("意图膨胀: 隐私策略阻断,包含 %d 个禁止来源", privacyGate.blocked_sources.length);
893
- return {
894
- task_id: "",
895
- prompt: [
896
- "## 阻塞:隐私/敏感信息策略",
897
- "",
898
- "任务内容已因隐私策略脱敏",
899
- "",
900
- "隐私门禁触发 hard_fail,以下来源包含禁止级别的敏感信息:",
901
- ...privacyGate.blocked_sources.map((s) => " - " + s),
902
- "",
903
- "治理发现:",
904
- ...privacyGate.findings.map((f) => ` [${f.severity}] ${f.rule}: ${f.message}`),
905
- "",
906
- "请移除敏感信息后重新调用。",
907
- ].join("\n"),
908
- scope: { allowed_paths: [], readonly_paths: [], new_files_allowed: false },
909
- acceptance: { automated: [], manual: [] },
910
- matched_patterns: [],
911
- matched_knowledge: [],
912
- is_legacy_code: false,
913
- degraded: true,
914
- sensitivity_labels: privacyLabels.length > 0 ? privacyLabels : undefined,
915
- redaction_records: privacyRedactions.length > 0 ? privacyRedactions : undefined,
916
- privacy_findings: privacyFindings.length > 0 ? privacyFindings : undefined,
917
- contract: {
918
- goal: safeGoal,
919
- scope: [],
920
- constraints: ["隐私/敏感信息策略阻断"],
921
- verification: [],
922
- stop_conditions: ["隐私/敏感信息策略阻断"],
923
- },
924
- input_materials: inputMaterials,
925
- ingestion_evidences: ingestionEvidences,
926
- prompt_injection_plan: promptInjectionPlan,
927
- output_artifact_record: outputArtifactRecord,
928
- workflow_trace: workflowContract ? {
929
- workflow_id: workflowContract.id,
930
- route: routeDecision.route,
931
- execution_shape: routeDecision.execution_shape,
932
- mutation_allowed: workflowContract.mutation_allowed,
861
+ return buildBlockedResult([
862
+ "## 阻塞:隐私/敏感信息策略",
863
+ "",
864
+ "任务内容已因隐私策略脱敏",
865
+ "",
866
+ "隐私门禁触发 hard_fail,以下来源包含禁止级别的敏感信息:",
867
+ ...privacyGate.blocked_sources.map((s) => " - " + s),
868
+ "",
869
+ "治理发现:",
870
+ ...privacyGate.findings.map((f) => ` [${f.severity}] ${f.rule}: ${f.message}`),
871
+ "",
872
+ "请移除敏感信息后重新调用。",
873
+ ].join("\n"), safeGoal, {
874
+ constraints: ["隐私/敏感信息策略阻断"], stopConditions: ["隐私/敏感信息策略阻断"],
875
+ sensitivityLabels: privacyLabels, redactionRecords: privacyRedactions, privacyFindings: privacyFindings,
876
+ inputMaterials: inputMaterials, ingestionEvidences: ingestionEvidences,
877
+ promptInjectionPlan: promptInjectionPlan, outputArtifactRecord: outputArtifactRecord,
878
+ workflowTrace: workflowContract ? {
879
+ workflow_id: workflowContract.id, route: routeDecision.route,
880
+ execution_shape: routeDecision.execution_shape, mutation_allowed: workflowContract.mutation_allowed,
933
881
  required_mechanisms: workflowContract.required_mechanisms,
934
882
  governance_findings: privacyFindings.map((f) => ({ severity: f.severity, rule: f.rule, message: f.message })),
935
- degraded: true,
936
- state_transition_target: "none",
883
+ degraded: true, state_transition_target: "none",
937
884
  } : undefined,
938
- };
885
+ });
939
886
  }
940
887
  // 1. 查询知识库(路由驱动匹配)
941
888
  const affectedRepos = getAffectedRepos(classification);
@@ -971,57 +918,42 @@ export async function expand(input) {
971
918
  severity: finding.severity,
972
919
  message_zh: finding.message_zh,
973
920
  })));
974
- if (projectKnowledgeReport.hard_fail_count > 0) {
921
+ if (!skipProjectKnowledgeHardBlock && projectKnowledgeReport.hard_fail_count > 0) {
975
922
  const lines = projectKnowledgeFindings
976
923
  .filter((finding) => finding.severity === "hard_fail")
977
924
  .map((finding) => `- [${finding.code}] ${finding.rel_path}: ${finding.message_zh}`);
978
- return {
979
- task_id: "",
980
- prompt: [
981
- "## 阻塞:用户项目知识规则不可消费",
982
- "",
983
- `任务: ${safeIntent}`,
984
- "",
985
- "项目 .soloforge/knowledge 中存在 hard rule 身份、路由或触发条件问题。",
986
- "这些规则如果继续放行,会让 AI 在真实任务中无法稳定知道该遵守哪些项目规则。",
987
- "",
988
- ...lines,
989
- "",
990
- "请修复以上项目规则后重新调用。",
991
- ].join("\n"),
992
- scope: { allowed_paths: [], readonly_paths: [], new_files_allowed: false },
993
- acceptance: { automated: [], manual: [] },
994
- matched_patterns: [],
995
- matched_knowledge: [],
996
- is_legacy_code: false,
997
- degraded: true,
998
- contract: {
999
- goal: safeGoal,
1000
- scope: [],
1001
- constraints: ["用户项目知识规则不可消费"],
1002
- verification: ["soloforge audit-project-knowledge"],
1003
- stop_conditions: ["修复 .soloforge/knowledge hard rule 路由或身份冲突"],
1004
- },
1005
- project_knowledge: {
925
+ return buildBlockedResult([
926
+ "## 阻塞:用户项目知识规则不可消费",
927
+ "",
928
+ `任务: ${safeIntent}`,
929
+ "",
930
+ "项目 .soloforge/knowledge 中存在 hard rule 身份、路由或触发条件问题。",
931
+ "这些规则如果继续放行,会让 AI 在真实任务中无法稳定知道该遵守哪些项目规则。",
932
+ "",
933
+ ...lines,
934
+ "",
935
+ "请修复以上项目规则后重新调用。",
936
+ ].join("\n"), safeGoal, {
937
+ constraints: ["用户项目知识规则不可消费"],
938
+ stopConditions: ["修复 .soloforge/knowledge hard rule 路由或身份冲突"],
939
+ projectKnowledge: {
1006
940
  total: projectKnowledgeReport.total,
1007
941
  hard_fail_count: projectKnowledgeReport.hard_fail_count,
1008
942
  warning_count: projectKnowledgeReport.warning_count,
1009
943
  selected: [],
1010
944
  findings: projectKnowledgeFindings,
1011
945
  },
1012
- workflow_trace: workflowContract ? {
1013
- workflow_id: workflowContract.id,
1014
- route: routeDecision?.route ?? "",
946
+ workflowTrace: workflowContract ? {
947
+ workflow_id: workflowContract.id, route: routeDecision?.route ?? "",
1015
948
  execution_shape: routeDecision?.execution_shape ?? "",
1016
949
  mutation_allowed: workflowContract.mutation_allowed,
1017
950
  required_mechanisms: workflowContract.required_mechanisms,
1018
951
  governance_findings: projectKnowledgeFindings
1019
952
  .filter((finding) => finding.severity === "hard_fail")
1020
953
  .map((finding) => ({ severity: "hard_fail", rule: finding.code, message: `${finding.rel_path}: ${finding.message_zh}` })),
1021
- degraded: true,
1022
- state_transition_target: "manual_required",
954
+ degraded: true, state_transition_target: "manual_required",
1023
955
  } : undefined,
1024
- };
956
+ });
1025
957
  }
1026
958
  const projectKnowledgeSelection = lifecycleKnowledgeDecision.knowledge_selection;
1027
959
  const selectedProjectKnowledge = projectKnowledgeSelection.selected.map((decision) => decision.asset);
@@ -1092,38 +1024,20 @@ export async function expand(input) {
1092
1024
  ? injectionReport.missing_required_knowledge.map((m) => m.reason).join("\n")
1093
1025
  : "";
1094
1026
  const missingSection = missingDesc ? `\n\n缺少的必需知识:\n${missingDesc}` : "";
1095
- return {
1096
- task_id: "",
1097
- prompt: `## 阻塞:注入校验失败\n\n任务: ${safeIntent}\n\n以下硬性校验未通过:\n${failMessages}${missingSection}\n\n请修正后重新调用。`,
1098
- scope: { allowed_paths: [], readonly_paths: [], new_files_allowed: false },
1099
- acceptance: { automated: [], manual: [] },
1100
- matched_patterns: [],
1101
- matched_knowledge: [],
1102
- is_legacy_code: false,
1103
- degraded: true,
1104
- injection_report: injectionReport,
1105
- input_materials: inputMaterials,
1106
- ingestion_evidences: ingestionEvidences,
1107
- prompt_injection_plan: promptInjectionPlan,
1108
- contract: {
1109
- goal: safeGoal,
1110
- scope: [],
1111
- constraints: ["注入校验失败,等待修正"],
1112
- verification: [],
1113
- stop_conditions: ["注入校验失败"],
1114
- },
1115
- output_artifact_record: outputArtifactRecord,
1116
- workflow_trace: workflowContract ? {
1117
- workflow_id: workflowContract.id,
1118
- route: routeDecision?.route ?? "",
1027
+ return buildBlockedResult(`## 阻塞:注入校验失败\n\n任务: ${safeIntent}\n\n以下硬性校验未通过:\n${failMessages}${missingSection}\n\n请修正后重新调用。`, safeGoal, {
1028
+ constraints: ["注入校验失败,等待修正"], stopConditions: ["注入校验失败"],
1029
+ injectionReport: injectionReport,
1030
+ inputMaterials: inputMaterials, ingestionEvidences: ingestionEvidences,
1031
+ promptInjectionPlan: promptInjectionPlan, outputArtifactRecord: outputArtifactRecord,
1032
+ workflowTrace: workflowContract ? {
1033
+ workflow_id: workflowContract.id, route: routeDecision?.route ?? "",
1119
1034
  execution_shape: routeDecision?.execution_shape ?? "",
1120
1035
  mutation_allowed: workflowContract.mutation_allowed,
1121
1036
  required_mechanisms: workflowContract.required_mechanisms,
1122
1037
  governance_findings: workflowGovernanceFindings,
1123
- degraded: true,
1124
- state_transition_target: "none",
1038
+ degraded: true, state_transition_target: "none",
1125
1039
  } : undefined,
1126
- };
1040
+ });
1127
1041
  }
1128
1042
  // 2. 检测遗留惯例(只对 code_execution 执行)
1129
1043
  const legacyConventions = [];
@@ -1205,8 +1119,8 @@ export async function expand(input) {
1205
1119
  const uncertaintyTriggers = skipHeavyAnalysis
1206
1120
  ? []
1207
1121
  : buildUncertaintyTriggers(classification, intent);
1208
- // 机制 1: 意图熔断器检查
1209
- const breakerResult = checkCircuitBreaker({
1122
+ // 机制 1: 意图熔断器检查(轻量路径跳过)
1123
+ const breakerResult = skipPreImplementationGates ? { tripped: false, confidence: 1.0 } : checkCircuitBreaker({
1210
1124
  intent,
1211
1125
  classification,
1212
1126
  matchedEntries: filteredEntries,
@@ -1238,10 +1152,10 @@ ${breakerResult.brainstorm.decision_questions.map((q, i) => `${i + 1}. ${q.quest
1238
1152
  : "请先补齐阻塞输入,或在产物中标记 [Manual Confirm] 后再生成产物。"}
1239
1153
  `;
1240
1154
  }
1241
- // problem-16: 技术决策验证 — 膨胀涉及架构决策时校验 human gate
1155
+ // problem-16: 技术决策验证 — 仅施工路径 + 高复杂度时校验 human gate
1242
1156
  let technologyDecisionEvidence;
1243
1157
  let techDecisionRequiresManual = false;
1244
- if (routeDecision?.execution_shape === "code_execution" && classification.complexity === "high") {
1158
+ if (!skipHeavyContractGates && routeDecision?.execution_shape === "code_execution" && classification.complexity === "high") {
1245
1159
  const decisionContract = {
1246
1160
  decision_id: `td-${input.task_id ?? "unknown"}`,
1247
1161
  decision_scope: "architecture",
@@ -1280,34 +1194,62 @@ ${breakerResult.brainstorm.decision_questions.map((q, i) => `${i + 1}. ${q.quest
1280
1194
  });
1281
1195
  }
1282
1196
  }
1283
- // problem-41: 脑暴完整性校验 — 熔断触发时验证候选方案
1197
+ // problem-41: 脑暴完整性校验 — 仅施工路径校验
1198
+ // 优先使用用户提供的 brainstorm_session,否则从 circuit breaker 自动生成
1284
1199
  let brainstormEvidence;
1285
1200
  let brainstormRequiresClarification = false;
1286
- if (circuitBreakerOutput?.brainstorm) {
1287
- const session = {
1288
- session_id: `bs-${input.task_id ?? "unknown"}`,
1289
- trigger: "ambiguous_goal",
1290
- problem_statement: input.intent,
1291
- options: circuitBreakerOutput.brainstorm.decision_questions.map((q, i) => ({
1292
- option_id: `opt-${i}`,
1293
- title: q.question,
1294
- description: `${q.option_a} vs ${q.option_b}`,
1295
- pros: [q.option_a],
1296
- cons: [q.option_b],
1297
- applicable_when: [],
1298
- not_applicable_when: [],
1299
- failure_conditions: [],
1300
- verification_method: [],
1301
- estimated_risk: "medium",
1302
- reversibility: "partially_reversible",
1303
- required_human_decisions: [],
1304
- })),
1305
- recommended_option_id: "opt-0",
1306
- rejected_option_ids: [],
1307
- human_gate_required: true,
1308
- evidence_refs: [],
1309
- brainstorm_status: "options_ready",
1310
- };
1201
+ if (!skipHeavyContractGates && (input.brainstorm_session || circuitBreakerOutput?.brainstorm)) {
1202
+ // 优先使用用户提供的 brainstorm_session
1203
+ let session;
1204
+ if (input.brainstorm_session) {
1205
+ session = input.brainstorm_session;
1206
+ }
1207
+ else {
1208
+ const bs = circuitBreakerOutput?.brainstorm;
1209
+ if (!bs) {
1210
+ // 仅在 circuitBreakerOutput?.brainstorm 存在时进入此分支,但 TS 无法推断
1211
+ // 安全跳过:如果 brainstorm 不存在则跳过整个校验
1212
+ return {
1213
+ task_id: "",
1214
+ prompt: `## 脑暴数据缺失\n\n任务: ${safeIntent}\n\n内部脑暴数据不可用,跳过脑暴校验。`,
1215
+ scope: { allowed_paths: [], readonly_paths: [], new_files_allowed: false },
1216
+ acceptance: { automated: [], manual: [] },
1217
+ matched_patterns: [],
1218
+ matched_knowledge: [],
1219
+ is_legacy_code: false,
1220
+ degraded: true,
1221
+ contract: { goal: safeGoal, scope: [], constraints: ["脑暴数据缺失"], verification: [], stop_conditions: [] },
1222
+ };
1223
+ }
1224
+ session = {
1225
+ session_id: `bs-${input.task_id ?? "unknown"}`,
1226
+ trigger: "ambiguous_goal",
1227
+ problem_statement: input.intent,
1228
+ options: bs.decision_questions.map((q, i) => ({
1229
+ option_id: `opt-${i}`,
1230
+ title: q.question,
1231
+ description: `${q.option_a} vs ${q.option_b}`,
1232
+ pros: [q.option_a],
1233
+ cons: [q.option_b],
1234
+ applicable_when: [],
1235
+ not_applicable_when: [],
1236
+ // 从 decision question 自动填充失败条件
1237
+ failure_conditions: [
1238
+ `选择 ${q.option_a} 失败的条件: ${q.option_a} 的局限性场景`,
1239
+ `选择 ${q.option_b} 失败的条件: ${q.option_b} 的局限性场景`,
1240
+ ],
1241
+ verification_method: [],
1242
+ estimated_risk: "medium",
1243
+ reversibility: "partially_reversible",
1244
+ required_human_decisions: [],
1245
+ })),
1246
+ recommended_option_id: "opt-0",
1247
+ rejected_option_ids: [],
1248
+ human_gate_required: true,
1249
+ evidence_refs: [],
1250
+ brainstorm_status: "options_ready",
1251
+ };
1252
+ }
1311
1253
  const brainstormResult = validateBrainstormSession(session);
1312
1254
  // 治理结果写入 workflow_trace.governance_findings
1313
1255
  if (!brainstormResult.valid) {
@@ -1635,7 +1577,7 @@ function inferTypeFromSubDir(subDir) {
1635
1577
  */
1636
1578
  function queryKnowledgeForRoute(knowledgeIndex, intent, scopeFilters, config, routeDecision, limit) {
1637
1579
  if (!routeDecision) {
1638
- // 无 route_decision 时回退到旧逻辑
1580
+ // 无 route_decision 时回退到旧逻辑(不传 route,不强制路由匹配)
1639
1581
  return knowledgeIndex.query({
1640
1582
  scope: scopeFilters,
1641
1583
  products: [config.product_profile],
@@ -1643,6 +1585,7 @@ function queryKnowledgeForRoute(knowledgeIndex, intent, scopeFilters, config, ro
1643
1585
  limit,
1644
1586
  });
1645
1587
  }
1588
+ const currentRoute = routeDecision.route;
1646
1589
  const seen = new Set();
1647
1590
  const results = [];
1648
1591
  function addEntries(entries) {
@@ -1660,6 +1603,7 @@ function queryKnowledgeForRoute(knowledgeIndex, intent, scopeFilters, config, ro
1660
1603
  scope: scopeFilters,
1661
1604
  products: [config.product_profile],
1662
1605
  type: "pipeline_procedure",
1606
+ route: currentRoute,
1663
1607
  keywords: extractKeywords(pipelineName),
1664
1608
  limit: 3,
1665
1609
  });
@@ -1680,6 +1624,7 @@ function queryKnowledgeForRoute(knowledgeIndex, intent, scopeFilters, config, ro
1680
1624
  scope: scopeFilters,
1681
1625
  products: [config.product_profile],
1682
1626
  type: "acceptance_template",
1627
+ route: currentRoute,
1683
1628
  keywords: [],
1684
1629
  limit: 100,
1685
1630
  });
@@ -1722,6 +1667,7 @@ function queryKnowledgeForRoute(knowledgeIndex, intent, scopeFilters, config, ro
1722
1667
  const keywordResults = knowledgeIndex.query({
1723
1668
  scope: scopeFilters,
1724
1669
  products: [config.product_profile],
1670
+ route: currentRoute,
1725
1671
  keywords: extractKeywords(intent),
1726
1672
  limit: limit,
1727
1673
  });