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.
- package/README.md +7 -6
- package/framework/agents/build/dev.md +343 -0
- package/framework/agents/clarity/architect.md +113 -0
- package/framework/agents/clarity/brief.md +183 -0
- package/framework/agents/clarity/brownfield-wu.md +182 -0
- package/framework/agents/clarity/detail.md +111 -0
- package/framework/agents/clarity/greenfield-wu.md +154 -0
- package/framework/agents/clarity/phases.md +1 -0
- package/framework/agents/clarity/tasks.md +1 -0
- package/framework/agents/clarity/ux.md +113 -0
- package/framework/agents/deploy/devops.md +1 -0
- package/framework/agents/quality/qa-implementation.md +1 -0
- package/framework/agents/quality/qa-planning.md +1 -0
- package/framework/config.yaml +3 -3
- package/framework/constitution.md +58 -1
- package/framework/context/governance.md +37 -0
- package/framework/context/protocols.md +34 -0
- package/framework/context/quality.md +27 -0
- package/framework/context/root.md +24 -0
- package/framework/data/entity-registry.yaml +1 -1
- package/framework/domains/agents/architect.yaml +51 -0
- package/framework/domains/agents/brief.yaml +47 -0
- package/framework/domains/agents/brownfield-wu.yaml +49 -0
- package/framework/domains/agents/detail.yaml +47 -0
- package/framework/domains/agents/dev.yaml +49 -0
- package/framework/domains/agents/devops.yaml +43 -0
- package/framework/domains/agents/greenfield-wu.yaml +47 -0
- package/framework/domains/agents/orchestrator.yaml +49 -0
- package/framework/domains/agents/phases.yaml +47 -0
- package/framework/domains/agents/qa-implementation.yaml +43 -0
- package/framework/domains/agents/qa-planning.yaml +44 -0
- package/framework/domains/agents/tasks.yaml +48 -0
- package/framework/domains/agents/ux.yaml +50 -0
- package/framework/domains/constitution.yaml +77 -0
- package/framework/domains/global.yaml +64 -0
- package/framework/domains/workflows/brownfield-discovery.yaml +16 -0
- package/framework/domains/workflows/brownfield-fullstack.yaml +26 -0
- package/framework/domains/workflows/brownfield-service.yaml +22 -0
- package/framework/domains/workflows/brownfield-ui.yaml +22 -0
- package/framework/domains/workflows/greenfield-fullstack.yaml +26 -0
- package/framework/hooks/constitution-guard.js +101 -0
- package/framework/hooks/mode-governance.js +92 -0
- package/framework/hooks/model-governance.js +76 -0
- package/framework/hooks/prism-engine.js +89 -0
- package/framework/hooks/session-digest.js +60 -0
- package/framework/hooks/settings.json +44 -0
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/context-engine.md +2 -2
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/migrations/v1.4-to-v2.0.yaml +167 -0
- package/framework/migrations/v2.0-to-v2.0.1.yaml +132 -0
- package/framework/orchestrator/chati.md +350 -7
- package/framework/schemas/session.schema.json +15 -0
- package/framework/tasks/architect-api-design.md +63 -0
- package/framework/tasks/architect-consolidate.md +47 -0
- package/framework/tasks/architect-db-design.md +73 -0
- package/framework/tasks/architect-design.md +95 -0
- package/framework/tasks/architect-security-review.md +62 -0
- package/framework/tasks/architect-stack-selection.md +53 -0
- package/framework/tasks/brief-consolidate.md +249 -0
- package/framework/tasks/brief-constraint-identify.md +277 -0
- package/framework/tasks/brief-extract-requirements.md +339 -0
- package/framework/tasks/brief-stakeholder-map.md +176 -0
- package/framework/tasks/brief-validate-completeness.md +121 -0
- package/framework/tasks/brownfield-wu-architecture-map.md +394 -0
- package/framework/tasks/brownfield-wu-deep-discovery.md +312 -0
- package/framework/tasks/brownfield-wu-dependency-scan.md +359 -0
- package/framework/tasks/brownfield-wu-migration-plan.md +483 -0
- package/framework/tasks/brownfield-wu-report.md +325 -0
- package/framework/tasks/brownfield-wu-risk-assess.md +424 -0
- package/framework/tasks/detail-acceptance-criteria.md +372 -0
- package/framework/tasks/detail-consolidate.md +138 -0
- package/framework/tasks/detail-edge-case-analysis.md +300 -0
- package/framework/tasks/detail-expand-prd.md +389 -0
- package/framework/tasks/detail-nfr-extraction.md +223 -0
- package/framework/tasks/dev-code-review.md +404 -0
- package/framework/tasks/dev-consolidate.md +543 -0
- package/framework/tasks/dev-debug.md +322 -0
- package/framework/tasks/dev-implement.md +252 -0
- package/framework/tasks/dev-iterate.md +411 -0
- package/framework/tasks/dev-pr-prepare.md +497 -0
- package/framework/tasks/dev-refactor.md +342 -0
- package/framework/tasks/dev-test-write.md +306 -0
- package/framework/tasks/devops-ci-setup.md +412 -0
- package/framework/tasks/devops-consolidate.md +712 -0
- package/framework/tasks/devops-deploy-config.md +598 -0
- package/framework/tasks/devops-monitoring-setup.md +658 -0
- package/framework/tasks/devops-release-prepare.md +673 -0
- package/framework/tasks/greenfield-wu-analyze-empty.md +169 -0
- package/framework/tasks/greenfield-wu-report.md +266 -0
- package/framework/tasks/greenfield-wu-scaffold-detection.md +203 -0
- package/framework/tasks/greenfield-wu-tech-stack-assess.md +255 -0
- package/framework/tasks/orchestrator-deviation.md +260 -0
- package/framework/tasks/orchestrator-escalate.md +276 -0
- package/framework/tasks/orchestrator-handoff.md +243 -0
- package/framework/tasks/orchestrator-health.md +372 -0
- package/framework/tasks/orchestrator-mode-switch.md +262 -0
- package/framework/tasks/orchestrator-resume.md +189 -0
- package/framework/tasks/orchestrator-route.md +169 -0
- package/framework/tasks/orchestrator-spawn-terminal.md +358 -0
- package/framework/tasks/orchestrator-status.md +260 -0
- package/framework/tasks/orchestrator-suggest-mode.md +372 -0
- package/framework/tasks/phases-breakdown.md +91 -0
- package/framework/tasks/phases-dependency-mapping.md +67 -0
- package/framework/tasks/phases-mvp-scoping.md +94 -0
- package/framework/tasks/qa-impl-consolidate.md +522 -0
- package/framework/tasks/qa-impl-performance-test.md +487 -0
- package/framework/tasks/qa-impl-regression-check.md +413 -0
- package/framework/tasks/qa-impl-sast-scan.md +402 -0
- package/framework/tasks/qa-impl-test-execute.md +344 -0
- package/framework/tasks/qa-impl-verdict.md +339 -0
- package/framework/tasks/qa-planning-consolidate.md +309 -0
- package/framework/tasks/qa-planning-coverage-plan.md +338 -0
- package/framework/tasks/qa-planning-gate-define.md +339 -0
- package/framework/tasks/qa-planning-risk-matrix.md +631 -0
- package/framework/tasks/qa-planning-test-strategy.md +217 -0
- package/framework/tasks/tasks-acceptance-write.md +75 -0
- package/framework/tasks/tasks-consolidate.md +57 -0
- package/framework/tasks/tasks-decompose.md +80 -0
- package/framework/tasks/tasks-estimate.md +66 -0
- package/framework/tasks/ux-a11y-check.md +49 -0
- package/framework/tasks/ux-component-map.md +55 -0
- package/framework/tasks/ux-consolidate.md +46 -0
- package/framework/tasks/ux-user-flow.md +46 -0
- package/framework/tasks/ux-wireframe.md +76 -0
- package/package.json +1 -1
- package/scripts/bundle-framework.js +2 -0
- package/scripts/changelog-generator.js +222 -0
- package/scripts/codebase-mapper.js +728 -0
- package/scripts/commit-message-generator.js +167 -0
- package/scripts/coverage-analyzer.js +260 -0
- package/scripts/dependency-analyzer.js +280 -0
- package/scripts/framework-analyzer.js +308 -0
- package/scripts/generate-constitution-domain.js +253 -0
- package/scripts/health-check.js +481 -0
- package/scripts/ide-sync.js +327 -0
- package/scripts/performance-analyzer.js +325 -0
- package/scripts/plan-tracker.js +278 -0
- package/scripts/populate-entity-registry.js +481 -0
- package/scripts/pr-review.js +317 -0
- package/scripts/rollback-manager.js +310 -0
- package/scripts/stuck-detector.js +343 -0
- package/scripts/test-quality-assessment.js +257 -0
- package/scripts/validate-agents.js +367 -0
- package/scripts/validate-tasks.js +465 -0
- package/src/autonomy/autonomous-gate.js +293 -0
- package/src/autonomy/index.js +51 -0
- package/src/autonomy/mode-manager.js +225 -0
- package/src/autonomy/mode-suggester.js +283 -0
- package/src/autonomy/progress-reporter.js +268 -0
- package/src/autonomy/safety-net.js +320 -0
- package/src/context/bracket-tracker.js +79 -0
- package/src/context/domain-loader.js +107 -0
- package/src/context/engine.js +144 -0
- package/src/context/formatter.js +184 -0
- package/src/context/index.js +4 -0
- package/src/context/layers/l0-constitution.js +28 -0
- package/src/context/layers/l1-global.js +37 -0
- package/src/context/layers/l2-agent.js +39 -0
- package/src/context/layers/l3-workflow.js +42 -0
- package/src/context/layers/l4-task.js +24 -0
- package/src/decision/analyzer.js +167 -0
- package/src/decision/engine.js +270 -0
- package/src/decision/index.js +38 -0
- package/src/decision/registry-healer.js +450 -0
- package/src/decision/registry-updater.js +330 -0
- package/src/gates/circuit-breaker.js +119 -0
- package/src/gates/g1-planning-complete.js +153 -0
- package/src/gates/g2-qa-planning.js +153 -0
- package/src/gates/g3-implementation.js +188 -0
- package/src/gates/g4-qa-implementation.js +207 -0
- package/src/gates/g5-deploy-ready.js +180 -0
- package/src/gates/gate-base.js +144 -0
- package/src/gates/index.js +46 -0
- package/src/installer/brownfield-upgrader.js +249 -0
- package/src/installer/core.js +55 -3
- package/src/installer/file-hasher.js +51 -0
- package/src/installer/manifest.js +117 -0
- package/src/installer/templates.js +17 -15
- package/src/installer/transaction.js +229 -0
- package/src/installer/validator.js +18 -1
- package/src/intelligence/registry-manager.js +2 -2
- package/src/memory/agent-memory.js +255 -0
- package/src/memory/gotchas-injector.js +72 -0
- package/src/memory/gotchas.js +361 -0
- package/src/memory/index.js +35 -0
- package/src/memory/search.js +233 -0
- package/src/memory/session-digest.js +239 -0
- package/src/merger/env-merger.js +112 -0
- package/src/merger/index.js +56 -0
- package/src/merger/replace-merger.js +51 -0
- package/src/merger/yaml-merger.js +127 -0
- package/src/orchestrator/agent-selector.js +285 -0
- package/src/orchestrator/deviation-handler.js +350 -0
- package/src/orchestrator/handoff-engine.js +271 -0
- package/src/orchestrator/index.js +67 -0
- package/src/orchestrator/intent-classifier.js +264 -0
- package/src/orchestrator/pipeline-manager.js +492 -0
- package/src/orchestrator/pipeline-state.js +223 -0
- package/src/orchestrator/session-manager.js +409 -0
- package/src/tasks/executor.js +195 -0
- package/src/tasks/handoff.js +226 -0
- package/src/tasks/index.js +4 -0
- package/src/tasks/loader.js +210 -0
- package/src/tasks/router.js +182 -0
- package/src/terminal/collector.js +216 -0
- package/src/terminal/index.js +30 -0
- package/src/terminal/isolation.js +129 -0
- package/src/terminal/monitor.js +277 -0
- package/src/terminal/spawner.js +269 -0
- package/src/upgrade/checker.js +1 -1
- 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
|
+
}
|