chati-dev 3.2.5 → 3.3.0

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 (98) hide show
  1. package/LICENSE +96 -0
  2. package/bin/chati.js +46 -0
  3. package/framework/agents/build/dev.md +122 -1
  4. package/framework/agents/deploy/devops.md +128 -3
  5. package/framework/agents/discover/brief.md +77 -15
  6. package/framework/agents/discover/brownfield-wu.md +2 -2
  7. package/framework/agents/discover/greenfield-wu.md +3 -3
  8. package/framework/agents/plan/architect.md +2 -2
  9. package/framework/agents/plan/detail.md +3 -3
  10. package/framework/agents/plan/phases.md +127 -2
  11. package/framework/agents/plan/tasks.md +127 -2
  12. package/framework/agents/plan/ux.md +269 -22
  13. package/framework/agents/quality/qa-implementation.md +172 -8
  14. package/framework/agents/quality/qa-planning.md +147 -2
  15. package/framework/config.yaml +9 -5
  16. package/framework/constitution.md +7 -1
  17. package/framework/context/quality.md +1 -1
  18. package/framework/context/root.md +1 -1
  19. package/framework/hooks/constitution-guard.js +18 -2
  20. package/framework/hooks/mode-governance.js +3 -3
  21. package/framework/hooks/read-protection.js +10 -2
  22. package/framework/i18n/en.yaml +6 -0
  23. package/framework/i18n/es.yaml +6 -0
  24. package/framework/i18n/fr.yaml +6 -0
  25. package/framework/i18n/pt.yaml +6 -0
  26. package/framework/orchestrator/chati.md +102 -6
  27. package/framework/schemas/task.schema.json +1 -1
  28. package/framework/tasks/architect-dep-audit.md +128 -0
  29. package/framework/tasks/architect-stack-selection.md +28 -0
  30. package/framework/workflows/brownfield-fullstack.yaml +2 -2
  31. package/framework/workflows/brownfield-service.yaml +2 -2
  32. package/framework/workflows/brownfield-ui.yaml +2 -2
  33. package/framework/workflows/greenfield-fullstack.yaml +6 -2
  34. package/framework/workflows/quick-flow.yaml +7 -5
  35. package/framework/workflows/standard-flow.yaml +171 -0
  36. package/package.json +4 -2
  37. package/src/api/index.js +129 -0
  38. package/src/autonomy/build-loop.js +93 -6
  39. package/src/autonomy/build-state.js +20 -2
  40. package/src/autonomy/cause-analyzer.js +177 -0
  41. package/src/autonomy/escalation.js +214 -0
  42. package/src/autonomy/safety-net.js +23 -5
  43. package/src/autonomy/worktree-manager.js +245 -0
  44. package/src/config/agent-customizer.js +227 -0
  45. package/src/config/ide-configs.js +57 -27
  46. package/src/decision/analyzer.js +148 -0
  47. package/src/decision/registry-healer.js +38 -21
  48. package/src/extensions/loader.js +151 -0
  49. package/src/extensions/registry.js +134 -0
  50. package/src/gates/circuit-breaker.js +32 -0
  51. package/src/gates/g3-implementation.js +30 -4
  52. package/src/gates/g4-qa-implementation.js +34 -5
  53. package/src/gates/gate-base.js +9 -0
  54. package/src/health/auto-fix.js +216 -0
  55. package/src/installer/core.js +24 -11
  56. package/src/installer/provider-overlay.js +82 -0
  57. package/src/installer/templates.js +22 -10
  58. package/src/installer/transaction.js +3 -2
  59. package/src/installer/validator.js +74 -0
  60. package/src/intelligence/context-status.js +9 -5
  61. package/src/intelligence/document-sharder.js +221 -0
  62. package/src/intelligence/elicitation.js +265 -0
  63. package/src/intelligence/timeline.js +5 -0
  64. package/src/memory/gotchas.js +78 -2
  65. package/src/merger/semantic-merger.js +292 -0
  66. package/src/orchestrator/agent-selector.js +20 -0
  67. package/src/orchestrator/handoff-engine.js +77 -0
  68. package/src/orchestrator/index.js +0 -8
  69. package/src/orchestrator/intent-classifier.js +182 -0
  70. package/src/orchestrator/pipeline-manager.js +125 -1
  71. package/src/orchestrator/session-manager.js +164 -2
  72. package/src/quality/metrics-collector.js +283 -0
  73. package/src/quality/test-runner.js +368 -0
  74. package/src/telemetry/collector.js +83 -0
  75. package/src/telemetry/config.js +119 -0
  76. package/src/telemetry/index.js +11 -0
  77. package/src/telemetry/schema.js +104 -0
  78. package/src/telemetry/sender.js +60 -0
  79. package/src/terminal/cli-registry.js +7 -1
  80. package/src/terminal/cost-tracker.js +197 -0
  81. package/src/terminal/handoff-parser.js +61 -4
  82. package/src/terminal/prompt-builder.js +56 -18
  83. package/src/terminal/rate-limiter.js +172 -0
  84. package/src/terminal/run-agent.js +39 -0
  85. package/src/terminal/run-parallel.js +22 -1
  86. package/src/terminal/spawner.js +181 -3
  87. package/src/upgrade/migrator.js +2 -2
  88. package/src/utils/event-bus.js +126 -0
  89. package/src/utils/file-lock.js +291 -0
  90. package/src/utils/schema-validator.js +226 -0
  91. package/src/wizard/i18n.js +11 -0
  92. package/src/wizard/index.js +42 -20
  93. package/src/wizard/questions.js +200 -39
  94. package/src/autonomy/execution-profile.js +0 -151
  95. package/src/intelligence/file-tracker.js +0 -117
  96. package/src/memory/gotchas-auto-capture.js +0 -253
  97. package/src/orchestrator/pipeline-state.js +0 -223
  98. package/src/terminal/wave-analyzer.js +0 -143
