chati-dev 4.2.2 → 4.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 (220) hide show
  1. package/README.md +80 -53
  2. package/bin/chati.js +150 -5
  3. package/framework/agents/build/dev.md +509 -76
  4. package/framework/agents/deploy/devops.md +40 -25
  5. package/framework/agents/discover/brief.md +156 -22
  6. package/framework/agents/discover/brownfield-wu.md +24 -14
  7. package/framework/agents/discover/greenfield-wu.md +100 -14
  8. package/framework/agents/plan/architect-data-engineer.md +6 -6
  9. package/framework/agents/plan/architect-system.md +46 -12
  10. package/framework/agents/plan/architect.md +40 -20
  11. package/framework/agents/plan/detail.md +36 -24
  12. package/framework/agents/plan/phases.md +36 -26
  13. package/framework/agents/plan/tasks.md +114 -33
  14. package/framework/agents/plan/ux-brand-architect.md +240 -8
  15. package/framework/agents/plan/ux-component-engineer.md +28 -13
  16. package/framework/agents/plan/ux-researcher.md +7 -6
  17. package/framework/agents/plan/ux.md +55 -22
  18. package/framework/agents/quality/qa-implementation.md +143 -74
  19. package/framework/agents/quality/qa-planning.md +115 -42
  20. package/framework/agents/quality/qa-visual.md +439 -0
  21. package/framework/agents/shared/visualizer.md +128 -0
  22. package/framework/config.yaml +7 -6
  23. package/framework/constitution.md +127 -44
  24. package/framework/context/governance.md +12 -7
  25. package/framework/context/quality.md +6 -5
  26. package/framework/context/root.md +6 -6
  27. package/framework/data/entity-registry.yaml +377 -4
  28. package/framework/data/model-limits.json +19 -0
  29. package/framework/domains/agents/qa-visual.yaml +74 -0
  30. package/framework/domains/constitution.yaml +46 -2
  31. package/framework/domains/workflows/greenfield-fullstack.yaml +2 -2
  32. package/framework/hooks/advance-trigger.js +131 -0
  33. package/framework/hooks/brief-validator.js +83 -0
  34. package/framework/hooks/constitution-guard.js +24 -5
  35. package/framework/hooks/license-guard.js +62 -27
  36. package/framework/hooks/mode-governance.js +13 -2
  37. package/framework/hooks/model-governance.js +1 -0
  38. package/framework/hooks/post-dev.js +207 -0
  39. package/framework/hooks/prism-engine.js +274 -105
  40. package/framework/hooks/reasoning-escalator.js +371 -0
  41. package/framework/hooks/reference-trigger.js +117 -0
  42. package/framework/hooks/session-digest.js +50 -1
  43. package/framework/hooks/settings.json +32 -1
  44. package/framework/hooks/style-guard.js +25 -6
  45. package/framework/hooks/team-quality-gate.js +19 -12
  46. package/framework/hooks/undercover-guard.js +4 -2
  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 +4 -5
  52. package/framework/intelligence/decision-engine.md +1 -1
  53. package/framework/intelligence/hooks-performance.md +3 -3
  54. package/framework/migrations/v1.0-to-v1.1.yaml +1 -1
  55. package/framework/migrations/v1.4-to-v2.0.yaml +11 -11
  56. package/framework/migrations/v4.0-to-v4.1.yaml +2 -2
  57. package/framework/migrations/v4.2-to-v4.3.yaml +29 -0
  58. package/framework/orchestrator/chati-router.js +387 -0
  59. package/framework/orchestrator/chati-update.md +40 -40
  60. package/framework/orchestrator/chati.md +294 -94
  61. package/framework/scaffold/motion-premium/README.md +89 -0
  62. package/framework/scaffold/motion-premium/app/globals.css.template +400 -0
  63. package/framework/scaffold/motion-premium/app/layout.tsx.template +110 -0
  64. package/framework/scaffold/motion-premium/components/animation/BackgroundCrossfadeProvider.tsx.template +170 -0
  65. package/framework/scaffold/motion-premium/components/animation/LenisProvider.tsx.template +49 -0
  66. package/framework/scaffold/motion-premium/components/animation/PageTransitionWrapper.tsx.template +83 -0
  67. package/framework/scaffold/motion-premium/components/animation/Preloader.tsx.template +171 -0
  68. package/framework/scaffold/motion-premium/components/ui/Container.tsx.template +69 -0
  69. package/framework/scaffold/motion-premium/components/ui/PageSection.tsx.template +74 -0
  70. package/framework/scaffold/motion-premium/lib/animations/gsap.ts.template +112 -0
  71. package/framework/scaffold/motion-premium/lib/animations/refreshCoordinator.ts.template +75 -0
  72. package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +119 -0
  73. package/framework/scaffold/motion-premium/lib/animations/useGsapContext.ts.template +92 -0
  74. package/framework/scaffold/motion-premium/lib/animations/useScrollSnapStepper.ts.template +265 -0
  75. package/framework/scaffold/motion-premium/lib/animations/useSmoothScroll.ts.template +67 -0
  76. package/framework/scaffold/motion-premium/lib/brand.ts.template +43 -0
  77. package/framework/scaffold/motion-premium/scaffold.yaml +174 -0
  78. package/framework/scaffold/motion-premium-3d/README.md +80 -0
  79. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoCanvas.tsx.template +81 -0
  80. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoClient.tsx.template +75 -0
  81. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/page.tsx.template +26 -0
  82. package/framework/scaffold/motion-premium-3d/components/3d/CameraRig.tsx.template +100 -0
  83. package/framework/scaffold/motion-premium-3d/components/3d/CanvasProvider.tsx.template +85 -0
  84. package/framework/scaffold/motion-premium-3d/components/3d/InvalidateOnScroll.tsx.template +51 -0
  85. package/framework/scaffold/motion-premium-3d/components/3d/MeshCrossfade.tsx.template +79 -0
  86. package/framework/scaffold/motion-premium-3d/components/3d/ScrollCrossfade.tsx.template +88 -0
  87. package/framework/scaffold/motion-premium-3d/components/3d/ScrollScene.tsx.template +121 -0
  88. package/framework/scaffold/motion-premium-3d/components/webgl/SceneFallback.tsx.template +65 -0
  89. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +68 -0
  90. package/framework/scaffold/motion-premium-3d/lib/webgl/detect.ts.template +69 -0
  91. package/framework/scaffold/motion-premium-3d/scaffold.yaml +133 -0
  92. package/framework/schemas/session.schema.json +109 -21
  93. package/framework/scripts/reference-capture.js +430 -0
  94. package/framework/scripts/visual-qa.js +674 -0
  95. package/framework/tasks/orchestrator-handoff.md +1 -1
  96. package/framework/tasks/orchestrator-resume.md +1 -1
  97. package/framework/tasks/orchestrator-route.md +1 -1
  98. package/framework/tasks/orchestrator-status.md +3 -3
  99. package/framework/tasks/qa-planning-gate-define.md +1 -1
  100. package/framework/templates/brandbook-html-tmpl.md +1 -1
  101. package/framework/templates/brandbook-tmpl.yaml +1 -1
  102. package/framework/templates/component-spec-tmpl.yaml +1 -1
  103. package/framework/templates/design-token-tmpl.yaml +1 -1
  104. package/framework/templates/icon-system-tmpl.yaml +1 -1
  105. package/framework/templates/team-planning-tasks.yaml +6 -5
  106. package/framework/workflows/brownfield-discovery.yaml +2 -2
  107. package/framework/workflows/brownfield-fullstack.yaml +15 -11
  108. package/framework/workflows/brownfield-service.yaml +14 -10
  109. package/framework/workflows/brownfield-ui.yaml +15 -11
  110. package/framework/workflows/greenfield-fullstack.yaml +16 -13
  111. package/framework/workflows/quick-flow.yaml +3 -3
  112. package/framework/workflows/standard-flow.yaml +12 -9
  113. package/package.json +10 -5
  114. package/src/autonomy/autonomous-gate.js +1 -0
  115. package/src/autonomy/build-state.js +1 -2
  116. package/src/autonomy/progress-reporter.js +1 -1
  117. package/src/config/agent-customizer.js +11 -3
  118. package/src/config/claude-settings-generator.js +27 -7
  119. package/src/config/context-file-generator.js +41 -21
  120. package/src/config/framework-adapter.js +1 -0
  121. package/src/config/gemini-hooks-generator.js +19 -7
  122. package/src/config/mcp-configs.js +1 -0
  123. package/src/context/layers/l1-global.js +2 -1
  124. package/src/dashboard/data-reader.js +4 -3
  125. package/src/dashboard/layout.js +2 -1
  126. package/src/decision/analyzer.js +6 -30
  127. package/src/decision/engine.js +4 -28
  128. package/src/decision/registry-healer.js +3 -2
  129. package/src/decision/registry-updater.js +23 -14
  130. package/src/extensions/loader.js +2 -8
  131. package/src/gates/g1-planning-complete.js +2 -1
  132. package/src/gates/g2-qa-planning.js +2 -1
  133. package/src/gates/g3-implementation.js +2 -1
  134. package/src/gates/g4-qa-implementation.js +3 -2
  135. package/src/gates/g5-deploy-ready.js +2 -1
  136. package/src/health/engine.js +4 -3
  137. package/src/installer/core.js +422 -81
  138. package/src/installer/preflight.js +131 -0
  139. package/src/installer/provider-overlay.js +3 -3
  140. package/src/installer/scaffold-applier.js +358 -0
  141. package/src/installer/templates.js +46 -29
  142. package/src/installer/validator.js +17 -12
  143. package/src/intelligence/registry-manager.js +22 -29
  144. package/src/intelligence/timeline.js +11 -6
  145. package/src/license/commands.js +1 -1
  146. package/src/license/wait.js +102 -0
  147. package/src/memory/agent-memory.js +81 -0
  148. package/src/memory/dream.js +32 -1
  149. package/src/merger/replace-merger.js +28 -15
  150. package/src/orchestrator/agent-selector.js +2 -1
  151. package/src/orchestrator/cli.js +1869 -71
  152. package/src/orchestrator/doctor.js +270 -0
  153. package/src/orchestrator/handoff-engine.js +4 -3
  154. package/src/orchestrator/index.js +2 -0
  155. package/src/orchestrator/pipeline-manager.js +306 -15
  156. package/src/orchestrator/session-manager.js +331 -6
  157. package/src/tasks/handoff.js +3 -2
  158. package/src/telemetry/config.js +4 -3
  159. package/src/telemetry/schema.js +1 -0
  160. package/src/terminal/collector.js +3 -2
  161. package/src/terminal/index.js +1 -2
  162. package/src/terminal/isolation.js +52 -18
  163. package/src/terminal/prompt-builder.js +42 -25
  164. package/src/terminal/run-parallel.js +1 -1
  165. package/src/terminal/run-team.js +3 -3
  166. package/src/terminal/team-task-list.js +43 -4
  167. package/src/upgrade/backup.js +3 -2
  168. package/src/upgrade/checker.js +3 -2
  169. package/src/upgrade/migrator.js +65 -7
  170. package/src/upgrade/tracked-files-detector.js +86 -0
  171. package/src/upgrade/user-messages.js +94 -0
  172. package/src/utils/config-parser.js +2 -1
  173. package/src/utils/feature-flags.js +2 -1
  174. package/src/utils/flatten-entities.js +69 -0
  175. package/src/utils/framework-dir.js +16 -0
  176. package/src/utils/model-id.js +85 -0
  177. package/src/utils/provider-limits.js +84 -23
  178. package/src/utils/schema-validator.js +1 -1
  179. package/src/wizard/i18n.js +5 -4
  180. package/src/wizard/index.js +14 -0
  181. package/assets/logo - c/303/263pia.png +0 -0
  182. package/assets/logo.svg +0 -42
  183. package/assets/logo2.png +0 -0
  184. package/assets/social-preview.png +0 -0
  185. package/scripts/bundle-framework.js +0 -69
  186. package/scripts/changelog-generator.js +0 -222
  187. package/scripts/codebase-mapper.js +0 -728
  188. package/scripts/commit-message-generator.js +0 -167
  189. package/scripts/coverage-analyzer.js +0 -260
  190. package/scripts/dependency-analyzer.js +0 -280
  191. package/scripts/doctor/checks/agents.js +0 -77
  192. package/scripts/doctor/checks/constitution.js +0 -41
  193. package/scripts/doctor/checks/domain-alignment.js +0 -58
  194. package/scripts/doctor/checks/prism-layers.js +0 -84
  195. package/scripts/doctor/checks/registry.js +0 -55
  196. package/scripts/doctor/checks/schemas.js +0 -61
  197. package/scripts/doctor/fixes/reference-fix.js +0 -100
  198. package/scripts/doctor/fixes/registry-fix.js +0 -56
  199. package/scripts/doctor/index.js +0 -212
  200. package/scripts/framework-analyzer.js +0 -308
  201. package/scripts/generate-constitution-domain.js +0 -253
  202. package/scripts/generate-signing-key.js +0 -33
  203. package/scripts/health-check.js +0 -481
  204. package/scripts/ide-sync.js +0 -326
  205. package/scripts/performance-analyzer.js +0 -325
  206. package/scripts/plan-tracker.js +0 -278
  207. package/scripts/populate-entity-registry.js +0 -481
  208. package/scripts/pr-review.js +0 -317
  209. package/scripts/rollback-manager.js +0 -310
  210. package/scripts/semantic-lint.js +0 -328
  211. package/scripts/sign-manifest.js +0 -53
  212. package/scripts/stuck-detector.js +0 -343
  213. package/scripts/test-quality-assessment.js +0 -257
  214. package/scripts/validate-agents.js +0 -368
  215. package/scripts/validate-package.js +0 -505
  216. package/scripts/validate-tasks.js +0 -465
  217. package/src/autonomy/worktree-manager.js +0 -250
  218. package/src/intelligence/decision-engine.js +0 -256
  219. package/src/intelligence/document-sharder.js +0 -221
  220. package/src/intelligence/elicitation.js +0 -265
