@wizdear/atlas-code 0.2.4 → 0.2.5

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 (93) hide show
  1. package/dist/agent-factory.d.ts +8 -1
  2. package/dist/agent-factory.d.ts.map +1 -1
  3. package/dist/agent-factory.js +42 -2
  4. package/dist/agent-factory.js.map +1 -1
  5. package/dist/cli.d.ts +7 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +8 -0
  8. package/dist/cli.js.map +1 -1
  9. package/dist/discovery.d.ts +9 -0
  10. package/dist/discovery.d.ts.map +1 -1
  11. package/dist/discovery.js +4 -4
  12. package/dist/discovery.js.map +1 -1
  13. package/dist/extension.d.ts +9 -2
  14. package/dist/extension.d.ts.map +1 -1
  15. package/dist/extension.js +1096 -333
  16. package/dist/extension.js.map +1 -1
  17. package/dist/gate.d.ts +1 -1
  18. package/dist/gate.d.ts.map +1 -1
  19. package/dist/gate.js.map +1 -1
  20. package/dist/orchestrator.d.ts +0 -2
  21. package/dist/orchestrator.d.ts.map +1 -1
  22. package/dist/orchestrator.js +0 -1
  23. package/dist/orchestrator.js.map +1 -1
  24. package/dist/pipeline-editor.d.ts +2 -0
  25. package/dist/pipeline-editor.d.ts.map +1 -1
  26. package/dist/pipeline-editor.js +36 -5
  27. package/dist/pipeline-editor.js.map +1 -1
  28. package/dist/pipeline.d.ts +2 -5
  29. package/dist/pipeline.d.ts.map +1 -1
  30. package/dist/pipeline.js +4 -3
  31. package/dist/pipeline.js.map +1 -1
  32. package/dist/planner.d.ts +9 -0
  33. package/dist/planner.d.ts.map +1 -1
  34. package/dist/planner.js +20 -10
  35. package/dist/planner.js.map +1 -1
  36. package/dist/roles/architect.d.ts +1 -1
  37. package/dist/roles/architect.d.ts.map +1 -1
  38. package/dist/roles/architect.js +1 -1
  39. package/dist/roles/architect.js.map +1 -1
  40. package/dist/roles/documenter.d.ts +1 -1
  41. package/dist/roles/documenter.d.ts.map +1 -1
  42. package/dist/roles/documenter.js +11 -0
  43. package/dist/roles/documenter.js.map +1 -1
  44. package/dist/roles/index.d.ts +1 -0
  45. package/dist/roles/index.d.ts.map +1 -1
  46. package/dist/roles/index.js +3 -0
  47. package/dist/roles/index.js.map +1 -1
  48. package/dist/roles/recover.d.ts +5 -0
  49. package/dist/roles/recover.d.ts.map +1 -0
  50. package/dist/roles/recover.js +82 -0
  51. package/dist/roles/recover.js.map +1 -0
  52. package/dist/router.d.ts.map +1 -1
  53. package/dist/router.js +6 -6
  54. package/dist/router.js.map +1 -1
  55. package/dist/standards.d.ts.map +1 -1
  56. package/dist/standards.js +1 -0
  57. package/dist/standards.js.map +1 -1
  58. package/dist/step-executor.d.ts +2 -0
  59. package/dist/step-executor.d.ts.map +1 -1
  60. package/dist/step-executor.js +16 -4
  61. package/dist/step-executor.js.map +1 -1
  62. package/dist/store.d.ts +3 -0
  63. package/dist/store.d.ts.map +1 -1
  64. package/dist/store.js +48 -19
  65. package/dist/store.js.map +1 -1
  66. package/dist/system-architect.d.ts +9 -0
  67. package/dist/system-architect.d.ts.map +1 -1
  68. package/dist/system-architect.js +11 -9
  69. package/dist/system-architect.js.map +1 -1
  70. package/dist/telegram/bridge.d.ts +39 -0
  71. package/dist/telegram/bridge.d.ts.map +1 -0
  72. package/dist/telegram/bridge.js +380 -0
  73. package/dist/telegram/bridge.js.map +1 -0
  74. package/dist/telegram/formatter.d.ts +15 -0
  75. package/dist/telegram/formatter.d.ts.map +1 -0
  76. package/dist/telegram/formatter.js +86 -0
  77. package/dist/telegram/formatter.js.map +1 -0
  78. package/dist/telegram/renderer.d.ts +45 -0
  79. package/dist/telegram/renderer.d.ts.map +1 -0
  80. package/dist/telegram/renderer.js +150 -0
  81. package/dist/telegram/renderer.js.map +1 -0
  82. package/dist/telegram/telegram-api.d.ts +84 -0
  83. package/dist/telegram/telegram-api.d.ts.map +1 -0
  84. package/dist/telegram/telegram-api.js +134 -0
  85. package/dist/telegram/telegram-api.js.map +1 -0
  86. package/dist/types.d.ts +10 -1
  87. package/dist/types.d.ts.map +1 -1
  88. package/dist/types.js.map +1 -1
  89. package/dist/ui.d.ts +1 -1
  90. package/dist/ui.d.ts.map +1 -1
  91. package/dist/ui.js +2 -0
  92. package/dist/ui.js.map +1 -1
  93. package/package.json +1 -1
package/dist/gate.d.ts CHANGED
@@ -5,7 +5,7 @@ import type { GateCondition, PipelineStep, VibeConfig } from "./types.js";
5
5
  /** Gate validation result. */
6
6
  export interface GateResult {
7
7
  passed: boolean;
8
- action: "approved" | "rejected" | "feedback" | "skip" | "abort";
8
+ action: "approved" | "rejected" | "feedback" | "skip" | "abort" | "pause";
9
9
  feedback?: string;
10
10
  }
11
11
  /** Escalation reason. */
