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,308 @@
1
+ /**
2
+ * Framework Analyzer — Detects project technology stack through static analysis.
3
+ *
4
+ * Inspects package.json, configuration files, and file patterns to identify
5
+ * frameworks, languages, package managers, ORMs, databases, and monorepo tools.
6
+ *
7
+ * @module scripts/framework-analyzer
8
+ */
9
+
10
+ import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+
13
+ /**
14
+ * @typedef {Object} FrameworkReport
15
+ * @property {string[]} frontendFrameworks
16
+ * @property {string[]} backendFrameworks
17
+ * @property {string[]} languages
18
+ * @property {string} packageManager
19
+ * @property {string[]} monorepoTools
20
+ * @property {string[]} orms
21
+ * @property {string[]} databases
22
+ * @property {string[]} testingFrameworks
23
+ * @property {string[]} buildTools
24
+ * @property {Object} raw — raw detection results before merging
25
+ */
26
+
27
+ /**
28
+ * Safely reads and parses a JSON file.
29
+ * @param {string} filePath
30
+ * @returns {Object|null}
31
+ */
32
+ function readJSON(filePath) {
33
+ try {
34
+ return JSON.parse(readFileSync(filePath, 'utf-8'));
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Checks if a file or directory exists.
42
+ * @param {...string} segments
43
+ * @returns {boolean}
44
+ */
45
+ function pathExists(...segments) {
46
+ return existsSync(join(...segments));
47
+ }
48
+
49
+ /**
50
+ * Detects technologies from package.json dependencies.
51
+ *
52
+ * @param {string} targetDir
53
+ * @returns {Partial<FrameworkReport>}
54
+ */
55
+ export function detectByPackageJson(targetDir) {
56
+ const pkg = readJSON(join(targetDir, 'package.json'));
57
+ if (!pkg) return {};
58
+
59
+ const allDeps = {
60
+ ...pkg.dependencies,
61
+ ...pkg.devDependencies,
62
+ ...pkg.peerDependencies,
63
+ };
64
+ const depNames = new Set(Object.keys(allDeps));
65
+
66
+ const frontendFrameworks = [];
67
+ const backendFrameworks = [];
68
+ const orms = [];
69
+ const databases = [];
70
+ const testingFrameworks = [];
71
+ const buildTools = [];
72
+ const monorepoTools = [];
73
+
74
+ // Frontend
75
+ if (depNames.has('next')) frontendFrameworks.push('Next.js');
76
+ else if (depNames.has('react')) frontendFrameworks.push('React');
77
+ if (depNames.has('nuxt') || depNames.has('nuxt3')) frontendFrameworks.push('Nuxt');
78
+ else if (depNames.has('vue')) frontendFrameworks.push('Vue');
79
+ if (depNames.has('@sveltejs/kit')) frontendFrameworks.push('SvelteKit');
80
+ else if (depNames.has('svelte')) frontendFrameworks.push('Svelte');
81
+ if (depNames.has('@angular/core')) frontendFrameworks.push('Angular');
82
+
83
+ // Backend
84
+ if (depNames.has('express')) backendFrameworks.push('Express');
85
+ if (depNames.has('fastify')) backendFrameworks.push('Fastify');
86
+ if (depNames.has('koa')) backendFrameworks.push('Koa');
87
+ if (depNames.has('@nestjs/core')) backendFrameworks.push('NestJS');
88
+ if (depNames.has('hono')) backendFrameworks.push('Hono');
89
+ if (depNames.has('@hapi/hapi') || depNames.has('hapi')) backendFrameworks.push('Hapi');
90
+
91
+ // ORM
92
+ if (depNames.has('prisma') || depNames.has('@prisma/client')) orms.push('Prisma');
93
+ if (depNames.has('drizzle-orm')) orms.push('Drizzle');
94
+ if (depNames.has('typeorm')) orms.push('TypeORM');
95
+ if (depNames.has('sequelize')) orms.push('Sequelize');
96
+ if (depNames.has('mongoose')) orms.push('Mongoose');
97
+ if (depNames.has('knex')) orms.push('Knex');
98
+
99
+ // Database
100
+ if (depNames.has('pg') || depNames.has('postgres') || depNames.has('@vercel/postgres')) databases.push('PostgreSQL');
101
+ if (depNames.has('mysql2') || depNames.has('mysql')) databases.push('MySQL');
102
+ if (depNames.has('mongodb') || depNames.has('mongoose')) databases.push('MongoDB');
103
+ if (depNames.has('better-sqlite3') || depNames.has('sqlite3')) databases.push('SQLite');
104
+ if (depNames.has('redis') || depNames.has('ioredis')) databases.push('Redis');
105
+
106
+ // Testing
107
+ if (depNames.has('jest')) testingFrameworks.push('Jest');
108
+ if (depNames.has('vitest')) testingFrameworks.push('Vitest');
109
+ if (depNames.has('mocha')) testingFrameworks.push('Mocha');
110
+ if (depNames.has('@playwright/test') || depNames.has('playwright')) testingFrameworks.push('Playwright');
111
+ if (depNames.has('cypress')) testingFrameworks.push('Cypress');
112
+
113
+ // Build tools
114
+ if (depNames.has('vite')) buildTools.push('Vite');
115
+ if (depNames.has('webpack')) buildTools.push('Webpack');
116
+ if (depNames.has('esbuild')) buildTools.push('esbuild');
117
+ if (depNames.has('rollup')) buildTools.push('Rollup');
118
+ if (depNames.has('tsup')) buildTools.push('tsup');
119
+ if (depNames.has('turbo')) buildTools.push('Turborepo');
120
+
121
+ // Monorepo
122
+ if (depNames.has('turbo') || depNames.has('turborepo')) monorepoTools.push('Turborepo');
123
+ if (depNames.has('lerna')) monorepoTools.push('Lerna');
124
+ if (depNames.has('nx') || depNames.has('@nrwl/workspace')) monorepoTools.push('Nx');
125
+
126
+ return { frontendFrameworks, backendFrameworks, orms, databases, testingFrameworks, buildTools, monorepoTools };
127
+ }
128
+
129
+ /**
130
+ * Detects technologies from configuration files present in the project.
131
+ *
132
+ * @param {string} targetDir
133
+ * @returns {Partial<FrameworkReport>}
134
+ */
135
+ export function detectByConfigFiles(targetDir) {
136
+ const languages = [];
137
+ const buildTools = [];
138
+ const monorepoTools = [];
139
+
140
+ // TypeScript
141
+ if (pathExists(targetDir, 'tsconfig.json') || pathExists(targetDir, 'tsconfig.base.json')) {
142
+ languages.push('TypeScript');
143
+ }
144
+
145
+ // Python
146
+ if (pathExists(targetDir, 'pyproject.toml') || pathExists(targetDir, 'setup.py') || pathExists(targetDir, 'requirements.txt')) {
147
+ languages.push('Python');
148
+ }
149
+
150
+ // Go
151
+ if (pathExists(targetDir, 'go.mod')) {
152
+ languages.push('Go');
153
+ }
154
+
155
+ // Rust
156
+ if (pathExists(targetDir, 'Cargo.toml')) {
157
+ languages.push('Rust');
158
+ }
159
+
160
+ // Build tools
161
+ if (pathExists(targetDir, 'vite.config.js') || pathExists(targetDir, 'vite.config.ts')) {
162
+ buildTools.push('Vite');
163
+ }
164
+ if (pathExists(targetDir, 'webpack.config.js') || pathExists(targetDir, 'webpack.config.ts')) {
165
+ buildTools.push('Webpack');
166
+ }
167
+ if (pathExists(targetDir, 'rollup.config.js') || pathExists(targetDir, 'rollup.config.mjs')) {
168
+ buildTools.push('Rollup');
169
+ }
170
+
171
+ // Monorepo
172
+ if (pathExists(targetDir, 'turbo.json')) monorepoTools.push('Turborepo');
173
+ if (pathExists(targetDir, 'lerna.json')) monorepoTools.push('Lerna');
174
+ if (pathExists(targetDir, 'nx.json')) monorepoTools.push('Nx');
175
+ if (pathExists(targetDir, 'pnpm-workspace.yaml')) monorepoTools.push('pnpm workspaces');
176
+
177
+ // Package manager detection
178
+ let packageManager = 'npm'; // default
179
+ if (pathExists(targetDir, 'bun.lockb') || pathExists(targetDir, 'bun.lock')) {
180
+ packageManager = 'bun';
181
+ } else if (pathExists(targetDir, 'pnpm-lock.yaml')) {
182
+ packageManager = 'pnpm';
183
+ } else if (pathExists(targetDir, 'yarn.lock')) {
184
+ packageManager = 'yarn';
185
+ }
186
+
187
+ return { languages, buildTools, monorepoTools, packageManager };
188
+ }
189
+
190
+ /**
191
+ * Detects technologies from file patterns in the project directory.
192
+ *
193
+ * @param {string} targetDir
194
+ * @returns {Partial<FrameworkReport>}
195
+ */
196
+ export function detectByFilePatterns(targetDir) {
197
+ const languages = [];
198
+
199
+ // Check top-level files and src/ for language clues
200
+ const dirsToCheck = [targetDir];
201
+ if (pathExists(targetDir, 'src')) dirsToCheck.push(join(targetDir, 'src'));
202
+
203
+ const extensions = new Set();
204
+ for (const dir of dirsToCheck) {
205
+ let entries;
206
+ try {
207
+ entries = readdirSync(dir);
208
+ } catch {
209
+ continue;
210
+ }
211
+ for (const entry of entries) {
212
+ try {
213
+ const stat = statSync(join(dir, entry));
214
+ if (stat.isFile()) {
215
+ const ext = entry.slice(entry.lastIndexOf('.'));
216
+ extensions.add(ext);
217
+ }
218
+ } catch {
219
+ // skip
220
+ }
221
+ }
222
+ }
223
+
224
+ if (extensions.has('.ts') || extensions.has('.tsx')) {
225
+ languages.push('TypeScript');
226
+ }
227
+ if (extensions.has('.js') || extensions.has('.jsx') || extensions.has('.mjs')) {
228
+ languages.push('JavaScript');
229
+ }
230
+ if (extensions.has('.py')) {
231
+ languages.push('Python');
232
+ }
233
+ if (extensions.has('.go')) {
234
+ languages.push('Go');
235
+ }
236
+ if (extensions.has('.rs')) {
237
+ languages.push('Rust');
238
+ }
239
+
240
+ return { languages };
241
+ }
242
+
243
+ /**
244
+ * Merges multiple partial detection reports, deduplicating arrays.
245
+ *
246
+ * @param {...Partial<FrameworkReport>} reports
247
+ * @returns {FrameworkReport}
248
+ */
249
+ export function mergeDetections(...reports) {
250
+ const merged = {
251
+ frontendFrameworks: [],
252
+ backendFrameworks: [],
253
+ languages: [],
254
+ packageManager: 'npm',
255
+ monorepoTools: [],
256
+ orms: [],
257
+ databases: [],
258
+ testingFrameworks: [],
259
+ buildTools: [],
260
+ raw: {},
261
+ };
262
+
263
+ const arrayFields = [
264
+ 'frontendFrameworks',
265
+ 'backendFrameworks',
266
+ 'languages',
267
+ 'monorepoTools',
268
+ 'orms',
269
+ 'databases',
270
+ 'testingFrameworks',
271
+ 'buildTools',
272
+ ];
273
+
274
+ for (const report of reports) {
275
+ if (!report) continue;
276
+ for (const field of arrayFields) {
277
+ if (Array.isArray(report[field])) {
278
+ for (const item of report[field]) {
279
+ if (!merged[field].includes(item)) {
280
+ merged[field].push(item);
281
+ }
282
+ }
283
+ }
284
+ }
285
+ if (report.packageManager) {
286
+ merged.packageManager = report.packageManager;
287
+ }
288
+ }
289
+
290
+ // Store raw detections for debugging
291
+ merged.raw = { reports: reports.filter(Boolean) };
292
+
293
+ return merged;
294
+ }
295
+
296
+ /**
297
+ * Full framework analysis for a target directory.
298
+ *
299
+ * @param {string} targetDir
300
+ * @returns {FrameworkReport}
301
+ */
302
+ export function analyzeFramework(targetDir) {
303
+ const byPackage = detectByPackageJson(targetDir);
304
+ const byConfig = detectByConfigFiles(targetDir);
305
+ const byFiles = detectByFilePatterns(targetDir);
306
+
307
+ return mergeDetections(byPackage, byConfig, byFiles);
308
+ }
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Generate Constitution Domain — Extracts governance rules from constitution.md
5
+ * and generates a structured YAML domain file.
6
+ *
7
+ * Exports:
8
+ * generateConstitutionDomain(frameworkDir) → writes domains/constitution.yaml
9
+ */
10
+
11
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
12
+ import { join } from 'path';
13
+ import yaml from 'js-yaml';
14
+
15
+ /**
16
+ * Map Roman numerals to integers.
17
+ * @param {string} roman - Roman numeral string.
18
+ * @returns {number} Integer value.
19
+ */
20
+ function romanToInt(roman) {
21
+ const values = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 };
22
+ let result = 0;
23
+ for (let i = 0; i < roman.length; i++) {
24
+ const current = values[roman[i]] || 0;
25
+ const next = values[roman[i + 1]] || 0;
26
+ result += current < next ? -current : current;
27
+ }
28
+ return result;
29
+ }
30
+
31
+ /**
32
+ * Extract articles from constitution.md content.
33
+ * @param {string} content - Full constitution markdown.
34
+ * @returns {object[]} Array of { id, numeral, title, rules, enforcement }.
35
+ */
36
+ function extractArticles(content) {
37
+ const articles = [];
38
+
39
+ // Split by article headers
40
+ const articleRegex = /^## Article ([IVXLCDM]+):\s*(.+)/gm;
41
+ let match;
42
+ const articlePositions = [];
43
+
44
+ while ((match = articleRegex.exec(content)) !== null) {
45
+ articlePositions.push({
46
+ numeral: match[1],
47
+ title: match[2].trim(),
48
+ start: match.index,
49
+ });
50
+ }
51
+
52
+ for (let i = 0; i < articlePositions.length; i++) {
53
+ const pos = articlePositions[i];
54
+ const end = i + 1 < articlePositions.length ? articlePositions[i + 1].start : content.length;
55
+ const section = content.slice(pos.start, end);
56
+
57
+ // Extract enforcement
58
+ const enforcementMatch = section.match(/\*\*Enforcement:\s*(BLOCK|GUIDE|WARN)\*\*/);
59
+ const enforcement = enforcementMatch ? enforcementMatch[1].toLowerCase() : 'unknown';
60
+
61
+ // Extract numbered rules (lines starting with number followed by period)
62
+ const rules = [];
63
+ const ruleLines = section.match(/^\d+\.\s+.+/gm) || [];
64
+ for (const line of ruleLines) {
65
+ const ruleText = line.replace(/^\d+\.\s+/, '').replace(/\*\*/g, '').trim();
66
+ if (ruleText.length > 10) {
67
+ rules.push(ruleText);
68
+ }
69
+ }
70
+
71
+ articles.push({
72
+ id: pos.numeral,
73
+ number: romanToInt(pos.numeral),
74
+ title: pos.title,
75
+ rules,
76
+ enforcement,
77
+ });
78
+ }
79
+
80
+ return articles;
81
+ }
82
+
83
+ /**
84
+ * Extract mode definitions from Article XI.
85
+ * @param {string} content - Full constitution markdown.
86
+ * @returns {object|null} Mode definitions.
87
+ */
88
+ function extractModes(content) {
89
+ // Find Article XI section
90
+ const xiMatch = content.match(/## Article XI[\s\S]*?(?=## Article [IVXLCDM]+:|$)/);
91
+ if (!xiMatch) return null;
92
+
93
+ const section = xiMatch[0];
94
+
95
+ const modes = {};
96
+
97
+ // Extract from the table
98
+ const tableRegex = /\|\s*\*\*(\w+)\*\*\s*\|([^|]*)\|([^|]*)\|([^|]*)\|/g;
99
+ let tableMatch;
100
+
101
+ while ((tableMatch = tableRegex.exec(section)) !== null) {
102
+ const modeName = tableMatch[1].toLowerCase();
103
+ const states = tableMatch[2].trim();
104
+ const reads = tableMatch[3].trim();
105
+ const writes = tableMatch[4].trim();
106
+
107
+ modes[modeName] = {
108
+ states: states.split(',').map(s => s.trim()).filter(Boolean),
109
+ reads: reads || 'Entire project',
110
+ writes: writes || 'unknown',
111
+ };
112
+ }
113
+
114
+ return Object.keys(modes).length > 0 ? modes : null;
115
+ }
116
+
117
+ /**
118
+ * Extract blocker codes from constitution content.
119
+ * @param {string} content - Full constitution markdown.
120
+ * @returns {{ critical: string[], general: string[] }}
121
+ */
122
+ function extractBlockers(content) {
123
+ const critical = [];
124
+ const general = [];
125
+
126
+ // Look for C01-C99 pattern (critical blockers)
127
+ const criticalMatches = content.match(/C\d{2}/g) || [];
128
+ for (const c of [...new Set(criticalMatches)]) {
129
+ critical.push(c);
130
+ }
131
+
132
+ // Look for G01-G99 pattern (general blockers)
133
+ const generalMatches = content.match(/G\d{2}/g) || [];
134
+ for (const g of [...new Set(generalMatches)]) {
135
+ general.push(g);
136
+ }
137
+
138
+ return { critical: critical.sort(), general: general.sort() };
139
+ }
140
+
141
+ /**
142
+ * Extract quality thresholds from constitution content.
143
+ * @param {string} content - Full constitution markdown.
144
+ * @returns {object} Quality thresholds.
145
+ */
146
+ function extractQualityThresholds(content) {
147
+ const thresholds = {};
148
+
149
+ // Look for percentage thresholds
150
+ const selfValidation = content.match(/>= (\d+)%.*(?:self|validation|criteria|quality)/i);
151
+ if (selfValidation) {
152
+ thresholds.selfValidation = parseInt(selfValidation[1], 10);
153
+ }
154
+
155
+ const testCoverage = content.match(/test coverage.*?>= (\d+)%/i) || content.match(/>= (\d+)%.*?test coverage/i);
156
+ if (testCoverage) {
157
+ thresholds.testCoverage = parseInt(testCoverage[1], 10);
158
+ }
159
+
160
+ const maxCorrections = content.match(/(?:max|maximum)\s+(\d+)\s+correction/i);
161
+ if (maxCorrections) {
162
+ thresholds.maxCorrectionLoops = parseInt(maxCorrections[1], 10);
163
+ }
164
+
165
+ return thresholds;
166
+ }
167
+
168
+ /**
169
+ * Generate a constitution domain YAML file from constitution.md.
170
+ * @param {string} frameworkDir - Path to the framework directory.
171
+ * @returns {{ path: string, domain: object }} The generated domain object and file path.
172
+ */
173
+ export function generateConstitutionDomain(frameworkDir) {
174
+ const constPath = join(frameworkDir, 'constitution.md');
175
+ if (!existsSync(constPath)) {
176
+ throw new Error(`constitution.md not found at: ${constPath}`);
177
+ }
178
+
179
+ const content = readFileSync(constPath, 'utf8');
180
+
181
+ const articles = extractArticles(content);
182
+ const modes = extractModes(content);
183
+ const blockers = extractBlockers(content);
184
+ const thresholds = extractQualityThresholds(content);
185
+
186
+ const domain = {
187
+ constitution: {
188
+ version: '1.0',
189
+ source: 'constitution.md',
190
+ generatedAt: new Date().toISOString(),
191
+ articleCount: articles.length,
192
+ articles: articles.map(a => ({
193
+ id: a.id,
194
+ title: a.title,
195
+ enforcement: a.enforcement,
196
+ rules: a.rules,
197
+ })),
198
+ },
199
+ };
200
+
201
+ if (modes) {
202
+ domain.constitution.modes = modes;
203
+ }
204
+
205
+ if (blockers.critical.length > 0 || blockers.general.length > 0) {
206
+ domain.constitution.blockers = blockers;
207
+ }
208
+
209
+ if (Object.keys(thresholds).length > 0) {
210
+ domain.constitution.qualityThresholds = thresholds;
211
+ }
212
+
213
+ // Write the domain file
214
+ const domainsDir = join(frameworkDir, 'domains');
215
+ mkdirSync(domainsDir, { recursive: true });
216
+
217
+ const outputPath = join(domainsDir, 'constitution.yaml');
218
+ const yamlContent = yaml.dump(domain, {
219
+ lineWidth: 120,
220
+ noRefs: true,
221
+ sortKeys: false,
222
+ });
223
+
224
+ writeFileSync(outputPath, `# Constitution Domain — Auto-generated from constitution.md\n# Do not edit manually. Regenerate with: node scripts/generate-constitution-domain.js\n\n${yamlContent}`, 'utf8');
225
+
226
+ return { path: outputPath, domain };
227
+ }
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // CLI entrypoint
231
+ // ---------------------------------------------------------------------------
232
+
233
+ const isMainModule = process.argv[1] && (
234
+ process.argv[1].endsWith('generate-constitution-domain.js') ||
235
+ process.argv[1].endsWith('generate-constitution-domain')
236
+ );
237
+
238
+ if (isMainModule) {
239
+ const frameworkDir = process.argv[2] || join(process.cwd(), 'chati.dev');
240
+
241
+ console.log(`Generating constitution domain from: ${frameworkDir}`);
242
+
243
+ try {
244
+ const { path, domain } = generateConstitutionDomain(frameworkDir);
245
+ console.log(`Articles extracted: ${domain.constitution.articleCount}`);
246
+ console.log(`Modes: ${domain.constitution.modes ? Object.keys(domain.constitution.modes).join(', ') : 'none'}`);
247
+ console.log(`Blockers: critical=${domain.constitution.blockers?.critical?.length || 0}, general=${domain.constitution.blockers?.general?.length || 0}`);
248
+ console.log(`Written to: ${path}`);
249
+ } catch (err) {
250
+ console.error(`Error: ${err.message}`);
251
+ process.exit(1);
252
+ }
253
+ }