chati-dev 1.3.3 → 2.0.1

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 (215) hide show
  1. package/README.md +7 -6
  2. package/framework/agents/build/dev.md +343 -0
  3. package/framework/agents/clarity/architect.md +113 -0
  4. package/framework/agents/clarity/brief.md +183 -0
  5. package/framework/agents/clarity/brownfield-wu.md +182 -0
  6. package/framework/agents/clarity/detail.md +111 -0
  7. package/framework/agents/clarity/greenfield-wu.md +154 -0
  8. package/framework/agents/clarity/phases.md +1 -0
  9. package/framework/agents/clarity/tasks.md +1 -0
  10. package/framework/agents/clarity/ux.md +113 -0
  11. package/framework/agents/deploy/devops.md +1 -0
  12. package/framework/agents/quality/qa-implementation.md +1 -0
  13. package/framework/agents/quality/qa-planning.md +1 -0
  14. package/framework/config.yaml +3 -3
  15. package/framework/constitution.md +58 -1
  16. package/framework/context/governance.md +37 -0
  17. package/framework/context/protocols.md +34 -0
  18. package/framework/context/quality.md +27 -0
  19. package/framework/context/root.md +24 -0
  20. package/framework/data/entity-registry.yaml +1 -1
  21. package/framework/domains/agents/architect.yaml +51 -0
  22. package/framework/domains/agents/brief.yaml +47 -0
  23. package/framework/domains/agents/brownfield-wu.yaml +49 -0
  24. package/framework/domains/agents/detail.yaml +47 -0
  25. package/framework/domains/agents/dev.yaml +49 -0
  26. package/framework/domains/agents/devops.yaml +43 -0
  27. package/framework/domains/agents/greenfield-wu.yaml +47 -0
  28. package/framework/domains/agents/orchestrator.yaml +49 -0
  29. package/framework/domains/agents/phases.yaml +47 -0
  30. package/framework/domains/agents/qa-implementation.yaml +43 -0
  31. package/framework/domains/agents/qa-planning.yaml +44 -0
  32. package/framework/domains/agents/tasks.yaml +48 -0
  33. package/framework/domains/agents/ux.yaml +50 -0
  34. package/framework/domains/constitution.yaml +77 -0
  35. package/framework/domains/global.yaml +64 -0
  36. package/framework/domains/workflows/brownfield-discovery.yaml +16 -0
  37. package/framework/domains/workflows/brownfield-fullstack.yaml +26 -0
  38. package/framework/domains/workflows/brownfield-service.yaml +22 -0
  39. package/framework/domains/workflows/brownfield-ui.yaml +22 -0
  40. package/framework/domains/workflows/greenfield-fullstack.yaml +26 -0
  41. package/framework/hooks/constitution-guard.js +101 -0
  42. package/framework/hooks/mode-governance.js +92 -0
  43. package/framework/hooks/model-governance.js +76 -0
  44. package/framework/hooks/prism-engine.js +89 -0
  45. package/framework/hooks/session-digest.js +60 -0
  46. package/framework/hooks/settings.json +44 -0
  47. package/framework/i18n/en.yaml +3 -3
  48. package/framework/i18n/es.yaml +3 -3
  49. package/framework/i18n/fr.yaml +3 -3
  50. package/framework/i18n/pt.yaml +3 -3
  51. package/framework/intelligence/context-engine.md +2 -2
  52. package/framework/intelligence/decision-engine.md +1 -1
  53. package/framework/migrations/v1.4-to-v2.0.yaml +167 -0
  54. package/framework/migrations/v2.0-to-v2.0.1.yaml +132 -0
  55. package/framework/orchestrator/chati.md +350 -7
  56. package/framework/schemas/session.schema.json +15 -0
  57. package/framework/tasks/architect-api-design.md +63 -0
  58. package/framework/tasks/architect-consolidate.md +47 -0
  59. package/framework/tasks/architect-db-design.md +73 -0
  60. package/framework/tasks/architect-design.md +95 -0
  61. package/framework/tasks/architect-security-review.md +62 -0
  62. package/framework/tasks/architect-stack-selection.md +53 -0
  63. package/framework/tasks/brief-consolidate.md +249 -0
  64. package/framework/tasks/brief-constraint-identify.md +277 -0
  65. package/framework/tasks/brief-extract-requirements.md +339 -0
  66. package/framework/tasks/brief-stakeholder-map.md +176 -0
  67. package/framework/tasks/brief-validate-completeness.md +121 -0
  68. package/framework/tasks/brownfield-wu-architecture-map.md +394 -0
  69. package/framework/tasks/brownfield-wu-deep-discovery.md +312 -0
  70. package/framework/tasks/brownfield-wu-dependency-scan.md +359 -0
  71. package/framework/tasks/brownfield-wu-migration-plan.md +483 -0
  72. package/framework/tasks/brownfield-wu-report.md +325 -0
  73. package/framework/tasks/brownfield-wu-risk-assess.md +424 -0
  74. package/framework/tasks/detail-acceptance-criteria.md +372 -0
  75. package/framework/tasks/detail-consolidate.md +138 -0
  76. package/framework/tasks/detail-edge-case-analysis.md +300 -0
  77. package/framework/tasks/detail-expand-prd.md +389 -0
  78. package/framework/tasks/detail-nfr-extraction.md +223 -0
  79. package/framework/tasks/dev-code-review.md +404 -0
  80. package/framework/tasks/dev-consolidate.md +543 -0
  81. package/framework/tasks/dev-debug.md +322 -0
  82. package/framework/tasks/dev-implement.md +252 -0
  83. package/framework/tasks/dev-iterate.md +411 -0
  84. package/framework/tasks/dev-pr-prepare.md +497 -0
  85. package/framework/tasks/dev-refactor.md +342 -0
  86. package/framework/tasks/dev-test-write.md +306 -0
  87. package/framework/tasks/devops-ci-setup.md +412 -0
  88. package/framework/tasks/devops-consolidate.md +712 -0
  89. package/framework/tasks/devops-deploy-config.md +598 -0
  90. package/framework/tasks/devops-monitoring-setup.md +658 -0
  91. package/framework/tasks/devops-release-prepare.md +673 -0
  92. package/framework/tasks/greenfield-wu-analyze-empty.md +169 -0
  93. package/framework/tasks/greenfield-wu-report.md +266 -0
  94. package/framework/tasks/greenfield-wu-scaffold-detection.md +203 -0
  95. package/framework/tasks/greenfield-wu-tech-stack-assess.md +255 -0
  96. package/framework/tasks/orchestrator-deviation.md +260 -0
  97. package/framework/tasks/orchestrator-escalate.md +276 -0
  98. package/framework/tasks/orchestrator-handoff.md +243 -0
  99. package/framework/tasks/orchestrator-health.md +372 -0
  100. package/framework/tasks/orchestrator-mode-switch.md +262 -0
  101. package/framework/tasks/orchestrator-resume.md +189 -0
  102. package/framework/tasks/orchestrator-route.md +169 -0
  103. package/framework/tasks/orchestrator-spawn-terminal.md +358 -0
  104. package/framework/tasks/orchestrator-status.md +260 -0
  105. package/framework/tasks/orchestrator-suggest-mode.md +372 -0
  106. package/framework/tasks/phases-breakdown.md +91 -0
  107. package/framework/tasks/phases-dependency-mapping.md +67 -0
  108. package/framework/tasks/phases-mvp-scoping.md +94 -0
  109. package/framework/tasks/qa-impl-consolidate.md +522 -0
  110. package/framework/tasks/qa-impl-performance-test.md +487 -0
  111. package/framework/tasks/qa-impl-regression-check.md +413 -0
  112. package/framework/tasks/qa-impl-sast-scan.md +402 -0
  113. package/framework/tasks/qa-impl-test-execute.md +344 -0
  114. package/framework/tasks/qa-impl-verdict.md +339 -0
  115. package/framework/tasks/qa-planning-consolidate.md +309 -0
  116. package/framework/tasks/qa-planning-coverage-plan.md +338 -0
  117. package/framework/tasks/qa-planning-gate-define.md +339 -0
  118. package/framework/tasks/qa-planning-risk-matrix.md +631 -0
  119. package/framework/tasks/qa-planning-test-strategy.md +217 -0
  120. package/framework/tasks/tasks-acceptance-write.md +75 -0
  121. package/framework/tasks/tasks-consolidate.md +57 -0
  122. package/framework/tasks/tasks-decompose.md +80 -0
  123. package/framework/tasks/tasks-estimate.md +66 -0
  124. package/framework/tasks/ux-a11y-check.md +49 -0
  125. package/framework/tasks/ux-component-map.md +55 -0
  126. package/framework/tasks/ux-consolidate.md +46 -0
  127. package/framework/tasks/ux-user-flow.md +46 -0
  128. package/framework/tasks/ux-wireframe.md +76 -0
  129. package/package.json +1 -1
  130. package/scripts/bundle-framework.js +2 -0
  131. package/scripts/changelog-generator.js +222 -0
  132. package/scripts/codebase-mapper.js +728 -0
  133. package/scripts/commit-message-generator.js +167 -0
  134. package/scripts/coverage-analyzer.js +260 -0
  135. package/scripts/dependency-analyzer.js +280 -0
  136. package/scripts/framework-analyzer.js +308 -0
  137. package/scripts/generate-constitution-domain.js +253 -0
  138. package/scripts/health-check.js +481 -0
  139. package/scripts/ide-sync.js +327 -0
  140. package/scripts/performance-analyzer.js +325 -0
  141. package/scripts/plan-tracker.js +278 -0
  142. package/scripts/populate-entity-registry.js +481 -0
  143. package/scripts/pr-review.js +317 -0
  144. package/scripts/rollback-manager.js +310 -0
  145. package/scripts/stuck-detector.js +343 -0
  146. package/scripts/test-quality-assessment.js +257 -0
  147. package/scripts/validate-agents.js +367 -0
  148. package/scripts/validate-tasks.js +465 -0
  149. package/src/autonomy/autonomous-gate.js +293 -0
  150. package/src/autonomy/index.js +51 -0
  151. package/src/autonomy/mode-manager.js +225 -0
  152. package/src/autonomy/mode-suggester.js +283 -0
  153. package/src/autonomy/progress-reporter.js +268 -0
  154. package/src/autonomy/safety-net.js +320 -0
  155. package/src/context/bracket-tracker.js +79 -0
  156. package/src/context/domain-loader.js +107 -0
  157. package/src/context/engine.js +144 -0
  158. package/src/context/formatter.js +184 -0
  159. package/src/context/index.js +4 -0
  160. package/src/context/layers/l0-constitution.js +28 -0
  161. package/src/context/layers/l1-global.js +37 -0
  162. package/src/context/layers/l2-agent.js +39 -0
  163. package/src/context/layers/l3-workflow.js +42 -0
  164. package/src/context/layers/l4-task.js +24 -0
  165. package/src/decision/analyzer.js +167 -0
  166. package/src/decision/engine.js +270 -0
  167. package/src/decision/index.js +38 -0
  168. package/src/decision/registry-healer.js +450 -0
  169. package/src/decision/registry-updater.js +330 -0
  170. package/src/gates/circuit-breaker.js +119 -0
  171. package/src/gates/g1-planning-complete.js +153 -0
  172. package/src/gates/g2-qa-planning.js +153 -0
  173. package/src/gates/g3-implementation.js +188 -0
  174. package/src/gates/g4-qa-implementation.js +207 -0
  175. package/src/gates/g5-deploy-ready.js +180 -0
  176. package/src/gates/gate-base.js +144 -0
  177. package/src/gates/index.js +46 -0
  178. package/src/installer/brownfield-upgrader.js +249 -0
  179. package/src/installer/core.js +55 -3
  180. package/src/installer/file-hasher.js +51 -0
  181. package/src/installer/manifest.js +117 -0
  182. package/src/installer/templates.js +17 -15
  183. package/src/installer/transaction.js +229 -0
  184. package/src/installer/validator.js +18 -1
  185. package/src/intelligence/registry-manager.js +2 -2
  186. package/src/memory/agent-memory.js +255 -0
  187. package/src/memory/gotchas-injector.js +72 -0
  188. package/src/memory/gotchas.js +361 -0
  189. package/src/memory/index.js +35 -0
  190. package/src/memory/search.js +233 -0
  191. package/src/memory/session-digest.js +239 -0
  192. package/src/merger/env-merger.js +112 -0
  193. package/src/merger/index.js +56 -0
  194. package/src/merger/replace-merger.js +51 -0
  195. package/src/merger/yaml-merger.js +127 -0
  196. package/src/orchestrator/agent-selector.js +285 -0
  197. package/src/orchestrator/deviation-handler.js +350 -0
  198. package/src/orchestrator/handoff-engine.js +271 -0
  199. package/src/orchestrator/index.js +67 -0
  200. package/src/orchestrator/intent-classifier.js +264 -0
  201. package/src/orchestrator/pipeline-manager.js +492 -0
  202. package/src/orchestrator/pipeline-state.js +223 -0
  203. package/src/orchestrator/session-manager.js +409 -0
  204. package/src/tasks/executor.js +195 -0
  205. package/src/tasks/handoff.js +226 -0
  206. package/src/tasks/index.js +4 -0
  207. package/src/tasks/loader.js +210 -0
  208. package/src/tasks/router.js +182 -0
  209. package/src/terminal/collector.js +216 -0
  210. package/src/terminal/index.js +30 -0
  211. package/src/terminal/isolation.js +129 -0
  212. package/src/terminal/monitor.js +277 -0
  213. package/src/terminal/spawner.js +269 -0
  214. package/src/upgrade/checker.js +1 -1
  215. package/src/wizard/i18n.js +3 -3
