chati-dev 1.2.1 → 1.3.0

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.
@@ -0,0 +1,188 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync, unlinkSync } from 'fs';
2
+ import { join, relative } from 'path';
3
+
4
+ const MEMORIES_DIR = '.chati/memories';
5
+
6
+ /**
7
+ * List memories with optional filters
8
+ */
9
+ export function listMemories(targetDir, options = {}) {
10
+ const memoriesPath = join(targetDir, MEMORIES_DIR);
11
+ if (!existsSync(memoriesPath)) return [];
12
+
13
+ const memories = [];
14
+ walkMemories(memoriesPath, (filePath) => {
15
+ const meta = parseMemoryFrontmatter(filePath);
16
+ if (!meta) return;
17
+
18
+ if (options.agent && meta.agent !== options.agent) return;
19
+ if (options.sector && meta.sector !== options.sector) return;
20
+ if (options.tier && meta.tier !== options.tier) return;
21
+
22
+ memories.push({
23
+ ...meta,
24
+ path: relative(memoriesPath, filePath),
25
+ });
26
+ });
27
+
28
+ return memories;
29
+ }
30
+
31
+ /**
32
+ * Search memories by query (matches tags and content)
33
+ */
34
+ export function searchMemories(targetDir, query) {
35
+ const memoriesPath = join(targetDir, MEMORIES_DIR);
36
+ if (!existsSync(memoriesPath)) return [];
37
+
38
+ const queryLower = query.toLowerCase();
39
+ const results = [];
40
+
41
+ walkMemories(memoriesPath, (filePath) => {
42
+ const content = readFileSync(filePath, 'utf-8');
43
+ const meta = parseMemoryFrontmatter(filePath);
44
+ if (!meta) return;
45
+
46
+ const tagMatch = meta.tags && meta.tags.some(t => t.toLowerCase().includes(queryLower));
47
+ const contentMatch = content.toLowerCase().includes(queryLower);
48
+
49
+ if (tagMatch || contentMatch) {
50
+ results.push({
51
+ ...meta,
52
+ path: relative(memoriesPath, filePath),
53
+ matchType: tagMatch ? 'tag' : 'content',
54
+ });
55
+ }
56
+ });
57
+
58
+ return results;
59
+ }
60
+
61
+ /**
62
+ * Clean expired or cold memories
63
+ */
64
+ export function cleanMemories(targetDir, options = {}) {
65
+ const memoriesPath = join(targetDir, MEMORIES_DIR);
66
+ if (!existsSync(memoriesPath)) return { cleaned: 0, skipped: 0 };
67
+
68
+ let cleaned = 0;
69
+ let skipped = 0;
70
+
71
+ walkMemories(memoriesPath, (filePath) => {
72
+ const meta = parseMemoryFrontmatter(filePath);
73
+ if (!meta) { skipped++; return; }
74
+
75
+ // Clean session-tier memories (they should be cleaned on new session start)
76
+ const isSessionTier = filePath.includes('/session/');
77
+
78
+ // Clean expired memories
79
+ const isExpired = meta.expires_at && new Date(meta.expires_at) < new Date();
80
+
81
+ if (isSessionTier || isExpired) {
82
+ if (!options.dryRun) {
83
+ unlinkSync(filePath);
84
+ }
85
+ cleaned++;
86
+ } else {
87
+ skipped++;
88
+ }
89
+ });
90
+
91
+ return { cleaned, skipped, dryRun: !!options.dryRun };
92
+ }
93
+
94
+ /**
95
+ * Get memory statistics
96
+ */
97
+ export function getMemoryStats(targetDir) {
98
+ const memoriesPath = join(targetDir, MEMORIES_DIR);
99
+ if (!existsSync(memoriesPath)) {
100
+ return { total: 0, byAgent: {}, bySector: {}, byTier: {}, diskUsage: 0 };
101
+ }
102
+
103
+ const stats = {
104
+ total: 0,
105
+ byAgent: {},
106
+ bySector: {},
107
+ byTier: { hot: 0, warm: 0, cold: 0 },
108
+ diskUsage: 0,
109
+ };
110
+
111
+ walkMemories(memoriesPath, (filePath) => {
112
+ const meta = parseMemoryFrontmatter(filePath);
113
+ const fileStats = statSync(filePath);
114
+ stats.diskUsage += fileStats.size;
115
+
116
+ if (!meta) return;
117
+ stats.total++;
118
+
119
+ if (meta.agent) {
120
+ stats.byAgent[meta.agent] = (stats.byAgent[meta.agent] || 0) + 1;
121
+ }
122
+ if (meta.sector) {
123
+ stats.bySector[meta.sector] = (stats.bySector[meta.sector] || 0) + 1;
124
+ }
125
+ if (meta.tier && stats.byTier[meta.tier] !== undefined) {
126
+ stats.byTier[meta.tier]++;
127
+ }
128
+ });
129
+
130
+ return stats;
131
+ }
132
+
133
+ /**
134
+ * Walk memory files recursively
135
+ */
136
+ export function walkMemories(dir, callback) {
137
+ if (!existsSync(dir)) return;
138
+
139
+ const entries = readdirSync(dir, { withFileTypes: true });
140
+ for (const entry of entries) {
141
+ const fullPath = join(dir, entry.name);
142
+ if (entry.isDirectory()) {
143
+ walkMemories(fullPath, callback);
144
+ } else if (entry.name.endsWith('.md')) {
145
+ callback(fullPath);
146
+ }
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Parse YAML frontmatter from a memory file
152
+ */
153
+ export function parseMemoryFrontmatter(filePath) {
154
+ try {
155
+ const content = readFileSync(filePath, 'utf-8');
156
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
157
+ if (!fmMatch) return null;
158
+
159
+ // Simple YAML parsing for frontmatter (no dependency on js-yaml)
160
+ const lines = fmMatch[1].split('\n');
161
+ const meta = {};
162
+ for (const line of lines) {
163
+ const colonIdx = line.indexOf(':');
164
+ if (colonIdx === -1) continue;
165
+ const key = line.slice(0, colonIdx).trim();
166
+ let value = line.slice(colonIdx + 1).trim();
167
+
168
+ // Handle arrays [a, b, c]
169
+ if (value.startsWith('[') && value.endsWith(']')) {
170
+ value = value.slice(1, -1).split(',').map(s => s.trim());
171
+ }
172
+ // Handle numbers
173
+ else if (!isNaN(value) && value !== '') {
174
+ value = parseFloat(value);
175
+ }
176
+ // Handle quoted strings
177
+ else if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
178
+ value = value.slice(1, -1);
179
+ }
180
+
181
+ meta[key] = value;
182
+ }
183
+
184
+ return meta;
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
@@ -0,0 +1,202 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import yaml from 'js-yaml';
4
+
5
+ const REGISTRY_PATH = 'chati.dev/data/entity-registry.yaml';
6
+
7
+ /**
8
+ * Check registry integrity against filesystem
9
+ */
10
+ export function checkRegistry(targetDir) {
11
+ const registryPath = join(targetDir, REGISTRY_PATH);
12
+ if (!existsSync(registryPath)) {
13
+ return { valid: false, error: 'Entity registry not found', missing: [], orphaned: [] };
14
+ }
15
+
16
+ const registry = loadRegistry(registryPath);
17
+ if (!registry) {
18
+ return { valid: false, error: 'Failed to parse entity registry', missing: [], orphaned: [] };
19
+ }
20
+
21
+ const missing = [];
22
+ const found = [];
23
+
24
+ // Check each registered entity exists on disk
25
+ const entities = flattenEntities(registry.entities);
26
+ for (const entity of entities) {
27
+ const filePath = join(targetDir, entity.path);
28
+ if (existsSync(filePath)) {
29
+ found.push(entity);
30
+ } else {
31
+ missing.push(entity);
32
+ }
33
+ }
34
+
35
+ return {
36
+ valid: missing.length === 0,
37
+ totalEntities: entities.length,
38
+ found: found.length,
39
+ missing,
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Get registry statistics
45
+ */
46
+ export function getRegistryStats(targetDir) {
47
+ const registryPath = join(targetDir, REGISTRY_PATH);
48
+ if (!existsSync(registryPath)) {
49
+ return { exists: false, totalEntities: 0, byType: {} };
50
+ }
51
+
52
+ const registry = loadRegistry(registryPath);
53
+ if (!registry) {
54
+ return { exists: true, totalEntities: 0, byType: {}, error: 'Parse error' };
55
+ }
56
+
57
+ const entities = flattenEntities(registry.entities);
58
+ const byType = {};
59
+ for (const entity of entities) {
60
+ const type = entity.type || 'unknown';
61
+ byType[type] = (byType[type] || 0) + 1;
62
+ }
63
+
64
+ return {
65
+ exists: true,
66
+ version: registry.metadata?.version || 'unknown',
67
+ totalEntities: entities.length,
68
+ declaredCount: registry.metadata?.entity_count || 0,
69
+ countMatch: entities.length === (registry.metadata?.entity_count || 0),
70
+ byType,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Validate all entities exist on disk
76
+ */
77
+ export function validateEntities(targetDir) {
78
+ const result = checkRegistry(targetDir);
79
+ return {
80
+ valid: result.valid,
81
+ total: result.totalEntities || 0,
82
+ found: result.found || 0,
83
+ missing: (result.missing || []).map(e => e.path),
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Run comprehensive health check
89
+ */
90
+ export function runHealthCheck(targetDir) {
91
+ const checks = {
92
+ registry: { pass: false, details: '' },
93
+ schemas: { pass: false, details: '' },
94
+ constitution: { pass: false, details: '' },
95
+ agents: { pass: false, details: '' },
96
+ entities: { pass: false, details: '' },
97
+ overall: 'UNHEALTHY',
98
+ };
99
+
100
+ // 1. Registry check
101
+ const registryStats = getRegistryStats(targetDir);
102
+ if (registryStats.exists && registryStats.totalEntities > 0) {
103
+ checks.registry.pass = true;
104
+ checks.registry.details = `${registryStats.totalEntities} entities registered (v${registryStats.version})`;
105
+ } else {
106
+ checks.registry.details = registryStats.exists ? 'Registry empty or invalid' : 'Registry not found';
107
+ }
108
+
109
+ // 2. Schema validation
110
+ const schemaFiles = [
111
+ 'session.schema.json', 'config.schema.json', 'task.schema.json',
112
+ 'context.schema.json', 'memory.schema.json',
113
+ ];
114
+ let validSchemas = 0;
115
+ for (const file of schemaFiles) {
116
+ const schemaPath = join(targetDir, 'chati.dev', 'schemas', file);
117
+ if (existsSync(schemaPath)) {
118
+ try {
119
+ JSON.parse(readFileSync(schemaPath, 'utf-8'));
120
+ validSchemas++;
121
+ } catch {
122
+ // Invalid JSON
123
+ }
124
+ }
125
+ }
126
+ checks.schemas.pass = validSchemas === schemaFiles.length;
127
+ checks.schemas.details = `${validSchemas}/${schemaFiles.length} valid`;
128
+
129
+ // 3. Constitution check
130
+ const constitutionPath = join(targetDir, 'chati.dev', 'constitution.md');
131
+ if (existsSync(constitutionPath)) {
132
+ const content = readFileSync(constitutionPath, 'utf-8');
133
+ const articleCount = (content.match(/^## Article/gm) || []).length;
134
+ checks.constitution.pass = articleCount >= 15;
135
+ checks.constitution.details = `${articleCount}/15 articles`;
136
+ } else {
137
+ checks.constitution.details = 'Not found';
138
+ }
139
+
140
+ // 4. Agent check
141
+ const agentPaths = [
142
+ 'orchestrator/chati.md',
143
+ 'agents/clarity/greenfield-wu.md', 'agents/clarity/brownfield-wu.md',
144
+ 'agents/clarity/brief.md', 'agents/clarity/detail.md',
145
+ 'agents/clarity/architect.md', 'agents/clarity/ux.md',
146
+ 'agents/clarity/phases.md', 'agents/clarity/tasks.md',
147
+ 'agents/quality/qa-planning.md', 'agents/quality/qa-implementation.md',
148
+ 'agents/build/dev.md', 'agents/deploy/devops.md',
149
+ ];
150
+ let foundAgents = 0;
151
+ for (const p of agentPaths) {
152
+ if (existsSync(join(targetDir, 'chati.dev', p))) foundAgents++;
153
+ }
154
+ checks.agents.pass = foundAgents === 13;
155
+ checks.agents.details = `${foundAgents}/13 present`;
156
+
157
+ // 5. Entity validation (registry vs filesystem)
158
+ const entityResult = validateEntities(targetDir);
159
+ checks.entities.pass = entityResult.valid;
160
+ checks.entities.details = `${entityResult.found}/${entityResult.total} present`;
161
+
162
+ // Overall
163
+ const passCount = Object.values(checks).filter(c => c && c.pass).length;
164
+ const totalChecks = 5;
165
+ checks.overall = passCount === totalChecks ? 'HEALTHY' : passCount >= 3 ? 'DEGRADED' : 'UNHEALTHY';
166
+ checks.passCount = passCount;
167
+ checks.totalChecks = totalChecks;
168
+
169
+ return checks;
170
+ }
171
+
172
+ /**
173
+ * Load and parse registry YAML
174
+ */
175
+ function loadRegistry(registryPath) {
176
+ try {
177
+ const content = readFileSync(registryPath, 'utf-8');
178
+ return yaml.load(content);
179
+ } catch {
180
+ return null;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Flatten nested entity categories into flat array
186
+ */
187
+ function flattenEntities(entities) {
188
+ if (!entities) return [];
189
+ const flat = [];
190
+
191
+ for (const [, items] of Object.entries(entities)) {
192
+ if (typeof items === 'object' && items !== null) {
193
+ for (const [name, entity] of Object.entries(items)) {
194
+ if (entity && entity.path) {
195
+ flat.push({ name, ...entity });
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ return flat;
202
+ }
@@ -28,6 +28,8 @@ export function createBackup(targetDir, currentVersion) {
28
28
  'i18n',
29
29
  'patterns',
30
30
  'migrations',
31
+ 'intelligence',
32
+ 'data',
31
33
  ];
32
34
 
33
35
  const filesToBackup = [
@@ -1,4 +1,4 @@
1
- import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, rmSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import yaml from 'js-yaml';
4
4
  import semver from 'semver';
@@ -26,7 +26,7 @@ export function logBanner(logoText, version) {
26
26
  console.log(brand(line));
27
27
  }
28
28
  console.log(brand(`chati.dev v${version}`));
29
- console.log(dim('AI-Powered Multi-Agent Development Framework'));
29
+ console.log(dim('AI-Powered Multi-Agent Orchestration System'));
30
30
  console.log(dim('═'.repeat(55)));
31
31
  console.log();
32
32
  }
@@ -26,15 +26,15 @@ const FALLBACK_EN = {
26
26
  agents_count: '13 agent definitions (CLARITY, BUILD, DEPLOY phases)',
27
27
  workflows_count: '5 workflow blueprints',
28
28
  templates_count: '5 templates (PRD, Brownfield PRD, Architecture, Task, QA Gate)',
29
- constitution: 'Constitution (10 Articles + Preamble)',
29
+ constitution: 'Constitution (15 Articles + Preamble)',
30
30
  session_mgmt: 'Session management system',
31
31
  quality_gates: 'Quality gates (4-tier validation)',
32
32
  proceed: 'Proceed with installation?',
33
33
  installing: 'Installing chati.dev...',
34
34
  created_chati: 'Created .chati/ session directory',
35
- created_framework: 'Created chati.dev/ framework directory (agents, templates, workflows)',
35
+ created_framework: 'Created chati.dev/ system directory (agents, templates, workflows)',
36
36
  created_commands: 'Created .claude/commands/ (thin router)',
37
- installed_constitution: 'Installed Constitution (Articles I-X)',
37
+ installed_constitution: 'Installed Constitution (Articles I-XV)',
38
38
  created_session: 'Created session.yaml schema',
39
39
  created_claude_md: 'Created CLAUDE.md',
40
40
  configured_mcps: 'Configured MCPs:',
@@ -42,7 +42,12 @@ const FALLBACK_EN = {
42
42
  agents_valid: 'All 13 agents implement 8 protocols',
43
43
  handoff_ok: 'Handoff protocol: OK',
44
44
  validation_ok: 'Self-validation criteria: OK',
45
- constitution_ok: 'Constitution: 10 articles verified',
45
+ constitution_ok: 'Constitution: 15 articles verified',
46
+ created_memories: 'Created .chati/memories/ (Memory Layer)',
47
+ installed_intelligence: 'Installed Intelligence Layer (Context Engine, Memory, Registry)',
48
+ intelligence_valid: 'Intelligence: 6 spec files verified',
49
+ registry_valid: 'Entity Registry: valid',
50
+ memories_valid: 'Memory directory tree: valid',
46
51
  session_ok: 'Session schema: valid',
47
52
  success: 'chati.dev installed successfully!',
48
53
  quick_start_title: 'Quick Start',
@@ -69,6 +69,8 @@ export async function runWizard(targetDir, options = {}) {
69
69
  showStep(t('installer.installed_constitution'));
70
70
  showStep(t('installer.created_session'));
71
71
  showStep(t('installer.created_claude_md'));
72
+ showStep(t('installer.created_memories'));
73
+ showStep(t('installer.installed_intelligence'));
72
74
 
73
75
  if (selectedMCPs.length > 0) {
74
76
  showStep(`${t('installer.configured_mcps')} ${selectedMCPs.join(', ')}`);
@@ -86,6 +88,9 @@ export async function runWizard(targetDir, options = {}) {
86
88
  showValidation(t('installer.handoff_ok'));
87
89
  showValidation(t('installer.validation_ok'));
88
90
  showValidation(t('installer.constitution_ok'));
91
+ showValidation(t('installer.intelligence_valid'));
92
+ showValidation(t('installer.registry_valid'));
93
+ showValidation(t('installer.memories_valid'));
89
94
  showValidation(t('installer.session_ok'));
90
95
 
91
96
  console.log();