chati-dev 4.3.1 → 4.4.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.
- package/README.md +18 -13
- package/bin/chati.js +12 -4
- package/framework/agents/build/dev.md +5 -1
- package/framework/agents/discover/greenfield-wu.md +16 -1
- package/framework/agents/plan/tasks.md +31 -0
- package/framework/agents/plan/ux-brand-architect.md +21 -2
- package/framework/agents/plan/ux-component-engineer.md +10 -0
- package/framework/agents/quality/qa-implementation.md +1 -1
- package/framework/agents/quality/qa-planning.md +1 -1
- package/framework/agents/quality/qa-visual.md +24 -21
- package/framework/config.yaml +5 -4
- package/framework/constitution.md +58 -11
- package/framework/context/governance.md +12 -10
- package/framework/context/root.md +2 -2
- package/framework/data/entity-registry.yaml +12 -4
- package/framework/data/qa-rubrics.yaml +335 -0
- package/framework/domains/constitution.yaml +3 -3
- package/framework/executors/json-validate.js +80 -0
- package/framework/executors/npm-script.js +72 -0
- package/framework/executors/path-check.js +67 -0
- package/framework/executors/registry.yaml +36 -0
- package/framework/hooks/advance-trigger.js +47 -3
- package/framework/hooks/constitution-guard.js +9 -4
- package/framework/hooks/git-push-authority.js +113 -0
- package/framework/hooks/license-guard.js +59 -9
- package/framework/hooks/model-governance.js +40 -17
- package/framework/hooks/prism-engine.js +78 -11
- package/framework/hooks/session-digest.js +12 -3
- package/framework/hooks/settings.json +7 -3
- package/framework/hooks/style-guard.js +52 -3
- package/framework/hooks/team-quality-gate.js +43 -5
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/confidence.yaml +85 -6
- package/framework/intelligence/context-engine.md +9 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/orchestrator/chati-router.js +1 -1
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +19 -1
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/saas-dashboard/README.md +58 -0
- package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
- package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
- package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
- package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
- package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
- package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
- package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
- package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
- package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
- package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
- package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
- package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
- package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
- package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
- package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
- package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
- package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
- package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
- package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
- package/framework/schemas/session.schema.json +5 -0
- package/framework/schemas/task.schema.json +9 -0
- package/framework/scripts/visual-qa.js +87 -1
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +3 -3
- package/src/config/context-file-generator.js +0 -6
- package/src/dashboard/renderer.js +0 -36
- package/src/executors/runner.js +204 -0
- package/src/installer/core.js +2 -1
- package/src/installer/templates.js +5 -6
- package/src/intelligence/registry-manager.js +1 -1
- package/src/license/client.js +9 -3
- package/src/license/commands.js +12 -2
- package/src/license/machine-id.js +42 -1
- package/src/memory/gotchas.js +58 -40
- package/src/memory/magic-docs.js +1 -1
- package/src/memory/session-digest.js +9 -4
- package/src/orchestrator/cli.js +174 -14
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +56 -8
- package/src/telemetry/sender.js +7 -19
- package/src/terminal/run-team.js +3 -3
- package/src/upgrade/tracked-files-detector.js +34 -16
- package/src/utils/feature-flags.js +1 -1
- package/src/utils/flatten-entities.js +4 -40
- package/src/utils/schema-validator.js +0 -14
- package/src/wizard/i18n.js +3 -3
- package/src/api/index.js +0 -120
- package/src/autonomy/autonomous-gate.js +0 -294
- package/src/autonomy/build-loop.js +0 -281
- package/src/autonomy/build-state.js +0 -286
- package/src/autonomy/cause-analyzer.js +0 -177
- package/src/autonomy/escalation.js +0 -214
- package/src/autonomy/index.js +0 -51
- package/src/autonomy/mode-manager.js +0 -225
- package/src/autonomy/mode-suggester.js +0 -283
- package/src/autonomy/progress-reporter.js +0 -275
- package/src/autonomy/safety-net.js +0 -370
- package/src/config/agent-customizer.js +0 -231
- package/src/decision/analyzer.js +0 -291
- package/src/decision/engine.js +0 -250
- package/src/decision/index.js +0 -38
- package/src/decision/registry-healer.js +0 -468
- package/src/decision/registry-updater.js +0 -339
- package/src/extensions/loader.js +0 -145
- package/src/extensions/registry.js +0 -134
- package/src/gates/circuit-breaker.js +0 -151
- package/src/gates/g1-planning-complete.js +0 -154
- package/src/gates/g2-qa-planning.js +0 -156
- package/src/gates/g3-implementation.js +0 -215
- package/src/gates/g4-qa-implementation.js +0 -240
- package/src/gates/g5-deploy-ready.js +0 -181
- package/src/gates/gate-base.js +0 -185
- package/src/gates/index.js +0 -46
- package/src/health/auto-fix.js +0 -216
- package/src/health/engine.js +0 -246
- package/src/merger/semantic-merger.js +0 -292
- package/src/preview/detector.js +0 -238
- package/src/preview/index.js +0 -20
- package/src/preview/launcher.js +0 -235
- package/src/preview/log-buffer.js +0 -103
- package/src/quality/metrics-collector.js +0 -281
- package/src/quality/test-runner.js +0 -366
- package/src/tasks/executor.js +0 -195
- package/src/tasks/index.js +0 -4
- package/src/tasks/loader.js +0 -210
- package/src/tasks/router.js +0 -182
- package/src/utils/event-bus.js +0 -126
package/src/tasks/loader.js
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Task Loader — Parses task definition files (YAML frontmatter + markdown body).
|
|
3
|
-
*
|
|
4
|
-
* Task files live in chati.dev/tasks/{agent}-{action}.md and contain:
|
|
5
|
-
* - YAML frontmatter: id, agent, trigger, phase, requires_input, parallelizable, outputs, handoff_to, autonomous_gate, criteria
|
|
6
|
-
* - Markdown body: Step-by-step instructions for the agent
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
10
|
-
import { join } from 'path';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Parse a single task definition file.
|
|
14
|
-
* @param {string} filePath - Absolute path to .md file with YAML frontmatter
|
|
15
|
-
* @returns {{ loaded: boolean, task: object|null, error: string|null }}
|
|
16
|
-
*/
|
|
17
|
-
export function loadTask(filePath) {
|
|
18
|
-
if (!existsSync(filePath)) {
|
|
19
|
-
return { loaded: false, task: null, error: `Task file not found: ${filePath}` };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const raw = readFileSync(filePath, 'utf-8');
|
|
24
|
-
return parseTaskContent(raw, filePath);
|
|
25
|
-
} catch (err) {
|
|
26
|
-
return { loaded: false, task: null, error: `Failed to read ${filePath}: ${err.message}` };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Parse task content string (YAML frontmatter + markdown body).
|
|
32
|
-
* @param {string} content - Raw file content
|
|
33
|
-
* @param {string} [source] - Source file path for error messages
|
|
34
|
-
* @returns {{ loaded: boolean, task: object|null, error: string|null }}
|
|
35
|
-
*/
|
|
36
|
-
export function parseTaskContent(content, source = 'unknown') {
|
|
37
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
38
|
-
|
|
39
|
-
if (!frontmatterMatch) {
|
|
40
|
-
return { loaded: false, task: null, error: `No YAML frontmatter found in ${source}` };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const yamlStr = frontmatterMatch[1];
|
|
44
|
-
const body = frontmatterMatch[2].trim();
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const meta = parseSimpleYaml(yamlStr);
|
|
48
|
-
|
|
49
|
-
if (!meta.id) {
|
|
50
|
-
return { loaded: false, task: null, error: `Missing required field 'id' in ${source}` };
|
|
51
|
-
}
|
|
52
|
-
if (!meta.agent) {
|
|
53
|
-
return { loaded: false, task: null, error: `Missing required field 'agent' in ${source}` };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
loaded: true,
|
|
58
|
-
task: {
|
|
59
|
-
id: meta.id,
|
|
60
|
-
agent: meta.agent,
|
|
61
|
-
trigger: meta.trigger || 'orchestrator',
|
|
62
|
-
phase: meta.phase || 'plan',
|
|
63
|
-
requires_input: meta.requires_input === true || meta.requires_input === 'true',
|
|
64
|
-
parallelizable: meta.parallelizable === true || meta.parallelizable === 'true',
|
|
65
|
-
outputs: parseArray(meta.outputs),
|
|
66
|
-
handoff_to: meta.handoff_to || null,
|
|
67
|
-
autonomous_gate: meta.autonomous_gate !== false && meta.autonomous_gate !== 'false',
|
|
68
|
-
criteria: parseArray(meta.criteria),
|
|
69
|
-
instructions: body,
|
|
70
|
-
source,
|
|
71
|
-
},
|
|
72
|
-
error: null,
|
|
73
|
-
};
|
|
74
|
-
} catch (err) {
|
|
75
|
-
return { loaded: false, task: null, error: `YAML parse error in ${source}: ${err.message}` };
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Load all task definitions from a directory.
|
|
81
|
-
* @param {string} tasksDir - Path to chati.dev/tasks/
|
|
82
|
-
* @returns {{ tasks: Map<string, object>, errors: string[] }}
|
|
83
|
-
*/
|
|
84
|
-
export function loadAllTasks(tasksDir) {
|
|
85
|
-
const tasks = new Map();
|
|
86
|
-
const errors = [];
|
|
87
|
-
|
|
88
|
-
if (!existsSync(tasksDir)) {
|
|
89
|
-
errors.push(`Tasks directory not found: ${tasksDir}`);
|
|
90
|
-
return { tasks, errors };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const files = readdirSync(tasksDir).filter(f => f.endsWith('.md'));
|
|
94
|
-
|
|
95
|
-
for (const file of files) {
|
|
96
|
-
const result = loadTask(join(tasksDir, file));
|
|
97
|
-
if (result.loaded) {
|
|
98
|
-
tasks.set(result.task.id, result.task);
|
|
99
|
-
} else {
|
|
100
|
-
errors.push(result.error);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return { tasks, errors };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Get all tasks for a specific agent.
|
|
109
|
-
* @param {Map<string, object>} taskMap - Map from loadAllTasks
|
|
110
|
-
* @param {string} agentName - Agent name to filter by
|
|
111
|
-
* @returns {object[]}
|
|
112
|
-
*/
|
|
113
|
-
export function getAgentTasks(taskMap, agentName) {
|
|
114
|
-
const result = [];
|
|
115
|
-
for (const task of taskMap.values()) {
|
|
116
|
-
if (task.agent === agentName) {
|
|
117
|
-
result.push(task);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return result;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Get a task's info summary (without instructions body).
|
|
125
|
-
* @param {object} task - Task object from loadTask
|
|
126
|
-
* @returns {object}
|
|
127
|
-
*/
|
|
128
|
-
export function getTaskSummary(task) {
|
|
129
|
-
return {
|
|
130
|
-
id: task.id,
|
|
131
|
-
agent: task.agent,
|
|
132
|
-
phase: task.phase,
|
|
133
|
-
parallelizable: task.parallelizable,
|
|
134
|
-
outputs: task.outputs,
|
|
135
|
-
handoff_to: task.handoff_to,
|
|
136
|
-
criteria_count: task.criteria.length,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Simple YAML parser for frontmatter (no external deps).
|
|
142
|
-
* Handles key: value, key: [array], and key: true/false.
|
|
143
|
-
*/
|
|
144
|
-
function parseSimpleYaml(str) {
|
|
145
|
-
const result = {};
|
|
146
|
-
let currentKey = null;
|
|
147
|
-
let currentArray = null;
|
|
148
|
-
|
|
149
|
-
for (const line of str.split('\n')) {
|
|
150
|
-
const trimmed = line.trim();
|
|
151
|
-
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
152
|
-
|
|
153
|
-
// Array item under current key
|
|
154
|
-
if (trimmed.startsWith('- ') && currentKey && currentArray) {
|
|
155
|
-
currentArray.push(trimmed.slice(2).trim());
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Key: value pair
|
|
160
|
-
const kvMatch = trimmed.match(/^(\w[\w_-]*):\s*(.*)$/);
|
|
161
|
-
if (kvMatch) {
|
|
162
|
-
// Save previous array if any
|
|
163
|
-
if (currentKey && currentArray) {
|
|
164
|
-
result[currentKey] = currentArray;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const key = kvMatch[1];
|
|
168
|
-
const val = kvMatch[2].trim();
|
|
169
|
-
|
|
170
|
-
if (val === '') {
|
|
171
|
-
// Could be start of an array
|
|
172
|
-
currentKey = key;
|
|
173
|
-
currentArray = [];
|
|
174
|
-
} else if (val.startsWith('[') && val.endsWith(']')) {
|
|
175
|
-
// Inline array: [a, b, c]
|
|
176
|
-
result[key] = val.slice(1, -1).split(',').map(s => s.trim()).filter(Boolean);
|
|
177
|
-
currentKey = null;
|
|
178
|
-
currentArray = null;
|
|
179
|
-
} else if (val === 'true') {
|
|
180
|
-
result[key] = true;
|
|
181
|
-
currentKey = null;
|
|
182
|
-
currentArray = null;
|
|
183
|
-
} else if (val === 'false') {
|
|
184
|
-
result[key] = false;
|
|
185
|
-
currentKey = null;
|
|
186
|
-
currentArray = null;
|
|
187
|
-
} else {
|
|
188
|
-
result[key] = val;
|
|
189
|
-
currentKey = null;
|
|
190
|
-
currentArray = null;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Save trailing array
|
|
196
|
-
if (currentKey && currentArray) {
|
|
197
|
-
result[currentKey] = currentArray;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return result;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Ensure a value is an array.
|
|
205
|
-
*/
|
|
206
|
-
function parseArray(val) {
|
|
207
|
-
if (Array.isArray(val)) return val;
|
|
208
|
-
if (typeof val === 'string') return [val];
|
|
209
|
-
return [];
|
|
210
|
-
}
|
package/src/tasks/router.js
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Task Router — Maps orchestrator intents to the correct task definition.
|
|
3
|
-
*
|
|
4
|
-
* The router is used internally by the orchestrator to determine
|
|
5
|
-
* which task an agent should execute based on:
|
|
6
|
-
* - Current pipeline position
|
|
7
|
-
* - Agent state
|
|
8
|
-
* - User intent signals
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { loadAllTasks, getAgentTasks } from './loader.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Task Router instance.
|
|
15
|
-
* Holds the loaded task registry and provides routing methods.
|
|
16
|
-
*/
|
|
17
|
-
export class TaskRouter {
|
|
18
|
-
/**
|
|
19
|
-
* @param {string} tasksDir - Path to chati.dev/tasks/
|
|
20
|
-
*/
|
|
21
|
-
constructor(tasksDir) {
|
|
22
|
-
this.tasksDir = tasksDir;
|
|
23
|
-
this.taskMap = new Map();
|
|
24
|
-
this.errors = [];
|
|
25
|
-
this.loaded = false;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Initialize the router by loading all task definitions.
|
|
30
|
-
* @returns {this}
|
|
31
|
-
*/
|
|
32
|
-
load() {
|
|
33
|
-
const result = loadAllTasks(this.tasksDir);
|
|
34
|
-
this.taskMap = result.tasks;
|
|
35
|
-
this.errors = result.errors;
|
|
36
|
-
this.loaded = true;
|
|
37
|
-
return this;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Route to the correct task for an agent at a given pipeline position.
|
|
42
|
-
*
|
|
43
|
-
* Routing priority:
|
|
44
|
-
* 1. Exact task ID match (if provided)
|
|
45
|
-
* 2. Agent's consolidate task (if pipeline position = consolidate)
|
|
46
|
-
* 3. Agent's first non-consolidate task (default entry point)
|
|
47
|
-
*
|
|
48
|
-
* @param {object} intent
|
|
49
|
-
* @param {string} intent.agent - Target agent name
|
|
50
|
-
* @param {string} [intent.taskId] - Explicit task ID (highest priority)
|
|
51
|
-
* @param {string} [intent.action] - Action keyword (e.g., 'consolidate', 'extract')
|
|
52
|
-
* @param {string} [intent.phase] - Pipeline phase filter
|
|
53
|
-
* @returns {{ found: boolean, task: object|null, reason: string }}
|
|
54
|
-
*/
|
|
55
|
-
route(intent) {
|
|
56
|
-
if (!this.loaded) {
|
|
57
|
-
return { found: false, task: null, reason: 'Router not loaded. Call load() first.' };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!intent || !intent.agent) {
|
|
61
|
-
return { found: false, task: null, reason: 'No agent specified in routing intent.' };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// 1. Exact task ID match
|
|
65
|
-
if (intent.taskId) {
|
|
66
|
-
const task = this.taskMap.get(intent.taskId);
|
|
67
|
-
if (task) {
|
|
68
|
-
return { found: true, task, reason: `Exact match: ${intent.taskId}` };
|
|
69
|
-
}
|
|
70
|
-
return { found: false, task: null, reason: `Task ID '${intent.taskId}' not found in registry.` };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Get all tasks for this agent
|
|
74
|
-
const agentTasks = getAgentTasks(this.taskMap, intent.agent);
|
|
75
|
-
if (agentTasks.length === 0) {
|
|
76
|
-
return { found: false, task: null, reason: `No tasks registered for agent '${intent.agent}'.` };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Filter by phase if provided
|
|
80
|
-
let candidates = agentTasks;
|
|
81
|
-
if (intent.phase) {
|
|
82
|
-
const phaseFiltered = agentTasks.filter(t => t.phase === intent.phase);
|
|
83
|
-
if (phaseFiltered.length > 0) {
|
|
84
|
-
candidates = phaseFiltered;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 2. Action keyword match
|
|
89
|
-
if (intent.action) {
|
|
90
|
-
const actionTask = candidates.find(t =>
|
|
91
|
-
t.id.includes(intent.action) || t.id.endsWith(`-${intent.action}`)
|
|
92
|
-
);
|
|
93
|
-
if (actionTask) {
|
|
94
|
-
return { found: true, task: actionTask, reason: `Action match: ${intent.action}` };
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 3. Consolidate task (if action is consolidate or implied)
|
|
99
|
-
if (intent.action === 'consolidate') {
|
|
100
|
-
const consolidateTask = candidates.find(t => t.id.endsWith('-consolidate'));
|
|
101
|
-
if (consolidateTask) {
|
|
102
|
-
return { found: true, task: consolidateTask, reason: 'Consolidate task selected.' };
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 4. Default: first non-consolidate task (entry point)
|
|
107
|
-
const entryTask = candidates.find(t => !t.id.endsWith('-consolidate')) || candidates[0];
|
|
108
|
-
return { found: true, task: entryTask, reason: `Default entry: ${entryTask.id}` };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Get the next task in an agent's task sequence.
|
|
113
|
-
* Uses the handoff_to field to determine chaining.
|
|
114
|
-
*
|
|
115
|
-
* @param {string} currentTaskId - Currently executing task ID
|
|
116
|
-
* @returns {{ found: boolean, task: object|null, reason: string }}
|
|
117
|
-
*/
|
|
118
|
-
getNextTask(currentTaskId) {
|
|
119
|
-
const current = this.taskMap.get(currentTaskId);
|
|
120
|
-
if (!current) {
|
|
121
|
-
return { found: false, task: null, reason: `Current task '${currentTaskId}' not found.` };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// If handoff_to is an agent name, it means we switch agents (orchestrator handles this)
|
|
125
|
-
// If handoff_to is a task ID within the same agent, it's internal chaining
|
|
126
|
-
if (!current.handoff_to) {
|
|
127
|
-
return { found: false, task: null, reason: 'No handoff defined for this task.' };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Check if handoff_to is a task ID
|
|
131
|
-
const nextTask = this.taskMap.get(current.handoff_to);
|
|
132
|
-
if (nextTask) {
|
|
133
|
-
return { found: true, task: nextTask, reason: `Internal chain: ${current.handoff_to}` };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Otherwise it's an agent-level handoff (handled by orchestrator)
|
|
137
|
-
return {
|
|
138
|
-
found: false,
|
|
139
|
-
task: null,
|
|
140
|
-
reason: `Agent handoff to '${current.handoff_to}'. Orchestrator handles inter-agent routing.`,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Get all parallelizable tasks for a given agent.
|
|
146
|
-
* @param {string} agentName
|
|
147
|
-
* @returns {object[]}
|
|
148
|
-
*/
|
|
149
|
-
getParallelTasks(agentName) {
|
|
150
|
-
return getAgentTasks(this.taskMap, agentName).filter(t => t.parallelizable);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Get routing statistics.
|
|
155
|
-
* @returns {{ totalTasks: number, byAgent: object, byPhase: object, errors: number }}
|
|
156
|
-
*/
|
|
157
|
-
getStats() {
|
|
158
|
-
const byAgent = {};
|
|
159
|
-
const byPhase = {};
|
|
160
|
-
|
|
161
|
-
for (const task of this.taskMap.values()) {
|
|
162
|
-
byAgent[task.agent] = (byAgent[task.agent] || 0) + 1;
|
|
163
|
-
byPhase[task.phase] = (byPhase[task.phase] || 0) + 1;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
totalTasks: this.taskMap.size,
|
|
168
|
-
byAgent,
|
|
169
|
-
byPhase,
|
|
170
|
-
errors: this.errors.length,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Create and load a TaskRouter instance.
|
|
177
|
-
* @param {string} tasksDir - Path to chati.dev/tasks/
|
|
178
|
-
* @returns {TaskRouter}
|
|
179
|
-
*/
|
|
180
|
-
export function createRouter(tasksDir) {
|
|
181
|
-
return new TaskRouter(tasksDir).load();
|
|
182
|
-
}
|
package/src/utils/event-bus.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Singleton event bus for the chati.dev framework.
|
|
3
|
-
*
|
|
4
|
-
* Provides a centralized EventEmitter for observing state changes
|
|
5
|
-
* across subsystems (build-loop, spawner, pipeline, safety-net).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @deprecated Not currently imported by any production module.
|
|
10
|
-
* Retained for potential future integration. Review for removal or integration by v5.0.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { EventEmitter } from 'node:events';
|
|
14
|
-
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// Event Names
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* All framework event names, organized by subsystem.
|
|
21
|
-
*/
|
|
22
|
-
export const EVENTS = {
|
|
23
|
-
// Build loop
|
|
24
|
-
BUILD_STARTED: 'build:started',
|
|
25
|
-
BUILD_COMPLETED: 'build:completed',
|
|
26
|
-
BUILD_FAILED: 'build:failed',
|
|
27
|
-
TASK_STARTED: 'build:task:started',
|
|
28
|
-
TASK_COMPLETED: 'build:task:completed',
|
|
29
|
-
TASK_FAILED: 'build:task:failed',
|
|
30
|
-
TASK_EXHAUSTED: 'build:task:exhausted',
|
|
31
|
-
|
|
32
|
-
// Spawner
|
|
33
|
-
TERMINAL_SPAWNED: 'terminal:spawned',
|
|
34
|
-
TERMINAL_EXITED: 'terminal:exited',
|
|
35
|
-
TERMINAL_KILLED: 'terminal:killed',
|
|
36
|
-
|
|
37
|
-
// Pipeline
|
|
38
|
-
PHASE_ADVANCED: 'pipeline:phase:advanced',
|
|
39
|
-
AGENT_COMPLETED: 'pipeline:agent:completed',
|
|
40
|
-
QA_GATE_PASSED: 'pipeline:qa:passed',
|
|
41
|
-
QA_GATE_FAILED: 'pipeline:qa:failed',
|
|
42
|
-
|
|
43
|
-
// Safety
|
|
44
|
-
SAFETY_TRIGGERED: 'safety:triggered',
|
|
45
|
-
|
|
46
|
-
// Health
|
|
47
|
-
HEALTH_CHECK_COMPLETED: 'health:completed',
|
|
48
|
-
|
|
49
|
-
// Escalation
|
|
50
|
-
ESCALATION_TRIGGERED: 'escalation:triggered',
|
|
51
|
-
ESCALATION_PAUSED: 'escalation:paused',
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
// Bus Instance
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
|
|
58
|
-
const bus = new EventEmitter();
|
|
59
|
-
bus.setMaxListeners(50);
|
|
60
|
-
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
// Public API
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Emit an event with data. Automatically adds a timestamp.
|
|
67
|
-
*
|
|
68
|
-
* @param {string} event - Event name from EVENTS
|
|
69
|
-
* @param {object} [data={}] - Event payload
|
|
70
|
-
*/
|
|
71
|
-
export function emit(event, data = {}) {
|
|
72
|
-
bus.emit(event, { ...data, timestamp: Date.now() });
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Subscribe to an event.
|
|
77
|
-
*
|
|
78
|
-
* @param {string} event - Event name
|
|
79
|
-
* @param {function} handler - Event handler
|
|
80
|
-
*/
|
|
81
|
-
export function on(event, handler) {
|
|
82
|
-
bus.on(event, handler);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Subscribe to an event once.
|
|
87
|
-
*
|
|
88
|
-
* @param {string} event - Event name
|
|
89
|
-
* @param {function} handler - Event handler
|
|
90
|
-
*/
|
|
91
|
-
export function once(event, handler) {
|
|
92
|
-
bus.once(event, handler);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Unsubscribe from an event.
|
|
97
|
-
*
|
|
98
|
-
* @param {string} event - Event name
|
|
99
|
-
* @param {function} handler - Event handler to remove
|
|
100
|
-
*/
|
|
101
|
-
export function off(event, handler) {
|
|
102
|
-
bus.off(event, handler);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Remove all listeners for an event, or all events if no event specified.
|
|
107
|
-
*
|
|
108
|
-
* @param {string} [event] - Event name (optional)
|
|
109
|
-
*/
|
|
110
|
-
export function removeAllListeners(event) {
|
|
111
|
-
if (event) {
|
|
112
|
-
bus.removeAllListeners(event);
|
|
113
|
-
} else {
|
|
114
|
-
bus.removeAllListeners();
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Get the count of listeners for an event.
|
|
120
|
-
*
|
|
121
|
-
* @param {string} event - Event name
|
|
122
|
-
* @returns {number}
|
|
123
|
-
*/
|
|
124
|
-
export function listenerCount(event) {
|
|
125
|
-
return bus.listenerCount(event);
|
|
126
|
-
}
|