chati-dev 1.3.3 → 2.0.1

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 (215) hide show
  1. package/README.md +7 -6
  2. package/framework/agents/build/dev.md +343 -0
  3. package/framework/agents/clarity/architect.md +113 -0
  4. package/framework/agents/clarity/brief.md +183 -0
  5. package/framework/agents/clarity/brownfield-wu.md +182 -0
  6. package/framework/agents/clarity/detail.md +111 -0
  7. package/framework/agents/clarity/greenfield-wu.md +154 -0
  8. package/framework/agents/clarity/phases.md +1 -0
  9. package/framework/agents/clarity/tasks.md +1 -0
  10. package/framework/agents/clarity/ux.md +113 -0
  11. package/framework/agents/deploy/devops.md +1 -0
  12. package/framework/agents/quality/qa-implementation.md +1 -0
  13. package/framework/agents/quality/qa-planning.md +1 -0
  14. package/framework/config.yaml +3 -3
  15. package/framework/constitution.md +58 -1
  16. package/framework/context/governance.md +37 -0
  17. package/framework/context/protocols.md +34 -0
  18. package/framework/context/quality.md +27 -0
  19. package/framework/context/root.md +24 -0
  20. package/framework/data/entity-registry.yaml +1 -1
  21. package/framework/domains/agents/architect.yaml +51 -0
  22. package/framework/domains/agents/brief.yaml +47 -0
  23. package/framework/domains/agents/brownfield-wu.yaml +49 -0
  24. package/framework/domains/agents/detail.yaml +47 -0
  25. package/framework/domains/agents/dev.yaml +49 -0
  26. package/framework/domains/agents/devops.yaml +43 -0
  27. package/framework/domains/agents/greenfield-wu.yaml +47 -0
  28. package/framework/domains/agents/orchestrator.yaml +49 -0
  29. package/framework/domains/agents/phases.yaml +47 -0
  30. package/framework/domains/agents/qa-implementation.yaml +43 -0
  31. package/framework/domains/agents/qa-planning.yaml +44 -0
  32. package/framework/domains/agents/tasks.yaml +48 -0
  33. package/framework/domains/agents/ux.yaml +50 -0
  34. package/framework/domains/constitution.yaml +77 -0
  35. package/framework/domains/global.yaml +64 -0
  36. package/framework/domains/workflows/brownfield-discovery.yaml +16 -0
  37. package/framework/domains/workflows/brownfield-fullstack.yaml +26 -0
  38. package/framework/domains/workflows/brownfield-service.yaml +22 -0
  39. package/framework/domains/workflows/brownfield-ui.yaml +22 -0
  40. package/framework/domains/workflows/greenfield-fullstack.yaml +26 -0
  41. package/framework/hooks/constitution-guard.js +101 -0
  42. package/framework/hooks/mode-governance.js +92 -0
  43. package/framework/hooks/model-governance.js +76 -0
  44. package/framework/hooks/prism-engine.js +89 -0
  45. package/framework/hooks/session-digest.js +60 -0
  46. package/framework/hooks/settings.json +44 -0
  47. package/framework/i18n/en.yaml +3 -3
  48. package/framework/i18n/es.yaml +3 -3
  49. package/framework/i18n/fr.yaml +3 -3
  50. package/framework/i18n/pt.yaml +3 -3
  51. package/framework/intelligence/context-engine.md +2 -2
  52. package/framework/intelligence/decision-engine.md +1 -1
  53. package/framework/migrations/v1.4-to-v2.0.yaml +167 -0
  54. package/framework/migrations/v2.0-to-v2.0.1.yaml +132 -0
  55. package/framework/orchestrator/chati.md +350 -7
  56. package/framework/schemas/session.schema.json +15 -0
  57. package/framework/tasks/architect-api-design.md +63 -0
  58. package/framework/tasks/architect-consolidate.md +47 -0
  59. package/framework/tasks/architect-db-design.md +73 -0
  60. package/framework/tasks/architect-design.md +95 -0
  61. package/framework/tasks/architect-security-review.md +62 -0
  62. package/framework/tasks/architect-stack-selection.md +53 -0
  63. package/framework/tasks/brief-consolidate.md +249 -0
  64. package/framework/tasks/brief-constraint-identify.md +277 -0
  65. package/framework/tasks/brief-extract-requirements.md +339 -0
  66. package/framework/tasks/brief-stakeholder-map.md +176 -0
  67. package/framework/tasks/brief-validate-completeness.md +121 -0
  68. package/framework/tasks/brownfield-wu-architecture-map.md +394 -0
  69. package/framework/tasks/brownfield-wu-deep-discovery.md +312 -0
  70. package/framework/tasks/brownfield-wu-dependency-scan.md +359 -0
  71. package/framework/tasks/brownfield-wu-migration-plan.md +483 -0
  72. package/framework/tasks/brownfield-wu-report.md +325 -0
  73. package/framework/tasks/brownfield-wu-risk-assess.md +424 -0
  74. package/framework/tasks/detail-acceptance-criteria.md +372 -0
  75. package/framework/tasks/detail-consolidate.md +138 -0
  76. package/framework/tasks/detail-edge-case-analysis.md +300 -0
  77. package/framework/tasks/detail-expand-prd.md +389 -0
  78. package/framework/tasks/detail-nfr-extraction.md +223 -0
  79. package/framework/tasks/dev-code-review.md +404 -0
  80. package/framework/tasks/dev-consolidate.md +543 -0
  81. package/framework/tasks/dev-debug.md +322 -0
  82. package/framework/tasks/dev-implement.md +252 -0
  83. package/framework/tasks/dev-iterate.md +411 -0
  84. package/framework/tasks/dev-pr-prepare.md +497 -0
  85. package/framework/tasks/dev-refactor.md +342 -0
  86. package/framework/tasks/dev-test-write.md +306 -0
  87. package/framework/tasks/devops-ci-setup.md +412 -0
  88. package/framework/tasks/devops-consolidate.md +712 -0
  89. package/framework/tasks/devops-deploy-config.md +598 -0
  90. package/framework/tasks/devops-monitoring-setup.md +658 -0
  91. package/framework/tasks/devops-release-prepare.md +673 -0
  92. package/framework/tasks/greenfield-wu-analyze-empty.md +169 -0
  93. package/framework/tasks/greenfield-wu-report.md +266 -0
  94. package/framework/tasks/greenfield-wu-scaffold-detection.md +203 -0
  95. package/framework/tasks/greenfield-wu-tech-stack-assess.md +255 -0
  96. package/framework/tasks/orchestrator-deviation.md +260 -0
  97. package/framework/tasks/orchestrator-escalate.md +276 -0
  98. package/framework/tasks/orchestrator-handoff.md +243 -0
  99. package/framework/tasks/orchestrator-health.md +372 -0
  100. package/framework/tasks/orchestrator-mode-switch.md +262 -0
  101. package/framework/tasks/orchestrator-resume.md +189 -0
  102. package/framework/tasks/orchestrator-route.md +169 -0
  103. package/framework/tasks/orchestrator-spawn-terminal.md +358 -0
  104. package/framework/tasks/orchestrator-status.md +260 -0
  105. package/framework/tasks/orchestrator-suggest-mode.md +372 -0
  106. package/framework/tasks/phases-breakdown.md +91 -0
  107. package/framework/tasks/phases-dependency-mapping.md +67 -0
  108. package/framework/tasks/phases-mvp-scoping.md +94 -0
  109. package/framework/tasks/qa-impl-consolidate.md +522 -0
  110. package/framework/tasks/qa-impl-performance-test.md +487 -0
  111. package/framework/tasks/qa-impl-regression-check.md +413 -0
  112. package/framework/tasks/qa-impl-sast-scan.md +402 -0
  113. package/framework/tasks/qa-impl-test-execute.md +344 -0
  114. package/framework/tasks/qa-impl-verdict.md +339 -0
  115. package/framework/tasks/qa-planning-consolidate.md +309 -0
  116. package/framework/tasks/qa-planning-coverage-plan.md +338 -0
  117. package/framework/tasks/qa-planning-gate-define.md +339 -0
  118. package/framework/tasks/qa-planning-risk-matrix.md +631 -0
  119. package/framework/tasks/qa-planning-test-strategy.md +217 -0
  120. package/framework/tasks/tasks-acceptance-write.md +75 -0
  121. package/framework/tasks/tasks-consolidate.md +57 -0
  122. package/framework/tasks/tasks-decompose.md +80 -0
  123. package/framework/tasks/tasks-estimate.md +66 -0
  124. package/framework/tasks/ux-a11y-check.md +49 -0
  125. package/framework/tasks/ux-component-map.md +55 -0
  126. package/framework/tasks/ux-consolidate.md +46 -0
  127. package/framework/tasks/ux-user-flow.md +46 -0
  128. package/framework/tasks/ux-wireframe.md +76 -0
  129. package/package.json +1 -1
  130. package/scripts/bundle-framework.js +2 -0
  131. package/scripts/changelog-generator.js +222 -0
  132. package/scripts/codebase-mapper.js +728 -0
  133. package/scripts/commit-message-generator.js +167 -0
  134. package/scripts/coverage-analyzer.js +260 -0
  135. package/scripts/dependency-analyzer.js +280 -0
  136. package/scripts/framework-analyzer.js +308 -0
  137. package/scripts/generate-constitution-domain.js +253 -0
  138. package/scripts/health-check.js +481 -0
  139. package/scripts/ide-sync.js +327 -0
  140. package/scripts/performance-analyzer.js +325 -0
  141. package/scripts/plan-tracker.js +278 -0
  142. package/scripts/populate-entity-registry.js +481 -0
  143. package/scripts/pr-review.js +317 -0
  144. package/scripts/rollback-manager.js +310 -0
  145. package/scripts/stuck-detector.js +343 -0
  146. package/scripts/test-quality-assessment.js +257 -0
  147. package/scripts/validate-agents.js +367 -0
  148. package/scripts/validate-tasks.js +465 -0
  149. package/src/autonomy/autonomous-gate.js +293 -0
  150. package/src/autonomy/index.js +51 -0
  151. package/src/autonomy/mode-manager.js +225 -0
  152. package/src/autonomy/mode-suggester.js +283 -0
  153. package/src/autonomy/progress-reporter.js +268 -0
  154. package/src/autonomy/safety-net.js +320 -0
  155. package/src/context/bracket-tracker.js +79 -0
  156. package/src/context/domain-loader.js +107 -0
  157. package/src/context/engine.js +144 -0
  158. package/src/context/formatter.js +184 -0
  159. package/src/context/index.js +4 -0
  160. package/src/context/layers/l0-constitution.js +28 -0
  161. package/src/context/layers/l1-global.js +37 -0
  162. package/src/context/layers/l2-agent.js +39 -0
  163. package/src/context/layers/l3-workflow.js +42 -0
  164. package/src/context/layers/l4-task.js +24 -0
  165. package/src/decision/analyzer.js +167 -0
  166. package/src/decision/engine.js +270 -0
  167. package/src/decision/index.js +38 -0
  168. package/src/decision/registry-healer.js +450 -0
  169. package/src/decision/registry-updater.js +330 -0
  170. package/src/gates/circuit-breaker.js +119 -0
  171. package/src/gates/g1-planning-complete.js +153 -0
  172. package/src/gates/g2-qa-planning.js +153 -0
  173. package/src/gates/g3-implementation.js +188 -0
  174. package/src/gates/g4-qa-implementation.js +207 -0
  175. package/src/gates/g5-deploy-ready.js +180 -0
  176. package/src/gates/gate-base.js +144 -0
  177. package/src/gates/index.js +46 -0
  178. package/src/installer/brownfield-upgrader.js +249 -0
  179. package/src/installer/core.js +55 -3
  180. package/src/installer/file-hasher.js +51 -0
  181. package/src/installer/manifest.js +117 -0
  182. package/src/installer/templates.js +17 -15
  183. package/src/installer/transaction.js +229 -0
  184. package/src/installer/validator.js +18 -1
  185. package/src/intelligence/registry-manager.js +2 -2
  186. package/src/memory/agent-memory.js +255 -0
  187. package/src/memory/gotchas-injector.js +72 -0
  188. package/src/memory/gotchas.js +361 -0
  189. package/src/memory/index.js +35 -0
  190. package/src/memory/search.js +233 -0
  191. package/src/memory/session-digest.js +239 -0
  192. package/src/merger/env-merger.js +112 -0
  193. package/src/merger/index.js +56 -0
  194. package/src/merger/replace-merger.js +51 -0
  195. package/src/merger/yaml-merger.js +127 -0
  196. package/src/orchestrator/agent-selector.js +285 -0
  197. package/src/orchestrator/deviation-handler.js +350 -0
  198. package/src/orchestrator/handoff-engine.js +271 -0
  199. package/src/orchestrator/index.js +67 -0
  200. package/src/orchestrator/intent-classifier.js +264 -0
  201. package/src/orchestrator/pipeline-manager.js +492 -0
  202. package/src/orchestrator/pipeline-state.js +223 -0
  203. package/src/orchestrator/session-manager.js +409 -0
  204. package/src/tasks/executor.js +195 -0
  205. package/src/tasks/handoff.js +226 -0
  206. package/src/tasks/index.js +4 -0
  207. package/src/tasks/loader.js +210 -0
  208. package/src/tasks/router.js +182 -0
  209. package/src/terminal/collector.js +216 -0
  210. package/src/terminal/index.js +30 -0
  211. package/src/terminal/isolation.js +129 -0
  212. package/src/terminal/monitor.js +277 -0
  213. package/src/terminal/spawner.js +269 -0
  214. package/src/upgrade/checker.js +1 -1
  215. package/src/wizard/i18n.js +3 -3
