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.
Files changed (147) hide show
  1. package/README.md +18 -13
  2. package/bin/chati.js +12 -4
  3. package/framework/agents/build/dev.md +5 -1
  4. package/framework/agents/discover/greenfield-wu.md +16 -1
  5. package/framework/agents/plan/tasks.md +31 -0
  6. package/framework/agents/plan/ux-brand-architect.md +21 -2
  7. package/framework/agents/plan/ux-component-engineer.md +10 -0
  8. package/framework/agents/quality/qa-implementation.md +1 -1
  9. package/framework/agents/quality/qa-planning.md +1 -1
  10. package/framework/agents/quality/qa-visual.md +24 -21
  11. package/framework/config.yaml +5 -4
  12. package/framework/constitution.md +58 -11
  13. package/framework/context/governance.md +12 -10
  14. package/framework/context/root.md +2 -2
  15. package/framework/data/entity-registry.yaml +12 -4
  16. package/framework/data/qa-rubrics.yaml +335 -0
  17. package/framework/domains/constitution.yaml +3 -3
  18. package/framework/executors/json-validate.js +80 -0
  19. package/framework/executors/npm-script.js +72 -0
  20. package/framework/executors/path-check.js +67 -0
  21. package/framework/executors/registry.yaml +36 -0
  22. package/framework/hooks/advance-trigger.js +47 -3
  23. package/framework/hooks/constitution-guard.js +9 -4
  24. package/framework/hooks/git-push-authority.js +113 -0
  25. package/framework/hooks/license-guard.js +59 -9
  26. package/framework/hooks/model-governance.js +40 -17
  27. package/framework/hooks/prism-engine.js +78 -11
  28. package/framework/hooks/session-digest.js +12 -3
  29. package/framework/hooks/settings.json +7 -3
  30. package/framework/hooks/style-guard.js +52 -3
  31. package/framework/hooks/team-quality-gate.js +43 -5
  32. package/framework/i18n/en.yaml +3 -3
  33. package/framework/i18n/es.yaml +3 -3
  34. package/framework/i18n/fr.yaml +3 -3
  35. package/framework/i18n/pt.yaml +3 -3
  36. package/framework/intelligence/confidence.yaml +85 -6
  37. package/framework/intelligence/context-engine.md +9 -5
  38. package/framework/intelligence/decision-engine.md +1 -1
  39. package/framework/orchestrator/chati-router.js +1 -1
  40. package/framework/orchestrator/chati-update.md +19 -3
  41. package/framework/orchestrator/chati.md +19 -1
  42. package/framework/quality-gates/planning-gate.md +2 -0
  43. package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
  44. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
  45. package/framework/scaffold/saas-dashboard/README.md +58 -0
  46. package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
  47. package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
  48. package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
  49. package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
  50. package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
  51. package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
  52. package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
  53. package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
  54. package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
  55. package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
  56. package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
  57. package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
  58. package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
  59. package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
  60. package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
  61. package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
  62. package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
  63. package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
  64. package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
  65. package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
  66. package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
  67. package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
  68. package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
  69. package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
  70. package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
  71. package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
  72. package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
  73. package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
  74. package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
  75. package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
  76. package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
  77. package/framework/schemas/session.schema.json +5 -0
  78. package/framework/schemas/task.schema.json +9 -0
  79. package/framework/scripts/visual-qa.js +87 -1
  80. package/framework/tasks/orchestrator-health.md +4 -4
  81. package/framework/tasks/qa-impl-verdict.md +11 -5
  82. package/framework/templates/qa-gate-tmpl.yaml +24 -11
  83. package/package.json +3 -3
  84. package/src/config/context-file-generator.js +0 -6
  85. package/src/dashboard/renderer.js +0 -36
  86. package/src/executors/runner.js +204 -0
  87. package/src/installer/core.js +2 -1
  88. package/src/installer/templates.js +5 -6
  89. package/src/intelligence/registry-manager.js +1 -1
  90. package/src/license/client.js +9 -3
  91. package/src/license/commands.js +12 -2
  92. package/src/license/machine-id.js +42 -1
  93. package/src/memory/gotchas.js +58 -40
  94. package/src/memory/magic-docs.js +1 -1
  95. package/src/memory/session-digest.js +9 -4
  96. package/src/orchestrator/cli.js +174 -14
  97. package/src/orchestrator/doctor.js +98 -16
  98. package/src/orchestrator/pipeline-manager.js +44 -22
  99. package/src/orchestrator/session-manager.js +56 -8
  100. package/src/telemetry/sender.js +7 -19
  101. package/src/terminal/run-team.js +3 -3
  102. package/src/upgrade/tracked-files-detector.js +34 -16
  103. package/src/utils/feature-flags.js +1 -1
  104. package/src/utils/flatten-entities.js +4 -40
  105. package/src/utils/schema-validator.js +0 -14
  106. package/src/wizard/i18n.js +3 -3
  107. package/src/api/index.js +0 -120
  108. package/src/autonomy/autonomous-gate.js +0 -294
  109. package/src/autonomy/build-loop.js +0 -281
  110. package/src/autonomy/build-state.js +0 -286
  111. package/src/autonomy/cause-analyzer.js +0 -177
  112. package/src/autonomy/escalation.js +0 -214
  113. package/src/autonomy/index.js +0 -51
  114. package/src/autonomy/mode-manager.js +0 -225
  115. package/src/autonomy/mode-suggester.js +0 -283
  116. package/src/autonomy/progress-reporter.js +0 -275
  117. package/src/autonomy/safety-net.js +0 -370
  118. package/src/config/agent-customizer.js +0 -231
  119. package/src/decision/analyzer.js +0 -291
  120. package/src/decision/engine.js +0 -250
  121. package/src/decision/index.js +0 -38
  122. package/src/decision/registry-healer.js +0 -468
  123. package/src/decision/registry-updater.js +0 -339
  124. package/src/extensions/loader.js +0 -145
  125. package/src/extensions/registry.js +0 -134
  126. package/src/gates/circuit-breaker.js +0 -151
  127. package/src/gates/g1-planning-complete.js +0 -154
  128. package/src/gates/g2-qa-planning.js +0 -156
  129. package/src/gates/g3-implementation.js +0 -215
  130. package/src/gates/g4-qa-implementation.js +0 -240
  131. package/src/gates/g5-deploy-ready.js +0 -181
  132. package/src/gates/gate-base.js +0 -185
  133. package/src/gates/index.js +0 -46
  134. package/src/health/auto-fix.js +0 -216
  135. package/src/health/engine.js +0 -246
  136. package/src/merger/semantic-merger.js +0 -292
  137. package/src/preview/detector.js +0 -238
  138. package/src/preview/index.js +0 -20
  139. package/src/preview/launcher.js +0 -235
  140. package/src/preview/log-buffer.js +0 -103
  141. package/src/quality/metrics-collector.js +0 -281
  142. package/src/quality/test-runner.js +0 -366
  143. package/src/tasks/executor.js +0 -195
  144. package/src/tasks/index.js +0 -4
  145. package/src/tasks/loader.js +0 -210
  146. package/src/tasks/router.js +0 -182
  147. package/src/utils/event-bus.js +0 -126
