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,481 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Populate Entity Registry — Scans filesystem and populates entity-registry.yaml.
5
+ *
6
+ * Exports:
7
+ * populateRegistry(frameworkDir) → writes data/entity-registry.yaml
8
+ * diffRegistry(frameworkDir) → { added: [], removed: [], modified: [] }
9
+ */
10
+
11
+ import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, mkdirSync } from 'fs';
12
+ import { join, basename, extname } from 'path';
13
+ import yaml from 'js-yaml';
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Internal Helpers
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /**
20
+ * Extract the first meaningful line or YAML description from a file.
21
+ * @param {string} filePath - Absolute path to the file.
22
+ * @returns {string} Purpose string.
23
+ */
24
+ function extractPurpose(filePath) {
25
+ try {
26
+ const content = readFileSync(filePath, 'utf8');
27
+ const ext = extname(filePath);
28
+
29
+ // YAML files — look for description or purpose field
30
+ if (ext === '.yaml' || ext === '.yml') {
31
+ const parsed = yaml.load(content);
32
+ if (parsed?.description) return parsed.description;
33
+ if (parsed?.purpose) return parsed.purpose;
34
+ if (parsed?.summary) return String(parsed.summary).slice(0, 120);
35
+ }
36
+
37
+ // JSON files — look for description
38
+ if (ext === '.json') {
39
+ const parsed = JSON.parse(content);
40
+ if (parsed.description) return parsed.description;
41
+ if (parsed.title) return parsed.title;
42
+ }
43
+
44
+ // Markdown files — first heading or first paragraph
45
+ if (ext === '.md') {
46
+ // Check for YAML frontmatter with purpose/description
47
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
48
+ if (fmMatch) {
49
+ try {
50
+ const fm = yaml.load(fmMatch[1]);
51
+ if (fm?.purpose) return fm.purpose;
52
+ if (fm?.description) return fm.description;
53
+ } catch {
54
+ // Ignore
55
+ }
56
+ }
57
+
58
+ // First heading
59
+ const headingMatch = content.match(/^#\s+(.+)/m);
60
+ if (headingMatch) return headingMatch[1].trim().slice(0, 120);
61
+
62
+ // First non-empty line
63
+ const firstLine = content.split('\n').find(l => l.trim().length > 0);
64
+ if (firstLine) return firstLine.trim().slice(0, 120);
65
+ }
66
+
67
+ // JS files — first JSDoc or comment
68
+ if (ext === '.js' || ext === '.mjs') {
69
+ const jsdocMatch = content.match(/\/\*\*\s*\n?\s*\*\s*(.+)/);
70
+ if (jsdocMatch) return jsdocMatch[1].trim().slice(0, 120);
71
+
72
+ const commentMatch = content.match(/^\/\/\s*(.+)/m);
73
+ if (commentMatch) return commentMatch[1].trim().slice(0, 120);
74
+ }
75
+
76
+ return '';
77
+ } catch {
78
+ return '';
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Extract keywords from a file.
84
+ * @param {string} filePath - Absolute path to the file.
85
+ * @returns {string[]} Extracted keywords.
86
+ */
87
+ function extractKeywords(filePath) {
88
+ try {
89
+ const content = readFileSync(filePath, 'utf8');
90
+ const ext = extname(filePath);
91
+
92
+ // YAML frontmatter — tags or keywords field
93
+ if (ext === '.md') {
94
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
95
+ if (fmMatch) {
96
+ try {
97
+ const fm = yaml.load(fmMatch[1]);
98
+ if (Array.isArray(fm?.tags)) return fm.tags;
99
+ if (Array.isArray(fm?.keywords)) return fm.keywords;
100
+ } catch {
101
+ // Ignore
102
+ }
103
+ }
104
+ }
105
+
106
+ // YAML files
107
+ if (ext === '.yaml' || ext === '.yml') {
108
+ const parsed = yaml.load(content);
109
+ if (Array.isArray(parsed?.keywords)) return parsed.keywords;
110
+ if (Array.isArray(parsed?.tags)) return parsed.tags;
111
+ }
112
+
113
+ return [];
114
+ } catch {
115
+ return [];
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Determine entity type from directory name and file extension.
121
+ * @param {string} dirCategory - Parent directory category (e.g., 'agents', 'schemas').
122
+ * @param {string} _ext - File extension.
123
+ * @returns {string} Entity type.
124
+ */
125
+ function entityType(dirCategory, _ext) {
126
+ const typeMap = {
127
+ agents: 'agent',
128
+ orchestrator: 'agent',
129
+ schemas: 'schema',
130
+ templates: 'template',
131
+ workflows: 'workflow',
132
+ domains: 'domain',
133
+ hooks: 'hook',
134
+ 'quality-gates': 'governance',
135
+ intelligence: 'intelligence',
136
+ frameworks: 'framework',
137
+ patterns: 'framework',
138
+ i18n: 'framework',
139
+ migrations: 'framework',
140
+ data: 'framework',
141
+ };
142
+
143
+ return typeMap[dirCategory] || 'unknown';
144
+ }
145
+
146
+ /**
147
+ * Scan a single directory for entities.
148
+ * @param {string} frameworkDir - Root framework directory.
149
+ * @param {string} relDir - Relative directory within framework.
150
+ * @param {string} category - Entity category name.
151
+ * @returns {object[]} Array of entity objects.
152
+ */
153
+ function scanEntities(frameworkDir, relDir, category) {
154
+ const dirPath = join(frameworkDir, relDir);
155
+ if (!existsSync(dirPath)) return [];
156
+
157
+ const entities = [];
158
+
159
+ try {
160
+ const entries = readdirSync(dirPath, { withFileTypes: true });
161
+
162
+ for (const entry of entries) {
163
+ if (entry.isDirectory()) {
164
+ // Recurse into subdirectories (e.g., agents/clarity/, agents/build/)
165
+ const subEntities = scanEntities(frameworkDir, join(relDir, entry.name), category);
166
+ entities.push(...subEntities);
167
+ continue;
168
+ }
169
+
170
+ if (!entry.isFile()) continue;
171
+
172
+ const ext = extname(entry.name);
173
+ // Only process known file types
174
+ if (!['.md', '.yaml', '.yml', '.json', '.js', '.mjs'].includes(ext)) continue;
175
+
176
+ const fullPath = join(dirPath, entry.name);
177
+ const entityName = basename(entry.name, ext);
178
+ const relPath = join(relDir, entry.name);
179
+ const stat = statSync(fullPath);
180
+
181
+ entities.push({
182
+ name: entityName,
183
+ path: `chati.dev/${relPath}`,
184
+ type: entityType(category, ext),
185
+ purpose: extractPurpose(fullPath),
186
+ keywords: extractKeywords(fullPath),
187
+ lastModified: stat.mtime.toISOString(),
188
+ });
189
+ }
190
+ } catch {
191
+ // Skip unreadable directories
192
+ }
193
+
194
+ return entities;
195
+ }
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // Public API
199
+ // ---------------------------------------------------------------------------
200
+
201
+ /**
202
+ * Scan the filesystem and build a complete entity registry.
203
+ * @param {string} frameworkDir - Path to the framework directory.
204
+ * @returns {{ path: string, registry: object }} Written registry.
205
+ */
206
+ export function populateRegistry(frameworkDir) {
207
+ if (!existsSync(frameworkDir)) {
208
+ throw new Error(`Framework directory not found: ${frameworkDir}`);
209
+ }
210
+
211
+ // Define scan targets
212
+ const scanTargets = [
213
+ { relDir: 'agents', category: 'agents' },
214
+ { relDir: 'orchestrator', category: 'orchestrator' },
215
+ { relDir: 'schemas', category: 'schemas' },
216
+ { relDir: 'templates', category: 'templates' },
217
+ { relDir: 'workflows', category: 'workflows' },
218
+ { relDir: 'domains', category: 'domains' },
219
+ { relDir: 'hooks', category: 'hooks' },
220
+ { relDir: 'quality-gates', category: 'quality-gates' },
221
+ { relDir: 'intelligence', category: 'intelligence' },
222
+ { relDir: 'frameworks', category: 'frameworks' },
223
+ { relDir: 'patterns', category: 'patterns' },
224
+ { relDir: 'i18n', category: 'i18n' },
225
+ { relDir: 'migrations', category: 'migrations' },
226
+ ];
227
+
228
+ // Collect all entities grouped by category
229
+ const entities = {};
230
+ let totalCount = 0;
231
+
232
+ for (const { relDir, category } of scanTargets) {
233
+ const found = scanEntities(frameworkDir, relDir, category);
234
+ if (found.length > 0) {
235
+ // Group into the entity category
236
+ const groupName = category === 'orchestrator' ? 'agents' : category;
237
+ if (!entities[groupName]) entities[groupName] = {};
238
+
239
+ for (const entity of found) {
240
+ entities[groupName][entity.name] = {
241
+ path: entity.path,
242
+ type: entity.type,
243
+ purpose: entity.purpose,
244
+ keywords: entity.keywords,
245
+ lastModified: entity.lastModified,
246
+ };
247
+ totalCount++;
248
+ }
249
+ }
250
+ }
251
+
252
+ // Also add constitution.md and config.yaml as special entities
253
+ const constPath = join(frameworkDir, 'constitution.md');
254
+ if (existsSync(constPath)) {
255
+ if (!entities.governance) entities.governance = {};
256
+ entities.governance.constitution = {
257
+ path: 'chati.dev/constitution.md',
258
+ type: 'governance',
259
+ purpose: extractPurpose(constPath),
260
+ keywords: ['constitution', 'governance', 'rules', 'articles', 'enforcement'],
261
+ lastModified: statSync(constPath).mtime.toISOString(),
262
+ };
263
+ totalCount++;
264
+ }
265
+
266
+ const configPath = join(frameworkDir, 'config.yaml');
267
+ if (existsSync(configPath)) {
268
+ if (!entities.config) entities.config = {};
269
+ entities.config.config = {
270
+ path: 'chati.dev/config.yaml',
271
+ type: 'framework',
272
+ purpose: extractPurpose(configPath),
273
+ keywords: ['config', 'version', 'installation', 'settings'],
274
+ lastModified: statSync(configPath).mtime.toISOString(),
275
+ };
276
+ totalCount++;
277
+ }
278
+
279
+ // Add entity-registry itself
280
+ const regPath = join(frameworkDir, 'data', 'entity-registry.yaml');
281
+ if (!entities.data) entities.data = {};
282
+ entities.data['entity-registry'] = {
283
+ path: 'chati.dev/data/entity-registry.yaml',
284
+ type: 'framework',
285
+ purpose: 'Central catalog of all system artifacts (this file)',
286
+ keywords: ['registry', 'catalog', 'entities', 'artifacts'],
287
+ lastModified: existsSync(regPath) ? statSync(regPath).mtime.toISOString() : new Date().toISOString(),
288
+ };
289
+ totalCount++;
290
+
291
+ // Build registry object
292
+ const registry = {
293
+ metadata: {
294
+ version: '1.0.0',
295
+ last_updated: new Date().toISOString(),
296
+ entity_count: totalCount,
297
+ checksum_algorithm: 'sha256',
298
+ },
299
+ entities,
300
+ };
301
+
302
+ // Write the file
303
+ const dataDir = join(frameworkDir, 'data');
304
+ mkdirSync(dataDir, { recursive: true });
305
+
306
+ const outputPath = join(dataDir, 'entity-registry.yaml');
307
+ const header = [
308
+ '# chati.dev Entity Registry — Central Catalog of All System Artifacts',
309
+ '# Used by the Decision Engine for REUSE/ADAPT/CREATE recommendations',
310
+ '# and by the Health Check for system integrity validation.',
311
+ '',
312
+ ].join('\n');
313
+
314
+ const yamlContent = yaml.dump(registry, {
315
+ lineWidth: 120,
316
+ noRefs: true,
317
+ sortKeys: false,
318
+ });
319
+
320
+ writeFileSync(outputPath, header + yamlContent, 'utf8');
321
+
322
+ return { path: outputPath, registry };
323
+ }
324
+
325
+ /**
326
+ * Compare the existing registry with the current filesystem state.
327
+ * @param {string} frameworkDir - Path to the framework directory.
328
+ * @returns {{ added: object[], removed: object[], modified: object[] }} Diff result.
329
+ */
330
+ export function diffRegistry(frameworkDir) {
331
+ const result = { added: [], removed: [], modified: [] };
332
+
333
+ // Load existing registry
334
+ const regPath = join(frameworkDir, 'data', 'entity-registry.yaml');
335
+ let existingEntities = {};
336
+
337
+ if (existsSync(regPath)) {
338
+ try {
339
+ const existing = yaml.load(readFileSync(regPath, 'utf8'));
340
+ existingEntities = existing?.entities || {};
341
+ } catch {
342
+ // Cannot parse existing registry
343
+ }
344
+ }
345
+
346
+ // Build current state
347
+ const { registry: currentRegistry } = populateRegistryDry(frameworkDir);
348
+ const currentEntities = currentRegistry?.entities || {};
349
+
350
+ // Flatten both registries for comparison
351
+ const flatten = (entities) => {
352
+ const flat = new Map();
353
+ for (const [category, items] of Object.entries(entities)) {
354
+ if (typeof items === 'object' && items !== null) {
355
+ for (const [name, data] of Object.entries(items)) {
356
+ flat.set(`${category}.${name}`, data);
357
+ }
358
+ }
359
+ }
360
+ return flat;
361
+ };
362
+
363
+ const existingFlat = flatten(existingEntities);
364
+ const currentFlat = flatten(currentEntities);
365
+
366
+ // Find added
367
+ for (const [key, data] of currentFlat) {
368
+ if (!existingFlat.has(key)) {
369
+ result.added.push({ key, ...data });
370
+ }
371
+ }
372
+
373
+ // Find removed
374
+ for (const [key, data] of existingFlat) {
375
+ if (!currentFlat.has(key)) {
376
+ result.removed.push({ key, ...data });
377
+ }
378
+ }
379
+
380
+ // Find modified (path changed or purpose changed)
381
+ for (const [key, currentData] of currentFlat) {
382
+ if (existingFlat.has(key)) {
383
+ const existingData = existingFlat.get(key);
384
+ if (existingData.path !== currentData.path || existingData.purpose !== currentData.purpose) {
385
+ result.modified.push({ key, old: existingData, new: currentData });
386
+ }
387
+ }
388
+ }
389
+
390
+ return result;
391
+ }
392
+
393
+ /**
394
+ * Build registry without writing to disk (for diff comparisons).
395
+ * @param {string} frameworkDir - Path to the framework directory.
396
+ * @returns {{ registry: object }}
397
+ */
398
+ function populateRegistryDry(frameworkDir) {
399
+ if (!existsSync(frameworkDir)) {
400
+ return { registry: { entities: {} } };
401
+ }
402
+
403
+ const scanTargets = [
404
+ { relDir: 'agents', category: 'agents' },
405
+ { relDir: 'orchestrator', category: 'orchestrator' },
406
+ { relDir: 'schemas', category: 'schemas' },
407
+ { relDir: 'templates', category: 'templates' },
408
+ { relDir: 'workflows', category: 'workflows' },
409
+ { relDir: 'domains', category: 'domains' },
410
+ { relDir: 'hooks', category: 'hooks' },
411
+ { relDir: 'quality-gates', category: 'quality-gates' },
412
+ { relDir: 'intelligence', category: 'intelligence' },
413
+ { relDir: 'frameworks', category: 'frameworks' },
414
+ { relDir: 'patterns', category: 'patterns' },
415
+ { relDir: 'i18n', category: 'i18n' },
416
+ { relDir: 'migrations', category: 'migrations' },
417
+ ];
418
+
419
+ const entities = {};
420
+ let totalCount = 0;
421
+
422
+ for (const { relDir, category } of scanTargets) {
423
+ const found = scanEntities(frameworkDir, relDir, category);
424
+ if (found.length > 0) {
425
+ const groupName = category === 'orchestrator' ? 'agents' : category;
426
+ if (!entities[groupName]) entities[groupName] = {};
427
+
428
+ for (const entity of found) {
429
+ entities[groupName][entity.name] = {
430
+ path: entity.path,
431
+ type: entity.type,
432
+ purpose: entity.purpose,
433
+ };
434
+ totalCount++;
435
+ }
436
+ }
437
+ }
438
+
439
+ return {
440
+ registry: {
441
+ metadata: { entity_count: totalCount },
442
+ entities,
443
+ },
444
+ };
445
+ }
446
+
447
+ // ---------------------------------------------------------------------------
448
+ // CLI entrypoint
449
+ // ---------------------------------------------------------------------------
450
+
451
+ const isMainModule = process.argv[1] && (
452
+ process.argv[1].endsWith('populate-entity-registry.js') ||
453
+ process.argv[1].endsWith('populate-entity-registry')
454
+ );
455
+
456
+ if (isMainModule) {
457
+ const frameworkDir = process.argv[2] || join(process.cwd(), 'chati.dev');
458
+ const mode = process.argv[3] || 'populate';
459
+
460
+ if (mode === 'diff') {
461
+ console.log(`Diffing registry against: ${frameworkDir}`);
462
+ const diff = diffRegistry(frameworkDir);
463
+ console.log(`Added: ${diff.added.length}`);
464
+ for (const a of diff.added) console.log(` + ${a.key}`);
465
+ console.log(`Removed: ${diff.removed.length}`);
466
+ for (const r of diff.removed) console.log(` - ${r.key}`);
467
+ console.log(`Modified: ${diff.modified.length}`);
468
+ for (const m of diff.modified) console.log(` ~ ${m.key}`);
469
+ } else {
470
+ console.log(`Populating registry from: ${frameworkDir}`);
471
+ try {
472
+ const { path, registry } = populateRegistry(frameworkDir);
473
+ console.log(`Entities: ${registry.metadata.entity_count}`);
474
+ console.log(`Categories: ${Object.keys(registry.entities).join(', ')}`);
475
+ console.log(`Written to: ${path}`);
476
+ } catch (err) {
477
+ console.error(`Error: ${err.message}`);
478
+ process.exit(1);
479
+ }
480
+ }
481
+ }