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.
- package/README.md +40 -24
- package/framework/agents/build/dev.md +343 -0
- package/framework/agents/clarity/architect.md +112 -0
- package/framework/agents/clarity/brief.md +182 -0
- package/framework/agents/clarity/brownfield-wu.md +181 -0
- package/framework/agents/clarity/detail.md +110 -0
- package/framework/agents/clarity/greenfield-wu.md +153 -0
- package/framework/agents/clarity/ux.md +112 -0
- package/framework/config.yaml +3 -3
- package/framework/constitution.md +31 -1
- package/framework/context/governance.md +37 -0
- package/framework/context/protocols.md +34 -0
- package/framework/context/quality.md +27 -0
- package/framework/context/root.md +24 -0
- package/framework/data/entity-registry.yaml +1 -1
- package/framework/domains/agents/architect.yaml +51 -0
- package/framework/domains/agents/brief.yaml +47 -0
- package/framework/domains/agents/brownfield-wu.yaml +49 -0
- package/framework/domains/agents/detail.yaml +47 -0
- package/framework/domains/agents/dev.yaml +49 -0
- package/framework/domains/agents/devops.yaml +43 -0
- package/framework/domains/agents/greenfield-wu.yaml +47 -0
- package/framework/domains/agents/orchestrator.yaml +49 -0
- package/framework/domains/agents/phases.yaml +47 -0
- package/framework/domains/agents/qa-implementation.yaml +43 -0
- package/framework/domains/agents/qa-planning.yaml +44 -0
- package/framework/domains/agents/tasks.yaml +48 -0
- package/framework/domains/agents/ux.yaml +50 -0
- package/framework/domains/constitution.yaml +77 -0
- package/framework/domains/global.yaml +64 -0
- package/framework/domains/workflows/brownfield-discovery.yaml +16 -0
- package/framework/domains/workflows/brownfield-fullstack.yaml +26 -0
- package/framework/domains/workflows/brownfield-service.yaml +22 -0
- package/framework/domains/workflows/brownfield-ui.yaml +22 -0
- package/framework/domains/workflows/greenfield-fullstack.yaml +26 -0
- package/framework/hooks/constitution-guard.js +101 -0
- package/framework/hooks/mode-governance.js +92 -0
- package/framework/hooks/model-governance.js +76 -0
- package/framework/hooks/prism-engine.js +89 -0
- package/framework/hooks/session-digest.js +60 -0
- package/framework/hooks/settings.json +44 -0
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/migrations/v1.4-to-v2.0.yaml +167 -0
- package/framework/migrations/v2.0-to-v2.0.1.yaml +132 -0
- package/framework/orchestrator/chati.md +284 -6
- package/framework/tasks/architect-api-design.md +63 -0
- package/framework/tasks/architect-consolidate.md +47 -0
- package/framework/tasks/architect-db-design.md +73 -0
- package/framework/tasks/architect-design.md +95 -0
- package/framework/tasks/architect-security-review.md +62 -0
- package/framework/tasks/architect-stack-selection.md +53 -0
- package/framework/tasks/brief-consolidate.md +249 -0
- package/framework/tasks/brief-constraint-identify.md +277 -0
- package/framework/tasks/brief-extract-requirements.md +339 -0
- package/framework/tasks/brief-stakeholder-map.md +176 -0
- package/framework/tasks/brief-validate-completeness.md +121 -0
- package/framework/tasks/brownfield-wu-architecture-map.md +394 -0
- package/framework/tasks/brownfield-wu-deep-discovery.md +312 -0
- package/framework/tasks/brownfield-wu-dependency-scan.md +359 -0
- package/framework/tasks/brownfield-wu-migration-plan.md +483 -0
- package/framework/tasks/brownfield-wu-report.md +325 -0
- package/framework/tasks/brownfield-wu-risk-assess.md +424 -0
- package/framework/tasks/detail-acceptance-criteria.md +372 -0
- package/framework/tasks/detail-consolidate.md +138 -0
- package/framework/tasks/detail-edge-case-analysis.md +300 -0
- package/framework/tasks/detail-expand-prd.md +389 -0
- package/framework/tasks/detail-nfr-extraction.md +223 -0
- package/framework/tasks/dev-code-review.md +404 -0
- package/framework/tasks/dev-consolidate.md +543 -0
- package/framework/tasks/dev-debug.md +322 -0
- package/framework/tasks/dev-implement.md +252 -0
- package/framework/tasks/dev-iterate.md +411 -0
- package/framework/tasks/dev-pr-prepare.md +497 -0
- package/framework/tasks/dev-refactor.md +342 -0
- package/framework/tasks/dev-test-write.md +306 -0
- package/framework/tasks/devops-ci-setup.md +412 -0
- package/framework/tasks/devops-consolidate.md +712 -0
- package/framework/tasks/devops-deploy-config.md +598 -0
- package/framework/tasks/devops-monitoring-setup.md +658 -0
- package/framework/tasks/devops-release-prepare.md +673 -0
- package/framework/tasks/greenfield-wu-analyze-empty.md +169 -0
- package/framework/tasks/greenfield-wu-report.md +266 -0
- package/framework/tasks/greenfield-wu-scaffold-detection.md +203 -0
- package/framework/tasks/greenfield-wu-tech-stack-assess.md +255 -0
- package/framework/tasks/orchestrator-deviation.md +260 -0
- package/framework/tasks/orchestrator-escalate.md +276 -0
- package/framework/tasks/orchestrator-handoff.md +243 -0
- package/framework/tasks/orchestrator-health.md +372 -0
- package/framework/tasks/orchestrator-mode-switch.md +262 -0
- package/framework/tasks/orchestrator-resume.md +189 -0
- package/framework/tasks/orchestrator-route.md +169 -0
- package/framework/tasks/orchestrator-spawn-terminal.md +358 -0
- package/framework/tasks/orchestrator-status.md +260 -0
- package/framework/tasks/orchestrator-suggest-mode.md +372 -0
- package/framework/tasks/phases-breakdown.md +91 -0
- package/framework/tasks/phases-dependency-mapping.md +67 -0
- package/framework/tasks/phases-mvp-scoping.md +94 -0
- package/framework/tasks/qa-impl-consolidate.md +522 -0
- package/framework/tasks/qa-impl-performance-test.md +487 -0
- package/framework/tasks/qa-impl-regression-check.md +413 -0
- package/framework/tasks/qa-impl-sast-scan.md +402 -0
- package/framework/tasks/qa-impl-test-execute.md +344 -0
- package/framework/tasks/qa-impl-verdict.md +339 -0
- package/framework/tasks/qa-planning-consolidate.md +309 -0
- package/framework/tasks/qa-planning-coverage-plan.md +338 -0
- package/framework/tasks/qa-planning-gate-define.md +339 -0
- package/framework/tasks/qa-planning-risk-matrix.md +631 -0
- package/framework/tasks/qa-planning-test-strategy.md +217 -0
- package/framework/tasks/tasks-acceptance-write.md +75 -0
- package/framework/tasks/tasks-consolidate.md +57 -0
- package/framework/tasks/tasks-decompose.md +80 -0
- package/framework/tasks/tasks-estimate.md +66 -0
- package/framework/tasks/ux-a11y-check.md +49 -0
- package/framework/tasks/ux-component-map.md +55 -0
- package/framework/tasks/ux-consolidate.md +46 -0
- package/framework/tasks/ux-user-flow.md +46 -0
- package/framework/tasks/ux-wireframe.md +76 -0
- package/package.json +2 -2
- package/scripts/bundle-framework.js +2 -0
- package/scripts/changelog-generator.js +222 -0
- package/scripts/codebase-mapper.js +728 -0
- package/scripts/commit-message-generator.js +167 -0
- package/scripts/coverage-analyzer.js +260 -0
- package/scripts/dependency-analyzer.js +280 -0
- package/scripts/framework-analyzer.js +308 -0
- package/scripts/generate-constitution-domain.js +253 -0
- package/scripts/health-check.js +481 -0
- package/scripts/ide-sync.js +327 -0
- package/scripts/performance-analyzer.js +325 -0
- package/scripts/plan-tracker.js +278 -0
- package/scripts/populate-entity-registry.js +481 -0
- package/scripts/pr-review.js +317 -0
- package/scripts/rollback-manager.js +310 -0
- package/scripts/stuck-detector.js +343 -0
- package/scripts/test-quality-assessment.js +257 -0
- package/scripts/validate-agents.js +367 -0
- package/scripts/validate-tasks.js +465 -0
- package/src/autonomy/autonomous-gate.js +293 -0
- package/src/autonomy/index.js +51 -0
- package/src/autonomy/mode-manager.js +225 -0
- package/src/autonomy/mode-suggester.js +283 -0
- package/src/autonomy/progress-reporter.js +268 -0
- package/src/autonomy/safety-net.js +320 -0
- package/src/context/bracket-tracker.js +79 -0
- package/src/context/domain-loader.js +107 -0
- package/src/context/engine.js +144 -0
- package/src/context/formatter.js +184 -0
- package/src/context/index.js +4 -0
- package/src/context/layers/l0-constitution.js +28 -0
- package/src/context/layers/l1-global.js +37 -0
- package/src/context/layers/l2-agent.js +39 -0
- package/src/context/layers/l3-workflow.js +42 -0
- package/src/context/layers/l4-task.js +24 -0
- package/src/decision/analyzer.js +167 -0
- package/src/decision/engine.js +270 -0
- package/src/decision/index.js +38 -0
- package/src/decision/registry-healer.js +450 -0
- package/src/decision/registry-updater.js +330 -0
- package/src/gates/circuit-breaker.js +119 -0
- package/src/gates/g1-planning-complete.js +153 -0
- package/src/gates/g2-qa-planning.js +153 -0
- package/src/gates/g3-implementation.js +188 -0
- package/src/gates/g4-qa-implementation.js +207 -0
- package/src/gates/g5-deploy-ready.js +180 -0
- package/src/gates/gate-base.js +144 -0
- package/src/gates/index.js +46 -0
- package/src/installer/brownfield-upgrader.js +249 -0
- package/src/installer/core.js +82 -11
- package/src/installer/file-hasher.js +51 -0
- package/src/installer/manifest.js +117 -0
- package/src/installer/templates.js +17 -15
- package/src/installer/transaction.js +229 -0
- package/src/installer/validator.js +18 -1
- package/src/intelligence/registry-manager.js +2 -2
- package/src/memory/agent-memory.js +255 -0
- package/src/memory/gotchas-injector.js +72 -0
- package/src/memory/gotchas.js +361 -0
- package/src/memory/index.js +35 -0
- package/src/memory/search.js +233 -0
- package/src/memory/session-digest.js +239 -0
- package/src/merger/env-merger.js +112 -0
- package/src/merger/index.js +56 -0
- package/src/merger/replace-merger.js +51 -0
- package/src/merger/yaml-merger.js +127 -0
- package/src/orchestrator/agent-selector.js +285 -0
- package/src/orchestrator/deviation-handler.js +350 -0
- package/src/orchestrator/handoff-engine.js +271 -0
- package/src/orchestrator/index.js +67 -0
- package/src/orchestrator/intent-classifier.js +264 -0
- package/src/orchestrator/pipeline-manager.js +492 -0
- package/src/orchestrator/pipeline-state.js +223 -0
- package/src/orchestrator/session-manager.js +409 -0
- package/src/tasks/executor.js +195 -0
- package/src/tasks/handoff.js +226 -0
- package/src/tasks/index.js +4 -0
- package/src/tasks/loader.js +210 -0
- package/src/tasks/router.js +182 -0
- package/src/terminal/collector.js +216 -0
- package/src/terminal/index.js +30 -0
- package/src/terminal/isolation.js +129 -0
- package/src/terminal/monitor.js +277 -0
- package/src/terminal/spawner.js +269 -0
- package/src/upgrade/checker.js +1 -1
- 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
|
+
}
|