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,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
|
+
}
|