@@ -0,0 +1,492 @@
1
+ /**
2
+ * @fileoverview Pipeline lifecycle management.
3
+ * Manages the complete pipeline state and transitions.
4
+ */
5
+
6
+ import { AGENT_PIPELINE, getNextAgent } from './agent-selector.js';
7
+
8
+ /**
9
+ * Pipeline phases in order.
10
+ */
11
+ export const PIPELINE_PHASES = ['clarity', 'build', 'deploy'];
12
+
13
+ /**
14
+ * Agent status values.
15
+ */
16
+ export const AGENT_STATUS = {
17
+ PENDING: 'pending',
18
+ IN_PROGRESS: 'in_progress',
19
+ COMPLETED: 'completed',
20
+ SKIPPED: 'skipped',
21
+ NEEDS_REVALIDATION: 'needs_revalidation',
22
+ FAILED: 'failed',
23
+ };
24
+
25
+ /**
26
+ * Required QA score for phase transitions.
27
+ */
28
+ const QA_PLANNING_THRESHOLD = 95;
29
+ const QA_IMPLEMENTATION_THRESHOLD = 90;
30
+
31
+ /**
32
+ * Initialize a new pipeline for a project.
33
+ *
34
+ * @param {object} options - { isGreenfield, mode }
35
+ * @returns {object} Pipeline state
36
+ */
37
+ export function initPipeline(options = {}) {
38
+ const { isGreenfield = true, mode = 'clarity' } = options;
39
+
40
+ const agents = {};
41
+ for (const agentDef of AGENT_PIPELINE) {
42
+ // Skip the WU agent that doesn't apply
43
+ if (agentDef.group === 'wu') {
44
+ const shouldInclude = isGreenfield
45
+ ? agentDef.name === 'greenfield-wu'
46
+ : agentDef.name === 'brownfield-wu';
47
+ if (!shouldInclude) {
48
+ continue;
49
+ }
50
+ }
51
+
52
+ agents[agentDef.name] = {
53
+ status: AGENT_STATUS.PENDING,
54
+ score: null,
55
+ startedAt: null,
56
+ completedAt: null,
57
+ };
58
+ }
59
+
60
+ return {
61
+ phase: mode,
62
+ isGreenfield,
63
+ startedAt: new Date().toISOString(),
64
+ completedAt: null,
65
+ agents,
66
+ completedAgents: [],
67
+ currentAgent: null,
68
+ modeTransitions: [],
69
+ history: [],
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Advance the pipeline after an agent completes.
75
+ *
76
+ * @param {object} pipelineState - Current state
77
+ * @param {string} completedAgent - Agent that just finished
78
+ * @param {object} [results] - Agent results (score, outputs)
79
+ * @returns {{ state: object, nextAction: string, nextAgent: string|null, needsModeSwitch: boolean }}
80
+ */
81
+ export function advancePipeline(pipelineState, completedAgent, results = {}) {
82
+ const newState = { ...pipelineState };
83
+
84
+ // Update completed agent status
85
+ if (!newState.agents[completedAgent]) {
86
+ throw new Error(`Unknown agent: ${completedAgent}`);
87
+ }
88
+
89
+ newState.agents[completedAgent] = {
90
+ ...newState.agents[completedAgent],
91
+ status: AGENT_STATUS.COMPLETED,
92
+ score: results.score || null,
93
+ completedAt: new Date().toISOString(),
94
+ };
95
+
96
+ // Add to completed list if not already there
97
+ if (!newState.completedAgents.includes(completedAgent)) {
98
+ newState.completedAgents.push(completedAgent);
99
+ }
100
+
101
+ // Add to history
102
+ newState.history.push({
103
+ agent: completedAgent,
104
+ action: 'completed',
105
+ timestamp: new Date().toISOString(),
106
+ score: results.score || null,
107
+ });
108
+
109
+ // Special handling for QA agents - check if they meet thresholds
110
+ const isQAAgent = completedAgent === 'qa-planning' || completedAgent === 'qa-implementation';
111
+ if (isQAAgent) {
112
+ const transitionCheck = checkPhaseTransition(newState);
113
+
114
+ if (transitionCheck.canAdvance) {
115
+ // QA passed - advance to next phase
116
+ const currentPhaseIndex = PIPELINE_PHASES.indexOf(newState.phase);
117
+ if (currentPhaseIndex < PIPELINE_PHASES.length - 1) {
118
+ const nextPhase = PIPELINE_PHASES[currentPhaseIndex + 1];
119
+
120
+ newState.modeTransitions.push({
121
+ from: newState.phase,
122
+ to: nextPhase,
123
+ trigger: 'autonomous',
124
+ timestamp: new Date().toISOString(),
125
+ reason: transitionCheck.reason,
126
+ });
127
+
128
+ newState.phase = nextPhase;
129
+
130
+ return {
131
+ state: newState,
132
+ nextAction: 'advance_phase',
133
+ nextAgent: getFirstAgentInPhase(newState, nextPhase),
134
+ needsModeSwitch: true,
135
+ };
136
+ }
137
+
138
+ // Pipeline complete
139
+ newState.completedAt = new Date().toISOString();
140
+ return {
141
+ state: newState,
142
+ nextAction: 'complete',
143
+ nextAgent: null,
144
+ needsModeSwitch: false,
145
+ };
146
+ } else {
147
+ // QA failed - wait for issues to be fixed
148
+ return {
149
+ state: newState,
150
+ nextAction: 'wait',
151
+ nextAgent: null,
152
+ needsModeSwitch: false,
153
+ };
154
+ }
155
+ }
156
+
157
+ // Check if phase transition is possible for non-QA agents
158
+ const transitionCheck = checkPhaseTransition(newState);
159
+
160
+ if (transitionCheck.canAdvance) {
161
+ // Advance to next phase
162
+ const currentPhaseIndex = PIPELINE_PHASES.indexOf(newState.phase);
163
+ if (currentPhaseIndex < PIPELINE_PHASES.length - 1) {
164
+ const nextPhase = PIPELINE_PHASES[currentPhaseIndex + 1];
165
+
166
+ newState.modeTransitions.push({
167
+ from: newState.phase,
168
+ to: nextPhase,
169
+ trigger: 'autonomous',
170
+ timestamp: new Date().toISOString(),
171
+ reason: transitionCheck.reason,
172
+ });
173
+
174
+ newState.phase = nextPhase;
175
+
176
+ return {
177
+ state: newState,
178
+ nextAction: 'advance_phase',
179
+ nextAgent: getFirstAgentInPhase(newState, nextPhase),
180
+ needsModeSwitch: true,
181
+ };
182
+ }
183
+
184
+ // Pipeline complete
185
+ newState.completedAt = new Date().toISOString();
186
+ return {
187
+ state: newState,
188
+ nextAction: 'complete',
189
+ nextAgent: null,
190
+ needsModeSwitch: false,
191
+ };
192
+ }
193
+
194
+ // Continue in current phase
195
+ const nextInfo = getNextAgent(completedAgent, newState.completedAgents);
196
+
197
+ if (nextInfo.next) {
198
+ newState.currentAgent = nextInfo.next;
199
+ if (newState.agents[nextInfo.next]) {
200
+ newState.agents[nextInfo.next].status = AGENT_STATUS.IN_PROGRESS;
201
+ newState.agents[nextInfo.next].startedAt = new Date().toISOString();
202
+ }
203
+
204
+ return {
205
+ state: newState,
206
+ nextAction: 'continue',
207
+ nextAgent: nextInfo.next,
208
+ needsModeSwitch: false,
209
+ };
210
+ }
211
+
212
+ // No next agent but can't advance phase - stuck
213
+ return {
214
+ state: newState,
215
+ nextAction: 'wait',
216
+ nextAgent: null,
217
+ needsModeSwitch: false,
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Get first agent in a phase.
223
+ *
224
+ * @param {object} state - Pipeline state
225
+ * @param {string} phase - Target phase
226
+ * @returns {string|null}
227
+ */
228
+ function getFirstAgentInPhase(state, phase) {
229
+ for (const agentDef of AGENT_PIPELINE) {
230
+ if (agentDef.phase !== phase) {
231
+ continue;
232
+ }
233
+
234
+ // Skip WU fork that doesn't apply
235
+ if (agentDef.group === 'wu') {
236
+ const targetAgent = state.isGreenfield ? 'greenfield-wu' : 'brownfield-wu';
237
+ if (agentDef.name !== targetAgent) {
238
+ continue;
239
+ }
240
+ }
241
+
242
+ if (state.agents[agentDef.name]) {
243
+ return agentDef.name;
244
+ }
245
+ }
246
+
247
+ return null;
248
+ }
249
+
250
+ /**
251
+ * Check if pipeline can transition to next phase.
252
+ *
253
+ * @param {object} pipelineState
254
+ * @returns {{ canAdvance: boolean, reason: string, requiredScore: number|null }}
255
+ */
256
+ export function checkPhaseTransition(pipelineState) {
257
+ const { phase, agents } = pipelineState;
258
+
259
+ // Check based on current phase
260
+ if (phase === 'clarity') {
261
+ // Need QA-Planning to be completed with score >= 95
262
+ const qaPlanning = agents['qa-planning'];
263
+ if (!qaPlanning) {
264
+ return {
265
+ canAdvance: false,
266
+ reason: 'QA-Planning agent not in pipeline',
267
+ requiredScore: null,
268
+ };
269
+ }
270
+
271
+ if (qaPlanning.status !== AGENT_STATUS.COMPLETED) {
272
+ return {
273
+ canAdvance: false,
274
+ reason: 'QA-Planning not yet completed',
275
+ requiredScore: QA_PLANNING_THRESHOLD,
276
+ };
277
+ }
278
+
279
+ if (qaPlanning.score === null || qaPlanning.score < QA_PLANNING_THRESHOLD) {
280
+ return {
281
+ canAdvance: false,
282
+ reason: `QA-Planning score ${qaPlanning.score || 0} below threshold ${QA_PLANNING_THRESHOLD}`,
283
+ requiredScore: QA_PLANNING_THRESHOLD,
284
+ };
285
+ }
286
+
287
+ return {
288
+ canAdvance: true,
289
+ reason: `QA-Planning approved with score ${qaPlanning.score}`,
290
+ requiredScore: null,
291
+ };
292
+ }
293
+
294
+ if (phase === 'build') {
295
+ // Need dev and QA-Implementation completed
296
+ const dev = agents['dev'];
297
+ const qaImpl = agents['qa-implementation'];
298
+
299
+ if (!dev || !qaImpl) {
300
+ return {
301
+ canAdvance: false,
302
+ reason: 'Build agents not in pipeline',
303
+ requiredScore: null,
304
+ };
305
+ }
306
+
307
+ if (dev.status !== AGENT_STATUS.COMPLETED) {
308
+ return {
309
+ canAdvance: false,
310
+ reason: 'Dev agent not yet completed',
311
+ requiredScore: null,
312
+ };
313
+ }
314
+
315
+ if (qaImpl.status !== AGENT_STATUS.COMPLETED) {
316
+ return {
317
+ canAdvance: false,
318
+ reason: 'QA-Implementation not yet completed',
319
+ requiredScore: QA_IMPLEMENTATION_THRESHOLD,
320
+ };
321
+ }
322
+
323
+ if (qaImpl.score === null || qaImpl.score < QA_IMPLEMENTATION_THRESHOLD) {
324
+ return {
325
+ canAdvance: false,
326
+ reason: `QA-Implementation score ${qaImpl.score || 0} below threshold ${QA_IMPLEMENTATION_THRESHOLD}`,
327
+ requiredScore: QA_IMPLEMENTATION_THRESHOLD,
328
+ };
329
+ }
330
+
331
+ return {
332
+ canAdvance: true,
333
+ reason: `QA-Implementation approved with score ${qaImpl.score}`,
334
+ requiredScore: null,
335
+ };
336
+ }
337
+
338
+ if (phase === 'deploy') {
339
+ // Deploy is final phase
340
+ const devops = agents['devops'];
341
+ if (!devops) {
342
+ return {
343
+ canAdvance: false,
344
+ reason: 'DevOps agent not in pipeline',
345
+ requiredScore: null,
346
+ };
347
+ }
348
+
349
+ if (devops.status === AGENT_STATUS.COMPLETED) {
350
+ return {
351
+ canAdvance: true,
352
+ reason: 'DevOps completed - pipeline finished',
353
+ requiredScore: null,
354
+ };
355
+ }
356
+
357
+ return {
358
+ canAdvance: false,
359
+ reason: 'DevOps not yet completed',
360
+ requiredScore: null,
361
+ };
362
+ }
363
+
364
+ return {
365
+ canAdvance: false,
366
+ reason: `Unknown phase: ${phase}`,
367
+ requiredScore: null,
368
+ };
369
+ }
370
+
371
+ /**
372
+ * Get pipeline progress summary.
373
+ *
374
+ * @param {object} pipelineState
375
+ * @returns {{ phase: string, progress: number, completedAgents: string[], currentAgent: string|null, nextAgent: string|null }}
376
+ */
377
+ export function getPipelineProgress(pipelineState) {
378
+ const { phase, agents, completedAgents, currentAgent } = pipelineState;
379
+
380
+ // Calculate progress percentage
381
+ const totalAgents = Object.keys(agents).length;
382
+ const completed = completedAgents.length;
383
+ const progress = totalAgents > 0 ? (completed / totalAgents) * 100 : 0;
384
+
385
+ // Find next agent
386
+ let nextAgent = null;
387
+ if (currentAgent) {
388
+ const nextInfo = getNextAgent(currentAgent, completedAgents);
389
+ nextAgent = nextInfo.next;
390
+ }
391
+
392
+ return {
393
+ phase,
394
+ progress: Math.round(progress),
395
+ completedAgents,
396
+ currentAgent,
397
+ nextAgent,
398
+ };
399
+ }
400
+
401
+ /**
402
+ * Reset pipeline to a specific agent (for rollback/deviation).
403
+ *
404
+ * @param {object} pipelineState
405
+ * @param {string} targetAgent
406
+ * @returns {object} Updated pipeline state
407
+ */
408
+ export function resetPipelineTo(pipelineState, targetAgent) {
409
+ const newState = { ...pipelineState };
410
+
411
+ // Find target agent in pipeline
412
+ const targetDef = AGENT_PIPELINE.find((a) => a.name === targetAgent);
413
+ if (!targetDef) {
414
+ throw new Error(`Unknown target agent: ${targetAgent}`);
415
+ }
416
+
417
+ // Reset phase to target agent's phase
418
+ newState.phase = targetDef.phase;
419
+
420
+ // Find all agents after target in pipeline
421
+ const targetIndex = AGENT_PIPELINE.findIndex((a) => a.name === targetAgent);
422
+ const agentsToReset = AGENT_PIPELINE.slice(targetIndex).map((a) => a.name);
423
+
424
+ // Reset their status
425
+ for (const agentName of agentsToReset) {
426
+ if (newState.agents[agentName]) {
427
+ newState.agents[agentName] = {
428
+ status: AGENT_STATUS.PENDING,
429
+ score: null,
430
+ startedAt: null,
431
+ completedAt: null,
432
+ };
433
+ }
434
+ }
435
+
436
+ // Update completed agents list
437
+ newState.completedAgents = newState.completedAgents.filter(
438
+ (name) => !agentsToReset.includes(name)
439
+ );
440
+
441
+ // Set current agent
442
+ newState.currentAgent = targetAgent;
443
+ if (newState.agents[targetAgent]) {
444
+ newState.agents[targetAgent].status = AGENT_STATUS.IN_PROGRESS;
445
+ newState.agents[targetAgent].startedAt = new Date().toISOString();
446
+ }
447
+
448
+ // Add to history
449
+ newState.history.push({
450
+ agent: targetAgent,
451
+ action: 'reset_to',
452
+ timestamp: new Date().toISOString(),
453
+ reason: 'Pipeline rollback',
454
+ });
455
+
456
+ return newState;
457
+ }
458
+
459
+ /**
460
+ * Check if pipeline is complete.
461
+ *
462
+ * @param {object} pipelineState
463
+ * @returns {boolean}
464
+ */
465
+ export function isPipelineComplete(pipelineState) {
466
+ return pipelineState.completedAt !== null;
467
+ }
468
+
469
+ /**
470
+ * Mark an agent as in progress.
471
+ *
472
+ * @param {object} pipelineState
473
+ * @param {string} agentName
474
+ * @returns {object} Updated pipeline state
475
+ */
476
+ export function markAgentInProgress(pipelineState, agentName) {
477
+ const newState = { ...pipelineState };
478
+
479
+ if (!newState.agents[agentName]) {
480
+ throw new Error(`Unknown agent: ${agentName}`);
481
+ }
482
+
483
+ newState.agents[agentName] = {
484
+ ...newState.agents[agentName],
485
+ status: AGENT_STATUS.IN_PROGRESS,
486
+ startedAt: new Date().toISOString(),
487
+ };
488
+
489
+ newState.currentAgent = agentName;
490
+
491
+ return newState;
492
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * @fileoverview Pipeline state persistence layer.
3
+ * Handles reading/writing pipeline state to session.yaml.
4
+ */
5
+
6
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from 'node:fs';
7
+ import { join, dirname } from 'node:path';
8
+ import yaml from 'js-yaml';
9
+
10
+ /**
11
+ * Default session file path relative to project root.
12
+ */
13
+ const SESSION_PATH = '.chati/session.yaml';
14
+
15
+ /**
16
+ * Get full path to session file.
17
+ *
18
+ * @param {string} projectDir - Project root directory
19
+ * @returns {string}
20
+ */
21
+ function getSessionPath(projectDir) {
22
+ return join(projectDir, SESSION_PATH);
23
+ }
24
+
25
+ /**
26
+ * Load pipeline state from session.yaml.
27
+ *
28
+ * @param {string} projectDir
29
+ * @returns {{ loaded: boolean, state: object|null }}
30
+ */
31
+ export function loadPipelineState(projectDir) {
32
+ const sessionPath = getSessionPath(projectDir);
33
+
34
+ if (!existsSync(sessionPath)) {
35
+ return {
36
+ loaded: false,
37
+ state: null,
38
+ };
39
+ }
40
+
41
+ try {
42
+ const content = readFileSync(sessionPath, 'utf8');
43
+ const session = yaml.load(content);
44
+
45
+ // Extract pipeline-relevant fields
46
+ const state = {
47
+ phase: session.mode || 'clarity',
48
+ isGreenfield: session.project_type === 'greenfield',
49
+ startedAt: session.started_at || null,
50
+ completedAt: session.completed_at || null,
51
+ agents: session.agents || {},
52
+ completedAgents: session.completed_agents || [],
53
+ currentAgent: session.current_agent || null,
54
+ modeTransitions: session.mode_transitions || [],
55
+ history: session.history || [],
56
+ };
57
+
58
+ return {
59
+ loaded: true,
60
+ state,
61
+ };
62
+ } catch (error) {
63
+ console.error(`Failed to load pipeline state: ${error.message}`);
64
+ return {
65
+ loaded: false,
66
+ state: null,
67
+ };
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Save pipeline state to session.yaml.
73
+ *
74
+ * @param {string} projectDir
75
+ * @param {object} state
76
+ * @returns {{ saved: boolean }}
77
+ */
78
+ export function savePipelineState(projectDir, state) {
79
+ const sessionPath = getSessionPath(projectDir);
80
+
81
+ // Ensure .chati directory exists
82
+ const chatDir = dirname(sessionPath);
83
+ if (!existsSync(chatDir)) {
84
+ mkdirSync(chatDir, { recursive: true });
85
+ }
86
+
87
+ try {
88
+ // Convert pipeline state to session format
89
+ const session = {
90
+ mode: state.phase,
91
+ project_type: state.isGreenfield ? 'greenfield' : 'brownfield',
92
+ started_at: state.startedAt,
93
+ completed_at: state.completedAt,
94
+ agents: state.agents,
95
+ completed_agents: state.completedAgents,
96
+ current_agent: state.currentAgent,
97
+ mode_transitions: state.modeTransitions,
98
+ history: state.history,
99
+ updated_at: new Date().toISOString(),
100
+ };
101
+
102
+ const content = yaml.dump(session, {
103
+ indent: 2,
104
+ lineWidth: 120,
105
+ noRefs: true,
106
+ });
107
+
108
+ writeFileSync(sessionPath, content, 'utf8');
109
+
110
+ return { saved: true };
111
+ } catch (error) {
112
+ console.error(`Failed to save pipeline state: ${error.message}`);
113
+ return { saved: false };
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Merge pipeline state into existing session.yaml (preserving other fields).
119
+ *
120
+ * @param {string} projectDir
121
+ * @param {object} updates - Partial pipeline state updates
122
+ * @returns {{ saved: boolean }}
123
+ */
124
+ export function updatePipelineState(projectDir, updates) {
125
+ const sessionPath = getSessionPath(projectDir);
126
+
127
+ // Load existing session or start with empty object
128
+ let session = {};
129
+ if (existsSync(sessionPath)) {
130
+ try {
131
+ const content = readFileSync(sessionPath, 'utf8');
132
+ session = yaml.load(content) || {};
133
+ } catch (error) {
134
+ console.error(`Failed to load existing session: ${error.message}`);
135
+ // Continue with empty session
136
+ }
137
+ }
138
+
139
+ // Ensure .chati directory exists
140
+ const chatDir = dirname(sessionPath);
141
+ if (!existsSync(chatDir)) {
142
+ mkdirSync(chatDir, { recursive: true });
143
+ }
144
+
145
+ try {
146
+ // Apply updates to session
147
+ if (updates.phase !== undefined) {
148
+ session.mode = updates.phase;
149
+ }
150
+ if (updates.isGreenfield !== undefined) {
151
+ session.project_type = updates.isGreenfield ? 'greenfield' : 'brownfield';
152
+ }
153
+ if (updates.startedAt !== undefined) {
154
+ session.started_at = updates.startedAt;
155
+ }
156
+ if (updates.completedAt !== undefined) {
157
+ session.completed_at = updates.completedAt;
158
+ }
159
+ if (updates.agents !== undefined) {
160
+ session.agents = updates.agents;
161
+ }
162
+ if (updates.completedAgents !== undefined) {
163
+ session.completed_agents = updates.completedAgents;
164
+ }
165
+ if (updates.currentAgent !== undefined) {
166
+ session.current_agent = updates.currentAgent;
167
+ }
168
+ if (updates.modeTransitions !== undefined) {
169
+ session.mode_transitions = updates.modeTransitions;
170
+ }
171
+ if (updates.history !== undefined) {
172
+ session.history = updates.history;
173
+ }
174
+
175
+ session.updated_at = new Date().toISOString();
176
+
177
+ const content = yaml.dump(session, {
178
+ indent: 2,
179
+ lineWidth: 120,
180
+ noRefs: true,
181
+ });
182
+
183
+ writeFileSync(sessionPath, content, 'utf8');
184
+
185
+ return { saved: true };
186
+ } catch (error) {
187
+ console.error(`Failed to update pipeline state: ${error.message}`);
188
+ return { saved: false };
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Check if session file exists.
194
+ *
195
+ * @param {string} projectDir
196
+ * @returns {boolean}
197
+ */
198
+ export function sessionExists(projectDir) {
199
+ const sessionPath = getSessionPath(projectDir);
200
+ return existsSync(sessionPath);
201
+ }
202
+
203
+ /**
204
+ * Delete session file.
205
+ *
206
+ * @param {string} projectDir
207
+ * @returns {{ deleted: boolean }}
208
+ */
209
+ export function deleteSession(projectDir) {
210
+ const sessionPath = getSessionPath(projectDir);
211
+
212
+ if (!existsSync(sessionPath)) {
213
+ return { deleted: true };
214
+ }
215
+
216
+ try {
217
+ unlinkSync(sessionPath);
218
+ return { deleted: true };
219
+ } catch (error) {
220
+ console.error(`Failed to delete session: ${error.message}`);
221
+ return { deleted: false };
222
+ }
223
+ }