@@ -0,0 +1,450 @@
1
+ /**
2
+ * Registry Healer - Self-healing for entity-registry.yaml
3
+ * Detects and fixes 6 types of registry issues
4
+ *
5
+ * @module decision/registry-healer
6
+ */
7
+
8
+ import { readFileSync, writeFileSync, existsSync, statSync, copyFileSync, mkdirSync } from 'fs';
9
+ import { join, isAbsolute } from 'path';
10
+ import yaml from 'js-yaml';
11
+ import { detectNewEntities, detectRemovedEntities, generateEntityMeta } from './registry-updater.js';
12
+
13
+ /**
14
+ * Run all healing rules and return diagnosis.
15
+ * @param {string} projectDir
16
+ * @param {object} [options] - { autoFix: boolean }
17
+ * @returns {{ healthy: boolean, issues: object[], fixes: object[] }}
18
+ */
19
+ export function healRegistry(projectDir, options = {}) {
20
+ const { autoFix = false } = options;
21
+
22
+ const issues = [];
23
+ const fixes = [];
24
+
25
+ // Rule 1: Missing entities
26
+ const missingIssues = detectMissingEntities(projectDir);
27
+ issues.push(...missingIssues);
28
+ if (autoFix) {
29
+ missingIssues.forEach(issue => {
30
+ fixes.push({
31
+ rule: 'missing_entity',
32
+ action: 'add',
33
+ path: issue.path,
34
+ entity: issue.entity
35
+ });
36
+ });
37
+ }
38
+
39
+ // Rule 2: Orphaned entries
40
+ const orphanedIssues = detectOrphanedEntries(projectDir);
41
+ issues.push(...orphanedIssues);
42
+ if (autoFix) {
43
+ orphanedIssues.forEach(issue => {
44
+ fixes.push({
45
+ rule: 'orphaned_entry',
46
+ action: 'remove',
47
+ path: issue.path
48
+ });
49
+ });
50
+ }
51
+
52
+ // Rule 3: Stale metadata
53
+ const staleIssues = detectStaleMetadata(projectDir);
54
+ issues.push(...staleIssues);
55
+ if (autoFix) {
56
+ staleIssues.forEach(issue => {
57
+ fixes.push({
58
+ rule: 'stale_metadata',
59
+ action: 'update',
60
+ path: issue.path,
61
+ newMeta: issue.newMeta
62
+ });
63
+ });
64
+ }
65
+
66
+ // Rule 4: Duplicates
67
+ const duplicateIssues = detectDuplicates(projectDir);
68
+ issues.push(...duplicateIssues);
69
+ if (autoFix) {
70
+ duplicateIssues.forEach(issue => {
71
+ fixes.push({
72
+ rule: 'duplicate_entry',
73
+ action: 'deduplicate',
74
+ path: issue.path,
75
+ count: issue.count
76
+ });
77
+ });
78
+ }
79
+
80
+ // Rule 5: Invalid paths
81
+ const invalidPathIssues = detectInvalidPaths(projectDir);
82
+ issues.push(...invalidPathIssues);
83
+ if (autoFix) {
84
+ invalidPathIssues.forEach(issue => {
85
+ fixes.push({
86
+ rule: 'invalid_path',
87
+ action: 'remove',
88
+ path: issue.path,
89
+ reason: issue.reason
90
+ });
91
+ });
92
+ }
93
+
94
+ // Rule 6: Count mismatch
95
+ const countIssues = detectCountMismatch(projectDir);
96
+ issues.push(...countIssues);
97
+ if (autoFix) {
98
+ countIssues.forEach(issue => {
99
+ fixes.push({
100
+ rule: 'count_mismatch',
101
+ action: 'fix_count',
102
+ expected: issue.actual,
103
+ current: issue.recorded
104
+ });
105
+ });
106
+ }
107
+
108
+ // Apply fixes if requested
109
+ if (autoFix && fixes.length > 0) {
110
+ const result = applyFixes(projectDir, fixes);
111
+ return {
112
+ healthy: issues.length === 0,
113
+ issues,
114
+ fixes,
115
+ applied: result.applied,
116
+ backup: result.backup
117
+ };
118
+ }
119
+
120
+ return {
121
+ healthy: issues.length === 0,
122
+ issues,
123
+ fixes
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Rule 1: Missing entities (file exists but not in registry).
129
+ * @param {string} projectDir
130
+ * @returns {object[]} Issues found
131
+ */
132
+ export function detectMissingEntities(projectDir) {
133
+ const newEntities = detectNewEntities(projectDir);
134
+ return newEntities.map(entity => ({
135
+ rule: 'missing_entity',
136
+ severity: 'warning',
137
+ path: entity.path,
138
+ message: `File exists but not in registry: ${entity.path}`,
139
+ entity
140
+ }));
141
+ }
142
+
143
+ /**
144
+ * Rule 2: Orphaned entries (in registry but file doesn't exist).
145
+ * @param {string} projectDir
146
+ * @returns {object[]} Issues found
147
+ */
148
+ export function detectOrphanedEntries(projectDir) {
149
+ const removedEntities = detectRemovedEntities(projectDir);
150
+ return removedEntities.map(entity => ({
151
+ rule: 'orphaned_entry',
152
+ severity: 'error',
153
+ path: entity.path,
154
+ message: `Registry entry exists but file not found: ${entity.path}`
155
+ }));
156
+ }
157
+
158
+ /**
159
+ * Rule 3: Stale metadata (file modified after registry entry).
160
+ * @param {string} projectDir
161
+ * @returns {object[]} Issues found
162
+ */
163
+ export function detectStaleMetadata(projectDir) {
164
+ const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
165
+ if (!existsSync(registryPath)) {
166
+ return [];
167
+ }
168
+
169
+ const registry = yaml.load(readFileSync(registryPath, 'utf8'));
170
+ const lastUpdated = registry.metadata?.last_updated
171
+ ? new Date(registry.metadata.last_updated)
172
+ : new Date(0);
173
+
174
+ const issues = [];
175
+
176
+ Object.entries(registry.entities || {}).forEach(([_type, entities]) => {
177
+ if (Array.isArray(entities)) {
178
+ entities.forEach(entity => {
179
+ if (entity.path) {
180
+ const fullPath = join(projectDir, entity.path);
181
+ if (existsSync(fullPath)) {
182
+ const stats = statSync(fullPath);
183
+ if (stats.mtime > lastUpdated) {
184
+ const newMeta = generateEntityMeta(projectDir, entity.path);
185
+ issues.push({
186
+ rule: 'stale_metadata',
187
+ severity: 'info',
188
+ path: entity.path,
189
+ message: `File modified after registry update: ${entity.path}`,
190
+ newMeta
191
+ });
192
+ }
193
+ }
194
+ }
195
+ });
196
+ }
197
+ });
198
+
199
+ return issues;
200
+ }
201
+
202
+ /**
203
+ * Rule 4: Duplicate entries (same path appears twice).
204
+ * @param {string} projectDir
205
+ * @returns {object[]} Issues found
206
+ */
207
+ export function detectDuplicates(projectDir) {
208
+ const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
209
+ if (!existsSync(registryPath)) {
210
+ return [];
211
+ }
212
+
213
+ const registry = yaml.load(readFileSync(registryPath, 'utf8'));
214
+ const pathCounts = new Map();
215
+ const issues = [];
216
+
217
+ Object.values(registry.entities || {}).forEach(entities => {
218
+ if (Array.isArray(entities)) {
219
+ entities.forEach(entity => {
220
+ if (entity.path) {
221
+ pathCounts.set(entity.path, (pathCounts.get(entity.path) || 0) + 1);
222
+ }
223
+ });
224
+ }
225
+ });
226
+
227
+ pathCounts.forEach((count, path) => {
228
+ if (count > 1) {
229
+ issues.push({
230
+ rule: 'duplicate_entry',
231
+ severity: 'error',
232
+ path,
233
+ count,
234
+ message: `Duplicate registry entries for path: ${path} (${count} times)`
235
+ });
236
+ }
237
+ });
238
+
239
+ return issues;
240
+ }
241
+
242
+ /**
243
+ * Rule 5: Invalid paths (malformed or absolute paths).
244
+ * @param {string} projectDir
245
+ * @returns {object[]} Issues found
246
+ */
247
+ export function detectInvalidPaths(projectDir) {
248
+ const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
249
+ if (!existsSync(registryPath)) {
250
+ return [];
251
+ }
252
+
253
+ const registry = yaml.load(readFileSync(registryPath, 'utf8'));
254
+ const issues = [];
255
+
256
+ Object.values(registry.entities || {}).forEach(entities => {
257
+ if (Array.isArray(entities)) {
258
+ entities.forEach(entity => {
259
+ if (!entity.path) {
260
+ issues.push({
261
+ rule: 'invalid_path',
262
+ severity: 'error',
263
+ path: '<missing>',
264
+ reason: 'Missing path field',
265
+ message: 'Entity missing path field'
266
+ });
267
+ } else if (isAbsolute(entity.path)) {
268
+ issues.push({
269
+ rule: 'invalid_path',
270
+ severity: 'error',
271
+ path: entity.path,
272
+ reason: 'Absolute path',
273
+ message: `Path should be relative: ${entity.path}`
274
+ });
275
+ } else if (entity.path.includes('..')) {
276
+ issues.push({
277
+ rule: 'invalid_path',
278
+ severity: 'error',
279
+ path: entity.path,
280
+ reason: 'Contains parent directory reference',
281
+ message: `Path contains ..: ${entity.path}`
282
+ });
283
+ }
284
+ });
285
+ }
286
+ });
287
+
288
+ return issues;
289
+ }
290
+
291
+ /**
292
+ * Rule 6: Entity count mismatch (metadata.entity_count != actual).
293
+ * @param {string} projectDir
294
+ * @returns {object[]} Issues found
295
+ */
296
+ export function detectCountMismatch(projectDir) {
297
+ const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
298
+ if (!existsSync(registryPath)) {
299
+ return [];
300
+ }
301
+
302
+ const registry = yaml.load(readFileSync(registryPath, 'utf8'));
303
+ let actualCount = 0;
304
+
305
+ Object.values(registry.entities || {}).forEach(entities => {
306
+ if (Array.isArray(entities)) {
307
+ actualCount += entities.length;
308
+ }
309
+ });
310
+
311
+ const recordedCount = registry.metadata?.entity_count || 0;
312
+
313
+ if (actualCount !== recordedCount) {
314
+ return [{
315
+ rule: 'count_mismatch',
316
+ severity: 'warning',
317
+ actual: actualCount,
318
+ recorded: recordedCount,
319
+ message: `Entity count mismatch: metadata says ${recordedCount}, actual is ${actualCount}`
320
+ }];
321
+ }
322
+
323
+ return [];
324
+ }
325
+
326
+ /**
327
+ * Apply fixes and create backup before modifying.
328
+ * @param {string} projectDir
329
+ * @param {object[]} fixes
330
+ * @returns {{ applied: number, backup: string }}
331
+ */
332
+ export function applyFixes(projectDir, fixes) {
333
+ const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
334
+
335
+ if (!existsSync(registryPath)) {
336
+ return { applied: 0, backup: null };
337
+ }
338
+
339
+ // Create backup
340
+ const backupDir = join(projectDir, '.chati', 'backups');
341
+ if (!existsSync(backupDir)) {
342
+ mkdirSync(backupDir, { recursive: true });
343
+ }
344
+
345
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
346
+ const backupPath = join(backupDir, `entity-registry-${timestamp}.yaml`);
347
+ copyFileSync(registryPath, backupPath);
348
+
349
+ // Load registry
350
+ const registry = yaml.load(readFileSync(registryPath, 'utf8'));
351
+
352
+ let applied = 0;
353
+
354
+ fixes.forEach(fix => {
355
+ try {
356
+ switch (fix.action) {
357
+ case 'add':
358
+ // Add missing entity
359
+ if (fix.entity) {
360
+ const type = fix.entity.type;
361
+ if (!registry.entities[type]) {
362
+ registry.entities[type] = [];
363
+ }
364
+ registry.entities[type].push(fix.entity);
365
+ applied++;
366
+ }
367
+ break;
368
+
369
+ case 'remove':
370
+ // Remove orphaned entry
371
+ Object.keys(registry.entities).forEach(type => {
372
+ if (Array.isArray(registry.entities[type])) {
373
+ registry.entities[type] = registry.entities[type].filter(
374
+ e => e.path !== fix.path
375
+ );
376
+ }
377
+ });
378
+ applied++;
379
+ break;
380
+
381
+ case 'update':
382
+ // Update stale metadata
383
+ Object.keys(registry.entities).forEach(type => {
384
+ if (Array.isArray(registry.entities[type])) {
385
+ const index = registry.entities[type].findIndex(
386
+ e => e.path === fix.path
387
+ );
388
+ if (index !== -1) {
389
+ registry.entities[type][index] = {
390
+ ...registry.entities[type][index],
391
+ ...fix.newMeta
392
+ };
393
+ }
394
+ }
395
+ });
396
+ applied++;
397
+ break;
398
+
399
+ case 'deduplicate':
400
+ // Remove duplicate entries (keep first occurrence)
401
+ Object.keys(registry.entities).forEach(type => {
402
+ if (Array.isArray(registry.entities[type])) {
403
+ const seen = new Set();
404
+ registry.entities[type] = registry.entities[type].filter(e => {
405
+ if (e.path === fix.path) {
406
+ if (seen.has(e.path)) {
407
+ return false; // Remove duplicate
408
+ }
409
+ seen.add(e.path);
410
+ }
411
+ return true;
412
+ });
413
+ }
414
+ });
415
+ applied++;
416
+ break;
417
+
418
+ case 'fix_count':
419
+ // Fix entity count
420
+ registry.metadata = registry.metadata || {};
421
+ registry.metadata.entity_count = fix.expected;
422
+ applied++;
423
+ break;
424
+ }
425
+ } catch {
426
+ // Skip failed fixes
427
+ }
428
+ });
429
+
430
+ // Update metadata
431
+ registry.metadata = registry.metadata || {};
432
+ registry.metadata.last_updated = new Date().toISOString();
433
+
434
+ // Recalculate entity count
435
+ let totalCount = 0;
436
+ Object.values(registry.entities).forEach(entities => {
437
+ if (Array.isArray(entities)) {
438
+ totalCount += entities.length;
439
+ }
440
+ });
441
+ registry.metadata.entity_count = totalCount;
442
+
443
+ // Write updated registry
444
+ writeFileSync(registryPath, yaml.dump(registry), 'utf8');
445
+
446
+ return {
447
+ applied,
448
+ backup: backupPath
449
+ };
450
+ }