@@ -1,223 +0,0 @@
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 || 'discover',
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
- }
@@ -1,143 +0,0 @@
1
- /**
2
- * @fileoverview Wave analyzer for task parallelization.
3
- *
4
- * Analyzes task dependencies to group them into waves —
5
- * sets of tasks that can execute in parallel because they
6
- * have no inter-dependencies.
7
- */
8
-
9
- // ---------------------------------------------------------------------------
10
- // Types
11
- // ---------------------------------------------------------------------------
12
-
13
- /**
14
- * @typedef {object} TaskNode
15
- * @property {string} id - Task identifier
16
- * @property {string[]} dependencies - IDs of tasks this depends on
17
- * @property {string[]} [writeScope] - Files/dirs this task writes to
18
- */
19
-
20
- /**
21
- * @typedef {object} Wave
22
- * @property {number} index - Wave number (0-based)
23
- * @property {string[]} taskIds - Task IDs in this wave
24
- */
25
-
26
- // ---------------------------------------------------------------------------
27
- // Wave Analysis
28
- // ---------------------------------------------------------------------------
29
-
30
- /**
31
- * Build a dependency graph and compute waves using topological sorting.
32
- *
33
- * @param {TaskNode[]} tasks - Array of tasks with dependencies
34
- * @returns {Wave[]}
35
- * @throws {Error} When circular dependencies are detected
36
- */
37
- export function analyzeWaves(tasks) {
38
- if (!tasks || tasks.length === 0) return [];
39
-
40
- const taskMap = new Map(tasks.map((t) => [t.id, t]));
41
- const inDegree = new Map();
42
- const adjList = new Map();
43
-
44
- // Initialize
45
- for (const task of tasks) {
46
- inDegree.set(task.id, 0);
47
- adjList.set(task.id, []);
48
- }
49
-
50
- // Build adjacency and in-degree
51
- for (const task of tasks) {
52
- for (const dep of task.dependencies || []) {
53
- if (taskMap.has(dep)) {
54
- adjList.get(dep).push(task.id);
55
- inDegree.set(task.id, (inDegree.get(task.id) || 0) + 1);
56
- }
57
- }
58
- }
59
-
60
- // Kahn's algorithm with wave grouping
61
- const waves = [];
62
- const remaining = new Set(tasks.map((t) => t.id));
63
-
64
- while (remaining.size > 0) {
65
- // Find all tasks with no pending dependencies (in-degree 0)
66
- const wave = [];
67
- for (const id of remaining) {
68
- if ((inDegree.get(id) || 0) === 0) {
69
- wave.push(id);
70
- }
71
- }
72
-
73
- if (wave.length === 0) {
74
- const circular = [...remaining].join(', ');
75
- throw new Error(`Circular dependency detected among tasks: ${circular}`);
76
- }
77
-
78
- waves.push({ index: waves.length, taskIds: wave });
79
-
80
- // Remove wave tasks and update in-degrees
81
- for (const id of wave) {
82
- remaining.delete(id);
83
- for (const dependent of adjList.get(id) || []) {
84
- inDegree.set(dependent, (inDegree.get(dependent) || 0) - 1);
85
- }
86
- }
87
- }
88
-
89
- return waves;
90
- }
91
-
92
- /**
93
- * Validate that tasks within a wave don't have write scope conflicts.
94
- *
95
- * @param {TaskNode[]} tasks - All tasks
96
- * @param {Wave} wave - Wave to validate
97
- * @returns {{ valid: boolean, conflicts: { taskA: string, taskB: string, path: string }[] }}
98
- */
99
- export function validateWaveScopes(tasks, wave) {
100
- const taskMap = new Map(tasks.map((t) => [t.id, t]));
101
- const conflicts = [];
102
-
103
- const waveTasks = wave.taskIds.map((id) => taskMap.get(id)).filter(Boolean);
104
-
105
- for (let i = 0; i < waveTasks.length; i++) {
106
- for (let j = i + 1; j < waveTasks.length; j++) {
107
- const scopeA = waveTasks[i].writeScope || [];
108
- const scopeB = waveTasks[j].writeScope || [];
109
-
110
- for (const pathA of scopeA) {
111
- for (const pathB of scopeB) {
112
- if (pathA === pathB || pathA.startsWith(pathB + '/') || pathB.startsWith(pathA + '/')) {
113
- conflicts.push({
114
- taskA: waveTasks[i].id,
115
- taskB: waveTasks[j].id,
116
- path: pathA,
117
- });
118
- }
119
- }
120
- }
121
- }
122
- }
123
-
124
- return { valid: conflicts.length === 0, conflicts };
125
- }
126
-
127
- /**
128
- * Get a summary of the wave analysis.
129
- *
130
- * @param {Wave[]} waves
131
- * @returns {{ totalWaves: number, totalTasks: number, maxParallel: number, sequential: boolean }}
132
- */
133
- export function getWaveSummary(waves) {
134
- const totalTasks = waves.reduce((sum, w) => sum + w.taskIds.length, 0);
135
- const maxParallel = Math.max(...waves.map((w) => w.taskIds.length), 0);
136
-
137
- return {
138
- totalWaves: waves.length,
139
- totalTasks,
140
- maxParallel,
141
- sequential: waves.every((w) => w.taskIds.length === 1),
142
- };
143
- }