@@ -1,465 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Validate Tasks — Validates task definition files (.md with YAML frontmatter).
5
- *
6
- * Exports:
7
- * validateAllTasks(tasksDir) → TaskValidationReport
8
- * validateTask(tasksDir, taskId) → single task validation
9
- * validateHandoffChain(tasksDir) → checks the entire handoff chain is connected
10
- * getTaskStats(tasksDir) → { total, byAgent, byPhase, withHandoffs, withoutHandoffs }
11
- */
12
-
13
- import { readFileSync, existsSync, readdirSync } from 'fs';
14
- import { join, basename } from 'path';
15
- import yaml from 'js-yaml';
16
-
17
- import { ALL_AGENT_NAMES } from './validate-agents.js';
18
-
19
- /**
20
- * Required fields in task frontmatter.
21
- */
22
- const REQUIRED_FIELDS = ['id', 'agent'];
23
-
24
- /**
25
- * Valid phase values.
26
- */
27
- const VALID_PHASES = ['discover', 'plan', 'planning', 'build', 'deploy', 'validate', 'quality'];
28
-
29
- // ---------------------------------------------------------------------------
30
- // Internal Helpers
31
- // ---------------------------------------------------------------------------
32
-
33
- /**
34
- * Parse YAML frontmatter from a markdown file.
35
- * @param {string} content - Full file content.
36
- * @returns {{ frontmatter: object|null, body: string, error: string|null }}
37
- */
38
- function parseFrontmatter(content) {
39
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
40
- if (!match) {
41
- return { frontmatter: null, body: content, error: 'No YAML frontmatter found' };
42
- }
43
-
44
- try {
45
- const frontmatter = yaml.load(match[1]);
46
- const body = content.slice(match[0].length).trim();
47
- return { frontmatter, body, error: null };
48
- } catch (err) {
49
- return { frontmatter: null, body: content, error: `Invalid YAML: ${err.message}` };
50
- }
51
- }
52
-
53
- /**
54
- * Validate a single task definition.
55
- * @param {string} tasksDir - Directory containing task .md files.
56
- * @param {string} filename - Task filename (e.g., 'brief-extract-requirements.md').
57
- * @returns {object} Validation result.
58
- */
59
- export function validateTask(tasksDir, filename) {
60
- const fullPath = join(tasksDir, filename);
61
- const taskName = basename(filename, '.md');
62
-
63
- const result = {
64
- name: taskName,
65
- file: filename,
66
- valid: true,
67
- errors: [],
68
- warnings: [],
69
- frontmatter: null,
70
- };
71
-
72
- // Check file exists
73
- if (!existsSync(fullPath)) {
74
- result.valid = false;
75
- result.errors.push(`File not found: ${filename}`);
76
- return result;
77
- }
78
-
79
- let content;
80
- try {
81
- content = readFileSync(fullPath, 'utf8');
82
- } catch (err) {
83
- result.valid = false;
84
- result.errors.push(`Cannot read file: ${err.message}`);
85
- return result;
86
- }
87
-
88
- // Parse frontmatter
89
- const { frontmatter, error } = parseFrontmatter(content);
90
- if (error) {
91
- result.valid = false;
92
- result.errors.push(error);
93
- return result;
94
- }
95
-
96
- result.frontmatter = frontmatter;
97
-
98
- // Check required fields
99
- for (const field of REQUIRED_FIELDS) {
100
- if (!frontmatter[field]) {
101
- result.valid = false;
102
- result.errors.push(`Missing required field: '${field}'`);
103
- }
104
- }
105
-
106
- // Check id matches filename
107
- if (frontmatter.id && frontmatter.id !== taskName) {
108
- result.warnings.push(`Task id '${frontmatter.id}' does not match filename '${taskName}'`);
109
- }
110
-
111
- // Check agent is a known agent
112
- if (frontmatter.agent) {
113
- const knownAgents = [...ALL_AGENT_NAMES, 'orchestrator'];
114
- if (!knownAgents.includes(frontmatter.agent)) {
115
- result.warnings.push(`Unknown agent: '${frontmatter.agent}'`);
116
- }
117
- }
118
-
119
- // Check phase is valid
120
- if (frontmatter.phase && !VALID_PHASES.includes(frontmatter.phase)) {
121
- result.warnings.push(`Unknown phase: '${frontmatter.phase}'`);
122
- }
123
-
124
- // Check criteria is not empty (if present)
125
- if (frontmatter.criteria !== undefined) {
126
- if (!Array.isArray(frontmatter.criteria) || frontmatter.criteria.length === 0) {
127
- result.warnings.push('criteria field is empty or not an array');
128
- }
129
- } else {
130
- result.warnings.push('No criteria defined');
131
- }
132
-
133
- // Check outputs is not empty (if present)
134
- if (frontmatter.outputs !== undefined) {
135
- if (!Array.isArray(frontmatter.outputs) || frontmatter.outputs.length === 0) {
136
- result.warnings.push('outputs field is empty or not an array');
137
- }
138
- }
139
-
140
- // Check handoff_to is a valid reference
141
- if (frontmatter.handoff_to) {
142
- // handoff_to could be a task id or agent name — validated in chain check
143
- }
144
-
145
- return result;
146
- }
147
-
148
- /**
149
- * Validate all task files in a directory.
150
- * @param {string} tasksDir - Directory containing task .md files.
151
- * @returns {object} TaskValidationReport.
152
- */
153
- export function validateAllTasks(tasksDir) {
154
- const report = {
155
- valid: true,
156
- tasks: [],
157
- errors: [],
158
- warnings: [],
159
- totalFiles: 0,
160
- totalValid: 0,
161
- duplicateIds: [],
162
- };
163
-
164
- if (!existsSync(tasksDir)) {
165
- report.valid = false;
166
- report.errors.push(`Tasks directory not found: ${tasksDir}`);
167
- return report;
168
- }
169
-
170
- let files;
171
- try {
172
- files = readdirSync(tasksDir).filter(f => f.endsWith('.md'));
173
- } catch (err) {
174
- report.valid = false;
175
- report.errors.push(`Cannot read directory: ${err.message}`);
176
- return report;
177
- }
178
-
179
- report.totalFiles = files.length;
180
-
181
- if (files.length === 0) {
182
- report.warnings.push('No task .md files found');
183
- return report;
184
- }
185
-
186
- // Track IDs for duplicate detection
187
- const seenIds = new Map();
188
-
189
- for (const file of files) {
190
- const result = validateTask(tasksDir, file);
191
- report.tasks.push(result);
192
-
193
- if (result.valid) {
194
- report.totalValid++;
195
- }
196
-
197
- // Track duplicate IDs
198
- const taskId = result.frontmatter?.id;
199
- if (taskId) {
200
- if (seenIds.has(taskId)) {
201
- report.duplicateIds.push({ id: taskId, files: [seenIds.get(taskId), file] });
202
- } else {
203
- seenIds.set(taskId, file);
204
- }
205
- }
206
- }
207
-
208
- // Report duplicate IDs
209
- if (report.duplicateIds.length > 0) {
210
- report.valid = false;
211
- for (const dup of report.duplicateIds) {
212
- report.errors.push(`Duplicate task id '${dup.id}' in: ${dup.files.join(', ')}`);
213
- }
214
- }
215
-
216
- return report;
217
- }
218
-
219
- /**
220
- * Validate the handoff chain across all tasks.
221
- * @param {string} tasksDir - Directory containing task .md files.
222
- * @returns {object} Handoff chain validation result.
223
- */
224
- export function validateHandoffChain(tasksDir) {
225
- const result = {
226
- valid: true,
227
- chains: [],
228
- orphans: [],
229
- brokenLinks: [],
230
- errors: [],
231
- };
232
-
233
- if (!existsSync(tasksDir)) {
234
- result.valid = false;
235
- result.errors.push(`Tasks directory not found: ${tasksDir}`);
236
- return result;
237
- }
238
-
239
- let files;
240
- try {
241
- files = readdirSync(tasksDir).filter(f => f.endsWith('.md'));
242
- } catch (err) {
243
- result.valid = false;
244
- result.errors.push(`Cannot read directory: ${err.message}`);
245
- return result;
246
- }
247
-
248
- // Load all tasks
249
- const tasks = new Map();
250
- const tasksByAgent = new Map();
251
-
252
- for (const file of files) {
253
- const fullPath = join(tasksDir, file);
254
- try {
255
- const content = readFileSync(fullPath, 'utf8');
256
- const { frontmatter } = parseFrontmatter(content);
257
- if (frontmatter?.id) {
258
- tasks.set(frontmatter.id, frontmatter);
259
- const agent = frontmatter.agent;
260
- if (agent) {
261
- if (!tasksByAgent.has(agent)) tasksByAgent.set(agent, []);
262
- tasksByAgent.get(agent).push(frontmatter.id);
263
- }
264
- }
265
- } catch {
266
- // Skip unparseable files
267
- }
268
- }
269
-
270
- // Check handoff_to references
271
- const targetedBy = new Set();
272
-
273
- for (const [taskId, task] of tasks) {
274
- if (task.handoff_to) {
275
- // handoff_to can reference a task id or an agent-prefixed task
276
- const target = task.handoff_to;
277
- // Check if it's a known task ID
278
- const isKnownTask = tasks.has(target);
279
- // Check if it's a known agent (handoff to any task of that agent)
280
- const isKnownAgent = [...ALL_AGENT_NAMES, 'orchestrator'].includes(target);
281
-
282
- if (isKnownTask) {
283
- targetedBy.add(target);
284
- } else if (isKnownAgent) {
285
- // Valid — handoff to an agent
286
- targetedBy.add(target);
287
- } else {
288
- result.brokenLinks.push({ from: taskId, to: target });
289
- }
290
- }
291
- }
292
-
293
- // Find orphaned tasks (not targeted by any handoff, and not triggered by orchestrator)
294
- for (const [taskId, task] of tasks) {
295
- if (!targetedBy.has(taskId) && task.trigger !== 'orchestrator') {
296
- result.orphans.push(taskId);
297
- }
298
- }
299
-
300
- // Check agent coverage — every agent should have at least 1 task
301
- const knownAgents = [...ALL_AGENT_NAMES, 'orchestrator'];
302
- const uncoveredAgents = knownAgents.filter(agent => !tasksByAgent.has(agent));
303
-
304
- if (uncoveredAgents.length > 0) {
305
- result.errors.push(`Agents without tasks: ${uncoveredAgents.join(', ')}`);
306
- }
307
-
308
- if (result.brokenLinks.length > 0) {
309
- result.valid = false;
310
- for (const link of result.brokenLinks) {
311
- result.errors.push(`Broken handoff: '${link.from}' -> '${link.to}'`);
312
- }
313
- }
314
-
315
- return result;
316
- }
317
-
318
- /**
319
- * Get statistics about task definitions.
320
- * @param {string} tasksDir - Directory containing task .md files.
321
- * @returns {object} Task statistics.
322
- */
323
- export function getTaskStats(tasksDir) {
324
- const stats = {
325
- total: 0,
326
- byAgent: {},
327
- byPhase: {},
328
- withHandoffs: 0,
329
- withoutHandoffs: 0,
330
- withCriteria: 0,
331
- withoutCriteria: 0,
332
- };
333
-
334
- if (!existsSync(tasksDir)) return stats;
335
-
336
- let files;
337
- try {
338
- files = readdirSync(tasksDir).filter(f => f.endsWith('.md'));
339
- } catch {
340
- return stats;
341
- }
342
-
343
- for (const file of files) {
344
- const fullPath = join(tasksDir, file);
345
- try {
346
- const content = readFileSync(fullPath, 'utf8');
347
- const { frontmatter } = parseFrontmatter(content);
348
- if (!frontmatter?.id) continue;
349
-
350
- stats.total++;
351
-
352
- // By agent
353
- if (frontmatter.agent) {
354
- stats.byAgent[frontmatter.agent] = (stats.byAgent[frontmatter.agent] || 0) + 1;
355
- }
356
-
357
- // By phase
358
- if (frontmatter.phase) {
359
- stats.byPhase[frontmatter.phase] = (stats.byPhase[frontmatter.phase] || 0) + 1;
360
- }
361
-
362
- // Handoffs
363
- if (frontmatter.handoff_to) {
364
- stats.withHandoffs++;
365
- } else {
366
- stats.withoutHandoffs++;
367
- }
368
-
369
- // Criteria
370
- if (Array.isArray(frontmatter.criteria) && frontmatter.criteria.length > 0) {
371
- stats.withCriteria++;
372
- } else {
373
- stats.withoutCriteria++;
374
- }
375
- } catch {
376
- // Skip
377
- }
378
- }
379
-
380
- return stats;
381
- }
382
-
383
- /**
384
- * Format task validation report as a human-readable string.
385
- * @param {object} report - Report from validateAllTasks.
386
- * @returns {string}
387
- */
388
- export function formatTaskReport(report) {
389
- const lines = [
390
- '=== Task Validation Report ===',
391
- `Status: ${report.valid ? '[PASS]' : '[FAIL]'}`,
392
- `Tasks: ${report.totalValid}/${report.totalFiles} valid`,
393
- '',
394
- ];
395
-
396
- if (report.duplicateIds.length > 0) {
397
- lines.push('Duplicate IDs:');
398
- for (const dup of report.duplicateIds) {
399
- lines.push(` - '${dup.id}' in ${dup.files.join(', ')}`);
400
- }
401
- lines.push('');
402
- }
403
-
404
- for (const task of report.tasks) {
405
- if (task.errors.length > 0 || task.warnings.length > 0) {
406
- const symbol = task.valid ? '[WARN]' : '[FAIL]';
407
- lines.push(` ${symbol} ${task.name}`);
408
- for (const err of task.errors) lines.push(` ERROR: ${err}`);
409
- for (const warn of task.warnings) lines.push(` WARN: ${warn}`);
410
- }
411
- }
412
-
413
- if (report.errors.length > 0) {
414
- lines.push('');
415
- lines.push('Errors:');
416
- for (const err of report.errors) lines.push(` - ${err}`);
417
- }
418
-
419
- lines.push('');
420
- lines.push('=== End Report ===');
421
- return lines.join('\n');
422
- }
423
-
424
- // ---------------------------------------------------------------------------
425
- // CLI entrypoint
426
- // ---------------------------------------------------------------------------
427
-
428
- const isMainModule = process.argv[1] && (
429
- process.argv[1].endsWith('validate-tasks.js') ||
430
- process.argv[1].endsWith('validate-tasks')
431
- );
432
-
433
- if (isMainModule) {
434
- const tasksDir = process.argv[2] || join(process.cwd(), 'chati.dev', 'tasks');
435
-
436
- console.log(`Validating tasks in: ${tasksDir}`);
437
-
438
- const report = validateAllTasks(tasksDir);
439
- console.log(formatTaskReport(report));
440
-
441
- console.log('\n--- Task Stats ---');
442
- const stats = getTaskStats(tasksDir);
443
- console.log(`Total: ${stats.total}`);
444
- console.log(`By agent:`, stats.byAgent);
445
- console.log(`By phase:`, stats.byPhase);
446
- console.log(`With handoffs: ${stats.withHandoffs}, Without: ${stats.withoutHandoffs}`);
447
- console.log(`With criteria: ${stats.withCriteria}, Without: ${stats.withoutCriteria}`);
448
-
449
- console.log('\n--- Handoff Chain ---');
450
- const chain = validateHandoffChain(tasksDir);
451
- if (chain.brokenLinks.length > 0) {
452
- console.log('Broken links:');
453
- for (const link of chain.brokenLinks) console.log(` ${link.from} -> ${link.to}`);
454
- }
455
- if (chain.orphans.length > 0) {
456
- console.log(`Orphaned tasks: ${chain.orphans.join(', ')}`);
457
- }
458
- if (chain.errors.length > 0) {
459
- for (const err of chain.errors) console.log(` ERROR: ${err}`);
460
- }
461
-
462
- if (!report.valid) {
463
- process.exit(1);
464
- }
465
- }
@@ -1,250 +0,0 @@
1
- /**
2
- * @fileoverview Git worktree isolation for build loop retries.
3
- *
4
- * Each retry attempt can optionally run in an isolated git worktree,
5
- * preventing failed attempts from corrupting the main working directory.
6
- *
7
- * Constitution Article XVII — Execution Mode Governance.
8
- */
9
-
10
- /**
11
- * @deprecated Not currently imported by any production module.
12
- * Retained for potential future integration. Review for removal or integration by v5.0.
13
- */
14
-
15
- import { execSync } from 'child_process';
16
- import { existsSync, mkdirSync, readdirSync, statSync, rmSync } from 'fs';
17
- import { join, resolve } from 'path';
18
-
19
- // ---------------------------------------------------------------------------
20
- // Constants
21
- // ---------------------------------------------------------------------------
22
-
23
- /** Directory for worktrees inside .chati/ */
24
- const WORKTREE_DIR = '.chati/worktrees';
25
-
26
- /** Maximum age (ms) before a worktree is considered stale (24h). */
27
- const MAX_WORKTREE_AGE_MS = 24 * 60 * 60 * 1000;
28
-
29
- // ---------------------------------------------------------------------------
30
- // Helpers
31
- // ---------------------------------------------------------------------------
32
-
33
- /**
34
- * Execute a git command in a given directory and return trimmed stdout.
35
- * Sanitizes `cmd` to reject shell metacharacters (prevents injection).
36
- *
37
- * @param {string} cmd - Git command (without the `git` prefix)
38
- * @param {string} cwd - Working directory
39
- * @returns {string} Trimmed stdout
40
- * @throws {Error} If cmd contains shell metacharacters
41
- */
42
- function git(cmd, cwd) {
43
- if (/[`$;|&><(){}]/.test(cmd)) {
44
- throw new Error(`Unsafe characters in git command: ${cmd}`);
45
- }
46
- return execSync(`git ${cmd}`, {
47
- cwd,
48
- encoding: 'utf-8',
49
- stdio: ['pipe', 'pipe', 'pipe'],
50
- }).trim();
51
- }
52
-
53
- // ---------------------------------------------------------------------------
54
- // Public API
55
- // ---------------------------------------------------------------------------
56
-
57
- /**
58
- * Check if a directory is inside a git repository.
59
- *
60
- * @param {string} dir - Directory path
61
- * @returns {boolean}
62
- */
63
- export function isGitRepo(dir) {
64
- try {
65
- git('rev-parse --is-inside-work-tree', dir);
66
- return true;
67
- } catch { /* expected: content may be malformed */
68
- return false;
69
- }
70
- }
71
-
72
- /**
73
- * Create an isolated git worktree for a retry attempt.
74
- *
75
- * The worktree is placed at `.chati/worktrees/<taskId>-attempt-<N>`.
76
- * A new branch is created from the current HEAD.
77
- *
78
- * @param {string} projectDir - Root project directory
79
- * @param {string} taskId - Task identifier
80
- * @param {number} attempt - Attempt number
81
- * @returns {{ path: string, branch: string, cleanup: () => void }}
82
- * @throws {Error} If not a git repo or worktree creation fails
83
- */
84
- export function createWorktree(projectDir, taskId, attempt) {
85
- const absProject = resolve(projectDir);
86
-
87
- if (!isGitRepo(absProject)) {
88
- throw new Error(`Not a git repository: ${absProject}`);
89
- }
90
-
91
- const safeName = `${taskId}-attempt-${attempt}`.replace(/[^a-zA-Z0-9._-]/g, '_');
92
- const worktreeBase = join(absProject, WORKTREE_DIR);
93
- const worktreePath = join(worktreeBase, safeName);
94
- const branch = `chati/${safeName}`;
95
-
96
- // Ensure worktree base directory exists
97
- mkdirSync(worktreeBase, { recursive: true });
98
-
99
- // Clean up if a worktree already exists at this path
100
- if (existsSync(worktreePath)) {
101
- try {
102
- git(`worktree remove "${worktreePath}" --force`, absProject);
103
- } catch { /* expected: operation may fail gracefully */
104
- rmSync(worktreePath, { recursive: true, force: true });
105
- }
106
- }
107
-
108
- // Delete branch if it exists from a previous run
109
- try {
110
- git(`branch -D "${branch}"`, absProject);
111
- } catch { /* expected: operation may fail gracefully */
112
- // Branch doesn't exist — fine
113
- }
114
-
115
- // Create worktree with new branch from HEAD
116
- git(`worktree add -b "${branch}" "${worktreePath}"`, absProject);
117
-
118
- const cleanup = () => {
119
- try {
120
- git(`worktree remove "${worktreePath}" --force`, absProject);
121
- } catch { /* expected: operation may fail gracefully */
122
- // Worktree already removed — ignore
123
- }
124
- try {
125
- git(`branch -D "${branch}"`, absProject);
126
- } catch { /* expected: operation may fail gracefully */
127
- // Branch already deleted — ignore
128
- }
129
- };
130
-
131
- return { path: worktreePath, branch, cleanup };
132
- }
133
-
134
- /**
135
- * Merge changes from a worktree branch back to the main branch.
136
- *
137
- * @param {string} projectDir - Root project directory
138
- * @param {string} worktreeBranch - Worktree branch name
139
- * @param {string} [mainBranch] - Target branch (auto-detected if omitted)
140
- * @returns {{ success: boolean, conflicts: string[] }}
141
- */
142
- export function mergeWorktree(projectDir, worktreeBranch, mainBranch) {
143
- const absProject = resolve(projectDir);
144
-
145
- // Auto-detect main branch if not specified
146
- if (!mainBranch) {
147
- try {
148
- mainBranch = git('rev-parse --abbrev-ref HEAD', absProject);
149
- } catch { /* expected: content may be malformed */
150
- mainBranch = 'main';
151
- }
152
- }
153
-
154
- // Ensure we're on the main branch
155
- try {
156
- git(`checkout "${mainBranch}"`, absProject);
157
- } catch { /* expected: operation may fail gracefully */
158
- return { success: false, conflicts: [`Failed to checkout ${mainBranch}`] };
159
- }
160
-
161
- // Attempt merge
162
- try {
163
- git(`merge "${worktreeBranch}" --no-edit`, absProject);
164
- return { success: true, conflicts: [] };
165
- } catch { /* expected: operation may fail gracefully */
166
- // Parse conflicts from error output
167
- const conflicts = [];
168
- try {
169
- const status = git('diff --name-only --diff-filter=U', absProject);
170
- if (status) {
171
- conflicts.push(...status.split('\n').filter(Boolean));
172
- }
173
- } catch { /* expected: operation may fail gracefully */
174
- conflicts.push('Unknown conflict — check git status');
175
- }
176
-
177
- // Abort the failed merge
178
- try {
179
- git('merge --abort', absProject);
180
- } catch { /* expected: operation may fail gracefully */
181
- // Already aborted or not in merging state
182
- }
183
-
184
- return { success: false, conflicts };
185
- }
186
- }
187
-
188
- /**
189
- * Clean up stale worktrees that exceed the maximum age.
190
- *
191
- * @param {string} projectDir - Root project directory
192
- * @param {{ maxAge?: number }} [options={}]
193
- * @returns {{ cleaned: string[], errors: string[] }}
194
- */
195
- export function cleanupWorktrees(projectDir, options = {}) {
196
- const absProject = resolve(projectDir);
197
- const maxAge = options.maxAge || MAX_WORKTREE_AGE_MS;
198
- const worktreeBase = join(absProject, WORKTREE_DIR);
199
- const cleaned = [];
200
- const errors = [];
201
-
202
- if (!existsSync(worktreeBase)) {
203
- return { cleaned, errors };
204
- }
205
-
206
- const now = Date.now();
207
- let entries;
208
- try {
209
- entries = readdirSync(worktreeBase);
210
- } catch { /* expected: directory may not exist */
211
- return { cleaned, errors: ['Failed to read worktree directory'] };
212
- }
213
-
214
- for (const entry of entries) {
215
- const entryPath = join(worktreeBase, entry);
216
-
217
- try {
218
- const stat = statSync(entryPath);
219
- if (!stat.isDirectory()) continue;
220
-
221
- const age = now - stat.mtimeMs;
222
- if (age > maxAge) {
223
- try {
224
- git(`worktree remove "${entryPath}" --force`, absProject);
225
- } catch { /* expected: operation may fail gracefully */
226
- rmSync(entryPath, { recursive: true, force: true });
227
- }
228
-
229
- // Also remove the branch
230
- const branch = `chati/${entry}`;
231
- try {
232
- git(`branch -D "${branch}"`, absProject);
233
- } catch { /* expected: operation may fail gracefully */
234
- // Branch already deleted
235
- }
236
-
237
- cleaned.push(entry);
238
- }
239
- } catch (err) {
240
- errors.push(`${entry}: ${err.message}`);
241
- }
242
- }
243
-
244
- return { cleaned, errors };
245
- }
246
-
247
- /**
248
- * Exported constants for testing.
249
- */
250
- export { WORKTREE_DIR, MAX_WORKTREE_AGE_MS };