@@ -1 +1 @@
1
- {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAqD,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAgB,aAAa,EAAqB,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAS3G,8BAA8B;AAC9B,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,yBAAyB;AACzB,MAAM,MAAM,gBAAgB,GACzB,oBAAoB,GACpB,wBAAwB,GACxB,2BAA2B,GAC3B,uBAAuB,CAAC;AAE3B,wBAAwB;AACxB,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;CACxB;AAED,sCAAsC;AACtC,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAIhH,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAoB9D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAShE;AAED,yCAAyC;AACzC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,mBAAmB,GAAG,IAAI,CAsB3F;AAED,0DAA0D;AAC1D,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAMzF;AAED,oDAAoD;AACpD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAwB9E;AA0BD;;;GAGG;AACH,qBAAa,WAAW;IAItB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IALxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAEnC,YACkB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,UAAU,EAClB,KAAK,CAAC,wBAAY,EACnC,MAAM,CAAC,EAAE,UAAU,EAGnB;IAED;;OAEG;IACG,cAAc,CAAC,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAoBlF;IAED;;;;;;OAMG;IACG,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CA6C9G;IAED;;OAEG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA4CzE;YAIa,eAAe;YAIf,aAAa;YAyBb,kBAAkB;YAwBlB,mBAAmB;YAwBnB,oBAAoB;YAIpB,wBAAwB;YAQxB,YAAY;CAa1B","sourcesContent":["import type { Model } from \"@mariozechner/pi-ai\";\nimport { createModuleLogger, type ModuleLogger, noopLogger, type VibeLogger } from \"./logger.js\";\nimport type { VibeStore } from \"./store.js\";\nimport type { ArtifactName, GateCondition, GateConditionType, PipelineStep, VibeConfig } from \"./types.js\";\nimport {\n\textractRegressionVerdictWithLLM,\n\textractReviewVerdictWithLLM,\n\textractTestVerdictWithLLM,\n} from \"./verdict-extractor.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Gate validation result. */\nexport interface GateResult {\n\tpassed: boolean;\n\taction: \"approved\" | \"rejected\" | \"feedback\" | \"skip\" | \"abort\";\n\tfeedback?: string;\n}\n\n/** Escalation reason. */\nexport type EscalationReason =\n\t| \"security_violation\"\n\t| \"architecture_violation\"\n\t| \"standards_fail_persistent\"\n\t| \"requirement_ambiguity\";\n\n/** Escalation event. */\nexport interface EscalationEvent {\n\treason: EscalationReason;\n\tfeatureId: string;\n\tdetail: string;\n\tsuggestedAction: string;\n}\n\n/** User approval request callback. */\nexport type ApprovalRequestFn = (step: PipelineStep, featureId: string, summary: string) => Promise<GateResult>;\n\n// ─── Parsing ─────────────────────────────────────────────────────────────────\n\n/** Parses test-report.md for overall pass/fail. Passes when failure count is 0. */\nexport function parseTestReportPassed(content: string): boolean {\n\tconst patterns: RegExp[] = [\n\t\t// Table: \"| 실패 | N |\", \"| 失敗 | N |\"\n\t\t/\\|\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t// Table: \"| Failures | N |\", \"| Failed | N |\", \"| Fail | N |\"\n\t\t/\\|\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t// List: \"- Failed: N\", \"* Failures: N\", \"- **Failed**: N\"\n\t\t/[-*]\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t// Plain text: \"Failed: N\", \"Failures: N\", \"**Failed**: N\"\n\t\t/\\*{0,2}[Ff]ail(?:ed|ures?)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t// Korean/Japanese list/plain: \"- 실패: N\", \"실패: N\", \"- 失敗: N\", \"失敗: N\"\n\t\t/[-*]?\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t];\n\tfor (const pattern of patterns) {\n\t\tconst match = content.match(pattern);\n\t\tif (match) {\n\t\t\treturn parseInt(match[1], 10) === 0;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Checks if any failure count regex matches in test-report.md.\n * Returns true if matched (parse succeeded, regardless of result), false if no match (fallback needed).\n */\nexport function hasTestReportRegexMatch(content: string): boolean {\n\tconst patterns: RegExp[] = [\n\t\t/\\|\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t/\\|\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t/[-*]\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t/\\*{0,2}[Ff]ail(?:ed|ures?)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t/[-*]?\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t];\n\treturn patterns.some((p) => p.test(content));\n}\n\n/** Parses the verdict from review.md. */\nexport function parseReviewVerdict(content: string): \"approved\" | \"changes_requested\" | null {\n\tconst patterns: RegExp[] = [\n\t\t// \"## 판정: APPROVED\" / \"## 판정: **APPROVED**\" / \"## 판정: CHANGES_REQUESTED\" (Korean heading)\n\t\t/##\\s*판정\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"## Verdict: APPROVED\" / \"## Verdict: **APPROVED**\"\n\t\t/##\\s*[Vv]erdict\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"판정: APPROVED\" (without ##) / \"판정: **APPROVED**\" (Korean plain)\n\t\t/판정\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"Verdict: APPROVED\" (without ##) / \"Verdict: **APPROVED**\"\n\t\t/[Vv]erdict\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"Review Status: APPROVED\" / \"**Review Status**: ✅ **APPROVED**\"\n\t\t/[Rr]eview\\s+[Ss]tatus\\s*[::]\\s*(?:✅\\s*)?(?:❌\\s*)?\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// Japanese: \"判定: APPROVED\"\n\t\t/判定\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t];\n\tfor (const pattern of patterns) {\n\t\tconst match = content.match(pattern);\n\t\tif (match) {\n\t\t\treturn match[1].toLowerCase() === \"approved\" ? \"approved\" : \"changes_requested\";\n\t\t}\n\t}\n\treturn null;\n}\n\n/** Parses Standards Compliance Overall from review.md. */\nexport function parseStandardsCompliance(content: string): \"pass\" | \"warn\" | \"fail\" | null {\n\tconst match = content.match(/Overall\\s*[::]\\s*\\*{0,2}(PASS|WARN|FAIL)\\*{0,2}/i);\n\tif (match) {\n\t\treturn match[1].toLowerCase() as \"pass\" | \"warn\" | \"fail\";\n\t}\n\treturn null;\n}\n\n/** Parses the verdict from regression-report.md. */\nexport function parseRegressionVerdict(content: string): \"pass\" | \"fail\" | null {\n\tconst patterns: RegExp[] = [\n\t\t// \"## 판정: PASS\" / \"## 판정: **PASS**\" / \"## 판정: FAIL\" (Korean heading)\n\t\t/##\\s*판정\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"## Verdict: PASS\" / \"## Verdict: **PASS**\"\n\t\t/##\\s*[Vv]erdict\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"판정: PASS\" (without ##) / \"판정: **PASS**\" (Korean plain)\n\t\t/판정\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"Verdict: PASS\" (without ##) / \"Verdict: **PASS**\"\n\t\t/[Vv]erdict\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"Regression Status: PASS\" / \"**Regression Status**: ✅ **PASS**\"\n\t\t/[Rr]egression\\s+[Ss]tatus\\s*[::]\\s*(?:✅\\s*)?(?:❌\\s*)?\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"Status: PASS\" / \"**Status**: PASS\" (generic fallback)\n\t\t/\\*{0,2}[Ss]tatus\\*{0,2}\\s*[::]\\s*(?:✅\\s*)?(?:❌\\s*)?\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// Japanese: \"判定: PASS\"\n\t\t/判定\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t];\n\tfor (const pattern of patterns) {\n\t\tconst match = content.match(pattern);\n\t\tif (match) {\n\t\t\treturn match[1].toLowerCase() as \"pass\" | \"fail\";\n\t\t}\n\t}\n\treturn null;\n}\n\n// ─── Gate-to-Config Mapping ──────────────────────────────────────────────────\n\n/** Checks if a gate type requires user approval in gate_auto mode. */\nfunction isGateEnabledInConfig(gateType: GateConditionType, config: VibeConfig): boolean {\n\tswitch (gateType) {\n\t\tcase \"review_approve\":\n\t\t\treturn config.gates.requireDesignApproval;\n\t\tcase \"merge_approve\":\n\t\t\treturn config.gates.requireMergeApproval;\n\t\tcase \"diagnosis_approve\":\n\t\t\treturn config.gates.requireDiagnosisApproval;\n\t\tcase \"test_pass\":\n\t\tcase \"all_tests_pass\":\n\t\tcase \"regression_pass\":\n\t\t\treturn false; // Test gates use automatic validation only\n\t\tcase \"file_exists\":\n\t\t\treturn false;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n// ─── GateChecker ─────────────────────────────────────────────────────────────\n\n/**\n * Gate checker. Manages artifact-parsing-based automatic validation\n * and approval flows based on autonomy level.\n */\nexport class GateChecker {\n\tprivate readonly log: ModuleLogger;\n\n\tconstructor(\n\t\tprivate readonly store: VibeStore,\n\t\tprivate readonly config: VibeConfig,\n\t\tprivate readonly model?: Model<any>,\n\t\tlogger?: VibeLogger,\n\t) {\n\t\tthis.log = createModuleLogger(logger ?? noopLogger, \"gate\");\n\t}\n\n\t/**\n\t * Automatically validates a gate condition.\n\t */\n\tasync checkCondition(condition: GateCondition, featureId: string): Promise<boolean> {\n\t\tthis.log.info(`Checking gate condition: ${condition.type}`, { featureId, target: condition.target });\n\t\tswitch (condition.type) {\n\t\t\tcase \"file_exists\":\n\t\t\t\treturn this.checkFileExists(condition.target, featureId);\n\t\t\tcase \"test_pass\":\n\t\t\t\treturn this.checkTestPass(featureId);\n\t\t\tcase \"review_approve\":\n\t\t\t\treturn this.checkReviewApprove(featureId);\n\t\t\tcase \"all_tests_pass\":\n\t\t\t\treturn this.checkRegressionPass(featureId);\n\t\t\tcase \"regression_pass\":\n\t\t\t\treturn this.checkRegressionPass(featureId);\n\t\t\tcase \"diagnosis_approve\":\n\t\t\t\treturn this.checkDiagnosisExists(featureId);\n\t\t\tcase \"merge_approve\":\n\t\t\t\treturn this.checkReviewApprove(featureId);\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Evaluates a gate based on autonomy level.\n\t *\n\t * - full_auto: Automatic validation only. Approved on pass, rejected on fail.\n\t * - gate_auto: Requests user approval only for configured gates. Others are automatic.\n\t * - step_by_step: Always requests user approval after automatic validation.\n\t */\n\tasync evaluate(step: PipelineStep, featureId: string, requestApproval?: ApprovalRequestFn): Promise<GateResult> {\n\t\tthis.log.info(`Evaluating gate for step: ${step.action}`, { featureId, autonomy: this.config.autonomyLevel });\n\n\t\t// No gate condition: handle based on autonomy level\n\t\tif (!step.gate) {\n\t\t\treturn this.handleNoGate(step, featureId, requestApproval);\n\t\t}\n\n\t\t// Perform automatic validation\n\t\tconst conditionPassed = await this.checkCondition(step.gate, featureId);\n\t\tthis.log.info(`Gate condition ${step.gate.type}: ${conditionPassed ? \"passed\" : \"failed\"}`, { featureId });\n\n\t\t// Additional Standards Compliance check for review_approve\n\t\tif (step.gate.type === \"review_approve\" && conditionPassed) {\n\t\t\tconst complianceResult = await this.checkStandardsCompliance(featureId);\n\t\t\tif (complianceResult === \"fail\") {\n\t\t\t\treturn { passed: false, action: \"rejected\", feedback: \"Standards Compliance Overall: FAIL\" };\n\t\t\t}\n\t\t}\n\n\t\tconst autonomy = this.config.autonomyLevel;\n\n\t\tif (autonomy === \"full_auto\") {\n\t\t\treturn conditionPassed ? { passed: true, action: \"approved\" } : { passed: false, action: \"rejected\" };\n\t\t}\n\n\t\tif (autonomy === \"gate_auto\") {\n\t\t\tif (!conditionPassed) {\n\t\t\t\treturn { passed: false, action: \"rejected\" };\n\t\t\t}\n\t\t\t// Check if this gate requires approval in config\n\t\t\tif (isGateEnabledInConfig(step.gate.type, this.config) && requestApproval) {\n\t\t\t\treturn requestApproval(step, featureId, `Gate: ${step.gate.type} passed. Approve to continue.`);\n\t\t\t}\n\t\t\treturn { passed: true, action: \"approved\" };\n\t\t}\n\n\t\t// step_by_step: always request user approval after automatic validation\n\t\tif (!conditionPassed) {\n\t\t\treturn { passed: false, action: \"rejected\" };\n\t\t}\n\t\tif (requestApproval) {\n\t\t\treturn requestApproval(step, featureId, `Step completed: ${step.action}. Approve to continue.`);\n\t\t}\n\t\treturn { passed: true, action: \"approved\" };\n\t}\n\n\t/**\n\t * Analyzes review.md to detect escalation conditions.\n\t */\n\tasync detectEscalation(featureId: string): Promise<EscalationEvent | null> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"review.md\"))) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst content = await this.store.readArtifact(featureId, \"review.md\");\n\n\t\t// security-policy FAIL 감지\n\t\tconst securityMatch = content.match(/security[_-]policy\\s*\\|\\s*FAIL/i);\n\t\tif (securityMatch) {\n\t\t\tthis.log.warn(\"Escalation detected: security_violation\", { featureId });\n\t\t\treturn {\n\t\t\t\treason: \"security_violation\",\n\t\t\t\tfeatureId,\n\t\t\t\tdetail: \"Security policy violation detected in review\",\n\t\t\t\tsuggestedAction: \"Halt pipeline immediately. Manual security review required.\",\n\t\t\t};\n\t\t}\n\n\t\t// architecture-principles FAIL 감지\n\t\tconst archMatch = content.match(/architecture[_-]principles\\s*\\|\\s*FAIL/i);\n\t\tif (archMatch) {\n\t\t\tthis.log.warn(\"Escalation detected: architecture_violation\", { featureId });\n\t\t\treturn {\n\t\t\t\treason: \"architecture_violation\",\n\t\t\t\tfeatureId,\n\t\t\t\tdetail: \"Architecture principles violation detected in review\",\n\t\t\t\tsuggestedAction: \"Consider reverting to Plan phase for architectural re-evaluation.\",\n\t\t\t};\n\t\t}\n\n\t\t// Standards Compliance Overall FAIL 감지\n\t\tconst compliance = parseStandardsCompliance(content);\n\t\tif (compliance === \"fail\") {\n\t\t\tthis.log.warn(\"Escalation detected: standards_fail_persistent\", { featureId });\n\t\t\treturn {\n\t\t\t\treason: \"standards_fail_persistent\",\n\t\t\t\tfeatureId,\n\t\t\t\tdetail: \"Standards Compliance Overall is FAIL\",\n\t\t\t\tsuggestedAction: \"User confirmation required to proceed or fix standards violations.\",\n\t\t\t};\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t// ─── Private ─────────────────────────────────────────────────────────────\n\n\tprivate async checkFileExists(target: string, featureId: string): Promise<boolean> {\n\t\treturn this.store.hasArtifact(featureId, target as ArtifactName);\n\t}\n\n\tprivate async checkTestPass(featureId: string): Promise<boolean> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"test-report.md\"))) {\n\t\t\treturn false;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"test-report.md\");\n\n\t\t// Regex match succeeded: use existing logic\n\t\tif (hasTestReportRegexMatch(content)) {\n\t\t\tconst result = parseTestReportPassed(content);\n\t\t\tthis.log.info(`Test report parsed via regex: ${result ? \"pass\" : \"fail\"}`, { featureId });\n\t\t\treturn result;\n\t\t}\n\n\t\t// Regex match failed: LLM fallback\n\t\tif (this.model) {\n\t\t\tthis.log.warn(\"Regex parse failed for test-report.md, using LLM fallback\", { featureId });\n\t\t\tconst verdict = await extractTestVerdictWithLLM(content, this.model);\n\t\t\tthis.log.info(`LLM fallback verdict for test-report: ${verdict}`, { featureId });\n\t\t\treturn verdict === \"pass\";\n\t\t}\n\n\t\tthis.log.warn(\"No regex match and no model for LLM fallback on test-report.md\", { featureId });\n\t\treturn false;\n\t}\n\n\tprivate async checkReviewApprove(featureId: string): Promise<boolean> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"review.md\"))) {\n\t\t\treturn false;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"review.md\");\n\t\tconst verdict = parseReviewVerdict(content);\n\n\t\tif (verdict !== null) {\n\t\t\tthis.log.info(`Review verdict parsed via regex: ${verdict}`, { featureId });\n\t\t\treturn verdict === \"approved\";\n\t\t}\n\n\t\t// Regex match failed: LLM fallback\n\t\tif (this.model) {\n\t\t\tthis.log.warn(\"Regex parse failed for review.md, using LLM fallback\", { featureId });\n\t\t\tconst fallbackVerdict = await extractReviewVerdictWithLLM(content, this.model);\n\t\t\tthis.log.info(`LLM fallback verdict for review: ${fallbackVerdict}`, { featureId });\n\t\t\treturn fallbackVerdict === \"approved\";\n\t\t}\n\n\t\tthis.log.warn(\"No regex match and no model for LLM fallback on review.md\", { featureId });\n\t\treturn false;\n\t}\n\n\tprivate async checkRegressionPass(featureId: string): Promise<boolean> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"regression-report.md\"))) {\n\t\t\treturn false;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"regression-report.md\");\n\t\tconst verdict = parseRegressionVerdict(content);\n\n\t\tif (verdict !== null) {\n\t\t\tthis.log.info(`Regression verdict parsed via regex: ${verdict}`, { featureId });\n\t\t\treturn verdict === \"pass\";\n\t\t}\n\n\t\t// Regex match failed: LLM fallback\n\t\tif (this.model) {\n\t\t\tthis.log.warn(\"Regex parse failed for regression-report.md, using LLM fallback\", { featureId });\n\t\t\tconst fallbackVerdict = await extractRegressionVerdictWithLLM(content, this.model);\n\t\t\tthis.log.info(`LLM fallback verdict for regression: ${fallbackVerdict}`, { featureId });\n\t\t\treturn fallbackVerdict === \"pass\";\n\t\t}\n\n\t\tthis.log.warn(\"No regex match and no model for LLM fallback on regression-report.md\", { featureId });\n\t\treturn false;\n\t}\n\n\tprivate async checkDiagnosisExists(featureId: string): Promise<boolean> {\n\t\treturn this.store.hasArtifact(featureId, \"diagnosis.md\");\n\t}\n\n\tprivate async checkStandardsCompliance(featureId: string): Promise<\"pass\" | \"warn\" | \"fail\" | null> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"review.md\"))) {\n\t\t\treturn null;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"review.md\");\n\t\treturn parseStandardsCompliance(content);\n\t}\n\n\tprivate async handleNoGate(\n\t\tstep: PipelineStep,\n\t\tfeatureId: string,\n\t\trequestApproval?: ApprovalRequestFn,\n\t): Promise<GateResult> {\n\t\tconst autonomy = this.config.autonomyLevel;\n\n\t\tif (autonomy === \"step_by_step\" && requestApproval) {\n\t\t\treturn requestApproval(step, featureId, `Step completed: ${step.action}. Approve to continue.`);\n\t\t}\n\n\t\treturn { passed: true, action: \"approved\" };\n\t}\n}\n"]}
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAqD,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAgB,aAAa,EAAqB,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAS3G,8BAA8B;AAC9B,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,yBAAyB;AACzB,MAAM,MAAM,gBAAgB,GACzB,oBAAoB,GACpB,wBAAwB,GACxB,2BAA2B,GAC3B,uBAAuB,CAAC;AAE3B,wBAAwB;AACxB,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;CACxB;AAED,sCAAsC;AACtC,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAIhH,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAoB9D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAShE;AAED,yCAAyC;AACzC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,mBAAmB,GAAG,IAAI,CAsB3F;AAED,0DAA0D;AAC1D,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAMzF;AAED,oDAAoD;AACpD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAwB9E;AA0BD;;;GAGG;AACH,qBAAa,WAAW;IAItB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IALxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAEnC,YACkB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,UAAU,EAClB,KAAK,CAAC,wBAAY,EACnC,MAAM,CAAC,EAAE,UAAU,EAGnB;IAED;;OAEG;IACG,cAAc,CAAC,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAoBlF;IAED;;;;;;OAMG;IACG,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CA6C9G;IAED;;OAEG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA4CzE;YAIa,eAAe;YAIf,aAAa;YAyBb,kBAAkB;YAwBlB,mBAAmB;YAwBnB,oBAAoB;YAIpB,wBAAwB;YAQxB,YAAY;CAa1B","sourcesContent":["import type { Model } from \"@mariozechner/pi-ai\";\nimport { createModuleLogger, type ModuleLogger, noopLogger, type VibeLogger } from \"./logger.js\";\nimport type { VibeStore } from \"./store.js\";\nimport type { ArtifactName, GateCondition, GateConditionType, PipelineStep, VibeConfig } from \"./types.js\";\nimport {\n\textractRegressionVerdictWithLLM,\n\textractReviewVerdictWithLLM,\n\textractTestVerdictWithLLM,\n} from \"./verdict-extractor.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Gate validation result. */\nexport interface GateResult {\n\tpassed: boolean;\n\taction: \"approved\" | \"rejected\" | \"feedback\" | \"skip\" | \"abort\" | \"pause\";\n\tfeedback?: string;\n}\n\n/** Escalation reason. */\nexport type EscalationReason =\n\t| \"security_violation\"\n\t| \"architecture_violation\"\n\t| \"standards_fail_persistent\"\n\t| \"requirement_ambiguity\";\n\n/** Escalation event. */\nexport interface EscalationEvent {\n\treason: EscalationReason;\n\tfeatureId: string;\n\tdetail: string;\n\tsuggestedAction: string;\n}\n\n/** User approval request callback. */\nexport type ApprovalRequestFn = (step: PipelineStep, featureId: string, summary: string) => Promise<GateResult>;\n\n// ─── Parsing ─────────────────────────────────────────────────────────────────\n\n/** Parses test-report.md for overall pass/fail. Passes when failure count is 0. */\nexport function parseTestReportPassed(content: string): boolean {\n\tconst patterns: RegExp[] = [\n\t\t// Table: \"| 실패 | N |\", \"| 失敗 | N |\"\n\t\t/\\|\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t// Table: \"| Failures | N |\", \"| Failed | N |\", \"| Fail | N |\"\n\t\t/\\|\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t// List: \"- Failed: N\", \"* Failures: N\", \"- **Failed**: N\"\n\t\t/[-*]\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t// Plain text: \"Failed: N\", \"Failures: N\", \"**Failed**: N\"\n\t\t/\\*{0,2}[Ff]ail(?:ed|ures?)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t// Korean/Japanese list/plain: \"- 실패: N\", \"실패: N\", \"- 失敗: N\", \"失敗: N\"\n\t\t/[-*]?\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t];\n\tfor (const pattern of patterns) {\n\t\tconst match = content.match(pattern);\n\t\tif (match) {\n\t\t\treturn parseInt(match[1], 10) === 0;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Checks if any failure count regex matches in test-report.md.\n * Returns true if matched (parse succeeded, regardless of result), false if no match (fallback needed).\n */\nexport function hasTestReportRegexMatch(content: string): boolean {\n\tconst patterns: RegExp[] = [\n\t\t/\\|\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t/\\|\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t/[-*]\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t/\\*{0,2}[Ff]ail(?:ed|ures?)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t/[-*]?\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t];\n\treturn patterns.some((p) => p.test(content));\n}\n\n/** Parses the verdict from review.md. */\nexport function parseReviewVerdict(content: string): \"approved\" | \"changes_requested\" | null {\n\tconst patterns: RegExp[] = [\n\t\t// \"## 판정: APPROVED\" / \"## 판정: **APPROVED**\" / \"## 판정: CHANGES_REQUESTED\" (Korean heading)\n\t\t/##\\s*판정\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"## Verdict: APPROVED\" / \"## Verdict: **APPROVED**\"\n\t\t/##\\s*[Vv]erdict\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"판정: APPROVED\" (without ##) / \"판정: **APPROVED**\" (Korean plain)\n\t\t/판정\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"Verdict: APPROVED\" (without ##) / \"Verdict: **APPROVED**\"\n\t\t/[Vv]erdict\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"Review Status: APPROVED\" / \"**Review Status**: ✅ **APPROVED**\"\n\t\t/[Rr]eview\\s+[Ss]tatus\\s*[::]\\s*(?:✅\\s*)?(?:❌\\s*)?\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// Japanese: \"判定: APPROVED\"\n\t\t/判定\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t];\n\tfor (const pattern of patterns) {\n\t\tconst match = content.match(pattern);\n\t\tif (match) {\n\t\t\treturn match[1].toLowerCase() === \"approved\" ? \"approved\" : \"changes_requested\";\n\t\t}\n\t}\n\treturn null;\n}\n\n/** Parses Standards Compliance Overall from review.md. */\nexport function parseStandardsCompliance(content: string): \"pass\" | \"warn\" | \"fail\" | null {\n\tconst match = content.match(/Overall\\s*[::]\\s*\\*{0,2}(PASS|WARN|FAIL)\\*{0,2}/i);\n\tif (match) {\n\t\treturn match[1].toLowerCase() as \"pass\" | \"warn\" | \"fail\";\n\t}\n\treturn null;\n}\n\n/** Parses the verdict from regression-report.md. */\nexport function parseRegressionVerdict(content: string): \"pass\" | \"fail\" | null {\n\tconst patterns: RegExp[] = [\n\t\t// \"## 판정: PASS\" / \"## 판정: **PASS**\" / \"## 판정: FAIL\" (Korean heading)\n\t\t/##\\s*판정\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"## Verdict: PASS\" / \"## Verdict: **PASS**\"\n\t\t/##\\s*[Vv]erdict\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"판정: PASS\" (without ##) / \"판정: **PASS**\" (Korean plain)\n\t\t/판정\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"Verdict: PASS\" (without ##) / \"Verdict: **PASS**\"\n\t\t/[Vv]erdict\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"Regression Status: PASS\" / \"**Regression Status**: ✅ **PASS**\"\n\t\t/[Rr]egression\\s+[Ss]tatus\\s*[::]\\s*(?:✅\\s*)?(?:❌\\s*)?\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"Status: PASS\" / \"**Status**: PASS\" (generic fallback)\n\t\t/\\*{0,2}[Ss]tatus\\*{0,2}\\s*[::]\\s*(?:✅\\s*)?(?:❌\\s*)?\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// Japanese: \"判定: PASS\"\n\t\t/判定\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t];\n\tfor (const pattern of patterns) {\n\t\tconst match = content.match(pattern);\n\t\tif (match) {\n\t\t\treturn match[1].toLowerCase() as \"pass\" | \"fail\";\n\t\t}\n\t}\n\treturn null;\n}\n\n// ─── Gate-to-Config Mapping ──────────────────────────────────────────────────\n\n/** Checks if a gate type requires user approval in gate_auto mode. */\nfunction isGateEnabledInConfig(gateType: GateConditionType, config: VibeConfig): boolean {\n\tswitch (gateType) {\n\t\tcase \"review_approve\":\n\t\t\treturn config.gates.requireDesignApproval;\n\t\tcase \"merge_approve\":\n\t\t\treturn config.gates.requireMergeApproval;\n\t\tcase \"diagnosis_approve\":\n\t\t\treturn config.gates.requireDiagnosisApproval;\n\t\tcase \"test_pass\":\n\t\tcase \"all_tests_pass\":\n\t\tcase \"regression_pass\":\n\t\t\treturn false; // Test gates use automatic validation only\n\t\tcase \"file_exists\":\n\t\t\treturn false;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n// ─── GateChecker ─────────────────────────────────────────────────────────────\n\n/**\n * Gate checker. Manages artifact-parsing-based automatic validation\n * and approval flows based on autonomy level.\n */\nexport class GateChecker {\n\tprivate readonly log: ModuleLogger;\n\n\tconstructor(\n\t\tprivate readonly store: VibeStore,\n\t\tprivate readonly config: VibeConfig,\n\t\tprivate readonly model?: Model<any>,\n\t\tlogger?: VibeLogger,\n\t) {\n\t\tthis.log = createModuleLogger(logger ?? noopLogger, \"gate\");\n\t}\n\n\t/**\n\t * Automatically validates a gate condition.\n\t */\n\tasync checkCondition(condition: GateCondition, featureId: string): Promise<boolean> {\n\t\tthis.log.info(`Checking gate condition: ${condition.type}`, { featureId, target: condition.target });\n\t\tswitch (condition.type) {\n\t\t\tcase \"file_exists\":\n\t\t\t\treturn this.checkFileExists(condition.target, featureId);\n\t\t\tcase \"test_pass\":\n\t\t\t\treturn this.checkTestPass(featureId);\n\t\t\tcase \"review_approve\":\n\t\t\t\treturn this.checkReviewApprove(featureId);\n\t\t\tcase \"all_tests_pass\":\n\t\t\t\treturn this.checkRegressionPass(featureId);\n\t\t\tcase \"regression_pass\":\n\t\t\t\treturn this.checkRegressionPass(featureId);\n\t\t\tcase \"diagnosis_approve\":\n\t\t\t\treturn this.checkDiagnosisExists(featureId);\n\t\t\tcase \"merge_approve\":\n\t\t\t\treturn this.checkReviewApprove(featureId);\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Evaluates a gate based on autonomy level.\n\t *\n\t * - full_auto: Automatic validation only. Approved on pass, rejected on fail.\n\t * - gate_auto: Requests user approval only for configured gates. Others are automatic.\n\t * - step_by_step: Always requests user approval after automatic validation.\n\t */\n\tasync evaluate(step: PipelineStep, featureId: string, requestApproval?: ApprovalRequestFn): Promise<GateResult> {\n\t\tthis.log.info(`Evaluating gate for step: ${step.action}`, { featureId, autonomy: this.config.autonomyLevel });\n\n\t\t// No gate condition: handle based on autonomy level\n\t\tif (!step.gate) {\n\t\t\treturn this.handleNoGate(step, featureId, requestApproval);\n\t\t}\n\n\t\t// Perform automatic validation\n\t\tconst conditionPassed = await this.checkCondition(step.gate, featureId);\n\t\tthis.log.info(`Gate condition ${step.gate.type}: ${conditionPassed ? \"passed\" : \"failed\"}`, { featureId });\n\n\t\t// Additional Standards Compliance check for review_approve\n\t\tif (step.gate.type === \"review_approve\" && conditionPassed) {\n\t\t\tconst complianceResult = await this.checkStandardsCompliance(featureId);\n\t\t\tif (complianceResult === \"fail\") {\n\t\t\t\treturn { passed: false, action: \"rejected\", feedback: \"Standards Compliance Overall: FAIL\" };\n\t\t\t}\n\t\t}\n\n\t\tconst autonomy = this.config.autonomyLevel;\n\n\t\tif (autonomy === \"full_auto\") {\n\t\t\treturn conditionPassed ? { passed: true, action: \"approved\" } : { passed: false, action: \"rejected\" };\n\t\t}\n\n\t\tif (autonomy === \"gate_auto\") {\n\t\t\tif (!conditionPassed) {\n\t\t\t\treturn { passed: false, action: \"rejected\" };\n\t\t\t}\n\t\t\t// Check if this gate requires approval in config\n\t\t\tif (isGateEnabledInConfig(step.gate.type, this.config) && requestApproval) {\n\t\t\t\treturn requestApproval(step, featureId, `Gate: ${step.gate.type} passed. Approve to continue.`);\n\t\t\t}\n\t\t\treturn { passed: true, action: \"approved\" };\n\t\t}\n\n\t\t// step_by_step: always request user approval after automatic validation\n\t\tif (!conditionPassed) {\n\t\t\treturn { passed: false, action: \"rejected\" };\n\t\t}\n\t\tif (requestApproval) {\n\t\t\treturn requestApproval(step, featureId, `Step completed: ${step.action}. Approve to continue.`);\n\t\t}\n\t\treturn { passed: true, action: \"approved\" };\n\t}\n\n\t/**\n\t * Analyzes review.md to detect escalation conditions.\n\t */\n\tasync detectEscalation(featureId: string): Promise<EscalationEvent | null> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"review.md\"))) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst content = await this.store.readArtifact(featureId, \"review.md\");\n\n\t\t// security-policy FAIL 감지\n\t\tconst securityMatch = content.match(/security[_-]policy\\s*\\|\\s*FAIL/i);\n\t\tif (securityMatch) {\n\t\t\tthis.log.warn(\"Escalation detected: security_violation\", { featureId });\n\t\t\treturn {\n\t\t\t\treason: \"security_violation\",\n\t\t\t\tfeatureId,\n\t\t\t\tdetail: \"Security policy violation detected in review\",\n\t\t\t\tsuggestedAction: \"Halt pipeline immediately. Manual security review required.\",\n\t\t\t};\n\t\t}\n\n\t\t// architecture-principles FAIL 감지\n\t\tconst archMatch = content.match(/architecture[_-]principles\\s*\\|\\s*FAIL/i);\n\t\tif (archMatch) {\n\t\t\tthis.log.warn(\"Escalation detected: architecture_violation\", { featureId });\n\t\t\treturn {\n\t\t\t\treason: \"architecture_violation\",\n\t\t\t\tfeatureId,\n\t\t\t\tdetail: \"Architecture principles violation detected in review\",\n\t\t\t\tsuggestedAction: \"Consider reverting to Plan phase for architectural re-evaluation.\",\n\t\t\t};\n\t\t}\n\n\t\t// Standards Compliance Overall FAIL 감지\n\t\tconst compliance = parseStandardsCompliance(content);\n\t\tif (compliance === \"fail\") {\n\t\t\tthis.log.warn(\"Escalation detected: standards_fail_persistent\", { featureId });\n\t\t\treturn {\n\t\t\t\treason: \"standards_fail_persistent\",\n\t\t\t\tfeatureId,\n\t\t\t\tdetail: \"Standards Compliance Overall is FAIL\",\n\t\t\t\tsuggestedAction: \"User confirmation required to proceed or fix standards violations.\",\n\t\t\t};\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t// ─── Private ─────────────────────────────────────────────────────────────\n\n\tprivate async checkFileExists(target: string, featureId: string): Promise<boolean> {\n\t\treturn this.store.hasArtifact(featureId, target as ArtifactName);\n\t}\n\n\tprivate async checkTestPass(featureId: string): Promise<boolean> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"test-report.md\"))) {\n\t\t\treturn false;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"test-report.md\");\n\n\t\t// Regex match succeeded: use existing logic\n\t\tif (hasTestReportRegexMatch(content)) {\n\t\t\tconst result = parseTestReportPassed(content);\n\t\t\tthis.log.info(`Test report parsed via regex: ${result ? \"pass\" : \"fail\"}`, { featureId });\n\t\t\treturn result;\n\t\t}\n\n\t\t// Regex match failed: LLM fallback\n\t\tif (this.model) {\n\t\t\tthis.log.warn(\"Regex parse failed for test-report.md, using LLM fallback\", { featureId });\n\t\t\tconst verdict = await extractTestVerdictWithLLM(content, this.model);\n\t\t\tthis.log.info(`LLM fallback verdict for test-report: ${verdict}`, { featureId });\n\t\t\treturn verdict === \"pass\";\n\t\t}\n\n\t\tthis.log.warn(\"No regex match and no model for LLM fallback on test-report.md\", { featureId });\n\t\treturn false;\n\t}\n\n\tprivate async checkReviewApprove(featureId: string): Promise<boolean> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"review.md\"))) {\n\t\t\treturn false;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"review.md\");\n\t\tconst verdict = parseReviewVerdict(content);\n\n\t\tif (verdict !== null) {\n\t\t\tthis.log.info(`Review verdict parsed via regex: ${verdict}`, { featureId });\n\t\t\treturn verdict === \"approved\";\n\t\t}\n\n\t\t// Regex match failed: LLM fallback\n\t\tif (this.model) {\n\t\t\tthis.log.warn(\"Regex parse failed for review.md, using LLM fallback\", { featureId });\n\t\t\tconst fallbackVerdict = await extractReviewVerdictWithLLM(content, this.model);\n\t\t\tthis.log.info(`LLM fallback verdict for review: ${fallbackVerdict}`, { featureId });\n\t\t\treturn fallbackVerdict === \"approved\";\n\t\t}\n\n\t\tthis.log.warn(\"No regex match and no model for LLM fallback on review.md\", { featureId });\n\t\treturn false;\n\t}\n\n\tprivate async checkRegressionPass(featureId: string): Promise<boolean> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"regression-report.md\"))) {\n\t\t\treturn false;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"regression-report.md\");\n\t\tconst verdict = parseRegressionVerdict(content);\n\n\t\tif (verdict !== null) {\n\t\t\tthis.log.info(`Regression verdict parsed via regex: ${verdict}`, { featureId });\n\t\t\treturn verdict === \"pass\";\n\t\t}\n\n\t\t// Regex match failed: LLM fallback\n\t\tif (this.model) {\n\t\t\tthis.log.warn(\"Regex parse failed for regression-report.md, using LLM fallback\", { featureId });\n\t\t\tconst fallbackVerdict = await extractRegressionVerdictWithLLM(content, this.model);\n\t\t\tthis.log.info(`LLM fallback verdict for regression: ${fallbackVerdict}`, { featureId });\n\t\t\treturn fallbackVerdict === \"pass\";\n\t\t}\n\n\t\tthis.log.warn(\"No regex match and no model for LLM fallback on regression-report.md\", { featureId });\n\t\treturn false;\n\t}\n\n\tprivate async checkDiagnosisExists(featureId: string): Promise<boolean> {\n\t\treturn this.store.hasArtifact(featureId, \"diagnosis.md\");\n\t}\n\n\tprivate async checkStandardsCompliance(featureId: string): Promise<\"pass\" | \"warn\" | \"fail\" | null> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"review.md\"))) {\n\t\t\treturn null;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"review.md\");\n\t\treturn parseStandardsCompliance(content);\n\t}\n\n\tprivate async handleNoGate(\n\t\tstep: PipelineStep,\n\t\tfeatureId: string,\n\t\trequestApproval?: ApprovalRequestFn,\n\t): Promise<GateResult> {\n\t\tconst autonomy = this.config.autonomyLevel;\n\n\t\tif (autonomy === \"step_by_step\" && requestApproval) {\n\t\t\treturn requestApproval(step, featureId, `Step completed: ${step.action}. Approve to continue.`);\n\t\t}\n\n\t\treturn { passed: true, action: \"approved\" };\n\t}\n}\n"]}
package/dist/gate.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Model } from \"@mariozechner/pi-ai\";\nimport { createModuleLogger, type ModuleLogger, noopLogger, type VibeLogger } from \"./logger.js\";\nimport type { VibeStore } from \"./store.js\";\nimport type { ArtifactName, GateCondition, GateConditionType, PipelineStep, VibeConfig } from \"./types.js\";\nimport {\n\textractRegressionVerdictWithLLM,\n\textractReviewVerdictWithLLM,\n\textractTestVerdictWithLLM,\n} from \"./verdict-extractor.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Gate validation result. */\nexport interface GateResult {\n\tpassed: boolean;\n\taction: \"approved\" | \"rejected\" | \"feedback\" | \"skip\" | \"abort\";\n\tfeedback?: string;\n}\n\n/** Escalation reason. */\nexport type EscalationReason =\n\t| \"security_violation\"\n\t| \"architecture_violation\"\n\t| \"standards_fail_persistent\"\n\t| \"requirement_ambiguity\";\n\n/** Escalation event. */\nexport interface EscalationEvent {\n\treason: EscalationReason;\n\tfeatureId: string;\n\tdetail: string;\n\tsuggestedAction: string;\n}\n\n/** User approval request callback. */\nexport type ApprovalRequestFn = (step: PipelineStep, featureId: string, summary: string) => Promise<GateResult>;\n\n// ─── Parsing ─────────────────────────────────────────────────────────────────\n\n/** Parses test-report.md for overall pass/fail. Passes when failure count is 0. */\nexport function parseTestReportPassed(content: string): boolean {\n\tconst patterns: RegExp[] = [\n\t\t// Table: \"| 실패 | N |\", \"| 失敗 | N |\"\n\t\t/\\|\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t// Table: \"| Failures | N |\", \"| Failed | N |\", \"| Fail | N |\"\n\t\t/\\|\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t// List: \"- Failed: N\", \"* Failures: N\", \"- **Failed**: N\"\n\t\t/[-*]\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t// Plain text: \"Failed: N\", \"Failures: N\", \"**Failed**: N\"\n\t\t/\\*{0,2}[Ff]ail(?:ed|ures?)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t// Korean/Japanese list/plain: \"- 실패: N\", \"실패: N\", \"- 失敗: N\", \"失敗: N\"\n\t\t/[-*]?\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t];\n\tfor (const pattern of patterns) {\n\t\tconst match = content.match(pattern);\n\t\tif (match) {\n\t\t\treturn parseInt(match[1], 10) === 0;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Checks if any failure count regex matches in test-report.md.\n * Returns true if matched (parse succeeded, regardless of result), false if no match (fallback needed).\n */\nexport function hasTestReportRegexMatch(content: string): boolean {\n\tconst patterns: RegExp[] = [\n\t\t/\\|\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t/\\|\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t/[-*]\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t/\\*{0,2}[Ff]ail(?:ed|ures?)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t/[-*]?\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t];\n\treturn patterns.some((p) => p.test(content));\n}\n\n/** Parses the verdict from review.md. */\nexport function parseReviewVerdict(content: string): \"approved\" | \"changes_requested\" | null {\n\tconst patterns: RegExp[] = [\n\t\t// \"## 판정: APPROVED\" / \"## 판정: **APPROVED**\" / \"## 판정: CHANGES_REQUESTED\" (Korean heading)\n\t\t/##\\s*판정\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"## Verdict: APPROVED\" / \"## Verdict: **APPROVED**\"\n\t\t/##\\s*[Vv]erdict\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"판정: APPROVED\" (without ##) / \"판정: **APPROVED**\" (Korean plain)\n\t\t/판정\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"Verdict: APPROVED\" (without ##) / \"Verdict: **APPROVED**\"\n\t\t/[Vv]erdict\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"Review Status: APPROVED\" / \"**Review Status**: ✅ **APPROVED**\"\n\t\t/[Rr]eview\\s+[Ss]tatus\\s*[::]\\s*(?:✅\\s*)?(?:❌\\s*)?\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// Japanese: \"判定: APPROVED\"\n\t\t/判定\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t];\n\tfor (const pattern of patterns) {\n\t\tconst match = content.match(pattern);\n\t\tif (match) {\n\t\t\treturn match[1].toLowerCase() === \"approved\" ? \"approved\" : \"changes_requested\";\n\t\t}\n\t}\n\treturn null;\n}\n\n/** Parses Standards Compliance Overall from review.md. */\nexport function parseStandardsCompliance(content: string): \"pass\" | \"warn\" | \"fail\" | null {\n\tconst match = content.match(/Overall\\s*[::]\\s*\\*{0,2}(PASS|WARN|FAIL)\\*{0,2}/i);\n\tif (match) {\n\t\treturn match[1].toLowerCase() as \"pass\" | \"warn\" | \"fail\";\n\t}\n\treturn null;\n}\n\n/** Parses the verdict from regression-report.md. */\nexport function parseRegressionVerdict(content: string): \"pass\" | \"fail\" | null {\n\tconst patterns: RegExp[] = [\n\t\t// \"## 판정: PASS\" / \"## 판정: **PASS**\" / \"## 판정: FAIL\" (Korean heading)\n\t\t/##\\s*판정\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"## Verdict: PASS\" / \"## Verdict: **PASS**\"\n\t\t/##\\s*[Vv]erdict\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"판정: PASS\" (without ##) / \"판정: **PASS**\" (Korean plain)\n\t\t/판정\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"Verdict: PASS\" (without ##) / \"Verdict: **PASS**\"\n\t\t/[Vv]erdict\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"Regression Status: PASS\" / \"**Regression Status**: ✅ **PASS**\"\n\t\t/[Rr]egression\\s+[Ss]tatus\\s*[::]\\s*(?:✅\\s*)?(?:❌\\s*)?\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"Status: PASS\" / \"**Status**: PASS\" (generic fallback)\n\t\t/\\*{0,2}[Ss]tatus\\*{0,2}\\s*[::]\\s*(?:✅\\s*)?(?:❌\\s*)?\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// Japanese: \"判定: PASS\"\n\t\t/判定\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t];\n\tfor (const pattern of patterns) {\n\t\tconst match = content.match(pattern);\n\t\tif (match) {\n\t\t\treturn match[1].toLowerCase() as \"pass\" | \"fail\";\n\t\t}\n\t}\n\treturn null;\n}\n\n// ─── Gate-to-Config Mapping ──────────────────────────────────────────────────\n\n/** Checks if a gate type requires user approval in gate_auto mode. */\nfunction isGateEnabledInConfig(gateType: GateConditionType, config: VibeConfig): boolean {\n\tswitch (gateType) {\n\t\tcase \"review_approve\":\n\t\t\treturn config.gates.requireDesignApproval;\n\t\tcase \"merge_approve\":\n\t\t\treturn config.gates.requireMergeApproval;\n\t\tcase \"diagnosis_approve\":\n\t\t\treturn config.gates.requireDiagnosisApproval;\n\t\tcase \"test_pass\":\n\t\tcase \"all_tests_pass\":\n\t\tcase \"regression_pass\":\n\t\t\treturn false; // Test gates use automatic validation only\n\t\tcase \"file_exists\":\n\t\t\treturn false;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n// ─── GateChecker ─────────────────────────────────────────────────────────────\n\n/**\n * Gate checker. Manages artifact-parsing-based automatic validation\n * and approval flows based on autonomy level.\n */\nexport class GateChecker {\n\tprivate readonly log: ModuleLogger;\n\n\tconstructor(\n\t\tprivate readonly store: VibeStore,\n\t\tprivate readonly config: VibeConfig,\n\t\tprivate readonly model?: Model<any>,\n\t\tlogger?: VibeLogger,\n\t) {\n\t\tthis.log = createModuleLogger(logger ?? noopLogger, \"gate\");\n\t}\n\n\t/**\n\t * Automatically validates a gate condition.\n\t */\n\tasync checkCondition(condition: GateCondition, featureId: string): Promise<boolean> {\n\t\tthis.log.info(`Checking gate condition: ${condition.type}`, { featureId, target: condition.target });\n\t\tswitch (condition.type) {\n\t\t\tcase \"file_exists\":\n\t\t\t\treturn this.checkFileExists(condition.target, featureId);\n\t\t\tcase \"test_pass\":\n\t\t\t\treturn this.checkTestPass(featureId);\n\t\t\tcase \"review_approve\":\n\t\t\t\treturn this.checkReviewApprove(featureId);\n\t\t\tcase \"all_tests_pass\":\n\t\t\t\treturn this.checkRegressionPass(featureId);\n\t\t\tcase \"regression_pass\":\n\t\t\t\treturn this.checkRegressionPass(featureId);\n\t\t\tcase \"diagnosis_approve\":\n\t\t\t\treturn this.checkDiagnosisExists(featureId);\n\t\t\tcase \"merge_approve\":\n\t\t\t\treturn this.checkReviewApprove(featureId);\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Evaluates a gate based on autonomy level.\n\t *\n\t * - full_auto: Automatic validation only. Approved on pass, rejected on fail.\n\t * - gate_auto: Requests user approval only for configured gates. Others are automatic.\n\t * - step_by_step: Always requests user approval after automatic validation.\n\t */\n\tasync evaluate(step: PipelineStep, featureId: string, requestApproval?: ApprovalRequestFn): Promise<GateResult> {\n\t\tthis.log.info(`Evaluating gate for step: ${step.action}`, { featureId, autonomy: this.config.autonomyLevel });\n\n\t\t// No gate condition: handle based on autonomy level\n\t\tif (!step.gate) {\n\t\t\treturn this.handleNoGate(step, featureId, requestApproval);\n\t\t}\n\n\t\t// Perform automatic validation\n\t\tconst conditionPassed = await this.checkCondition(step.gate, featureId);\n\t\tthis.log.info(`Gate condition ${step.gate.type}: ${conditionPassed ? \"passed\" : \"failed\"}`, { featureId });\n\n\t\t// Additional Standards Compliance check for review_approve\n\t\tif (step.gate.type === \"review_approve\" && conditionPassed) {\n\t\t\tconst complianceResult = await this.checkStandardsCompliance(featureId);\n\t\t\tif (complianceResult === \"fail\") {\n\t\t\t\treturn { passed: false, action: \"rejected\", feedback: \"Standards Compliance Overall: FAIL\" };\n\t\t\t}\n\t\t}\n\n\t\tconst autonomy = this.config.autonomyLevel;\n\n\t\tif (autonomy === \"full_auto\") {\n\t\t\treturn conditionPassed ? { passed: true, action: \"approved\" } : { passed: false, action: \"rejected\" };\n\t\t}\n\n\t\tif (autonomy === \"gate_auto\") {\n\t\t\tif (!conditionPassed) {\n\t\t\t\treturn { passed: false, action: \"rejected\" };\n\t\t\t}\n\t\t\t// Check if this gate requires approval in config\n\t\t\tif (isGateEnabledInConfig(step.gate.type, this.config) && requestApproval) {\n\t\t\t\treturn requestApproval(step, featureId, `Gate: ${step.gate.type} passed. Approve to continue.`);\n\t\t\t}\n\t\t\treturn { passed: true, action: \"approved\" };\n\t\t}\n\n\t\t// step_by_step: always request user approval after automatic validation\n\t\tif (!conditionPassed) {\n\t\t\treturn { passed: false, action: \"rejected\" };\n\t\t}\n\t\tif (requestApproval) {\n\t\t\treturn requestApproval(step, featureId, `Step completed: ${step.action}. Approve to continue.`);\n\t\t}\n\t\treturn { passed: true, action: \"approved\" };\n\t}\n\n\t/**\n\t * Analyzes review.md to detect escalation conditions.\n\t */\n\tasync detectEscalation(featureId: string): Promise<EscalationEvent | null> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"review.md\"))) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst content = await this.store.readArtifact(featureId, \"review.md\");\n\n\t\t// security-policy FAIL 감지\n\t\tconst securityMatch = content.match(/security[_-]policy\\s*\\|\\s*FAIL/i);\n\t\tif (securityMatch) {\n\t\t\tthis.log.warn(\"Escalation detected: security_violation\", { featureId });\n\t\t\treturn {\n\t\t\t\treason: \"security_violation\",\n\t\t\t\tfeatureId,\n\t\t\t\tdetail: \"Security policy violation detected in review\",\n\t\t\t\tsuggestedAction: \"Halt pipeline immediately. Manual security review required.\",\n\t\t\t};\n\t\t}\n\n\t\t// architecture-principles FAIL 감지\n\t\tconst archMatch = content.match(/architecture[_-]principles\\s*\\|\\s*FAIL/i);\n\t\tif (archMatch) {\n\t\t\tthis.log.warn(\"Escalation detected: architecture_violation\", { featureId });\n\t\t\treturn {\n\t\t\t\treason: \"architecture_violation\",\n\t\t\t\tfeatureId,\n\t\t\t\tdetail: \"Architecture principles violation detected in review\",\n\t\t\t\tsuggestedAction: \"Consider reverting to Plan phase for architectural re-evaluation.\",\n\t\t\t};\n\t\t}\n\n\t\t// Standards Compliance Overall FAIL 감지\n\t\tconst compliance = parseStandardsCompliance(content);\n\t\tif (compliance === \"fail\") {\n\t\t\tthis.log.warn(\"Escalation detected: standards_fail_persistent\", { featureId });\n\t\t\treturn {\n\t\t\t\treason: \"standards_fail_persistent\",\n\t\t\t\tfeatureId,\n\t\t\t\tdetail: \"Standards Compliance Overall is FAIL\",\n\t\t\t\tsuggestedAction: \"User confirmation required to proceed or fix standards violations.\",\n\t\t\t};\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t// ─── Private ─────────────────────────────────────────────────────────────\n\n\tprivate async checkFileExists(target: string, featureId: string): Promise<boolean> {\n\t\treturn this.store.hasArtifact(featureId, target as ArtifactName);\n\t}\n\n\tprivate async checkTestPass(featureId: string): Promise<boolean> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"test-report.md\"))) {\n\t\t\treturn false;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"test-report.md\");\n\n\t\t// Regex match succeeded: use existing logic\n\t\tif (hasTestReportRegexMatch(content)) {\n\t\t\tconst result = parseTestReportPassed(content);\n\t\t\tthis.log.info(`Test report parsed via regex: ${result ? \"pass\" : \"fail\"}`, { featureId });\n\t\t\treturn result;\n\t\t}\n\n\t\t// Regex match failed: LLM fallback\n\t\tif (this.model) {\n\t\t\tthis.log.warn(\"Regex parse failed for test-report.md, using LLM fallback\", { featureId });\n\t\t\tconst verdict = await extractTestVerdictWithLLM(content, this.model);\n\t\t\tthis.log.info(`LLM fallback verdict for test-report: ${verdict}`, { featureId });\n\t\t\treturn verdict === \"pass\";\n\t\t}\n\n\t\tthis.log.warn(\"No regex match and no model for LLM fallback on test-report.md\", { featureId });\n\t\treturn false;\n\t}\n\n\tprivate async checkReviewApprove(featureId: string): Promise<boolean> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"review.md\"))) {\n\t\t\treturn false;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"review.md\");\n\t\tconst verdict = parseReviewVerdict(content);\n\n\t\tif (verdict !== null) {\n\t\t\tthis.log.info(`Review verdict parsed via regex: ${verdict}`, { featureId });\n\t\t\treturn verdict === \"approved\";\n\t\t}\n\n\t\t// Regex match failed: LLM fallback\n\t\tif (this.model) {\n\t\t\tthis.log.warn(\"Regex parse failed for review.md, using LLM fallback\", { featureId });\n\t\t\tconst fallbackVerdict = await extractReviewVerdictWithLLM(content, this.model);\n\t\t\tthis.log.info(`LLM fallback verdict for review: ${fallbackVerdict}`, { featureId });\n\t\t\treturn fallbackVerdict === \"approved\";\n\t\t}\n\n\t\tthis.log.warn(\"No regex match and no model for LLM fallback on review.md\", { featureId });\n\t\treturn false;\n\t}\n\n\tprivate async checkRegressionPass(featureId: string): Promise<boolean> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"regression-report.md\"))) {\n\t\t\treturn false;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"regression-report.md\");\n\t\tconst verdict = parseRegressionVerdict(content);\n\n\t\tif (verdict !== null) {\n\t\t\tthis.log.info(`Regression verdict parsed via regex: ${verdict}`, { featureId });\n\t\t\treturn verdict === \"pass\";\n\t\t}\n\n\t\t// Regex match failed: LLM fallback\n\t\tif (this.model) {\n\t\t\tthis.log.warn(\"Regex parse failed for regression-report.md, using LLM fallback\", { featureId });\n\t\t\tconst fallbackVerdict = await extractRegressionVerdictWithLLM(content, this.model);\n\t\t\tthis.log.info(`LLM fallback verdict for regression: ${fallbackVerdict}`, { featureId });\n\t\t\treturn fallbackVerdict === \"pass\";\n\t\t}\n\n\t\tthis.log.warn(\"No regex match and no model for LLM fallback on regression-report.md\", { featureId });\n\t\treturn false;\n\t}\n\n\tprivate async checkDiagnosisExists(featureId: string): Promise<boolean> {\n\t\treturn this.store.hasArtifact(featureId, \"diagnosis.md\");\n\t}\n\n\tprivate async checkStandardsCompliance(featureId: string): Promise<\"pass\" | \"warn\" | \"fail\" | null> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"review.md\"))) {\n\t\t\treturn null;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"review.md\");\n\t\treturn parseStandardsCompliance(content);\n\t}\n\n\tprivate async handleNoGate(\n\t\tstep: PipelineStep,\n\t\tfeatureId: string,\n\t\trequestApproval?: ApprovalRequestFn,\n\t): Promise<GateResult> {\n\t\tconst autonomy = this.config.autonomyLevel;\n\n\t\tif (autonomy === \"step_by_step\" && requestApproval) {\n\t\t\treturn requestApproval(step, featureId, `Step completed: ${step.action}. Approve to continue.`);\n\t\t}\n\n\t\treturn { passed: true, action: \"approved\" };\n\t}\n}\n"]}
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Model } from \"@mariozechner/pi-ai\";\nimport { createModuleLogger, type ModuleLogger, noopLogger, type VibeLogger } from \"./logger.js\";\nimport type { VibeStore } from \"./store.js\";\nimport type { ArtifactName, GateCondition, GateConditionType, PipelineStep, VibeConfig } from \"./types.js\";\nimport {\n\textractRegressionVerdictWithLLM,\n\textractReviewVerdictWithLLM,\n\textractTestVerdictWithLLM,\n} from \"./verdict-extractor.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Gate validation result. */\nexport interface GateResult {\n\tpassed: boolean;\n\taction: \"approved\" | \"rejected\" | \"feedback\" | \"skip\" | \"abort\" | \"pause\";\n\tfeedback?: string;\n}\n\n/** Escalation reason. */\nexport type EscalationReason =\n\t| \"security_violation\"\n\t| \"architecture_violation\"\n\t| \"standards_fail_persistent\"\n\t| \"requirement_ambiguity\";\n\n/** Escalation event. */\nexport interface EscalationEvent {\n\treason: EscalationReason;\n\tfeatureId: string;\n\tdetail: string;\n\tsuggestedAction: string;\n}\n\n/** User approval request callback. */\nexport type ApprovalRequestFn = (step: PipelineStep, featureId: string, summary: string) => Promise<GateResult>;\n\n// ─── Parsing ─────────────────────────────────────────────────────────────────\n\n/** Parses test-report.md for overall pass/fail. Passes when failure count is 0. */\nexport function parseTestReportPassed(content: string): boolean {\n\tconst patterns: RegExp[] = [\n\t\t// Table: \"| 실패 | N |\", \"| 失敗 | N |\"\n\t\t/\\|\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t// Table: \"| Failures | N |\", \"| Failed | N |\", \"| Fail | N |\"\n\t\t/\\|\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t// List: \"- Failed: N\", \"* Failures: N\", \"- **Failed**: N\"\n\t\t/[-*]\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t// Plain text: \"Failed: N\", \"Failures: N\", \"**Failed**: N\"\n\t\t/\\*{0,2}[Ff]ail(?:ed|ures?)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t// Korean/Japanese list/plain: \"- 실패: N\", \"실패: N\", \"- 失敗: N\", \"失敗: N\"\n\t\t/[-*]?\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t];\n\tfor (const pattern of patterns) {\n\t\tconst match = content.match(pattern);\n\t\tif (match) {\n\t\t\treturn parseInt(match[1], 10) === 0;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Checks if any failure count regex matches in test-report.md.\n * Returns true if matched (parse succeeded, regardless of result), false if no match (fallback needed).\n */\nexport function hasTestReportRegexMatch(content: string): boolean {\n\tconst patterns: RegExp[] = [\n\t\t/\\|\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t/\\|\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*\\|\\s*(\\d+)\\s*\\|/,\n\t\t/[-*]\\s*\\*{0,2}[Ff]ail(?:ed|ures?)?\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t/\\*{0,2}[Ff]ail(?:ed|ures?)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t\t/[-*]?\\s*\\*{0,2}(?:실패|失敗)\\*{0,2}\\s*[::]\\s*(\\d+)/,\n\t];\n\treturn patterns.some((p) => p.test(content));\n}\n\n/** Parses the verdict from review.md. */\nexport function parseReviewVerdict(content: string): \"approved\" | \"changes_requested\" | null {\n\tconst patterns: RegExp[] = [\n\t\t// \"## 판정: APPROVED\" / \"## 판정: **APPROVED**\" / \"## 판정: CHANGES_REQUESTED\" (Korean heading)\n\t\t/##\\s*판정\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"## Verdict: APPROVED\" / \"## Verdict: **APPROVED**\"\n\t\t/##\\s*[Vv]erdict\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"판정: APPROVED\" (without ##) / \"판정: **APPROVED**\" (Korean plain)\n\t\t/판정\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"Verdict: APPROVED\" (without ##) / \"Verdict: **APPROVED**\"\n\t\t/[Vv]erdict\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// \"Review Status: APPROVED\" / \"**Review Status**: ✅ **APPROVED**\"\n\t\t/[Rr]eview\\s+[Ss]tatus\\s*[::]\\s*(?:✅\\s*)?(?:❌\\s*)?\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t\t// Japanese: \"判定: APPROVED\"\n\t\t/判定\\s*[::]\\s*\\*{0,2}(APPROVED|CHANGES_REQUESTED)\\*{0,2}/i,\n\t];\n\tfor (const pattern of patterns) {\n\t\tconst match = content.match(pattern);\n\t\tif (match) {\n\t\t\treturn match[1].toLowerCase() === \"approved\" ? \"approved\" : \"changes_requested\";\n\t\t}\n\t}\n\treturn null;\n}\n\n/** Parses Standards Compliance Overall from review.md. */\nexport function parseStandardsCompliance(content: string): \"pass\" | \"warn\" | \"fail\" | null {\n\tconst match = content.match(/Overall\\s*[::]\\s*\\*{0,2}(PASS|WARN|FAIL)\\*{0,2}/i);\n\tif (match) {\n\t\treturn match[1].toLowerCase() as \"pass\" | \"warn\" | \"fail\";\n\t}\n\treturn null;\n}\n\n/** Parses the verdict from regression-report.md. */\nexport function parseRegressionVerdict(content: string): \"pass\" | \"fail\" | null {\n\tconst patterns: RegExp[] = [\n\t\t// \"## 판정: PASS\" / \"## 판정: **PASS**\" / \"## 판정: FAIL\" (Korean heading)\n\t\t/##\\s*판정\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"## Verdict: PASS\" / \"## Verdict: **PASS**\"\n\t\t/##\\s*[Vv]erdict\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"판정: PASS\" (without ##) / \"판정: **PASS**\" (Korean plain)\n\t\t/판정\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"Verdict: PASS\" (without ##) / \"Verdict: **PASS**\"\n\t\t/[Vv]erdict\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"Regression Status: PASS\" / \"**Regression Status**: ✅ **PASS**\"\n\t\t/[Rr]egression\\s+[Ss]tatus\\s*[::]\\s*(?:✅\\s*)?(?:❌\\s*)?\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// \"Status: PASS\" / \"**Status**: PASS\" (generic fallback)\n\t\t/\\*{0,2}[Ss]tatus\\*{0,2}\\s*[::]\\s*(?:✅\\s*)?(?:❌\\s*)?\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t\t// Japanese: \"判定: PASS\"\n\t\t/判定\\s*[::]\\s*\\*{0,2}(PASS|FAIL)\\*{0,2}/i,\n\t];\n\tfor (const pattern of patterns) {\n\t\tconst match = content.match(pattern);\n\t\tif (match) {\n\t\t\treturn match[1].toLowerCase() as \"pass\" | \"fail\";\n\t\t}\n\t}\n\treturn null;\n}\n\n// ─── Gate-to-Config Mapping ──────────────────────────────────────────────────\n\n/** Checks if a gate type requires user approval in gate_auto mode. */\nfunction isGateEnabledInConfig(gateType: GateConditionType, config: VibeConfig): boolean {\n\tswitch (gateType) {\n\t\tcase \"review_approve\":\n\t\t\treturn config.gates.requireDesignApproval;\n\t\tcase \"merge_approve\":\n\t\t\treturn config.gates.requireMergeApproval;\n\t\tcase \"diagnosis_approve\":\n\t\t\treturn config.gates.requireDiagnosisApproval;\n\t\tcase \"test_pass\":\n\t\tcase \"all_tests_pass\":\n\t\tcase \"regression_pass\":\n\t\t\treturn false; // Test gates use automatic validation only\n\t\tcase \"file_exists\":\n\t\t\treturn false;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n// ─── GateChecker ─────────────────────────────────────────────────────────────\n\n/**\n * Gate checker. Manages artifact-parsing-based automatic validation\n * and approval flows based on autonomy level.\n */\nexport class GateChecker {\n\tprivate readonly log: ModuleLogger;\n\n\tconstructor(\n\t\tprivate readonly store: VibeStore,\n\t\tprivate readonly config: VibeConfig,\n\t\tprivate readonly model?: Model<any>,\n\t\tlogger?: VibeLogger,\n\t) {\n\t\tthis.log = createModuleLogger(logger ?? noopLogger, \"gate\");\n\t}\n\n\t/**\n\t * Automatically validates a gate condition.\n\t */\n\tasync checkCondition(condition: GateCondition, featureId: string): Promise<boolean> {\n\t\tthis.log.info(`Checking gate condition: ${condition.type}`, { featureId, target: condition.target });\n\t\tswitch (condition.type) {\n\t\t\tcase \"file_exists\":\n\t\t\t\treturn this.checkFileExists(condition.target, featureId);\n\t\t\tcase \"test_pass\":\n\t\t\t\treturn this.checkTestPass(featureId);\n\t\t\tcase \"review_approve\":\n\t\t\t\treturn this.checkReviewApprove(featureId);\n\t\t\tcase \"all_tests_pass\":\n\t\t\t\treturn this.checkRegressionPass(featureId);\n\t\t\tcase \"regression_pass\":\n\t\t\t\treturn this.checkRegressionPass(featureId);\n\t\t\tcase \"diagnosis_approve\":\n\t\t\t\treturn this.checkDiagnosisExists(featureId);\n\t\t\tcase \"merge_approve\":\n\t\t\t\treturn this.checkReviewApprove(featureId);\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Evaluates a gate based on autonomy level.\n\t *\n\t * - full_auto: Automatic validation only. Approved on pass, rejected on fail.\n\t * - gate_auto: Requests user approval only for configured gates. Others are automatic.\n\t * - step_by_step: Always requests user approval after automatic validation.\n\t */\n\tasync evaluate(step: PipelineStep, featureId: string, requestApproval?: ApprovalRequestFn): Promise<GateResult> {\n\t\tthis.log.info(`Evaluating gate for step: ${step.action}`, { featureId, autonomy: this.config.autonomyLevel });\n\n\t\t// No gate condition: handle based on autonomy level\n\t\tif (!step.gate) {\n\t\t\treturn this.handleNoGate(step, featureId, requestApproval);\n\t\t}\n\n\t\t// Perform automatic validation\n\t\tconst conditionPassed = await this.checkCondition(step.gate, featureId);\n\t\tthis.log.info(`Gate condition ${step.gate.type}: ${conditionPassed ? \"passed\" : \"failed\"}`, { featureId });\n\n\t\t// Additional Standards Compliance check for review_approve\n\t\tif (step.gate.type === \"review_approve\" && conditionPassed) {\n\t\t\tconst complianceResult = await this.checkStandardsCompliance(featureId);\n\t\t\tif (complianceResult === \"fail\") {\n\t\t\t\treturn { passed: false, action: \"rejected\", feedback: \"Standards Compliance Overall: FAIL\" };\n\t\t\t}\n\t\t}\n\n\t\tconst autonomy = this.config.autonomyLevel;\n\n\t\tif (autonomy === \"full_auto\") {\n\t\t\treturn conditionPassed ? { passed: true, action: \"approved\" } : { passed: false, action: \"rejected\" };\n\t\t}\n\n\t\tif (autonomy === \"gate_auto\") {\n\t\t\tif (!conditionPassed) {\n\t\t\t\treturn { passed: false, action: \"rejected\" };\n\t\t\t}\n\t\t\t// Check if this gate requires approval in config\n\t\t\tif (isGateEnabledInConfig(step.gate.type, this.config) && requestApproval) {\n\t\t\t\treturn requestApproval(step, featureId, `Gate: ${step.gate.type} passed. Approve to continue.`);\n\t\t\t}\n\t\t\treturn { passed: true, action: \"approved\" };\n\t\t}\n\n\t\t// step_by_step: always request user approval after automatic validation\n\t\tif (!conditionPassed) {\n\t\t\treturn { passed: false, action: \"rejected\" };\n\t\t}\n\t\tif (requestApproval) {\n\t\t\treturn requestApproval(step, featureId, `Step completed: ${step.action}. Approve to continue.`);\n\t\t}\n\t\treturn { passed: true, action: \"approved\" };\n\t}\n\n\t/**\n\t * Analyzes review.md to detect escalation conditions.\n\t */\n\tasync detectEscalation(featureId: string): Promise<EscalationEvent | null> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"review.md\"))) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst content = await this.store.readArtifact(featureId, \"review.md\");\n\n\t\t// security-policy FAIL 감지\n\t\tconst securityMatch = content.match(/security[_-]policy\\s*\\|\\s*FAIL/i);\n\t\tif (securityMatch) {\n\t\t\tthis.log.warn(\"Escalation detected: security_violation\", { featureId });\n\t\t\treturn {\n\t\t\t\treason: \"security_violation\",\n\t\t\t\tfeatureId,\n\t\t\t\tdetail: \"Security policy violation detected in review\",\n\t\t\t\tsuggestedAction: \"Halt pipeline immediately. Manual security review required.\",\n\t\t\t};\n\t\t}\n\n\t\t// architecture-principles FAIL 감지\n\t\tconst archMatch = content.match(/architecture[_-]principles\\s*\\|\\s*FAIL/i);\n\t\tif (archMatch) {\n\t\t\tthis.log.warn(\"Escalation detected: architecture_violation\", { featureId });\n\t\t\treturn {\n\t\t\t\treason: \"architecture_violation\",\n\t\t\t\tfeatureId,\n\t\t\t\tdetail: \"Architecture principles violation detected in review\",\n\t\t\t\tsuggestedAction: \"Consider reverting to Plan phase for architectural re-evaluation.\",\n\t\t\t};\n\t\t}\n\n\t\t// Standards Compliance Overall FAIL 감지\n\t\tconst compliance = parseStandardsCompliance(content);\n\t\tif (compliance === \"fail\") {\n\t\t\tthis.log.warn(\"Escalation detected: standards_fail_persistent\", { featureId });\n\t\t\treturn {\n\t\t\t\treason: \"standards_fail_persistent\",\n\t\t\t\tfeatureId,\n\t\t\t\tdetail: \"Standards Compliance Overall is FAIL\",\n\t\t\t\tsuggestedAction: \"User confirmation required to proceed or fix standards violations.\",\n\t\t\t};\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t// ─── Private ─────────────────────────────────────────────────────────────\n\n\tprivate async checkFileExists(target: string, featureId: string): Promise<boolean> {\n\t\treturn this.store.hasArtifact(featureId, target as ArtifactName);\n\t}\n\n\tprivate async checkTestPass(featureId: string): Promise<boolean> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"test-report.md\"))) {\n\t\t\treturn false;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"test-report.md\");\n\n\t\t// Regex match succeeded: use existing logic\n\t\tif (hasTestReportRegexMatch(content)) {\n\t\t\tconst result = parseTestReportPassed(content);\n\t\t\tthis.log.info(`Test report parsed via regex: ${result ? \"pass\" : \"fail\"}`, { featureId });\n\t\t\treturn result;\n\t\t}\n\n\t\t// Regex match failed: LLM fallback\n\t\tif (this.model) {\n\t\t\tthis.log.warn(\"Regex parse failed for test-report.md, using LLM fallback\", { featureId });\n\t\t\tconst verdict = await extractTestVerdictWithLLM(content, this.model);\n\t\t\tthis.log.info(`LLM fallback verdict for test-report: ${verdict}`, { featureId });\n\t\t\treturn verdict === \"pass\";\n\t\t}\n\n\t\tthis.log.warn(\"No regex match and no model for LLM fallback on test-report.md\", { featureId });\n\t\treturn false;\n\t}\n\n\tprivate async checkReviewApprove(featureId: string): Promise<boolean> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"review.md\"))) {\n\t\t\treturn false;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"review.md\");\n\t\tconst verdict = parseReviewVerdict(content);\n\n\t\tif (verdict !== null) {\n\t\t\tthis.log.info(`Review verdict parsed via regex: ${verdict}`, { featureId });\n\t\t\treturn verdict === \"approved\";\n\t\t}\n\n\t\t// Regex match failed: LLM fallback\n\t\tif (this.model) {\n\t\t\tthis.log.warn(\"Regex parse failed for review.md, using LLM fallback\", { featureId });\n\t\t\tconst fallbackVerdict = await extractReviewVerdictWithLLM(content, this.model);\n\t\t\tthis.log.info(`LLM fallback verdict for review: ${fallbackVerdict}`, { featureId });\n\t\t\treturn fallbackVerdict === \"approved\";\n\t\t}\n\n\t\tthis.log.warn(\"No regex match and no model for LLM fallback on review.md\", { featureId });\n\t\treturn false;\n\t}\n\n\tprivate async checkRegressionPass(featureId: string): Promise<boolean> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"regression-report.md\"))) {\n\t\t\treturn false;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"regression-report.md\");\n\t\tconst verdict = parseRegressionVerdict(content);\n\n\t\tif (verdict !== null) {\n\t\t\tthis.log.info(`Regression verdict parsed via regex: ${verdict}`, { featureId });\n\t\t\treturn verdict === \"pass\";\n\t\t}\n\n\t\t// Regex match failed: LLM fallback\n\t\tif (this.model) {\n\t\t\tthis.log.warn(\"Regex parse failed for regression-report.md, using LLM fallback\", { featureId });\n\t\t\tconst fallbackVerdict = await extractRegressionVerdictWithLLM(content, this.model);\n\t\t\tthis.log.info(`LLM fallback verdict for regression: ${fallbackVerdict}`, { featureId });\n\t\t\treturn fallbackVerdict === \"pass\";\n\t\t}\n\n\t\tthis.log.warn(\"No regex match and no model for LLM fallback on regression-report.md\", { featureId });\n\t\treturn false;\n\t}\n\n\tprivate async checkDiagnosisExists(featureId: string): Promise<boolean> {\n\t\treturn this.store.hasArtifact(featureId, \"diagnosis.md\");\n\t}\n\n\tprivate async checkStandardsCompliance(featureId: string): Promise<\"pass\" | \"warn\" | \"fail\" | null> {\n\t\tif (!(await this.store.hasArtifact(featureId, \"review.md\"))) {\n\t\t\treturn null;\n\t\t}\n\t\tconst content = await this.store.readArtifact(featureId, \"review.md\");\n\t\treturn parseStandardsCompliance(content);\n\t}\n\n\tprivate async handleNoGate(\n\t\tstep: PipelineStep,\n\t\tfeatureId: string,\n\t\trequestApproval?: ApprovalRequestFn,\n\t): Promise<GateResult> {\n\t\tconst autonomy = this.config.autonomyLevel;\n\n\t\tif (autonomy === \"step_by_step\" && requestApproval) {\n\t\t\treturn requestApproval(step, featureId, `Step completed: ${step.action}. Approve to continue.`);\n\t\t}\n\n\t\treturn { passed: true, action: \"approved\" };\n\t}\n}\n"]}
@@ -27,8 +27,6 @@ export interface OrchestrationOptions {
27
27
  logger?: VibeLogger;
28
28
  /** Lightweight skill index. Included in the agent system prompt. */
29
29
  availableSkills?: string;
30
- /** Project context (from project-context.md). Injected into system prompt for project awareness. */
31
- projectContext?: string;
32
30
  }
