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,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview G5 — Deploy Ready Gate
|
|
3
|
+
*
|
|
4
|
+
* Pre-Deploy gate that validates everything is ready for deployment.
|
|
5
|
+
* Checks that QA-Implementation passed, documentation is updated,
|
|
6
|
+
* release notes exist, all tasks are complete, and no blockers remain.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { GateBase } from './gate-base.js';
|
|
12
|
+
import { loadSession } from '../orchestrator/session-manager.js';
|
|
13
|
+
import { loadHandoff } from '../tasks/handoff.js';
|
|
14
|
+
|
|
15
|
+
export class DeployReadyGate extends GateBase {
|
|
16
|
+
constructor() {
|
|
17
|
+
super({
|
|
18
|
+
id: 'g5-deploy-ready',
|
|
19
|
+
name: 'Deploy Ready',
|
|
20
|
+
pipelinePoint: 'pre-deploy',
|
|
21
|
+
agent: 'devops',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Collect evidence about deployment readiness.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} projectDir
|
|
29
|
+
* @returns {object} Evidence about QA status, docs, release notes, blockers
|
|
30
|
+
*/
|
|
31
|
+
_collectEvidence(projectDir) {
|
|
32
|
+
const evidence = {
|
|
33
|
+
qaImplPassed: false,
|
|
34
|
+
readmeUpdated: false,
|
|
35
|
+
changelogUpdated: false,
|
|
36
|
+
releaseNotesPrepared: false,
|
|
37
|
+
allTasksCompleted: false,
|
|
38
|
+
noOpenBlockers: true,
|
|
39
|
+
sessionState: null,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Check QA-Implementation status
|
|
43
|
+
const qaHandoff = loadHandoff(projectDir, 'qa-implementation');
|
|
44
|
+
if (qaHandoff.loaded && qaHandoff.handoff) {
|
|
45
|
+
evidence.qaImplPassed = qaHandoff.handoff.score >= 90 &&
|
|
46
|
+
qaHandoff.handoff.status === 'complete';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check README exists
|
|
50
|
+
const readmePaths = [
|
|
51
|
+
join(projectDir, 'README.md'),
|
|
52
|
+
join(projectDir, 'readme.md'),
|
|
53
|
+
];
|
|
54
|
+
for (const readmePath of readmePaths) {
|
|
55
|
+
if (existsSync(readmePath)) {
|
|
56
|
+
evidence.readmeUpdated = true;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check CHANGELOG exists
|
|
62
|
+
const changelogPaths = [
|
|
63
|
+
join(projectDir, 'CHANGELOG.md'),
|
|
64
|
+
join(projectDir, 'docs', 'CHANGELOG.md'),
|
|
65
|
+
join(projectDir, 'changelog.md'),
|
|
66
|
+
join(projectDir, 'CHANGES.md'),
|
|
67
|
+
];
|
|
68
|
+
for (const clPath of changelogPaths) {
|
|
69
|
+
if (existsSync(clPath)) {
|
|
70
|
+
evidence.changelogUpdated = true;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check release notes
|
|
76
|
+
const releaseNotePaths = [
|
|
77
|
+
join(projectDir, 'RELEASE.md'),
|
|
78
|
+
join(projectDir, 'chati.dev', 'artifacts', 'release-notes.md'),
|
|
79
|
+
];
|
|
80
|
+
for (const rnPath of releaseNotePaths) {
|
|
81
|
+
if (existsSync(rnPath)) {
|
|
82
|
+
evidence.releaseNotesPrepared = true;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Load session state
|
|
88
|
+
const sessionResult = loadSession(projectDir);
|
|
89
|
+
if (sessionResult.loaded && sessionResult.session) {
|
|
90
|
+
const agents = sessionResult.session.agents || {};
|
|
91
|
+
const completedAgents = sessionResult.session.completed_agents || [];
|
|
92
|
+
|
|
93
|
+
// Check all required agents are completed
|
|
94
|
+
const requiredAgents = Object.keys(agents).filter(
|
|
95
|
+
a => agents[a].status !== 'skipped'
|
|
96
|
+
);
|
|
97
|
+
const allComplete = requiredAgents.every(
|
|
98
|
+
a => completedAgents.includes(a) || agents[a].status === 'completed'
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
evidence.allTasksCompleted = allComplete;
|
|
102
|
+
|
|
103
|
+
// Check for open blockers in any handoff
|
|
104
|
+
for (const agentName of completedAgents) {
|
|
105
|
+
const agentHandoff = loadHandoff(projectDir, agentName);
|
|
106
|
+
if (agentHandoff.loaded && agentHandoff.handoff) {
|
|
107
|
+
const blockers = agentHandoff.handoff.blockers || [];
|
|
108
|
+
if (blockers.length > 0) {
|
|
109
|
+
evidence.noOpenBlockers = false;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
evidence.sessionState = {
|
|
116
|
+
mode: sessionResult.session.mode,
|
|
117
|
+
completedCount: completedAgents.length,
|
|
118
|
+
totalAgents: Object.keys(agents).length,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return evidence;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Validate deployment readiness evidence.
|
|
127
|
+
*
|
|
128
|
+
* @param {object} evidence
|
|
129
|
+
* @returns {{ score: number, criteriaResults: string[], allCriteria: string[], warnings: string[] }}
|
|
130
|
+
*/
|
|
131
|
+
_validateEvidence(evidence) {
|
|
132
|
+
const allCriteria = [
|
|
133
|
+
'QA-Implementation passed',
|
|
134
|
+
'README updated',
|
|
135
|
+
'CHANGELOG updated',
|
|
136
|
+
'Release notes prepared',
|
|
137
|
+
'All tasks completed',
|
|
138
|
+
'No open blockers',
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const criteriaResults = [];
|
|
142
|
+
const warnings = [];
|
|
143
|
+
|
|
144
|
+
if (evidence.qaImplPassed) {
|
|
145
|
+
criteriaResults.push('QA-Implementation passed');
|
|
146
|
+
} else {
|
|
147
|
+
warnings.push('QA-Implementation has not passed');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (evidence.readmeUpdated) {
|
|
151
|
+
criteriaResults.push('README updated');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (evidence.changelogUpdated) {
|
|
155
|
+
criteriaResults.push('CHANGELOG updated');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (evidence.releaseNotesPrepared) {
|
|
159
|
+
criteriaResults.push('Release notes prepared');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (evidence.allTasksCompleted) {
|
|
163
|
+
criteriaResults.push('All tasks completed');
|
|
164
|
+
} else {
|
|
165
|
+
warnings.push('Not all tasks are completed');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (evidence.noOpenBlockers) {
|
|
169
|
+
criteriaResults.push('No open blockers');
|
|
170
|
+
} else {
|
|
171
|
+
warnings.push('Open blockers remain from agent handoffs');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const score = allCriteria.length > 0
|
|
175
|
+
? Math.round((criteriaResults.length / allCriteria.length) * 100)
|
|
176
|
+
: 0;
|
|
177
|
+
|
|
178
|
+
return { score, criteriaResults, allCriteria, warnings };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview GateBase — Template Method pattern base class for quality gates.
|
|
3
|
+
*
|
|
4
|
+
* Provides the abstract skeleton for all pipeline quality gates.
|
|
5
|
+
* Subclasses implement _collectEvidence() and _validateEvidence()
|
|
6
|
+
* while this base class handles evaluation orchestration.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { evaluateGate, getGateThreshold, resolveGateAction } from '../autonomy/autonomous-gate.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Abstract base class for quality gates.
|
|
13
|
+
*
|
|
14
|
+
* Uses the Template Method pattern: evaluate() is the template that
|
|
15
|
+
* calls _collectEvidence() and _validateEvidence() which subclasses implement.
|
|
16
|
+
*/
|
|
17
|
+
export class GateBase {
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} config
|
|
20
|
+
* @param {string} config.id - Unique gate identifier (e.g. 'g1-planning-complete')
|
|
21
|
+
* @param {string} config.name - Human-readable gate name
|
|
22
|
+
* @param {string} config.pipelinePoint - Pipeline point this gate guards
|
|
23
|
+
* @param {string} config.agent - Agent whose output this gate evaluates
|
|
24
|
+
*/
|
|
25
|
+
constructor(config) {
|
|
26
|
+
if (new.target === GateBase) {
|
|
27
|
+
throw new Error('GateBase is abstract and cannot be instantiated directly.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { id, name, pipelinePoint, agent } = config;
|
|
31
|
+
|
|
32
|
+
if (!id || !name || !pipelinePoint || !agent) {
|
|
33
|
+
throw new Error('GateBase requires id, name, pipelinePoint, and agent in config.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.id = id;
|
|
37
|
+
this.name = name;
|
|
38
|
+
this.pipelinePoint = pipelinePoint;
|
|
39
|
+
this.agent = agent;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Evaluate the quality gate.
|
|
44
|
+
*
|
|
45
|
+
* Template Method: collects evidence, validates it, then either runs
|
|
46
|
+
* autonomous evaluation or returns recommendation for human review.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} projectDir - Project root directory
|
|
49
|
+
* @param {string} [mode='autonomous'] - 'autonomous' or 'human-in-the-loop'
|
|
50
|
+
* @returns {object} GateResult: { result, score, evidence, recommendation, canProceed }
|
|
51
|
+
*/
|
|
52
|
+
evaluate(projectDir, mode = 'autonomous') {
|
|
53
|
+
// Step 1: Collect evidence (subclass implements)
|
|
54
|
+
const evidence = this._collectEvidence(projectDir);
|
|
55
|
+
|
|
56
|
+
// Step 2: Validate evidence (subclass implements)
|
|
57
|
+
const validation = this._validateEvidence(evidence);
|
|
58
|
+
|
|
59
|
+
const { score, criteriaResults, allCriteria, warnings = [] } = validation;
|
|
60
|
+
|
|
61
|
+
if (mode === 'autonomous') {
|
|
62
|
+
// Step 3a: Autonomous evaluation via autonomous-gate module
|
|
63
|
+
const gateResult = evaluateGate({
|
|
64
|
+
agent: this.agent,
|
|
65
|
+
score,
|
|
66
|
+
criteriaResults,
|
|
67
|
+
allCriteria,
|
|
68
|
+
warnings,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const action = resolveGateAction(gateResult.result, mode);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
gateId: this.id,
|
|
75
|
+
gateName: this.name,
|
|
76
|
+
result: gateResult.result,
|
|
77
|
+
score: gateResult.score,
|
|
78
|
+
evidence,
|
|
79
|
+
recommendation: action.action,
|
|
80
|
+
canProceed: gateResult.canProceed,
|
|
81
|
+
details: gateResult.details,
|
|
82
|
+
warnings,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Step 3b: Human-in-the-loop — return evidence + recommendation
|
|
87
|
+
const threshold = getGateThreshold(this.agent);
|
|
88
|
+
const recommendation = score >= threshold
|
|
89
|
+
? 'Recommend approval — criteria met.'
|
|
90
|
+
: `Recommend review — score ${score} below threshold ${threshold}.`;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
gateId: this.id,
|
|
94
|
+
gateName: this.name,
|
|
95
|
+
result: 'review',
|
|
96
|
+
score,
|
|
97
|
+
evidence,
|
|
98
|
+
recommendation,
|
|
99
|
+
canProceed: false, // Human must explicitly approve
|
|
100
|
+
details: {
|
|
101
|
+
criteriaResults,
|
|
102
|
+
allCriteria,
|
|
103
|
+
threshold,
|
|
104
|
+
},
|
|
105
|
+
warnings,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Collect evidence from the project filesystem.
|
|
111
|
+
* ABSTRACT — subclass MUST implement.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} _projectDir
|
|
114
|
+
* @returns {object} Evidence data
|
|
115
|
+
*/
|
|
116
|
+
_collectEvidence(_projectDir) {
|
|
117
|
+
throw new Error(`${this.constructor.name} must implement _collectEvidence()`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validate collected evidence and produce a score.
|
|
122
|
+
* ABSTRACT — subclass MUST implement.
|
|
123
|
+
*
|
|
124
|
+
* @param {object} _evidence
|
|
125
|
+
* @returns {{ score: number, criteriaResults: string[], allCriteria: string[], warnings: string[] }}
|
|
126
|
+
*/
|
|
127
|
+
_validateEvidence(_evidence) {
|
|
128
|
+
throw new Error(`${this.constructor.name} must implement _validateEvidence()`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get gate metadata.
|
|
133
|
+
*
|
|
134
|
+
* @returns {{ id: string, name: string, pipelinePoint: string, threshold: number }}
|
|
135
|
+
*/
|
|
136
|
+
getInfo() {
|
|
137
|
+
return {
|
|
138
|
+
id: this.id,
|
|
139
|
+
name: this.name,
|
|
140
|
+
pipelinePoint: this.pipelinePoint,
|
|
141
|
+
threshold: getGateThreshold(this.agent),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Quality Gates barrel exports and registry.
|
|
3
|
+
*
|
|
4
|
+
* Provides all gate classes and a registry function to look up
|
|
5
|
+
* the appropriate gate for a given pipeline point.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { GateBase } from './gate-base.js';
|
|
9
|
+
export { CircuitBreaker, CIRCUIT_STATES } from './circuit-breaker.js';
|
|
10
|
+
export { PlanningCompleteGate } from './g1-planning-complete.js';
|
|
11
|
+
export { QAPlanningGate } from './g2-qa-planning.js';
|
|
12
|
+
export { ImplementationGate } from './g3-implementation.js';
|
|
13
|
+
export { QAImplementationGate, QA_IMPL_VERDICTS } from './g4-qa-implementation.js';
|
|
14
|
+
export { DeployReadyGate } from './g5-deploy-ready.js';
|
|
15
|
+
|
|
16
|
+
import { PlanningCompleteGate as _G1 } from './g1-planning-complete.js';
|
|
17
|
+
import { QAPlanningGate as _G2 } from './g2-qa-planning.js';
|
|
18
|
+
import { ImplementationGate as _G3 } from './g3-implementation.js';
|
|
19
|
+
import { QAImplementationGate as _G4 } from './g4-qa-implementation.js';
|
|
20
|
+
import { DeployReadyGate as _G5 } from './g5-deploy-ready.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Pipeline point to gate class mapping.
|
|
24
|
+
*/
|
|
25
|
+
const PIPELINE_POINT_MAP = {
|
|
26
|
+
'pre-build': _G1,
|
|
27
|
+
'post-qa-planning': _G2,
|
|
28
|
+
'post-dev': _G3,
|
|
29
|
+
'post-qa-impl': _G4,
|
|
30
|
+
'pre-deploy': _G5,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get the gate instance for a specific pipeline point.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} point - Pipeline point identifier
|
|
37
|
+
* @returns {import('./gate-base.js').GateBase} Gate instance
|
|
38
|
+
* @throws {Error} If no gate is registered for the given point
|
|
39
|
+
*/
|
|
40
|
+
export function getGateForPipelinePoint(point) {
|
|
41
|
+
const GateClass = PIPELINE_POINT_MAP[point];
|
|
42
|
+
if (!GateClass) {
|
|
43
|
+
throw new Error(`No gate registered for pipeline point: ${point}`);
|
|
44
|
+
}
|
|
45
|
+
return new GateClass();
|
|
46
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { hashFile } from './file-hasher.js';
|
|
4
|
+
import { loadManifest, generateManifest, compareManifests, saveManifest } from './manifest.js';
|
|
5
|
+
import { mergeFile } from '../merger/index.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Categorize changes between current and new manifests.
|
|
9
|
+
*
|
|
10
|
+
* @param {object} currentManifest - The currently installed manifest
|
|
11
|
+
* @param {object} newManifest - The new framework version manifest
|
|
12
|
+
* @returns {{ added: string[], removed: string[], modified: string[], unchanged: string[] }}
|
|
13
|
+
*/
|
|
14
|
+
export function categorizeChanges(currentManifest, newManifest) {
|
|
15
|
+
return compareManifests(currentManifest, newManifest);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Detect files the user has modified since installation.
|
|
20
|
+
* Compares the current on-disk hash of each file against the hash
|
|
21
|
+
* recorded in the manifest at install time.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} targetDir - Project root directory
|
|
24
|
+
* @param {object} manifest - The installed manifest
|
|
25
|
+
* @returns {{ path: string, recordedHash: string, currentHash: string }[]}
|
|
26
|
+
*/
|
|
27
|
+
export function detectUserModifications(targetDir, manifest) {
|
|
28
|
+
const modified = [];
|
|
29
|
+
const files = manifest.files || {};
|
|
30
|
+
|
|
31
|
+
for (const [relPath, entry] of Object.entries(files)) {
|
|
32
|
+
const absPath = join(targetDir, relPath);
|
|
33
|
+
if (!existsSync(absPath)) continue;
|
|
34
|
+
|
|
35
|
+
const currentHash = hashFile(absPath);
|
|
36
|
+
if (currentHash !== entry.hash) {
|
|
37
|
+
modified.push({
|
|
38
|
+
path: relPath,
|
|
39
|
+
recordedHash: entry.hash,
|
|
40
|
+
currentHash,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return modified;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Plan an upgrade without making any changes (dry run).
|
|
50
|
+
* Shows what would happen if upgradeInstallation were called.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} targetDir - Project root where framework is installed
|
|
53
|
+
* @param {string} newFrameworkDir - Directory containing the new framework version
|
|
54
|
+
* @param {string} [newVersion='0.0.0'] - New version string
|
|
55
|
+
* @returns {{ upgraded: string[], skipped: string[], added: string[], removed: string[], preserved: string[], errors: Array<{ path: string, error: string }> }}
|
|
56
|
+
*/
|
|
57
|
+
export function planUpgrade(targetDir, newFrameworkDir, newVersion = '0.0.0') {
|
|
58
|
+
const result = {
|
|
59
|
+
upgraded: [],
|
|
60
|
+
skipped: [],
|
|
61
|
+
added: [],
|
|
62
|
+
removed: [],
|
|
63
|
+
preserved: [],
|
|
64
|
+
errors: [],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Load current manifest
|
|
68
|
+
const currentManifest = loadManifest(targetDir);
|
|
69
|
+
if (!currentManifest) {
|
|
70
|
+
result.errors.push({
|
|
71
|
+
path: '.chati/manifest.json',
|
|
72
|
+
error: 'No existing manifest found. Cannot plan upgrade without a manifest.',
|
|
73
|
+
});
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Build new manifest from the new framework directory
|
|
78
|
+
const newManifest = generateManifest(
|
|
79
|
+
newFrameworkDir,
|
|
80
|
+
collectRelativePaths(newFrameworkDir),
|
|
81
|
+
newVersion
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Categorize
|
|
85
|
+
const changes = categorizeChanges(currentManifest, newManifest);
|
|
86
|
+
|
|
87
|
+
// Detect user modifications
|
|
88
|
+
const userMods = detectUserModifications(targetDir, currentManifest);
|
|
89
|
+
const userModPaths = new Set(userMods.map((m) => m.path));
|
|
90
|
+
|
|
91
|
+
// Added files => will be added
|
|
92
|
+
result.added = [...changes.added];
|
|
93
|
+
|
|
94
|
+
// Removed files => will be flagged for removal
|
|
95
|
+
result.removed = [...changes.removed];
|
|
96
|
+
|
|
97
|
+
// Modified files => check if user also modified them
|
|
98
|
+
for (const filePath of changes.modified) {
|
|
99
|
+
if (userModPaths.has(filePath)) {
|
|
100
|
+
// User modified this file => skip (preserve user changes)
|
|
101
|
+
result.preserved.push(filePath);
|
|
102
|
+
result.skipped.push(filePath);
|
|
103
|
+
} else {
|
|
104
|
+
// User did NOT modify => safe to upgrade
|
|
105
|
+
result.upgraded.push(filePath);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Execute an upgrade: update files from the new framework version
|
|
114
|
+
* while preserving user-modified files.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} targetDir - Project root where framework is installed
|
|
117
|
+
* @param {string} newFrameworkDir - Directory containing the new framework version
|
|
118
|
+
* @param {string} [newVersion='0.0.0'] - New version string
|
|
119
|
+
* @returns {{ upgraded: string[], skipped: string[], added: string[], removed: string[], preserved: string[], errors: Array<{ path: string, error: string }> }}
|
|
120
|
+
*/
|
|
121
|
+
export function upgradeInstallation(targetDir, newFrameworkDir, newVersion = '0.0.0') {
|
|
122
|
+
const result = {
|
|
123
|
+
upgraded: [],
|
|
124
|
+
skipped: [],
|
|
125
|
+
added: [],
|
|
126
|
+
removed: [],
|
|
127
|
+
preserved: [],
|
|
128
|
+
errors: [],
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Load current manifest
|
|
132
|
+
const currentManifest = loadManifest(targetDir);
|
|
133
|
+
if (!currentManifest) {
|
|
134
|
+
result.errors.push({
|
|
135
|
+
path: '.chati/manifest.json',
|
|
136
|
+
error: 'No existing manifest found. Cannot upgrade without a manifest.',
|
|
137
|
+
});
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Build new manifest
|
|
142
|
+
const newManifest = generateManifest(
|
|
143
|
+
newFrameworkDir,
|
|
144
|
+
collectRelativePaths(newFrameworkDir),
|
|
145
|
+
newVersion
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// Categorize
|
|
149
|
+
const changes = categorizeChanges(currentManifest, newManifest);
|
|
150
|
+
|
|
151
|
+
// Detect user modifications
|
|
152
|
+
const userMods = detectUserModifications(targetDir, currentManifest);
|
|
153
|
+
const userModPaths = new Set(userMods.map((m) => m.path));
|
|
154
|
+
|
|
155
|
+
// Process ADDED files
|
|
156
|
+
for (const filePath of changes.added) {
|
|
157
|
+
try {
|
|
158
|
+
const srcPath = join(newFrameworkDir, filePath);
|
|
159
|
+
const destPath = join(targetDir, filePath);
|
|
160
|
+
const destDir = dirname(destPath);
|
|
161
|
+
|
|
162
|
+
if (!existsSync(destDir)) {
|
|
163
|
+
mkdirSync(destDir, { recursive: true });
|
|
164
|
+
}
|
|
165
|
+
copyFileSync(srcPath, destPath);
|
|
166
|
+
result.added.push(filePath);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
result.errors.push({ path: filePath, error: err.message });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Process MODIFIED files
|
|
173
|
+
for (const filePath of changes.modified) {
|
|
174
|
+
if (userModPaths.has(filePath)) {
|
|
175
|
+
// User modified => try smart merge, otherwise preserve
|
|
176
|
+
try {
|
|
177
|
+
const existingContent = readFileSync(join(targetDir, filePath), 'utf-8');
|
|
178
|
+
const newContent = readFileSync(join(newFrameworkDir, filePath), 'utf-8');
|
|
179
|
+
const merged = mergeFile(filePath, existingContent, newContent);
|
|
180
|
+
|
|
181
|
+
// Only write if merge produced a different result from existing
|
|
182
|
+
if (merged !== existingContent) {
|
|
183
|
+
writeFileSync(join(targetDir, filePath), merged, 'utf-8');
|
|
184
|
+
result.upgraded.push(filePath);
|
|
185
|
+
} else {
|
|
186
|
+
result.preserved.push(filePath);
|
|
187
|
+
result.skipped.push(filePath);
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
// Merge failed => preserve user's version
|
|
191
|
+
result.preserved.push(filePath);
|
|
192
|
+
result.skipped.push(filePath);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
// User did NOT modify => safe to replace
|
|
196
|
+
try {
|
|
197
|
+
const srcPath = join(newFrameworkDir, filePath);
|
|
198
|
+
const destPath = join(targetDir, filePath);
|
|
199
|
+
copyFileSync(srcPath, destPath);
|
|
200
|
+
result.upgraded.push(filePath);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
result.errors.push({ path: filePath, error: err.message });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Process REMOVED files (mark but do not delete for safety)
|
|
208
|
+
result.removed = [...changes.removed];
|
|
209
|
+
|
|
210
|
+
// Save updated manifest
|
|
211
|
+
try {
|
|
212
|
+
saveManifest(targetDir, newManifest);
|
|
213
|
+
} catch (err) {
|
|
214
|
+
result.errors.push({ path: '.chati/manifest.json', error: err.message });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Collect all relative file paths under a directory recursively.
|
|
222
|
+
* Skips hidden directories (starting with '.').
|
|
223
|
+
*
|
|
224
|
+
* @param {string} rootDir - Directory to scan
|
|
225
|
+
* @param {string} [base=''] - Base path for recursion (internal)
|
|
226
|
+
* @returns {string[]} Array of relative file paths
|
|
227
|
+
*/
|
|
228
|
+
function collectRelativePaths(rootDir, base = '') {
|
|
229
|
+
const paths = [];
|
|
230
|
+
|
|
231
|
+
let entries;
|
|
232
|
+
try {
|
|
233
|
+
entries = readdirSync(join(rootDir, base), { withFileTypes: true });
|
|
234
|
+
} catch {
|
|
235
|
+
return paths;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (const entry of entries) {
|
|
239
|
+
const relPath = base ? `${base}/${entry.name}` : entry.name;
|
|
240
|
+
if (entry.isDirectory()) {
|
|
241
|
+
if (entry.name.startsWith('.')) continue;
|
|
242
|
+
paths.push(...collectRelativePaths(rootDir, relPath));
|
|
243
|
+
} else if (entry.isFile()) {
|
|
244
|
+
paths.push(relPath);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return paths;
|
|
249
|
+
}
|