@@ -1,339 +0,0 @@
1
- /**
2
- * Registry Updater - Auto-updates entity-registry.yaml
3
- * Scans project for new/changed/removed entities
4
- *
5
- * @module decision/registry-updater
6
- */
7
-
8
- import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'fs';
9
- import { join, basename, relative } from 'path';
10
- import yaml from 'js-yaml';
11
- import { resolveFrameworkDir } from '../utils/framework-dir.js';
12
-
13
- /**
14
- * Build KNOWN_DIRS using resolveFrameworkDir so paths are correct
15
- * for both chati.dev/ and .chati.dev/ installations.
16
- * @param {string} projectDir
17
- * @returns {{ path: string, type: string }[]}
18
- */
19
- function buildKnownDirs(projectDir) {
20
- const fw = resolveFrameworkDir(projectDir);
21
- return [
22
- { path: `${fw}/agents`, type: 'agent' },
23
- { path: `${fw}/tasks`, type: 'task' },
24
- { path: `${fw}/schemas`, type: 'schema' },
25
- { path: `${fw}/domains`, type: 'domain' },
26
- { path: `${fw}/hooks`, type: 'hook' },
27
- { path: `${fw}/workflows`, type: 'workflow' },
28
- { path: `${fw}/templates`, type: 'template' },
29
- ];
30
- }
31
-
32
- /**
33
- * Scan project for new/changed files and update registry.
34
- * @param {string} projectDir
35
- * @param {object} [options] - { dryRun: boolean }
36
- * @returns {{ added: string[], removed: string[], updated: string[], unchanged: number }}
37
- */
38
- export function updateRegistry(projectDir, options = {}) {
39
- const { dryRun = false } = options;
40
- const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
41
-
42
- let registry = {
43
- metadata: {
44
- version: '1.0',
45
- entity_count: 0,
46
- last_updated: new Date().toISOString()
47
- },
48
- entities: {}
49
- };
50
-
51
- // Load existing registry
52
- if (existsSync(registryPath)) {
53
- registry = yaml.load(readFileSync(registryPath, 'utf8'));
54
- }
55
-
56
- const existingPaths = new Set();
57
- Object.values(registry.entities || {}).forEach(typeEntities => {
58
- if (Array.isArray(typeEntities)) {
59
- typeEntities.forEach(entity => {
60
- if (entity.path) {
61
- existingPaths.add(entity.path);
62
- }
63
- });
64
- }
65
- });
66
-
67
- // Detect new entities
68
- const newEntities = detectNewEntities(projectDir);
69
- const newPaths = new Set(newEntities.map(e => e.path));
70
-
71
- // Detect removed entities
72
- const removedEntities = detectRemovedEntities(projectDir);
73
- const removedPaths = new Set(removedEntities.map(e => e.path));
74
-
75
- // Track changes
76
- const changes = {
77
- added: [],
78
- removed: [],
79
- updated: [],
80
- unchanged: 0
81
- };
82
-
83
- // Add new entities
84
- newEntities.forEach(entity => {
85
- if (!existingPaths.has(entity.path)) {
86
- changes.added.push(entity.path);
87
-
88
- if (!dryRun) {
89
- const type = entity.type;
90
- if (!registry.entities[type]) {
91
- registry.entities[type] = [];
92
- }
93
- registry.entities[type].push(entity);
94
- }
95
- }
96
- });
97
-
98
- // Remove deleted entities
99
- removedEntities.forEach(entity => {
100
- if (existingPaths.has(entity.path)) {
101
- changes.removed.push(entity.path);
102
-
103
- if (!dryRun) {
104
- const type = entity.type;
105
- if (registry.entities[type]) {
106
- registry.entities[type] = registry.entities[type].filter(
107
- e => e.path !== entity.path
108
- );
109
- }
110
- }
111
- }
112
- });
113
-
114
- // Check for updates (files modified after last registry update)
115
- existingPaths.forEach(path => {
116
- if (!removedPaths.has(path) && !newPaths.has(path)) {
117
- const fullPath = join(projectDir, path);
118
- if (existsSync(fullPath)) {
119
- const stats = statSync(fullPath);
120
- const lastUpdated = registry.metadata?.last_updated
121
- ? new Date(registry.metadata.last_updated)
122
- : new Date(0);
123
-
124
- if (stats.mtime > lastUpdated) {
125
- changes.updated.push(path);
126
-
127
- if (!dryRun) {
128
- // Regenerate metadata
129
- const newMeta = generateEntityMeta(projectDir, path);
130
- Object.entries(registry.entities).forEach(([type, entities]) => {
131
- const index = entities.findIndex(e => e.path === path);
132
- if (index !== -1) {
133
- registry.entities[type][index] = { ...newMeta, path };
134
- }
135
- });
136
- }
137
- } else {
138
- changes.unchanged++;
139
- }
140
- }
141
- }
142
- });
143
-
144
- // Update metadata
145
- if (!dryRun) {
146
- let totalCount = 0;
147
- Object.values(registry.entities).forEach(typeEntities => {
148
- if (Array.isArray(typeEntities)) {
149
- totalCount += typeEntities.length;
150
- }
151
- });
152
-
153
- registry.metadata.entity_count = totalCount;
154
- registry.metadata.last_updated = new Date().toISOString();
155
-
156
- writeFileSync(registryPath, yaml.dump(registry), 'utf8');
157
- }
158
-
159
- return changes;
160
- }
161
-
162
- /**
163
- * Detect new entities that aren't in the registry.
164
- * Scans known directories (chati.dev/agents, chati.dev/tasks, etc).
165
- * @param {string} projectDir
166
- * @returns {object[]} New entity candidates
167
- */
168
- export function detectNewEntities(projectDir) {
169
- const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
170
- const existingPaths = new Set();
171
-
172
- if (existsSync(registryPath)) {
173
- const registry = yaml.load(readFileSync(registryPath, 'utf8'));
174
- Object.values(registry.entities || {}).forEach(typeEntities => {
175
- if (Array.isArray(typeEntities)) {
176
- typeEntities.forEach(entity => {
177
- if (entity.path) {
178
- existingPaths.add(entity.path);
179
- }
180
- });
181
- }
182
- });
183
- }
184
-
185
- const newEntities = [];
186
-
187
- buildKnownDirs(projectDir).forEach(({ path: dirPath, type }) => {
188
- const fullPath = join(projectDir, dirPath);
189
- if (!existsSync(fullPath)) {
190
- return;
191
- }
192
-
193
- const files = scanDirectory(fullPath);
194
- files.forEach(file => {
195
- const relativePath = relative(projectDir, file);
196
- if (!existingPaths.has(relativePath)) {
197
- const meta = generateEntityMeta(projectDir, relativePath);
198
- newEntities.push({
199
- ...meta,
200
- path: relativePath,
201
- type
202
- });
203
- }
204
- });
205
- });
206
-
207
- return newEntities;
208
- }
209
-
210
- /**
211
- * Detect removed entities (in registry but not on disk).
212
- * @param {string} projectDir
213
- * @returns {object[]} Removed entity candidates
214
- */
215
- export function detectRemovedEntities(projectDir) {
216
- const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
217
- if (!existsSync(registryPath)) {
218
- return [];
219
- }
220
-
221
- const registry = yaml.load(readFileSync(registryPath, 'utf8'));
222
- const removedEntities = [];
223
-
224
- Object.entries(registry.entities || {}).forEach(([type, entities]) => {
225
- if (Array.isArray(entities)) {
226
- entities.forEach(entity => {
227
- if (entity.path) {
228
- const fullPath = join(projectDir, entity.path);
229
- if (!existsSync(fullPath)) {
230
- removedEntities.push({ ...entity, type });
231
- }
232
- }
233
- });
234
- }
235
- });
236
-
237
- return removedEntities;
238
- }
239
-
240
- /**
241
- * Generate entity metadata from file.
242
- * Extracts type, purpose, keywords from filename and content.
243
- * @param {string} projectDir
244
- * @param {string} filePath
245
- * @returns {object} Entity metadata
246
- */
247
- export function generateEntityMeta(projectDir, filePath) {
248
- const fullPath = join(projectDir, filePath);
249
- const fileName = basename(filePath, '.md');
250
-
251
- const meta = {
252
- name: fileName,
253
- keywords: [],
254
- purpose: ''
255
- };
256
-
257
- // Extract keywords from filename
258
- const nameWords = fileName
259
- .replace(/[._-]/g, ' ')
260
- .toLowerCase()
261
- .split(/\W+/)
262
- .filter(w => w.length > 3);
263
- meta.keywords.push(...nameWords);
264
-
265
- // Try to extract purpose from file content
266
- if (existsSync(fullPath)) {
267
- try {
268
- const content = readFileSync(fullPath, 'utf8');
269
- const lines = content.split('\n').slice(0, 20); // First 20 lines
270
-
271
- // Look for common purpose patterns
272
- for (const line of lines) {
273
- const purposeMatch = line.match(/^#+\s*(.+)$/); // Markdown heading
274
- if (purposeMatch) {
275
- meta.purpose = purposeMatch[1].trim();
276
- break;
277
- }
278
-
279
- const descMatch = line.match(/^(?:description|purpose|summary):\s*(.+)$/i);
280
- if (descMatch) {
281
- meta.purpose = descMatch[1].trim();
282
- break;
283
- }
284
- }
285
-
286
- // Extract additional keywords from content
287
- const contentWords = content
288
- .toLowerCase()
289
- .split(/\W+/)
290
- .filter(w => w.length > 5); // Longer words only
291
- const wordFreq = new Map();
292
- contentWords.forEach(w => {
293
- wordFreq.set(w, (wordFreq.get(w) || 0) + 1);
294
- });
295
-
296
- // Add top 5 frequent words as keywords
297
- const topWords = [...wordFreq.entries()]
298
- .sort((a, b) => b[1] - a[1])
299
- .slice(0, 5)
300
- .map(([word]) => word);
301
- meta.keywords.push(...topWords);
302
- } catch { /* expected: operation may fail gracefully */
303
- // Ignore read errors
304
- }
305
- }
306
-
307
- // Remove duplicates
308
- meta.keywords = [...new Set(meta.keywords)];
309
-
310
- return meta;
311
- }
312
-
313
- /**
314
- * Recursively scan directory for files.
315
- * @private
316
- * @param {string} dirPath
317
- * @returns {string[]} Array of file paths
318
- */
319
- function scanDirectory(dirPath) {
320
- const files = [];
321
-
322
- try {
323
- const entries = readdirSync(dirPath);
324
- entries.forEach(entry => {
325
- const fullPath = join(dirPath, entry);
326
- const stats = statSync(fullPath);
327
-
328
- if (stats.isDirectory()) {
329
- files.push(...scanDirectory(fullPath));
330
- } else if (stats.isFile() && entry.endsWith('.md')) {
331
- files.push(fullPath);
332
- }
333
- });
334
- } catch { /* expected: operation may fail gracefully */
335
- // Ignore read errors
336
- }
337
-
338
- return files;
339
- }
@@ -1,145 +0,0 @@
1
- /**
2
- * @fileoverview Extension loader for chati.dev.
3
- *
4
- * Discovers and loads project-level extensions from
5
- * `chati.dev/extensions/*.js`, validates them, and registers
6
- * them in the extension registry.
7
- *
8
- * Constitution Article XIV — Framework Registry Governance.
9
- */
10
-
11
- import { existsSync, readdirSync } from 'fs';
12
- import { join, extname, basename } from 'path';
13
- import { registerExtension, EXTENSION_POINTS } from './registry.js';
14
- import { resolveFrameworkDir } from '../utils/framework-dir.js';
15
-
16
- /** Required fields in an extension module's default export. */
17
- const REQUIRED_FIELDS = ['name', 'point', 'handler'];
18
-
19
- // ---------------------------------------------------------------------------
20
- // Public API
21
- // ---------------------------------------------------------------------------
22
-
23
- /**
24
- * Validate an extension object has the required shape.
25
- *
26
- * @param {object} ext - Extension object to validate
27
- * @returns {{ valid: boolean, errors: string[] }}
28
- */
29
- export function validateExtension(ext) {
30
- const errors = [];
31
-
32
- if (!ext || typeof ext !== 'object') {
33
- errors.push('Extension must be a non-null object');
34
- return { valid: false, errors };
35
- }
36
-
37
- for (const field of REQUIRED_FIELDS) {
38
- if (ext[field] === undefined || ext[field] === null) {
39
- errors.push(`Missing required field: "${field}"`);
40
- }
41
- }
42
-
43
- if (ext.name !== undefined && typeof ext.name !== 'string') {
44
- errors.push('name must be a string');
45
- }
46
-
47
- if (ext.point !== undefined) {
48
- const validPoints = Object.values(EXTENSION_POINTS);
49
- if (!validPoints.includes(ext.point)) {
50
- errors.push(`Invalid extension point: "${ext.point}". Valid: ${validPoints.join(', ')}`);
51
- }
52
- }
53
-
54
- if (ext.handler !== undefined && typeof ext.handler !== 'function') {
55
- errors.push('handler must be a function');
56
- }
57
-
58
- if (ext.priority !== undefined && typeof ext.priority !== 'number') {
59
- errors.push('priority must be a number');
60
- }
61
-
62
- return { valid: errors.length === 0, errors };
63
- }
64
-
65
- /**
66
- * Discover extension files in the project extensions directory.
67
- *
68
- * @param {string} projectDir - Project root directory
69
- * @returns {{ files: string[], dir: string }}
70
- */
71
- export function discoverExtensions(projectDir) {
72
- if (!projectDir) {
73
- return { files: [], dir: '' };
74
- }
75
-
76
- const extDir = join(projectDir, resolveFrameworkDir(projectDir), 'extensions');
77
-
78
- if (!existsSync(extDir)) {
79
- return { files: [], dir: extDir };
80
- }
81
-
82
- try {
83
- const entries = readdirSync(extDir);
84
- const jsFiles = entries
85
- .filter(f => extname(f) === '.js')
86
- .map(f => join(extDir, f));
87
-
88
- return { files: jsFiles, dir: extDir };
89
- } catch { /* expected: directory may not exist */
90
- return { files: [], dir: extDir };
91
- }
92
- }
93
-
94
- /**
95
- * Load and register all extensions from a project directory.
96
- *
97
- * @param {string} projectDir - Project root directory
98
- * @returns {Promise<{ loaded: number, failed: number, errors: Array<{ file: string, error: string }> }>}
99
- */
100
- export async function loadProjectExtensions(projectDir) {
101
- const { files } = discoverExtensions(projectDir);
102
- let loaded = 0;
103
- let failed = 0;
104
- const errors = [];
105
-
106
- for (const filePath of files) {
107
- try {
108
- const mod = await import(filePath);
109
- const ext = mod.default || mod;
110
-
111
- const validation = validateExtension(ext);
112
- if (!validation.valid) {
113
- failed++;
114
- errors.push({
115
- file: basename(filePath),
116
- error: validation.errors.join('; '),
117
- });
118
- continue;
119
- }
120
-
121
- const result = registerExtension(ext.point, ext.handler, {
122
- name: ext.name,
123
- priority: ext.priority || 0,
124
- });
125
-
126
- if (result.registered) {
127
- loaded++;
128
- } else {
129
- failed++;
130
- errors.push({
131
- file: basename(filePath),
132
- error: result.error || 'Registration failed',
133
- });
134
- }
135
- } catch (err) {
136
- failed++;
137
- errors.push({
138
- file: basename(filePath),
139
- error: err.message,
140
- });
141
- }
142
- }
143
-
144
- return { loaded, failed, errors };
145
- }
@@ -1,134 +0,0 @@
1
- /**
2
- * @fileoverview Extension point registry for chati.dev.
3
- *
4
- * Provides a mechanism for registering, retrieving, and
5
- * executing extension handlers at defined extension points.
6
- *
7
- * Constitution Article XIV — Framework Registry Governance.
8
- */
9
-
10
- // ---------------------------------------------------------------------------
11
- // Constants
12
- // ---------------------------------------------------------------------------
13
-
14
- /** Available extension points in the pipeline lifecycle. */
15
- export const EXTENSION_POINTS = {
16
- PRE_AGENT: 'pre_agent',
17
- POST_AGENT: 'post_agent',
18
- PRE_BUILD: 'pre_build',
19
- POST_BUILD: 'post_build',
20
- HEALTH_CHECK: 'health_check',
21
- CUSTOM_GATE: 'custom_gate',
22
- };
23
-
24
- // ---------------------------------------------------------------------------
25
- // Internal State
26
- // ---------------------------------------------------------------------------
27
-
28
- /** @type {Map<string, Array<{ handler: Function, name: string, priority: number }>>} */
29
- const _registry = new Map();
30
-
31
- // Initialize registry with empty arrays for each point
32
- for (const point of Object.values(EXTENSION_POINTS)) {
33
- _registry.set(point, []);
34
- }
35
-
36
- // ---------------------------------------------------------------------------
37
- // Public API
38
- // ---------------------------------------------------------------------------
39
-
40
- /**
41
- * Register an extension handler at a specific point.
42
- *
43
- * @param {string} point - Extension point (from EXTENSION_POINTS)
44
- * @param {Function} handler - Handler function
45
- * @param {{ name?: string, priority?: number }} [options={}]
46
- * @returns {{ registered: boolean, error?: string }}
47
- */
48
- export function registerExtension(point, handler, options = {}) {
49
- if (!Object.values(EXTENSION_POINTS).includes(point)) {
50
- return { registered: false, error: `Unknown extension point: ${point}` };
51
- }
52
-
53
- if (typeof handler !== 'function') {
54
- return { registered: false, error: 'Handler must be a function' };
55
- }
56
-
57
- const name = options.name || handler.name || 'anonymous';
58
- const priority = options.priority || 0;
59
-
60
- const extensions = _registry.get(point);
61
- extensions.push({ handler, name, priority });
62
-
63
- // Sort by priority (higher first)
64
- extensions.sort((a, b) => b.priority - a.priority);
65
-
66
- return { registered: true };
67
- }
68
-
69
- /**
70
- * Get all extensions registered at a point.
71
- *
72
- * @param {string} point - Extension point
73
- * @returns {Array<{ handler: Function, name: string, priority: number }>}
74
- */
75
- export function getExtensions(point) {
76
- return _registry.get(point) || [];
77
- }
78
-
79
- /**
80
- * Execute all extensions at a point with given context.
81
- *
82
- * @param {string} point - Extension point
83
- * @param {object} context - Context passed to each handler
84
- * @returns {Promise<{ results: Array<{ name: string, result: any, error?: string }>, executed: number }>}
85
- */
86
- export async function executeExtensions(point, context = {}) {
87
- const extensions = getExtensions(point);
88
- const results = [];
89
-
90
- for (const ext of extensions) {
91
- try {
92
- const result = await ext.handler(context);
93
- results.push({ name: ext.name, result });
94
- } catch (err) {
95
- results.push({ name: ext.name, result: null, error: err.message });
96
- }
97
- }
98
-
99
- return { results, executed: extensions.length };
100
- }
101
-
102
- /**
103
- * Clear all extensions (or extensions at a specific point).
104
- *
105
- * @param {string} [point] - Optional point to clear (clears all if omitted)
106
- */
107
- export function clearExtensions(point) {
108
- if (point) {
109
- if (_registry.has(point)) {
110
- _registry.set(point, []);
111
- }
112
- } else {
113
- for (const key of _registry.keys()) {
114
- _registry.set(key, []);
115
- }
116
- }
117
- }
118
-
119
- /**
120
- * Get count of registered extensions.
121
- *
122
- * @returns {{ total: number, byPoint: Record<string, number> }}
123
- */
124
- export function getExtensionStats() {
125
- const byPoint = {};
126
- let total = 0;
127
-
128
- for (const [point, extensions] of _registry.entries()) {
129
- byPoint[point] = extensions.length;
130
- total += extensions.length;
131
- }
132
-
133
- return { total, byPoint };
134
- }