33
31
  /** Orchestration execution result */
34
32
  export interface OrchestrationResult {
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAkC,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGlD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIlE,sCAAsC;AACtC,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,QAAQ,CAAC;IACf,eAAe,CAAC,EAAE,iBAAiB,CAAC;IACpC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,qEAAqE;IACrE,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8DAA8D;IAC9D,qBAAqB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,0CAA0C;IAC1C,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oGAAoG;IACpG,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qCAAqC;AACrC,MAAM,WAAW,mBAAmB;IACnC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB;AAaD;;GAEG;AACH,wBAAgB,cAAc,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAevG;AAID;;;GAGG;AACH,wBAAuB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,GAAG,cAAc,CAAC,SAAS,EAAE,mBAAmB,CAAC,CA8KrH","sourcesContent":["import type { Agent } from \"@mariozechner/pi-agent-core\";\nimport type { Model } from \"@mariozechner/pi-ai\";\nimport type { GetApiKeyFn } from \"./agent-factory.js\";\nimport type { ApprovalRequestFn } from \"./gate.js\";\nimport { createModuleLogger, noopLogger, type VibeLogger } from \"./logger.js\";\nimport type { StepExecutor } from \"./pipeline.js\";\nimport { PipelineRunner } from \"./pipeline.js\";\nimport { createPipeline, resolveExecutionOrder, resolveFeatureWorkflowType } from \"./router.js\";\nimport type { VibeStore } from \"./store.js\";\nimport type { PlanData, VibeConfig, VibeEvent } from \"./types.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Orchestration execution options */\nexport interface OrchestrationOptions {\n\tstore: VibeStore;\n\tconfig: VibeConfig;\n\tprojectRoot: string;\n\tplan: PlanData;\n\trequestApproval?: ApprovalRequestFn;\n\tstepExecutor?: StepExecutor;\n\tgetApiKey?: GetApiKeyFn;\n\t/** Model to use. Uses the Agent's default model if not specified. */\n\tmodel?: Model<any>;\n\tonAgentCreated?: (agent: Agent) => void;\n\tonAgentFinished?: () => void;\n\t/** Parent feature ID for enhancement workflows. Passed to createPipeline. */\n\tparentFeatureId?: string;\n\t/** Specifies already-completed features to skip on resume. */\n\tskipCompletedFeatures?: Set<string>;\n\t/** Logger. Uses noop if not specified. */\n\tlogger?: VibeLogger;\n\t/** Lightweight skill index. Included in the agent system prompt. */\n\tavailableSkills?: string;\n\t/** Project context (from project-context.md). Injected into system prompt for project awareness. */\n\tprojectContext?: string;\n}\n\n/** Orchestration execution result */\nexport interface OrchestrationResult {\n\tcompleted: string[];\n\tfailed: string[];\n\tskipped: string[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction createEvent(type: VibeEvent[\"type\"], featureId: string, data?: Record<string, unknown>): VibeEvent {\n\treturn {\n\t\ttype,\n\t\tfeatureId,\n\t\ttimestamp: new Date().toISOString(),\n\t\tdata,\n\t};\n}\n\n/**\n * Returns feature IDs that directly or indirectly depend on failedId in the dependency graph.\n */\nexport function findDependents(dependencyGraph: Record<string, string[]>, failedId: string): Set<string> {\n\tconst dependents = new Set<string>();\n\tconst queue = [failedId];\n\n\twhile (queue.length > 0) {\n\t\tconst current = queue.shift()!;\n\t\tfor (const [featureId, deps] of Object.entries(dependencyGraph)) {\n\t\t\tif (deps.includes(current) && !dependents.has(featureId)) {\n\t\t\t\tdependents.add(featureId);\n\t\t\t\tqueue.push(featureId);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn dependents;\n}\n\n// ─── Orchestrator ────────────────────────────────────────────────────────────\n\n/**\n * Sequentially executes features from plan.json in dependency graph order.\n * Skips dependent features when a predecessor fails.\n */\nexport async function* runOrchestration(options: OrchestrationOptions): AsyncGenerator<VibeEvent, OrchestrationResult> {\n\tconst {\n\t\tstore,\n\t\tconfig,\n\t\tprojectRoot,\n\t\tplan,\n\t\trequestApproval,\n\t\tstepExecutor,\n\t\tgetApiKey,\n\t\tmodel,\n\t\tonAgentCreated,\n\t\tonAgentFinished,\n\t\tparentFeatureId,\n\t\tskipCompletedFeatures,\n\t\tlogger,\n\t} = options;\n\n\tconst log = createModuleLogger(logger ?? noopLogger, \"orchestrator\");\n\n\tconst executionOrder = resolveExecutionOrder(plan.dependencyGraph);\n\tlog.info(`Execution order resolved: [${executionOrder.join(\", \")}]`, {\n\t\tfeatureCount: executionOrder.length,\n\t});\n\tconst completed = new Set<string>(skipCompletedFeatures ?? []);\n\tconst failed = new Set<string>();\n\tconst skipped = new Set<string>();\n\n\tyield createEvent(\"orchestration_start\", \"orchestrator\", {\n\t\tfeatureCount: executionOrder.length,\n\t\texecutionOrder,\n\t});\n\n\tfor (const featureId of executionOrder) {\n\t\t// Skip already-completed features (on resume)\n\t\tif (completed.has(featureId)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Features to skip due to dependency failure\n\t\tif (skipped.has(featureId)) {\n\t\t\tlog.warn(`Feature skipped due to dependency failure: ${featureId}`, { featureId });\n\t\t\tconst feature = plan.features.find((f) => f.featureId === featureId);\n\t\t\tyield createEvent(\"feature_skipped\", featureId, {\n\t\t\t\ttitle: feature?.title,\n\t\t\t\treason: \"dependency failed\",\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst feature = plan.features.find((f) => f.featureId === featureId);\n\t\tlog.info(`Feature starting: ${featureId}`, { featureId, title: feature?.title });\n\n\t\tyield createEvent(\"feature_start\", featureId, {\n\t\t\ttitle: feature?.title,\n\t\t\tpriority: feature?.priority,\n\t\t});\n\n\t\t// Ensure spec.md exists (must be pre-created by planner)\n\t\tawait store.ensureFeatureDir(featureId);\n\n\t\t// Create per-feature pipeline. Restore currentStep if pipeline.json exists on disk.\n\t\tconst pipeline = createPipeline(resolveFeatureWorkflowType(featureId, plan.workflowType), featureId, {\n\t\t\tmaxRetries: config.retry.maxTestRetries,\n\t\t\tparentFeatureId,\n\t\t});\n\n\t\tconst persisted = await store.loadPipelineState(featureId);\n\t\tif (persisted && persisted.currentStep > 0) {\n\t\t\tlog.info(`Restoring pipeline state: ${featureId} from step ${persisted.currentStep}`, {\n\t\t\t\tfeatureId,\n\t\t\t\tcurrentStep: persisted.currentStep,\n\t\t\t});\n\t\t\tpipeline.currentStep = persisted.currentStep;\n\t\t\tpipeline.retryCount = persisted.retryCount;\n\t\t}\n\n\t\t// Execute pipeline\n\t\tconst runner = new PipelineRunner({\n\t\t\tstore,\n\t\t\tconfig,\n\t\t\tprojectRoot,\n\t\t\trequestApproval,\n\t\t\tstepExecutor,\n\t\t\tgetApiKey,\n\t\t\tmodel,\n\t\t\tonAgentCreated,\n\t\t\tonAgentFinished,\n\t\t\tlogger,\n\t\t\tavailableSkills: options.availableSkills,\n\t\t\tprojectContext: options.projectContext,\n\t\t});\n\n\t\tlet featureFailed = false;\n\t\tlet featureAborted = false;\n\n\t\tfor await (const event of runner.run(pipeline)) {\n\t\t\tyield event;\n\n\t\t\tif (event.type === \"step_failed\") {\n\t\t\t\tfeatureFailed = true;\n\t\t\t}\n\t\t\tif (event.type === \"feature_aborted\") {\n\t\t\t\tfeatureAborted = true;\n\t\t\t}\n\t\t}\n\n\t\t// Abort: feature artifacts already deleted by PipelineRunner.\n\t\t// Stop orchestration immediately. Completed features are preserved.\n\t\tif (featureAborted || pipeline.status === \"aborted\") {\n\t\t\tlog.error(`Feature aborted: ${featureId}, stopping orchestration`, { featureId });\n\t\t\tyield createEvent(\"orchestration_complete\", \"orchestrator\", {\n\t\t\t\tcompleted: [...completed],\n\t\t\t\tfailed: [...failed],\n\t\t\t\tskipped: [...skipped],\n\t\t\t\tabortedFeature: featureId,\n\t\t\t\ttotal: executionOrder.length,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tcompleted: [...completed],\n\t\t\t\tfailed: [...failed],\n\t\t\t\tskipped: [...skipped],\n\t\t\t};\n\t\t}\n\n\t\tif (featureFailed || pipeline.status === \"failed\") {\n\t\t\tlog.warn(`Feature failed: ${featureId}`, { featureId });\n\t\t\tfailed.add(featureId);\n\n\t\t\t// Mark dependent features as skipped\n\t\t\tconst dependents = findDependents(plan.dependencyGraph, featureId);\n\t\t\tfor (const dep of dependents) {\n\t\t\t\tskipped.add(dep);\n\t\t\t}\n\n\t\t\tyield createEvent(\"feature_skipped\", featureId, {\n\t\t\t\ttitle: feature?.title,\n\t\t\t\treason: \"failed\",\n\t\t\t});\n\t\t} else if (pipeline.status === \"blocked\") {\n\t\t\tfailed.add(featureId);\n\n\t\t\tconst dependents = findDependents(plan.dependencyGraph, featureId);\n\t\t\tfor (const dep of dependents) {\n\t\t\t\tskipped.add(dep);\n\t\t\t}\n\t\t} else {\n\t\t\tlog.info(`Feature completed: ${featureId}`, { featureId });\n\t\t\tcompleted.add(featureId);\n\n\t\t\tyield createEvent(\"feature_complete\", featureId, {\n\t\t\t\ttitle: feature?.title,\n\t\t\t});\n\t\t}\n\t}\n\n\tlog.info(`Orchestration complete: ${completed.size} completed, ${failed.size} failed, ${skipped.size} skipped`, {\n\t\tcompleted: [...completed],\n\t\tfailed: [...failed],\n\t\tskipped: [...skipped],\n\t});\n\n\tyield createEvent(\"orchestration_complete\", \"orchestrator\", {\n\t\tcompleted: [...completed],\n\t\tfailed: [...failed],\n\t\tskipped: [...skipped],\n\t\ttotal: executionOrder.length,\n\t});\n\n\treturn {\n\t\tcompleted: [...completed],\n\t\tfailed: [...failed],\n\t\tskipped: [...skipped],\n\t};\n}\n"]}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAkC,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGlD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIlE,sCAAsC;AACtC,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,QAAQ,CAAC;IACf,eAAe,CAAC,EAAE,iBAAiB,CAAC;IACpC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,qEAAqE;IACrE,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8DAA8D;IAC9D,qBAAqB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,0CAA0C;IAC1C,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qCAAqC;AACrC,MAAM,WAAW,mBAAmB;IACnC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB;AAaD;;GAEG;AACH,wBAAgB,cAAc,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAevG;AAID;;;GAGG;AACH,wBAAuB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,GAAG,cAAc,CAAC,SAAS,EAAE,mBAAmB,CAAC,CA6KrH","sourcesContent":["import type { Agent } from \"@mariozechner/pi-agent-core\";\nimport type { Model } from \"@mariozechner/pi-ai\";\nimport type { GetApiKeyFn } from \"./agent-factory.js\";\nimport type { ApprovalRequestFn } from \"./gate.js\";\nimport { createModuleLogger, noopLogger, type VibeLogger } from \"./logger.js\";\nimport type { StepExecutor } from \"./pipeline.js\";\nimport { PipelineRunner } from \"./pipeline.js\";\nimport { createPipeline, resolveExecutionOrder, resolveFeatureWorkflowType } from \"./router.js\";\nimport type { VibeStore } from \"./store.js\";\nimport type { PlanData, VibeConfig, VibeEvent } from \"./types.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Orchestration execution options */\nexport interface OrchestrationOptions {\n\tstore: VibeStore;\n\tconfig: VibeConfig;\n\tprojectRoot: string;\n\tplan: PlanData;\n\trequestApproval?: ApprovalRequestFn;\n\tstepExecutor?: StepExecutor;\n\tgetApiKey?: GetApiKeyFn;\n\t/** Model to use. Uses the Agent's default model if not specified. */\n\tmodel?: Model<any>;\n\tonAgentCreated?: (agent: Agent) => void;\n\tonAgentFinished?: () => void;\n\t/** Parent feature ID for enhancement workflows. Passed to createPipeline. */\n\tparentFeatureId?: string;\n\t/** Specifies already-completed features to skip on resume. */\n\tskipCompletedFeatures?: Set<string>;\n\t/** Logger. Uses noop if not specified. */\n\tlogger?: VibeLogger;\n\t/** Lightweight skill index. Included in the agent system prompt. */\n\tavailableSkills?: string;\n}\n\n/** Orchestration execution result */\nexport interface OrchestrationResult {\n\tcompleted: string[];\n\tfailed: string[];\n\tskipped: string[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction createEvent(type: VibeEvent[\"type\"], featureId: string, data?: Record<string, unknown>): VibeEvent {\n\treturn {\n\t\ttype,\n\t\tfeatureId,\n\t\ttimestamp: new Date().toISOString(),\n\t\tdata,\n\t};\n}\n\n/**\n * Returns feature IDs that directly or indirectly depend on failedId in the dependency graph.\n */\nexport function findDependents(dependencyGraph: Record<string, string[]>, failedId: string): Set<string> {\n\tconst dependents = new Set<string>();\n\tconst queue = [failedId];\n\n\twhile (queue.length > 0) {\n\t\tconst current = queue.shift()!;\n\t\tfor (const [featureId, deps] of Object.entries(dependencyGraph)) {\n\t\t\tif (deps.includes(current) && !dependents.has(featureId)) {\n\t\t\t\tdependents.add(featureId);\n\t\t\t\tqueue.push(featureId);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn dependents;\n}\n\n// ─── Orchestrator ────────────────────────────────────────────────────────────\n\n/**\n * Sequentially executes features from plan.json in dependency graph order.\n * Skips dependent features when a predecessor fails.\n */\nexport async function* runOrchestration(options: OrchestrationOptions): AsyncGenerator<VibeEvent, OrchestrationResult> {\n\tconst {\n\t\tstore,\n\t\tconfig,\n\t\tprojectRoot,\n\t\tplan,\n\t\trequestApproval,\n\t\tstepExecutor,\n\t\tgetApiKey,\n\t\tmodel,\n\t\tonAgentCreated,\n\t\tonAgentFinished,\n\t\tparentFeatureId,\n\t\tskipCompletedFeatures,\n\t\tlogger,\n\t} = options;\n\n\tconst log = createModuleLogger(logger ?? noopLogger, \"orchestrator\");\n\n\tconst executionOrder = resolveExecutionOrder(plan.dependencyGraph);\n\tlog.info(`Execution order resolved: [${executionOrder.join(\", \")}]`, {\n\t\tfeatureCount: executionOrder.length,\n\t});\n\tconst completed = new Set<string>(skipCompletedFeatures ?? []);\n\tconst failed = new Set<string>();\n\tconst skipped = new Set<string>();\n\n\tyield createEvent(\"orchestration_start\", \"orchestrator\", {\n\t\tfeatureCount: executionOrder.length,\n\t\texecutionOrder,\n\t});\n\n\tfor (const featureId of executionOrder) {\n\t\t// Skip already-completed features (on resume)\n\t\tif (completed.has(featureId)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Features to skip due to dependency failure\n\t\tif (skipped.has(featureId)) {\n\t\t\tlog.warn(`Feature skipped due to dependency failure: ${featureId}`, { featureId });\n\t\t\tconst feature = plan.features.find((f) => f.featureId === featureId);\n\t\t\tyield createEvent(\"feature_skipped\", featureId, {\n\t\t\t\ttitle: feature?.title,\n\t\t\t\treason: \"dependency failed\",\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst feature = plan.features.find((f) => f.featureId === featureId);\n\t\tlog.info(`Feature starting: ${featureId}`, { featureId, title: feature?.title });\n\n\t\tyield createEvent(\"feature_start\", featureId, {\n\t\t\ttitle: feature?.title,\n\t\t\tpriority: feature?.priority,\n\t\t});\n\n\t\t// Ensure spec.md exists (must be pre-created by planner)\n\t\tawait store.ensureFeatureDir(featureId);\n\n\t\t// Create per-feature pipeline. Restore currentStep if pipeline.json exists on disk.\n\t\tconst pipeline = createPipeline(resolveFeatureWorkflowType(featureId, plan.workflowType), featureId, {\n\t\t\tmaxRetries: config.retry.maxTestRetries,\n\t\t\tparentFeatureId,\n\t\t});\n\n\t\tconst persisted = await store.loadPipelineState(featureId);\n\t\tif (persisted && persisted.currentStep > 0) {\n\t\t\tlog.info(`Restoring pipeline state: ${featureId} from step ${persisted.currentStep}`, {\n\t\t\t\tfeatureId,\n\t\t\t\tcurrentStep: persisted.currentStep,\n\t\t\t});\n\t\t\tpipeline.currentStep = persisted.currentStep;\n\t\t\tpipeline.retryCount = persisted.retryCount;\n\t\t}\n\n\t\t// Execute pipeline\n\t\tconst runner = new PipelineRunner({\n\t\t\tstore,\n\t\t\tconfig,\n\t\t\tprojectRoot,\n\t\t\trequestApproval,\n\t\t\tstepExecutor,\n\t\t\tgetApiKey,\n\t\t\tmodel,\n\t\t\tonAgentCreated,\n\t\t\tonAgentFinished,\n\t\t\tlogger,\n\t\t\tavailableSkills: options.availableSkills,\n\t\t});\n\n\t\tlet featureFailed = false;\n\t\tlet featureAborted = false;\n\n\t\tfor await (const event of runner.run(pipeline)) {\n\t\t\tyield event;\n\n\t\t\tif (event.type === \"step_failed\") {\n\t\t\t\tfeatureFailed = true;\n\t\t\t}\n\t\t\tif (event.type === \"feature_aborted\") {\n\t\t\t\tfeatureAborted = true;\n\t\t\t}\n\t\t}\n\n\t\t// Abort: feature artifacts already deleted by PipelineRunner.\n\t\t// Stop orchestration immediately. Completed features are preserved.\n\t\tif (featureAborted || pipeline.status === \"aborted\") {\n\t\t\tlog.error(`Feature aborted: ${featureId}, stopping orchestration`, { featureId });\n\t\t\tyield createEvent(\"orchestration_complete\", \"orchestrator\", {\n\t\t\t\tcompleted: [...completed],\n\t\t\t\tfailed: [...failed],\n\t\t\t\tskipped: [...skipped],\n\t\t\t\tabortedFeature: featureId,\n\t\t\t\ttotal: executionOrder.length,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tcompleted: [...completed],\n\t\t\t\tfailed: [...failed],\n\t\t\t\tskipped: [...skipped],\n\t\t\t};\n\t\t}\n\n\t\tif (featureFailed || pipeline.status === \"failed\") {\n\t\t\tlog.warn(`Feature failed: ${featureId}`, { featureId });\n\t\t\tfailed.add(featureId);\n\n\t\t\t// Mark dependent features as skipped\n\t\t\tconst dependents = findDependents(plan.dependencyGraph, featureId);\n\t\t\tfor (const dep of dependents) {\n\t\t\t\tskipped.add(dep);\n\t\t\t}\n\n\t\t\tyield createEvent(\"feature_skipped\", featureId, {\n\t\t\t\ttitle: feature?.title,\n\t\t\t\treason: \"failed\",\n\t\t\t});\n\t\t} else if (pipeline.status === \"blocked\") {\n\t\t\tfailed.add(featureId);\n\n\t\t\tconst dependents = findDependents(plan.dependencyGraph, featureId);\n\t\t\tfor (const dep of dependents) {\n\t\t\t\tskipped.add(dep);\n\t\t\t}\n\t\t} else {\n\t\t\tlog.info(`Feature completed: ${featureId}`, { featureId });\n\t\t\tcompleted.add(featureId);\n\n\t\t\tyield createEvent(\"feature_complete\", featureId, {\n\t\t\t\ttitle: feature?.title,\n\t\t\t});\n\t\t}\n\t}\n\n\tlog.info(`Orchestration complete: ${completed.size} completed, ${failed.size} failed, ${skipped.size} skipped`, {\n\t\tcompleted: [...completed],\n\t\tfailed: [...failed],\n\t\tskipped: [...skipped],\n\t});\n\n\tyield createEvent(\"orchestration_complete\", \"orchestrator\", {\n\t\tcompleted: [...completed],\n\t\tfailed: [...failed],\n\t\tskipped: [...skipped],\n\t\ttotal: executionOrder.length,\n\t});\n\n\treturn {\n\t\tcompleted: [...completed],\n\t\tfailed: [...failed],\n\t\tskipped: [...skipped],\n\t};\n}\n"]}
@@ -96,7 +96,6 @@ export async function* runOrchestration(options) {
96
96
  onAgentFinished,
97
97
  logger,
98
98
  availableSkills: options.availableSkills,
99
- projectContext: options.projectContext,
100
99
  });
