chati-dev 1.4.0 → 2.0.2

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 (208) hide show
  1. package/README.md +40 -24
  2. package/framework/agents/build/dev.md +343 -0
  3. package/framework/agents/clarity/architect.md +112 -0
  4. package/framework/agents/clarity/brief.md +182 -0
  5. package/framework/agents/clarity/brownfield-wu.md +181 -0
  6. package/framework/agents/clarity/detail.md +110 -0
  7. package/framework/agents/clarity/greenfield-wu.md +153 -0
  8. package/framework/agents/clarity/ux.md +112 -0
  9. package/framework/config.yaml +3 -3
  10. package/framework/constitution.md +31 -1
  11. package/framework/context/governance.md +37 -0
  12. package/framework/context/protocols.md +34 -0
  13. package/framework/context/quality.md +27 -0
  14. package/framework/context/root.md +24 -0
  15. package/framework/data/entity-registry.yaml +1 -1
  16. package/framework/domains/agents/architect.yaml +51 -0
  17. package/framework/domains/agents/brief.yaml +47 -0
  18. package/framework/domains/agents/brownfield-wu.yaml +49 -0
  19. package/framework/domains/agents/detail.yaml +47 -0
  20. package/framework/domains/agents/dev.yaml +49 -0
  21. package/framework/domains/agents/devops.yaml +43 -0
  22. package/framework/domains/agents/greenfield-wu.yaml +47 -0
  23. package/framework/domains/agents/orchestrator.yaml +49 -0
  24. package/framework/domains/agents/phases.yaml +47 -0
  25. package/framework/domains/agents/qa-implementation.yaml +43 -0
  26. package/framework/domains/agents/qa-planning.yaml +44 -0
  27. package/framework/domains/agents/tasks.yaml +48 -0
  28. package/framework/domains/agents/ux.yaml +50 -0
  29. package/framework/domains/constitution.yaml +77 -0
  30. package/framework/domains/global.yaml +64 -0
  31. package/framework/domains/workflows/brownfield-discovery.yaml +16 -0
  32. package/framework/domains/workflows/brownfield-fullstack.yaml +26 -0
  33. package/framework/domains/workflows/brownfield-service.yaml +22 -0
  34. package/framework/domains/workflows/brownfield-ui.yaml +22 -0
  35. package/framework/domains/workflows/greenfield-fullstack.yaml +26 -0
  36. package/framework/hooks/constitution-guard.js +101 -0
  37. package/framework/hooks/mode-governance.js +92 -0
  38. package/framework/hooks/model-governance.js +76 -0
  39. package/framework/hooks/prism-engine.js +89 -0
  40. package/framework/hooks/session-digest.js +60 -0
  41. package/framework/hooks/settings.json +44 -0
  42. package/framework/i18n/en.yaml +3 -3
  43. package/framework/i18n/es.yaml +3 -3
  44. package/framework/i18n/fr.yaml +3 -3
  45. package/framework/i18n/pt.yaml +3 -3
  46. package/framework/intelligence/decision-engine.md +1 -1
  47. package/framework/migrations/v1.4-to-v2.0.yaml +167 -0
  48. package/framework/migrations/v2.0-to-v2.0.1.yaml +132 -0
  49. package/framework/orchestrator/chati.md +284 -6
  50. package/framework/tasks/architect-api-design.md +63 -0
  51. package/framework/tasks/architect-consolidate.md +47 -0
  52. package/framework/tasks/architect-db-design.md +73 -0
  53. package/framework/tasks/architect-design.md +95 -0
  54. package/framework/tasks/architect-security-review.md +62 -0
  55. package/framework/tasks/architect-stack-selection.md +53 -0
  56. package/framework/tasks/brief-consolidate.md +249 -0
  57. package/framework/tasks/brief-constraint-identify.md +277 -0
  58. package/framework/tasks/brief-extract-requirements.md +339 -0
  59. package/framework/tasks/brief-stakeholder-map.md +176 -0
  60. package/framework/tasks/brief-validate-completeness.md +121 -0
  61. package/framework/tasks/brownfield-wu-architecture-map.md +394 -0
  62. package/framework/tasks/brownfield-wu-deep-discovery.md +312 -0
  63. package/framework/tasks/brownfield-wu-dependency-scan.md +359 -0
  64. package/framework/tasks/brownfield-wu-migration-plan.md +483 -0
  65. package/framework/tasks/brownfield-wu-report.md +325 -0
  66. package/framework/tasks/brownfield-wu-risk-assess.md +424 -0
  67. package/framework/tasks/detail-acceptance-criteria.md +372 -0
  68. package/framework/tasks/detail-consolidate.md +138 -0
  69. package/framework/tasks/detail-edge-case-analysis.md +300 -0
  70. package/framework/tasks/detail-expand-prd.md +389 -0
  71. package/framework/tasks/detail-nfr-extraction.md +223 -0
  72. package/framework/tasks/dev-code-review.md +404 -0
  73. package/framework/tasks/dev-consolidate.md +543 -0
  74. package/framework/tasks/dev-debug.md +322 -0
  75. package/framework/tasks/dev-implement.md +252 -0
  76. package/framework/tasks/dev-iterate.md +411 -0
  77. package/framework/tasks/dev-pr-prepare.md +497 -0
  78. package/framework/tasks/dev-refactor.md +342 -0
  79. package/framework/tasks/dev-test-write.md +306 -0
  80. package/framework/tasks/devops-ci-setup.md +412 -0
  81. package/framework/tasks/devops-consolidate.md +712 -0
  82. package/framework/tasks/devops-deploy-config.md +598 -0
  83. package/framework/tasks/devops-monitoring-setup.md +658 -0
  84. package/framework/tasks/devops-release-prepare.md +673 -0
  85. package/framework/tasks/greenfield-wu-analyze-empty.md +169 -0
  86. package/framework/tasks/greenfield-wu-report.md +266 -0
  87. package/framework/tasks/greenfield-wu-scaffold-detection.md +203 -0
  88. package/framework/tasks/greenfield-wu-tech-stack-assess.md +255 -0
  89. package/framework/tasks/orchestrator-deviation.md +260 -0
  90. package/framework/tasks/orchestrator-escalate.md +276 -0
  91. package/framework/tasks/orchestrator-handoff.md +243 -0
  92. package/framework/tasks/orchestrator-health.md +372 -0
  93. package/framework/tasks/orchestrator-mode-switch.md +262 -0
  94. package/framework/tasks/orchestrator-resume.md +189 -0
  95. package/framework/tasks/orchestrator-route.md +169 -0
  96. package/framework/tasks/orchestrator-spawn-terminal.md +358 -0
  97. package/framework/tasks/orchestrator-status.md +260 -0
  98. package/framework/tasks/orchestrator-suggest-mode.md +372 -0
  99. package/framework/tasks/phases-breakdown.md +91 -0
  100. package/framework/tasks/phases-dependency-mapping.md +67 -0
  101. package/framework/tasks/phases-mvp-scoping.md +94 -0
  102. package/framework/tasks/qa-impl-consolidate.md +522 -0
  103. package/framework/tasks/qa-impl-performance-test.md +487 -0
  104. package/framework/tasks/qa-impl-regression-check.md +413 -0
  105. package/framework/tasks/qa-impl-sast-scan.md +402 -0
  106. package/framework/tasks/qa-impl-test-execute.md +344 -0
  107. package/framework/tasks/qa-impl-verdict.md +339 -0
  108. package/framework/tasks/qa-planning-consolidate.md +309 -0
  109. package/framework/tasks/qa-planning-coverage-plan.md +338 -0
  110. package/framework/tasks/qa-planning-gate-define.md +339 -0
  111. package/framework/tasks/qa-planning-risk-matrix.md +631 -0
  112. package/framework/tasks/qa-planning-test-strategy.md +217 -0
  113. package/framework/tasks/tasks-acceptance-write.md +75 -0
  114. package/framework/tasks/tasks-consolidate.md +57 -0
  115. package/framework/tasks/tasks-decompose.md +80 -0
  116. package/framework/tasks/tasks-estimate.md +66 -0
  117. package/framework/tasks/ux-a11y-check.md +49 -0
  118. package/framework/tasks/ux-component-map.md +55 -0
  119. package/framework/tasks/ux-consolidate.md +46 -0
  120. package/framework/tasks/ux-user-flow.md +46 -0
  121. package/framework/tasks/ux-wireframe.md +76 -0
  122. package/package.json +2 -2
  123. package/scripts/bundle-framework.js +2 -0
  124. package/scripts/changelog-generator.js +222 -0
  125. package/scripts/codebase-mapper.js +728 -0
  126. package/scripts/commit-message-generator.js +167 -0
  127. package/scripts/coverage-analyzer.js +260 -0
  128. package/scripts/dependency-analyzer.js +280 -0
  129. package/scripts/framework-analyzer.js +308 -0
  130. package/scripts/generate-constitution-domain.js +253 -0
  131. package/scripts/health-check.js +481 -0
  132. package/scripts/ide-sync.js +327 -0
  133. package/scripts/performance-analyzer.js +325 -0
  134. package/scripts/plan-tracker.js +278 -0
  135. package/scripts/populate-entity-registry.js +481 -0
  136. package/scripts/pr-review.js +317 -0
  137. package/scripts/rollback-manager.js +310 -0
  138. package/scripts/stuck-detector.js +343 -0
  139. package/scripts/test-quality-assessment.js +257 -0
  140. package/scripts/validate-agents.js +367 -0
  141. package/scripts/validate-tasks.js +465 -0
  142. package/src/autonomy/autonomous-gate.js +293 -0
  143. package/src/autonomy/index.js +51 -0
  144. package/src/autonomy/mode-manager.js +225 -0
  145. package/src/autonomy/mode-suggester.js +283 -0
  146. package/src/autonomy/progress-reporter.js +268 -0
  147. package/src/autonomy/safety-net.js +320 -0
  148. package/src/context/bracket-tracker.js +79 -0
  149. package/src/context/domain-loader.js +107 -0
  150. package/src/context/engine.js +144 -0
  151. package/src/context/formatter.js +184 -0
  152. package/src/context/index.js +4 -0
  153. package/src/context/layers/l0-constitution.js +28 -0
  154. package/src/context/layers/l1-global.js +37 -0
  155. package/src/context/layers/l2-agent.js +39 -0
  156. package/src/context/layers/l3-workflow.js +42 -0
  157. package/src/context/layers/l4-task.js +24 -0
  158. package/src/decision/analyzer.js +167 -0
  159. package/src/decision/engine.js +270 -0
  160. package/src/decision/index.js +38 -0
  161. package/src/decision/registry-healer.js +450 -0
  162. package/src/decision/registry-updater.js +330 -0
  163. package/src/gates/circuit-breaker.js +119 -0
  164. package/src/gates/g1-planning-complete.js +153 -0
  165. package/src/gates/g2-qa-planning.js +153 -0
  166. package/src/gates/g3-implementation.js +188 -0
  167. package/src/gates/g4-qa-implementation.js +207 -0
  168. package/src/gates/g5-deploy-ready.js +180 -0
  169. package/src/gates/gate-base.js +144 -0
  170. package/src/gates/index.js +46 -0
  171. package/src/installer/brownfield-upgrader.js +249 -0
  172. package/src/installer/core.js +82 -11
  173. package/src/installer/file-hasher.js +51 -0
  174. package/src/installer/manifest.js +117 -0
  175. package/src/installer/templates.js +17 -15
  176. package/src/installer/transaction.js +229 -0
  177. package/src/installer/validator.js +18 -1
  178. package/src/intelligence/registry-manager.js +2 -2
  179. package/src/memory/agent-memory.js +255 -0
  180. package/src/memory/gotchas-injector.js +72 -0
  181. package/src/memory/gotchas.js +361 -0
  182. package/src/memory/index.js +35 -0
  183. package/src/memory/search.js +233 -0
  184. package/src/memory/session-digest.js +239 -0
  185. package/src/merger/env-merger.js +112 -0
  186. package/src/merger/index.js +56 -0
  187. package/src/merger/replace-merger.js +51 -0
  188. package/src/merger/yaml-merger.js +127 -0
  189. package/src/orchestrator/agent-selector.js +285 -0
  190. package/src/orchestrator/deviation-handler.js +350 -0
  191. package/src/orchestrator/handoff-engine.js +271 -0
  192. package/src/orchestrator/index.js +67 -0
  193. package/src/orchestrator/intent-classifier.js +264 -0
  194. package/src/orchestrator/pipeline-manager.js +492 -0
  195. package/src/orchestrator/pipeline-state.js +223 -0
  196. package/src/orchestrator/session-manager.js +409 -0
  197. package/src/tasks/executor.js +195 -0
  198. package/src/tasks/handoff.js +226 -0
  199. package/src/tasks/index.js +4 -0
  200. package/src/tasks/loader.js +210 -0
  201. package/src/tasks/router.js +182 -0
  202. package/src/terminal/collector.js +216 -0
  203. package/src/terminal/index.js +30 -0
  204. package/src/terminal/isolation.js +129 -0
  205. package/src/terminal/monitor.js +277 -0
  206. package/src/terminal/spawner.js +269 -0
  207. package/src/upgrade/checker.js +1 -1
  208. package/src/wizard/i18n.js +3 -3
@@ -0,0 +1,465 @@
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 = ['clarity', '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
+ }