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,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
+ }