101
100
  let featureFailed = false;
102
101
  let featureAborted = false;
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent } from \"@mariozechner/pi-agent-core\";\nimport type { Model } from \"@mariozechner/pi-ai\";\nimport type { GetApiKeyFn } from \"./agent-factory.js\";\nimport type { ApprovalRequestFn } from \"./gate.js\";\nimport { createModuleLogger, noopLogger, type VibeLogger } from \"./logger.js\";\nimport type { StepExecutor } from \"./pipeline.js\";\nimport { PipelineRunner } from \"./pipeline.js\";\nimport { createPipeline, resolveExecutionOrder, resolveFeatureWorkflowType } from \"./router.js\";\nimport type { VibeStore } from \"./store.js\";\nimport type { PlanData, VibeConfig, VibeEvent } from \"./types.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Orchestration execution options */\nexport interface OrchestrationOptions {\n\tstore: VibeStore;\n\tconfig: VibeConfig;\n\tprojectRoot: string;\n\tplan: PlanData;\n\trequestApproval?: ApprovalRequestFn;\n\tstepExecutor?: StepExecutor;\n\tgetApiKey?: GetApiKeyFn;\n\t/** Model to use. Uses the Agent's default model if not specified. */\n\tmodel?: Model<any>;\n\tonAgentCreated?: (agent: Agent) => void;\n\tonAgentFinished?: () => void;\n\t/** Parent feature ID for enhancement workflows. Passed to createPipeline. */\n\tparentFeatureId?: string;\n\t/** Specifies already-completed features to skip on resume. */\n\tskipCompletedFeatures?: Set<string>;\n\t/** Logger. Uses noop if not specified. */\n\tlogger?: VibeLogger;\n\t/** Lightweight skill index. Included in the agent system prompt. */\n\tavailableSkills?: string;\n\t/** Project context (from project-context.md). Injected into system prompt for project awareness. */\n\tprojectContext?: string;\n}\n\n/** Orchestration execution result */\nexport interface OrchestrationResult {\n\tcompleted: string[];\n\tfailed: string[];\n\tskipped: string[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction createEvent(type: VibeEvent[\"type\"], featureId: string, data?: Record<string, unknown>): VibeEvent {\n\treturn {\n\t\ttype,\n\t\tfeatureId,\n\t\ttimestamp: new Date().toISOString(),\n\t\tdata,\n\t};\n}\n\n/**\n * Returns feature IDs that directly or indirectly depend on failedId in the dependency graph.\n */\nexport function findDependents(dependencyGraph: Record<string, string[]>, failedId: string): Set<string> {\n\tconst dependents = new Set<string>();\n\tconst queue = [failedId];\n\n\twhile (queue.length > 0) {\n\t\tconst current = queue.shift()!;\n\t\tfor (const [featureId, deps] of Object.entries(dependencyGraph)) {\n\t\t\tif (deps.includes(current) && !dependents.has(featureId)) {\n\t\t\t\tdependents.add(featureId);\n\t\t\t\tqueue.push(featureId);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn dependents;\n}\n\n// ─── Orchestrator ────────────────────────────────────────────────────────────\n\n/**\n * Sequentially executes features from plan.json in dependency graph order.\n * Skips dependent features when a predecessor fails.\n */\nexport async function* runOrchestration(options: OrchestrationOptions): AsyncGenerator<VibeEvent, OrchestrationResult> {\n\tconst {\n\t\tstore,\n\t\tconfig,\n\t\tprojectRoot,\n\t\tplan,\n\t\trequestApproval,\n\t\tstepExecutor,\n\t\tgetApiKey,\n\t\tmodel,\n\t\tonAgentCreated,\n\t\tonAgentFinished,\n\t\tparentFeatureId,\n\t\tskipCompletedFeatures,\n\t\tlogger,\n\t} = options;\n\n\tconst log = createModuleLogger(logger ?? noopLogger, \"orchestrator\");\n\n\tconst executionOrder = resolveExecutionOrder(plan.dependencyGraph);\n\tlog.info(`Execution order resolved: [${executionOrder.join(\", \")}]`, {\n\t\tfeatureCount: executionOrder.length,\n\t});\n\tconst completed = new Set<string>(skipCompletedFeatures ?? []);\n\tconst failed = new Set<string>();\n\tconst skipped = new Set<string>();\n\n\tyield createEvent(\"orchestration_start\", \"orchestrator\", {\n\t\tfeatureCount: executionOrder.length,\n\t\texecutionOrder,\n\t});\n\n\tfor (const featureId of executionOrder) {\n\t\t// Skip already-completed features (on resume)\n\t\tif (completed.has(featureId)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Features to skip due to dependency failure\n\t\tif (skipped.has(featureId)) {\n\t\t\tlog.warn(`Feature skipped due to dependency failure: ${featureId}`, { featureId });\n\t\t\tconst feature = plan.features.find((f) => f.featureId === featureId);\n\t\t\tyield createEvent(\"feature_skipped\", featureId, {\n\t\t\t\ttitle: feature?.title,\n\t\t\t\treason: \"dependency failed\",\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst feature = plan.features.find((f) => f.featureId === featureId);\n\t\tlog.info(`Feature starting: ${featureId}`, { featureId, title: feature?.title });\n\n\t\tyield createEvent(\"feature_start\", featureId, {\n\t\t\ttitle: feature?.title,\n\t\t\tpriority: feature?.priority,\n\t\t});\n\n\t\t// Ensure spec.md exists (must be pre-created by planner)\n\t\tawait store.ensureFeatureDir(featureId);\n\n\t\t// Create per-feature pipeline. Restore currentStep if pipeline.json exists on disk.\n\t\tconst pipeline = createPipeline(resolveFeatureWorkflowType(featureId, plan.workflowType), featureId, {\n\t\t\tmaxRetries: config.retry.maxTestRetries,\n\t\t\tparentFeatureId,\n\t\t});\n\n\t\tconst persisted = await store.loadPipelineState(featureId);\n\t\tif (persisted && persisted.currentStep > 0) {\n\t\t\tlog.info(`Restoring pipeline state: ${featureId} from step ${persisted.currentStep}`, {\n\t\t\t\tfeatureId,\n\t\t\t\tcurrentStep: persisted.currentStep,\n\t\t\t});\n\t\t\tpipeline.currentStep = persisted.currentStep;\n\t\t\tpipeline.retryCount = persisted.retryCount;\n\t\t}\n\n\t\t// Execute pipeline\n\t\tconst runner = new PipelineRunner({\n\t\t\tstore,\n\t\t\tconfig,\n\t\t\tprojectRoot,\n\t\t\trequestApproval,\n\t\t\tstepExecutor,\n\t\t\tgetApiKey,\n\t\t\tmodel,\n\t\t\tonAgentCreated,\n\t\t\tonAgentFinished,\n\t\t\tlogger,\n\t\t\tavailableSkills: options.availableSkills,\n\t\t\tprojectContext: options.projectContext,\n\t\t});\n\n\t\tlet featureFailed = false;\n\t\tlet featureAborted = false;\n\n\t\tfor await (const event of runner.run(pipeline)) {\n\t\t\tyield event;\n\n\t\t\tif (event.type === \"step_failed\") {\n\t\t\t\tfeatureFailed = true;\n\t\t\t}\n\t\t\tif (event.type === \"feature_aborted\") {\n\t\t\t\tfeatureAborted = true;\n\t\t\t}\n\t\t}\n\n\t\t// Abort: feature artifacts already deleted by PipelineRunner.\n\t\t// Stop orchestration immediately. Completed features are preserved.\n\t\tif (featureAborted || pipeline.status === \"aborted\") {\n\t\t\tlog.error(`Feature aborted: ${featureId}, stopping orchestration`, { featureId });\n\t\t\tyield createEvent(\"orchestration_complete\", \"orchestrator\", {\n\t\t\t\tcompleted: [...completed],\n\t\t\t\tfailed: [...failed],\n\t\t\t\tskipped: [...skipped],\n\t\t\t\tabortedFeature: featureId,\n\t\t\t\ttotal: executionOrder.length,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tcompleted: [...completed],\n\t\t\t\tfailed: [...failed],\n\t\t\t\tskipped: [...skipped],\n\t\t\t};\n\t\t}\n\n\t\tif (featureFailed || pipeline.status === \"failed\") {\n\t\t\tlog.warn(`Feature failed: ${featureId}`, { featureId });\n\t\t\tfailed.add(featureId);\n\n\t\t\t// Mark dependent features as skipped\n\t\t\tconst dependents = findDependents(plan.dependencyGraph, featureId);\n\t\t\tfor (const dep of dependents) {\n\t\t\t\tskipped.add(dep);\n\t\t\t}\n\n\t\t\tyield createEvent(\"feature_skipped\", featureId, {\n\t\t\t\ttitle: feature?.title,\n\t\t\t\treason: \"failed\",\n\t\t\t});\n\t\t} else if (pipeline.status === \"blocked\") {\n\t\t\tfailed.add(featureId);\n\n\t\t\tconst dependents = findDependents(plan.dependencyGraph, featureId);\n\t\t\tfor (const dep of dependents) {\n\t\t\t\tskipped.add(dep);\n\t\t\t}\n\t\t} else {\n\t\t\tlog.info(`Feature completed: ${featureId}`, { featureId });\n\t\t\tcompleted.add(featureId);\n\n\t\t\tyield createEvent(\"feature_complete\", featureId, {\n\t\t\t\ttitle: feature?.title,\n\t\t\t});\n\t\t}\n\t}\n\n\tlog.info(`Orchestration complete: ${completed.size} completed, ${failed.size} failed, ${skipped.size} skipped`, {\n\t\tcompleted: [...completed],\n\t\tfailed: [...failed],\n\t\tskipped: [...skipped],\n\t});\n\n\tyield createEvent(\"orchestration_complete\", \"orchestrator\", {\n\t\tcompleted: [...completed],\n\t\tfailed: [...failed],\n\t\tskipped: [...skipped],\n\t\ttotal: executionOrder.length,\n\t});\n\n\treturn {\n\t\tcompleted: [...completed],\n\t\tfailed: [...failed],\n\t\tskipped: [...skipped],\n\t};\n}\n"]}
1
+ {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent } from \"@mariozechner/pi-agent-core\";\nimport type { Model } from \"@mariozechner/pi-ai\";\nimport type { GetApiKeyFn } from \"./agent-factory.js\";\nimport type { ApprovalRequestFn } from \"./gate.js\";\nimport { createModuleLogger, noopLogger, type VibeLogger } from \"./logger.js\";\nimport type { StepExecutor } from \"./pipeline.js\";\nimport { PipelineRunner } from \"./pipeline.js\";\nimport { createPipeline, resolveExecutionOrder, resolveFeatureWorkflowType } from \"./router.js\";\nimport type { VibeStore } from \"./store.js\";\nimport type { PlanData, VibeConfig, VibeEvent } from \"./types.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Orchestration execution options */\nexport interface OrchestrationOptions {\n\tstore: VibeStore;\n\tconfig: VibeConfig;\n\tprojectRoot: string;\n\tplan: PlanData;\n\trequestApproval?: ApprovalRequestFn;\n\tstepExecutor?: StepExecutor;\n\tgetApiKey?: GetApiKeyFn;\n\t/** Model to use. Uses the Agent's default model if not specified. */\n\tmodel?: Model<any>;\n\tonAgentCreated?: (agent: Agent) => void;\n\tonAgentFinished?: () => void;\n\t/** Parent feature ID for enhancement workflows. Passed to createPipeline. */\n\tparentFeatureId?: string;\n\t/** Specifies already-completed features to skip on resume. */\n\tskipCompletedFeatures?: Set<string>;\n\t/** Logger. Uses noop if not specified. */\n\tlogger?: VibeLogger;\n\t/** Lightweight skill index. Included in the agent system prompt. */\n\tavailableSkills?: string;\n}\n\n/** Orchestration execution result */\nexport interface OrchestrationResult {\n\tcompleted: string[];\n\tfailed: string[];\n\tskipped: string[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction createEvent(type: VibeEvent[\"type\"], featureId: string, data?: Record<string, unknown>): VibeEvent {\n\treturn {\n\t\ttype,\n\t\tfeatureId,\n\t\ttimestamp: new Date().toISOString(),\n\t\tdata,\n\t};\n}\n\n/**\n * Returns feature IDs that directly or indirectly depend on failedId in the dependency graph.\n */\nexport function findDependents(dependencyGraph: Record<string, string[]>, failedId: string): Set<string> {\n\tconst dependents = new Set<string>();\n\tconst queue = [failedId];\n\n\twhile (queue.length > 0) {\n\t\tconst current = queue.shift()!;\n\t\tfor (const [featureId, deps] of Object.entries(dependencyGraph)) {\n\t\t\tif (deps.includes(current) && !dependents.has(featureId)) {\n\t\t\t\tdependents.add(featureId);\n\t\t\t\tqueue.push(featureId);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn dependents;\n}\n\n// ─── Orchestrator ────────────────────────────────────────────────────────────\n\n/**\n * Sequentially executes features from plan.json in dependency graph order.\n * Skips dependent features when a predecessor fails.\n */\nexport async function* runOrchestration(options: OrchestrationOptions): AsyncGenerator<VibeEvent, OrchestrationResult> {\n\tconst {\n\t\tstore,\n\t\tconfig,\n\t\tprojectRoot,\n\t\tplan,\n\t\trequestApproval,\n\t\tstepExecutor,\n\t\tgetApiKey,\n\t\tmodel,\n\t\tonAgentCreated,\n\t\tonAgentFinished,\n\t\tparentFeatureId,\n\t\tskipCompletedFeatures,\n\t\tlogger,\n\t} = options;\n\n\tconst log = createModuleLogger(logger ?? noopLogger, \"orchestrator\");\n\n\tconst executionOrder = resolveExecutionOrder(plan.dependencyGraph);\n\tlog.info(`Execution order resolved: [${executionOrder.join(\", \")}]`, {\n\t\tfeatureCount: executionOrder.length,\n\t});\n\tconst completed = new Set<string>(skipCompletedFeatures ?? []);\n\tconst failed = new Set<string>();\n\tconst skipped = new Set<string>();\n\n\tyield createEvent(\"orchestration_start\", \"orchestrator\", {\n\t\tfeatureCount: executionOrder.length,\n\t\texecutionOrder,\n\t});\n\n\tfor (const featureId of executionOrder) {\n\t\t// Skip already-completed features (on resume)\n\t\tif (completed.has(featureId)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Features to skip due to dependency failure\n\t\tif (skipped.has(featureId)) {\n\t\t\tlog.warn(`Feature skipped due to dependency failure: ${featureId}`, { featureId });\n\t\t\tconst feature = plan.features.find((f) => f.featureId === featureId);\n\t\t\tyield createEvent(\"feature_skipped\", featureId, {\n\t\t\t\ttitle: feature?.title,\n\t\t\t\treason: \"dependency failed\",\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst feature = plan.features.find((f) => f.featureId === featureId);\n\t\tlog.info(`Feature starting: ${featureId}`, { featureId, title: feature?.title });\n\n\t\tyield createEvent(\"feature_start\", featureId, {\n\t\t\ttitle: feature?.title,\n\t\t\tpriority: feature?.priority,\n\t\t});\n\n\t\t// Ensure spec.md exists (must be pre-created by planner)\n\t\tawait store.ensureFeatureDir(featureId);\n\n\t\t// Create per-feature pipeline. Restore currentStep if pipeline.json exists on disk.\n\t\tconst pipeline = createPipeline(resolveFeatureWorkflowType(featureId, plan.workflowType), featureId, {\n\t\t\tmaxRetries: config.retry.maxTestRetries,\n\t\t\tparentFeatureId,\n\t\t});\n\n\t\tconst persisted = await store.loadPipelineState(featureId);\n\t\tif (persisted && persisted.currentStep > 0) {\n\t\t\tlog.info(`Restoring pipeline state: ${featureId} from step ${persisted.currentStep}`, {\n\t\t\t\tfeatureId,\n\t\t\t\tcurrentStep: persisted.currentStep,\n\t\t\t});\n\t\t\tpipeline.currentStep = persisted.currentStep;\n\t\t\tpipeline.retryCount = persisted.retryCount;\n\t\t}\n\n\t\t// Execute pipeline\n\t\tconst runner = new PipelineRunner({\n\t\t\tstore,\n\t\t\tconfig,\n\t\t\tprojectRoot,\n\t\t\trequestApproval,\n\t\t\tstepExecutor,\n\t\t\tgetApiKey,\n\t\t\tmodel,\n\t\t\tonAgentCreated,\n\t\t\tonAgentFinished,\n\t\t\tlogger,\n\t\t\tavailableSkills: options.availableSkills,\n\t\t});\n\n\t\tlet featureFailed = false;\n\t\tlet featureAborted = false;\n\n\t\tfor await (const event of runner.run(pipeline)) {\n\t\t\tyield event;\n\n\t\t\tif (event.type === \"step_failed\") {\n\t\t\t\tfeatureFailed = true;\n\t\t\t}\n\t\t\tif (event.type === \"feature_aborted\") {\n\t\t\t\tfeatureAborted = true;\n\t\t\t}\n\t\t}\n\n\t\t// Abort: feature artifacts already deleted by PipelineRunner.\n\t\t// Stop orchestration immediately. Completed features are preserved.\n\t\tif (featureAborted || pipeline.status === \"aborted\") {\n\t\t\tlog.error(`Feature aborted: ${featureId}, stopping orchestration`, { featureId });\n\t\t\tyield createEvent(\"orchestration_complete\", \"orchestrator\", {\n\t\t\t\tcompleted: [...completed],\n\t\t\t\tfailed: [...failed],\n\t\t\t\tskipped: [...skipped],\n\t\t\t\tabortedFeature: featureId,\n\t\t\t\ttotal: executionOrder.length,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tcompleted: [...completed],\n\t\t\t\tfailed: [...failed],\n\t\t\t\tskipped: [...skipped],\n\t\t\t};\n\t\t}\n\n\t\tif (featureFailed || pipeline.status === \"failed\") {\n\t\t\tlog.warn(`Feature failed: ${featureId}`, { featureId });\n\t\t\tfailed.add(featureId);\n\n\t\t\t// Mark dependent features as skipped\n\t\t\tconst dependents = findDependents(plan.dependencyGraph, featureId);\n\t\t\tfor (const dep of dependents) {\n\t\t\t\tskipped.add(dep);\n\t\t\t}\n\n\t\t\tyield createEvent(\"feature_skipped\", featureId, {\n\t\t\t\ttitle: feature?.title,\n\t\t\t\treason: \"failed\",\n\t\t\t});\n\t\t} else if (pipeline.status === \"blocked\") {\n\t\t\tfailed.add(featureId);\n\n\t\t\tconst dependents = findDependents(plan.dependencyGraph, featureId);\n\t\t\tfor (const dep of dependents) {\n\t\t\t\tskipped.add(dep);\n\t\t\t}\n\t\t} else {\n\t\t\tlog.info(`Feature completed: ${featureId}`, { featureId });\n\t\t\tcompleted.add(featureId);\n\n\t\t\tyield createEvent(\"feature_complete\", featureId, {\n\t\t\t\ttitle: feature?.title,\n\t\t\t});\n\t\t}\n\t}\n\n\tlog.info(`Orchestration complete: ${completed.size} completed, ${failed.size} failed, ${skipped.size} skipped`, {\n\t\tcompleted: [...completed],\n\t\tfailed: [...failed],\n\t\tskipped: [...skipped],\n\t});\n\n\tyield createEvent(\"orchestration_complete\", \"orchestrator\", {\n\t\tcompleted: [...completed],\n\t\tfailed: [...failed],\n\t\tskipped: [...skipped],\n\t\ttotal: executionOrder.length,\n\t});\n\n\treturn {\n\t\tcompleted: [...completed],\n\t\tfailed: [...failed],\n\t\tskipped: [...skipped],\n\t};\n}\n"]}
@@ -40,6 +40,8 @@ export declare class PipelineEditorComponent implements EditorComponent {
40
40
  setDetail(detail: string): void;
41
41
  /** Adds an entry to the activity log. Removes oldest when exceeding MAX_ACTIVITY_LINES. */
42
42
  addActivity(text: string): void;
43
+ /** Replaces the last activity entry, or adds if empty. Used for streaming text in-place updates. */
44
+ updateLastActivity(text: string): void;
43
45
  /** Returns a read-only snapshot of the current activity log. */
44
46
  getActivityLog(): readonly string[];
45
47
  /** Clears the activity log. */
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline-editor.d.ts","sourceRoot":"","sources":["../src/pipeline-editor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EAEN,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,GAAG,EAGR,MAAM,sBAAsB,CAAC;AAqB9B,oEAAoE;AACpE,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;CACnD;AAaD,0BAA0B;AAC1B,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAY9C,CAAC;AAIF;;;;GAIG;AACH,qBAAa,uBAAwB,YAAW,eAAe;IAC9D,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,eAAe,CAA+C;IACtE,OAAO,CAAC,WAAW,CAAgB;IAEnC,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC,yCAAyC;IACzC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAEtC,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B,YAAY,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,kBAAkB,EAG1E;IAED,8BAA8B;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAK5B;IAED,qCAAqC;IACrC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAK9B;IAED,2FAA2F;IAC3F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAM9B;IAED,gEAAgE;IAChE,cAAc,IAAI,SAAS,MAAM,EAAE,CAElC;IAED,+BAA+B;IAC/B,aAAa,IAAI,IAAI,CAKpB;IAED,mCAAmC;IACnC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAGxC;IAED,6CAA6C;IAC7C,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAmBlC;IAED,sBAAsB;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAM5B;IAED,2DAA2D;IAC3D,YAAY,IAAI,IAAI,CAMnB;IAED,wCAAwC;IACxC,WAAW,IAAI,IAAI,CAKlB;IAID,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAuD9B;IAED,OAAO,IAAI,MAAM,CAEhB;IAED,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE3B;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAY9B;IAED,UAAU,IAAI,IAAI,CAEjB;IAID,yEAAyE;IACzE,OAAO,CAAC,mBAAmB;IAmB3B,kCAAkC;IAClC,OAAO,CAAC,cAAc;IAwBtB,uCAAuC;IACvC,OAAO,CAAC,QAAQ;CAKhB","sourcesContent":["import type { KeybindingsManager } from \"@mariozechner/pi-coding-agent\";\nimport {\n\tCURSOR_MARKER,\n\ttype EditorComponent,\n\ttype EditorTheme,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@mariozechner/pi-tui\";\n\n// ─── ANSI Helpers ────────────────────────────────────────────────────────────\n\nconst ANSI = {\n\treset: \"\\x1b[0m\",\n\tbold: \"\\x1b[1m\",\n\tdim: \"\\x1b[2m\",\n\tgreen: \"\\x1b[32m\",\n\tyellow: \"\\x1b[33m\",\n\tred: \"\\x1b[31m\",\n\tcyan: \"\\x1b[36m\",\n\tgray: \"\\x1b[90m\",\n} as const;\n\nfunction ansi(style: string, text: string): string {\n\treturn `${style}${text}${ANSI.reset}`;\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Step info for rendering (lightweight subset of PipelineStep). */\nexport interface PipelineStepInfo {\n\tagent: string;\n\taction: string;\n\tstatus: \"done\" | \"running\" | \"pending\" | \"skipped\";\n}\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\n/** Braille spinner frames. */\nconst SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n\n/** Max visible rows for step list. */\nconst MAX_VISIBLE_STEPS = 8;\n\n/** Max visible rows for activity log. */\nconst MAX_ACTIVITY_LINES = 6;\n\n/** Role label mapping. */\nexport const ROLE_LABELS: Record<string, string> = {\n\tplanner: \"Planner\",\n\tarchitect: \"Architect\",\n\tdeveloper: \"Developer\",\n\ttester: \"Tester\",\n\treviewer: \"Reviewer\",\n\tcicd: \"CI/CD\",\n\tanalyzer: \"Analyzer\",\n\tdiagnostician: \"Diagnostician\",\n\tdiscovery: \"Discovery\",\n\tprojectAnalyzer: \"Project Analyzer\",\n\tsystemArchitect: \"System Architect\",\n};\n\n// ─── PipelineEditorComponent ─────────────────────────────────────────────────\n\n/**\n * Read-only component that replaces the default editor during pipeline execution.\n * Shows current step and tool activity, ignoring normal input.\n * Supports Esc for abort and expandTools key for expand/collapse.\n */\nexport class PipelineEditorComponent implements EditorComponent {\n\tprivate phase = \"\";\n\tprivate detail = \"\";\n\tprivate tui: TUI;\n\tprivate keybindings: KeybindingsManager | undefined;\n\tprivate steps: PipelineStepInfo[] = [];\n\tprivate currentStepIndex = -1;\n\tprivate title = \" Vibe Pipeline \";\n\tprivate spinnerFrame = 0;\n\tprivate spinnerInterval: ReturnType<typeof setInterval> | null = null;\n\tprivate activityLog: string[] = [];\n\n\t/** Callbacks set by pi. */\n\tonSubmit?: (text: string) => void;\n\tonChange?: (text: string) => void;\n\n\t/** Border color function (set by pi). */\n\tborderColor?: (str: string) => string;\n\n\t/** Abort callback (set by vibe). */\n\tonAbort?: () => void;\n\n\t/** Toggle expand/collapse callback (set by vibe). */\n\tonToggleExpand?: () => void;\n\n\tconstructor(tui: TUI, _theme: EditorTheme, keybindings?: KeybindingsManager) {\n\t\tthis.tui = tui;\n\t\tthis.keybindings = keybindings;\n\t}\n\n\t/** Sets the current phase. */\n\tsetPhase(phase: string): void {\n\t\tif (this.phase !== phase) {\n\t\t\tthis.phase = phase;\n\t\t\tthis.tui.requestRender();\n\t\t}\n\t}\n\n\t/** Sets the detail progress info. */\n\tsetDetail(detail: string): void {\n\t\tif (this.detail !== detail) {\n\t\t\tthis.detail = detail;\n\t\t\tthis.tui.requestRender();\n\t\t}\n\t}\n\n\t/** Adds an entry to the activity log. Removes oldest when exceeding MAX_ACTIVITY_LINES. */\n\taddActivity(text: string): void {\n\t\tthis.activityLog.push(text);\n\t\tif (this.activityLog.length > MAX_ACTIVITY_LINES) {\n\t\t\tthis.activityLog.splice(0, this.activityLog.length - MAX_ACTIVITY_LINES);\n\t\t}\n\t\tthis.tui.requestRender();\n\t}\n\n\t/** Returns a read-only snapshot of the current activity log. */\n\tgetActivityLog(): readonly string[] {\n\t\treturn this.activityLog;\n\t}\n\n\t/** Clears the activity log. */\n\tclearActivity(): void {\n\t\tif (this.activityLog.length > 0) {\n\t\t\tthis.activityLog = [];\n\t\t\tthis.tui.requestRender();\n\t\t}\n\t}\n\n\t/** Sets the pipeline step list. */\n\tsetSteps(steps: PipelineStepInfo[]): void {\n\t\tthis.steps = steps;\n\t\tthis.tui.requestRender();\n\t}\n\n\t/** Sets the currently running step index. */\n\tsetCurrentStep(index: number): void {\n\t\tif (this.currentStepIndex !== index) {\n\t\t\tthis.currentStepIndex = index;\n\t\t\tthis.activityLog = [];\n\t\t\t// Update step states: previous steps → done, current → running\n\t\t\tfor (let i = 0; i < this.steps.length; i++) {\n\t\t\t\tif (this.steps[i].status === \"skipped\") continue;\n\t\t\t\tif (i < index) {\n\t\t\t\t\tthis.steps[i].status = \"done\";\n\t\t\t\t} else if (i === index) {\n\t\t\t\t\tthis.steps[i].status = \"running\";\n\t\t\t\t} else {\n\t\t\t\t\tif (this.steps[i].status !== \"skipped\") {\n\t\t\t\t\t\tthis.steps[i].status = \"pending\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t}\n\t}\n\n\t/** Sets the title. */\n\tsetTitle(title: string): void {\n\t\tconst formatted = ` ${title} `;\n\t\tif (this.title !== formatted) {\n\t\t\tthis.title = formatted;\n\t\t\tthis.tui.requestRender();\n\t\t}\n\t}\n\n\t/** Starts automatic spinner rotation at 80ms intervals. */\n\tstartSpinner(): void {\n\t\tif (this.spinnerInterval) return;\n\t\tthis.spinnerInterval = setInterval(() => {\n\t\t\tthis.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;\n\t\t\tthis.tui.requestRender();\n\t\t}, 80);\n\t}\n\n\t/** Stops automatic spinner rotation. */\n\tstopSpinner(): void {\n\t\tif (this.spinnerInterval) {\n\t\t\tclearInterval(this.spinnerInterval);\n\t\t\tthis.spinnerInterval = null;\n\t\t}\n\t}\n\n\t// ─── EditorComponent Implementation ─────────────────────────────────\n\n\trender(width: number): string[] {\n\t\tconst bc = this.borderColor ?? ((s: string) => s);\n\t\tconst innerWidth = Math.max(width - 4, 10); // border + padding\n\n\t\t// Top border: ┌ + ─ + title + ─ + ┐ = innerWidth + 4 = width\n\t\tconst titleText = this.title;\n\t\tconst topBarWidth = Math.max(innerWidth + 2 - visibleWidth(titleText), 0);\n\t\tconst topLeft = Math.floor(topBarWidth / 2);\n\t\tconst topRight = topBarWidth - topLeft;\n\t\tconst top = bc(`┌${\"─\".repeat(topLeft)}${titleText}${\"─\".repeat(topRight)}┐`);\n\n\t\tconst contentLines: string[] = [];\n\n\t\tif (this.steps.length > 0) {\n\t\t\t// Step list mode\n\t\t\tif (this.phase) {\n\t\t\t\tconst phaseHeader = ansi(ANSI.bold, this.phase);\n\t\t\t\tcontentLines.push(this.wrapLine(bc, phaseHeader, innerWidth));\n\t\t\t}\n\t\t\tconst { start, end } = this.computeVisibleRange();\n\t\t\tfor (let i = start; i < end; i++) {\n\t\t\t\tconst step = this.steps[i];\n\t\t\t\tconst line = this.renderStepLine(step, i);\n\t\t\t\tcontentLines.push(this.wrapLine(bc, line, innerWidth));\n\n\t\t\t\t// Show activity log below the running step\n\t\t\t\tif (step.status === \"running\" && this.activityLog.length > 0) {\n\t\t\t\t\tfor (const activity of this.activityLog) {\n\t\t\t\t\t\tconst activityText = ansi(ANSI.dim, ` ↳ ${activity}`);\n\t\t\t\t\t\tconst truncated = truncateToWidth(activityText, innerWidth);\n\t\t\t\t\t\tcontentLines.push(this.wrapLine(bc, truncated, innerWidth));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Single-state mode (Discovery, Planning, etc.)\n\t\t\tconst spinner = ansi(ANSI.cyan, SPINNER_FRAMES[this.spinnerFrame]);\n\t\t\tconst statusText = this.detail\n\t\t\t\t? `${spinner} ${ansi(ANSI.bold, this.phase)}: ${this.detail}`\n\t\t\t\t: `${spinner} ${ansi(ANSI.bold, this.phase)}...`;\n\t\t\tconst truncated = truncateToWidth(statusText, innerWidth);\n\t\t\tcontentLines.push(this.wrapLine(bc, truncated, innerWidth));\n\t\t}\n\n\t\t// Hint row\n\t\tconst expandKey = this.keybindings?.getKeys(\"expandTools\")[0] ?? \"ctrl+o\";\n\t\tconst hint = ansi(ANSI.dim, `Esc: abort ${expandKey}: expand/collapse`);\n\t\tconst hintPad = Math.max(innerWidth - visibleWidth(hint), 0);\n\t\tconst hintLine = `${bc(\"│\")} ${\" \".repeat(hintPad)}${hint} ${bc(\"│\")}`;\n\t\tcontentLines.push(hintLine);\n\n\t\t// Bottom border\n\t\tconst bottom = bc(`└${\"─\".repeat(innerWidth + 2)}┘`);\n\n\t\treturn [`${CURSOR_MARKER}${top}`, ...contentLines, bottom];\n\t}\n\n\tgetText(): string {\n\t\treturn \"\";\n\t}\n\n\tsetText(_text: string): void {\n\t\t// no-op: read-only\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Detect Esc key\n\t\tif (data === \"\\x1b\" || data === \"\\x1b\\x1b\") {\n\t\t\tthis.onAbort?.();\n\t\t\treturn;\n\t\t}\n\t\t// Detect expandTools key (Ctrl+O by default)\n\t\tif (this.keybindings?.matches(data, \"expandTools\")) {\n\t\t\tthis.onToggleExpand?.();\n\t\t\treturn;\n\t\t}\n\t\t// Ignore all other input\n\t}\n\n\tinvalidate(): void {\n\t\t// Cache invalidation. Next render() will use the latest state.\n\t}\n\n\t// ─── Private Helpers ─────────────────────────────────────────────────\n\n\t/** Computes visible range. Windows around the currently running step. */\n\tprivate computeVisibleRange(): { start: number; end: number } {\n\t\tconst total = this.steps.length;\n\t\tif (total <= MAX_VISIBLE_STEPS) {\n\t\t\treturn { start: 0, end: total };\n\t\t}\n\n\t\tconst center = Math.max(0, this.currentStepIndex);\n\t\tconst half = Math.floor(MAX_VISIBLE_STEPS / 2);\n\t\tlet start = Math.max(0, center - half);\n\t\tlet end = start + MAX_VISIBLE_STEPS;\n\n\t\tif (end > total) {\n\t\t\tend = total;\n\t\t\tstart = Math.max(0, end - MAX_VISIBLE_STEPS);\n\t\t}\n\n\t\treturn { start, end };\n\t}\n\n\t/** Renders a single step line. */\n\tprivate renderStepLine(step: PipelineStepInfo, index: number): string {\n\t\tconst label = ROLE_LABELS[step.agent] ?? step.agent;\n\t\tconst num = `${index + 1}`.padStart(2);\n\n\t\tswitch (step.status) {\n\t\t\tcase \"done\": {\n\t\t\t\tconst marker = ansi(ANSI.green, \"✓\");\n\t\t\t\treturn ansi(ANSI.dim, `${marker} ${ansi(ANSI.dim, `${num}. ${label}: ${step.action}`)}`);\n\t\t\t}\n\t\t\tcase \"running\": {\n\t\t\t\tconst spinner = ansi(`${ANSI.cyan}${ANSI.bold}`, SPINNER_FRAMES[this.spinnerFrame]);\n\t\t\t\treturn `${spinner} ${ansi(ANSI.bold, `${num}. ${label}: ${step.action}`)}`;\n\t\t\t}\n\t\t\tcase \"skipped\": {\n\t\t\t\tconst marker = ansi(ANSI.gray, \"⊘\");\n\t\t\t\treturn ansi(ANSI.dim, `${marker} ${ansi(ANSI.dim, `${num}. ${label}: ${step.action}`)}`);\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tconst marker = ansi(ANSI.gray, \"○\");\n\t\t\t\treturn ansi(ANSI.dim, `${marker} ${ansi(ANSI.dim, `${num}. ${label}: ${step.action}`)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Wraps content text as a box row. */\n\tprivate wrapLine(bc: (s: string) => string, content: string, innerWidth: number): string {\n\t\tconst truncated = truncateToWidth(content, innerWidth);\n\t\tconst pad = Math.max(innerWidth - visibleWidth(truncated), 0);\n\t\treturn `${bc(\"│\")} ${truncated}${\" \".repeat(pad)} ${bc(\"│\")}`;\n\t}\n}\n"]}
1
+ {"version":3,"file":"pipeline-editor.d.ts","sourceRoot":"","sources":["../src/pipeline-editor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EAEN,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,GAAG,EAGR,MAAM,sBAAsB,CAAC;AAqB9B,oEAAoE;AACpE,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;CACnD;AAaD,0BAA0B;AAC1B,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAY9C,CAAC;AAIF;;;;GAIG;AACH,qBAAa,uBAAwB,YAAW,eAAe;IAC9D,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,eAAe,CAA+C;IACtE,OAAO,CAAC,WAAW,CAAgB;IAEnC,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC,yCAAyC;IACzC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAEtC,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B,YAAY,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,kBAAkB,EAG1E;IAED,8BAA8B;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAK5B;IAED,qCAAqC;IACrC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAK9B;IAED,2FAA2F;IAC3F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAM9B;IAED,oGAAoG;IACpG,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOrC;IAED,gEAAgE;IAChE,cAAc,IAAI,SAAS,MAAM,EAAE,CAElC;IAED,+BAA+B;IAC/B,aAAa,IAAI,IAAI,CAKpB;IAED,mCAAmC;IACnC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAGxC;IAED,6CAA6C;IAC7C,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAmBlC;IAED,sBAAsB;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAM5B;IAED,2DAA2D;IAC3D,YAAY,IAAI,IAAI,CAMnB;IAED,wCAAwC;IACxC,WAAW,IAAI,IAAI,CAKlB;IAID,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA+E9B;IAED,OAAO,IAAI,MAAM,CAEhB;IAED,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE3B;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAY9B;IAED,UAAU,IAAI,IAAI,CAEjB;IAID,yEAAyE;IACzE,OAAO,CAAC,mBAAmB;IAmB3B,kCAAkC;IAClC,OAAO,CAAC,cAAc;IAwBtB,uCAAuC;IACvC,OAAO,CAAC,QAAQ;CAKhB","sourcesContent":["import type { KeybindingsManager } from \"@mariozechner/pi-coding-agent\";\nimport {\n\tCURSOR_MARKER,\n\ttype EditorComponent,\n\ttype EditorTheme,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@mariozechner/pi-tui\";\n\n// ─── ANSI Helpers ────────────────────────────────────────────────────────────\n\nconst ANSI = {\n\treset: \"\\x1b[0m\",\n\tbold: \"\\x1b[1m\",\n\tdim: \"\\x1b[2m\",\n\tgreen: \"\\x1b[32m\",\n\tyellow: \"\\x1b[33m\",\n\tred: \"\\x1b[31m\",\n\tcyan: \"\\x1b[36m\",\n\tgray: \"\\x1b[90m\",\n} as const;\n\nfunction ansi(style: string, text: string): string {\n\treturn `${style}${text}${ANSI.reset}`;\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Step info for rendering (lightweight subset of PipelineStep). */\nexport interface PipelineStepInfo {\n\tagent: string;\n\taction: string;\n\tstatus: \"done\" | \"running\" | \"pending\" | \"skipped\";\n}\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\n/** Braille spinner frames. */\nconst SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n\n/** Max visible rows for step list. */\nconst MAX_VISIBLE_STEPS = 8;\n\n/** Max visible rows for activity log. */\nconst MAX_ACTIVITY_LINES = 6;\n\n/** Role label mapping. */\nexport const ROLE_LABELS: Record<string, string> = {\n\tplanner: \"Planner\",\n\tarchitect: \"Architect\",\n\tdeveloper: \"Developer\",\n\ttester: \"Tester\",\n\treviewer: \"Reviewer\",\n\tcicd: \"CI/CD\",\n\tanalyzer: \"Analyzer\",\n\tdiagnostician: \"Diagnostician\",\n\tdiscovery: \"Discovery\",\n\tprojectAnalyzer: \"Project Analyzer\",\n\tsystemArchitect: \"System Architect\",\n};\n\n// ─── PipelineEditorComponent ─────────────────────────────────────────────────\n\n/**\n * Read-only component that replaces the default editor during pipeline execution.\n * Shows current step and tool activity, ignoring normal input.\n * Supports Esc for abort and expandTools key for expand/collapse.\n */\nexport class PipelineEditorComponent implements EditorComponent {\n\tprivate phase = \"\";\n\tprivate detail = \"\";\n\tprivate tui: TUI;\n\tprivate keybindings: KeybindingsManager | undefined;\n\tprivate steps: PipelineStepInfo[] = [];\n\tprivate currentStepIndex = -1;\n\tprivate title = \" Vibe Pipeline \";\n\tprivate spinnerFrame = 0;\n\tprivate spinnerInterval: ReturnType<typeof setInterval> | null = null;\n\tprivate activityLog: string[] = [];\n\n\t/** Callbacks set by pi. */\n\tonSubmit?: (text: string) => void;\n\tonChange?: (text: string) => void;\n\n\t/** Border color function (set by pi). */\n\tborderColor?: (str: string) => string;\n\n\t/** Abort callback (set by vibe). */\n\tonAbort?: () => void;\n\n\t/** Toggle expand/collapse callback (set by vibe). */\n\tonToggleExpand?: () => void;\n\n\tconstructor(tui: TUI, _theme: EditorTheme, keybindings?: KeybindingsManager) {\n\t\tthis.tui = tui;\n\t\tthis.keybindings = keybindings;\n\t}\n\n\t/** Sets the current phase. */\n\tsetPhase(phase: string): void {\n\t\tif (this.phase !== phase) {\n\t\t\tthis.phase = phase;\n\t\t\tthis.tui.requestRender();\n\t\t}\n\t}\n\n\t/** Sets the detail progress info. */\n\tsetDetail(detail: string): void {\n\t\tif (this.detail !== detail) {\n\t\t\tthis.detail = detail;\n\t\t\tthis.tui.requestRender();\n\t\t}\n\t}\n\n\t/** Adds an entry to the activity log. Removes oldest when exceeding MAX_ACTIVITY_LINES. */\n\taddActivity(text: string): void {\n\t\tthis.activityLog.push(text);\n\t\tif (this.activityLog.length > MAX_ACTIVITY_LINES) {\n\t\t\tthis.activityLog.splice(0, this.activityLog.length - MAX_ACTIVITY_LINES);\n\t\t}\n\t\tthis.tui.requestRender();\n\t}\n\n\t/** Replaces the last activity entry, or adds if empty. Used for streaming text in-place updates. */\n\tupdateLastActivity(text: string): void {\n\t\tif (this.activityLog.length > 0) {\n\t\t\tthis.activityLog[this.activityLog.length - 1] = text;\n\t\t} else {\n\t\t\tthis.activityLog.push(text);\n\t\t}\n\t\tthis.tui.requestRender();\n\t}\n\n\t/** Returns a read-only snapshot of the current activity log. */\n\tgetActivityLog(): readonly string[] {\n\t\treturn this.activityLog;\n\t}\n\n\t/** Clears the activity log. */\n\tclearActivity(): void {\n\t\tif (this.activityLog.length > 0) {\n\t\t\tthis.activityLog = [];\n\t\t\tthis.tui.requestRender();\n\t\t}\n\t}\n\n\t/** Sets the pipeline step list. */\n\tsetSteps(steps: PipelineStepInfo[]): void {\n\t\tthis.steps = steps;\n\t\tthis.tui.requestRender();\n\t}\n\n\t/** Sets the currently running step index. */\n\tsetCurrentStep(index: number): void {\n\t\tif (this.currentStepIndex !== index) {\n\t\t\tthis.currentStepIndex = index;\n\t\t\tthis.activityLog = [];\n\t\t\t// Update step states: previous steps → done, current → running\n\t\t\tfor (let i = 0; i < this.steps.length; i++) {\n\t\t\t\tif (this.steps[i].status === \"skipped\") continue;\n\t\t\t\tif (i < index) {\n\t\t\t\t\tthis.steps[i].status = \"done\";\n\t\t\t\t} else if (i === index) {\n\t\t\t\t\tthis.steps[i].status = \"running\";\n\t\t\t\t} else {\n\t\t\t\t\tif (this.steps[i].status !== \"skipped\") {\n\t\t\t\t\t\tthis.steps[i].status = \"pending\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t}\n\t}\n\n\t/** Sets the title. */\n\tsetTitle(title: string): void {\n\t\tconst formatted = ` ${title} `;\n\t\tif (this.title !== formatted) {\n\t\t\tthis.title = formatted;\n\t\t\tthis.tui.requestRender();\n\t\t}\n\t}\n\n\t/** Starts automatic spinner rotation at 80ms intervals. */\n\tstartSpinner(): void {\n\t\tif (this.spinnerInterval) return;\n\t\tthis.spinnerInterval = setInterval(() => {\n\t\t\tthis.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;\n\t\t\tthis.tui.requestRender();\n\t\t}, 80);\n\t}\n\n\t/** Stops automatic spinner rotation. */\n\tstopSpinner(): void {\n\t\tif (this.spinnerInterval) {\n\t\t\tclearInterval(this.spinnerInterval);\n\t\t\tthis.spinnerInterval = null;\n\t\t}\n\t}\n\n\t// ─── EditorComponent Implementation ─────────────────────────────────\n\n\trender(width: number): string[] {\n\t\tconst bc = this.borderColor ?? ((s: string) => s);\n\t\tconst innerWidth = Math.max(width - 4, 10); // border + padding\n\n\t\t// Top border: ┌ + ─ + title + ─ + ┐ = innerWidth + 4 = width\n\t\tconst titleText = this.title;\n\t\tconst topBarWidth = Math.max(innerWidth + 2 - visibleWidth(titleText), 0);\n\t\tconst topLeft = Math.floor(topBarWidth / 2);\n\t\tconst topRight = topBarWidth - topLeft;\n\t\tconst top = bc(`┌${\"─\".repeat(topLeft)}${titleText}${\"─\".repeat(topRight)}┐`);\n\n\t\tconst contentLines: string[] = [];\n\n\t\tif (this.steps.length > 0) {\n\t\t\t// Step list mode\n\t\t\tif (this.phase) {\n\t\t\t\tconst phaseHeader = ansi(ANSI.bold, this.phase);\n\t\t\t\tcontentLines.push(this.wrapLine(bc, phaseHeader, innerWidth));\n\t\t\t}\n\t\t\tconst { start, end } = this.computeVisibleRange();\n\t\t\tfor (let i = start; i < end; i++) {\n\t\t\t\tconst step = this.steps[i];\n\t\t\t\tconst line = this.renderStepLine(step, i);\n\t\t\t\tcontentLines.push(this.wrapLine(bc, line, innerWidth));\n\n\t\t\t\t// Show activity log below the running step\n\t\t\t\tif (step.status === \"running\" && this.activityLog.length > 0) {\n\t\t\t\t\tfor (const activity of this.activityLog) {\n\t\t\t\t\t\tconst activityText = ansi(ANSI.dim, ` ↳ ${activity}`);\n\t\t\t\t\t\tconst truncated = truncateToWidth(activityText, innerWidth);\n\t\t\t\t\t\tcontentLines.push(this.wrapLine(bc, truncated, innerWidth));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Single-state mode (Discovery, Planning, etc.)\n\t\t\tconst spinner = ansi(ANSI.cyan, SPINNER_FRAMES[this.spinnerFrame]);\n\t\t\tconst phaseLine = `${spinner} ${ansi(ANSI.bold, this.phase)}${this.detail ? \":\" : \"...\"}`;\n\t\t\tcontentLines.push(this.wrapLine(bc, truncateToWidth(phaseLine, innerWidth), innerWidth));\n\n\t\t\t// Show detail lines (streaming text) — fill available space\n\t\t\tif (this.detail) {\n\t\t\t\tconst detailLines = this.detail.split(\"\\n\");\n\t\t\t\t// Reserve space for activity log + padding\n\t\t\t\tconst maxDetailLines = MAX_ACTIVITY_LINES;\n\t\t\t\tconst visibleDetailLines = detailLines.slice(-maxDetailLines);\n\t\t\t\tfor (const line of visibleDetailLines) {\n\t\t\t\t\tconst truncated = truncateToWidth(line, innerWidth);\n\t\t\t\t\tcontentLines.push(this.wrapLine(bc, truncated, innerWidth));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Show activity log in single-state mode\n\t\t\tif (this.activityLog.length > 0) {\n\t\t\t\tcontentLines.push(this.wrapLine(bc, \"\", innerWidth));\n\t\t\t\tfor (const activity of this.activityLog) {\n\t\t\t\t\tconst activityText = ansi(ANSI.dim, ` ↳ ${activity}`);\n\t\t\t\t\tcontentLines.push(this.wrapLine(bc, truncateToWidth(activityText, innerWidth), innerWidth));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Pad to minimum height so the editor doesn't look too small\n\t\t\tconst minContentLines = MAX_ACTIVITY_LINES + 2; // phase + blank + activity slots\n\t\t\twhile (contentLines.length < minContentLines) {\n\t\t\t\tcontentLines.push(this.wrapLine(bc, \"\", innerWidth));\n\t\t\t}\n\t\t}\n\n\t\t// Hint row\n\t\tconst expandKey = this.keybindings?.getKeys(\"expandTools\")[0] ?? \"ctrl+o\";\n\t\tconst hint = ansi(ANSI.dim, `Esc: abort ${expandKey}: expand/collapse`);\n\t\tconst hintPad = Math.max(innerWidth - visibleWidth(hint), 0);\n\t\tconst hintLine = `${bc(\"│\")} ${\" \".repeat(hintPad)}${hint} ${bc(\"│\")}`;\n\t\tcontentLines.push(hintLine);\n\n\t\t// Bottom border\n\t\tconst bottom = bc(`└${\"─\".repeat(innerWidth + 2)}┘`);\n\n\t\treturn [`${CURSOR_MARKER}${top}`, ...contentLines, bottom];\n\t}\n\n\tgetText(): string {\n\t\treturn \"\";\n\t}\n\n\tsetText(_text: string): void {\n\t\t// no-op: read-only\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Detect Esc key\n\t\tif (data === \"\\x1b\" || data === \"\\x1b\\x1b\") {\n\t\t\tthis.onAbort?.();\n\t\t\treturn;\n\t\t}\n\t\t// Detect expandTools key (Ctrl+O by default)\n\t\tif (this.keybindings?.matches(data, \"expandTools\")) {\n\t\t\tthis.onToggleExpand?.();\n\t\t\treturn;\n\t\t}\n\t\t// Ignore all other input\n\t}\n\n\tinvalidate(): void {\n\t\t// Cache invalidation. Next render() will use the latest state.\n\t}\n\n\t// ─── Private Helpers ─────────────────────────────────────────────────\n\n\t/** Computes visible range. Windows around the currently running step. */\n\tprivate computeVisibleRange(): { start: number; end: number } {\n\t\tconst total = this.steps.length;\n\t\tif (total <= MAX_VISIBLE_STEPS) {\n\t\t\treturn { start: 0, end: total };\n\t\t}\n\n\t\tconst center = Math.max(0, this.currentStepIndex);\n\t\tconst half = Math.floor(MAX_VISIBLE_STEPS / 2);\n\t\tlet start = Math.max(0, center - half);\n\t\tlet end = start + MAX_VISIBLE_STEPS;\n\n\t\tif (end > total) {\n\t\t\tend = total;\n\t\t\tstart = Math.max(0, end - MAX_VISIBLE_STEPS);\n\t\t}\n\n\t\treturn { start, end };\n\t}\n\n\t/** Renders a single step line. */\n\tprivate renderStepLine(step: PipelineStepInfo, index: number): string {\n\t\tconst label = ROLE_LABELS[step.agent] ?? step.agent;\n\t\tconst num = `${index + 1}`.padStart(2);\n\n\t\tswitch (step.status) {\n\t\t\tcase \"done\": {\n\t\t\t\tconst marker = ansi(ANSI.green, \"✓\");\n\t\t\t\treturn ansi(ANSI.dim, `${marker} ${ansi(ANSI.dim, `${num}. ${label}: ${step.action}`)}`);\n\t\t\t}\n\t\t\tcase \"running\": {\n\t\t\t\tconst spinner = ansi(`${ANSI.cyan}${ANSI.bold}`, SPINNER_FRAMES[this.spinnerFrame]);\n\t\t\t\treturn `${spinner} ${ansi(ANSI.bold, `${num}. ${label}: ${step.action}`)}`;\n\t\t\t}\n\t\t\tcase \"skipped\": {\n\t\t\t\tconst marker = ansi(ANSI.gray, \"⊘\");\n\t\t\t\treturn ansi(ANSI.dim, `${marker} ${ansi(ANSI.dim, `${num}. ${label}: ${step.action}`)}`);\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tconst marker = ansi(ANSI.gray, \"○\");\n\t\t\t\treturn ansi(ANSI.dim, `${marker} ${ansi(ANSI.dim, `${num}. ${label}: ${step.action}`)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Wraps content text as a box row. */\n\tprivate wrapLine(bc: (s: string) => string, content: string, innerWidth: number): string {\n\t\tconst truncated = truncateToWidth(content, innerWidth);\n\t\tconst pad = Math.max(innerWidth - visibleWidth(truncated), 0);\n\t\treturn `${bc(\"│\")} ${truncated}${\" \".repeat(pad)} ${bc(\"│\")}`;\n\t}\n}\n"]}
@@ -86,6 +86,16 @@ export class PipelineEditorComponent {
86
86
  }
87
87
  this.tui.requestRender();
88
88
  }
89
+ /** Replaces the last activity entry, or adds if empty. Used for streaming text in-place updates. */
90
+ updateLastActivity(text) {
91
+ if (this.activityLog.length > 0) {
92
+ this.activityLog[this.activityLog.length - 1] = text;
93
+ }
94
+ else {
95
+ this.activityLog.push(text);
96
+ }
97
+ this.tui.requestRender();
98
+ }
89
99
  /** Returns a read-only snapshot of the current activity log. */
90
100
  getActivityLog() {
91
101
  return this.activityLog;
@@ -185,11 +195,32 @@ export class PipelineEditorComponent {
185
195
  else {
186
196
  // Single-state mode (Discovery, Planning, etc.)
187
197
  const spinner = ansi(ANSI.cyan, SPINNER_FRAMES[this.spinnerFrame]);
188
- const statusText = this.detail
189
- ? `${spinner} ${ansi(ANSI.bold, this.phase)}: ${this.detail}`
190
- : `${spinner} ${ansi(ANSI.bold, this.phase)}...`;
191
- const truncated = truncateToWidth(statusText, innerWidth);
192
- contentLines.push(this.wrapLine(bc, truncated, innerWidth));
198
+ const phaseLine = `${spinner} ${ansi(ANSI.bold, this.phase)}${this.detail ? ":" : "..."}`;
199
+ contentLines.push(this.wrapLine(bc, truncateToWidth(phaseLine, innerWidth), innerWidth));
200
+ // Show detail lines (streaming text) — fill available space
201
+ if (this.detail) {
202
+ const detailLines = this.detail.split("\n");
203
+ // Reserve space for activity log + padding
204
+ const maxDetailLines = MAX_ACTIVITY_LINES;
205
+ const visibleDetailLines = detailLines.slice(-maxDetailLines);
206
+ for (const line of visibleDetailLines) {
207
+ const truncated = truncateToWidth(line, innerWidth);
208
+ contentLines.push(this.wrapLine(bc, truncated, innerWidth));
209
+ }
210
+ }
211
+ // Show activity log in single-state mode
212
+ if (this.activityLog.length > 0) {
213
+ contentLines.push(this.wrapLine(bc, "", innerWidth));
214
+ for (const activity of this.activityLog) {
215
+ const activityText = ansi(ANSI.dim, ` ↳ ${activity}`);
216
+ contentLines.push(this.wrapLine(bc, truncateToWidth(activityText, innerWidth), innerWidth));
217
+ }
218
+ }
219
+ // Pad to minimum height so the editor doesn't look too small
220
+ const minContentLines = MAX_ACTIVITY_LINES + 2; // phase + blank + activity slots
221
+ while (contentLines.length < minContentLines) {
222
+ contentLines.push(this.wrapLine(bc, "", innerWidth));
223
+ }
193
224
  }
194
225
  // Hint row
195
226
  const expandKey = this.keybindings?.getKeys("expandTools")[0] ?? "ctrl+o";