chati-dev 4.3.1 → 4.4.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.
- package/README.md +18 -13
- package/bin/chati.js +12 -4
- package/framework/agents/build/dev.md +5 -1
- package/framework/agents/discover/greenfield-wu.md +16 -1
- package/framework/agents/plan/tasks.md +31 -0
- package/framework/agents/plan/ux-brand-architect.md +21 -2
- package/framework/agents/plan/ux-component-engineer.md +10 -0
- package/framework/agents/quality/qa-implementation.md +1 -1
- package/framework/agents/quality/qa-planning.md +1 -1
- package/framework/agents/quality/qa-visual.md +24 -21
- package/framework/config.yaml +5 -4
- package/framework/constitution.md +58 -11
- package/framework/context/governance.md +12 -10
- package/framework/context/root.md +2 -2
- package/framework/data/entity-registry.yaml +12 -4
- package/framework/data/qa-rubrics.yaml +335 -0
- package/framework/domains/constitution.yaml +3 -3
- package/framework/executors/json-validate.js +80 -0
- package/framework/executors/npm-script.js +72 -0
- package/framework/executors/path-check.js +67 -0
- package/framework/executors/registry.yaml +36 -0
- package/framework/hooks/advance-trigger.js +47 -3
- package/framework/hooks/constitution-guard.js +9 -4
- package/framework/hooks/git-push-authority.js +113 -0
- package/framework/hooks/license-guard.js +59 -9
- package/framework/hooks/model-governance.js +40 -17
- package/framework/hooks/prism-engine.js +78 -11
- package/framework/hooks/session-digest.js +12 -3
- package/framework/hooks/settings.json +7 -3
- package/framework/hooks/style-guard.js +52 -3
- package/framework/hooks/team-quality-gate.js +43 -5
- 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/confidence.yaml +85 -6
- package/framework/intelligence/context-engine.md +9 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/orchestrator/chati-router.js +1 -1
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +19 -1
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/saas-dashboard/README.md +58 -0
- package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
- package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
- package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
- package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
- package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
- package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
- package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
- package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
- package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
- package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
- package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
- package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
- package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
- package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
- package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
- package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
- package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
- package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
- package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
- package/framework/schemas/session.schema.json +5 -0
- package/framework/schemas/task.schema.json +9 -0
- package/framework/scripts/visual-qa.js +87 -1
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +3 -3
- package/src/config/context-file-generator.js +0 -6
- package/src/dashboard/renderer.js +0 -36
- package/src/executors/runner.js +204 -0
- package/src/installer/core.js +2 -1
- package/src/installer/templates.js +5 -6
- package/src/intelligence/registry-manager.js +1 -1
- package/src/license/client.js +9 -3
- package/src/license/commands.js +12 -2
- package/src/license/machine-id.js +42 -1
- package/src/memory/gotchas.js +58 -40
- package/src/memory/magic-docs.js +1 -1
- package/src/memory/session-digest.js +9 -4
- package/src/orchestrator/cli.js +174 -14
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +56 -8
- package/src/telemetry/sender.js +7 -19
- package/src/terminal/run-team.js +3 -3
- package/src/upgrade/tracked-files-detector.js +34 -16
- package/src/utils/feature-flags.js +1 -1
- package/src/utils/flatten-entities.js +4 -40
- package/src/utils/schema-validator.js +0 -14
- package/src/wizard/i18n.js +3 -3
- package/src/api/index.js +0 -120
- package/src/autonomy/autonomous-gate.js +0 -294
- package/src/autonomy/build-loop.js +0 -281
- package/src/autonomy/build-state.js +0 -286
- package/src/autonomy/cause-analyzer.js +0 -177
- package/src/autonomy/escalation.js +0 -214
- package/src/autonomy/index.js +0 -51
- package/src/autonomy/mode-manager.js +0 -225
- package/src/autonomy/mode-suggester.js +0 -283
- package/src/autonomy/progress-reporter.js +0 -275
- package/src/autonomy/safety-net.js +0 -370
- package/src/config/agent-customizer.js +0 -231
- package/src/decision/analyzer.js +0 -291
- package/src/decision/engine.js +0 -250
- package/src/decision/index.js +0 -38
- package/src/decision/registry-healer.js +0 -468
- package/src/decision/registry-updater.js +0 -339
- package/src/extensions/loader.js +0 -145
- package/src/extensions/registry.js +0 -134
- package/src/gates/circuit-breaker.js +0 -151
- package/src/gates/g1-planning-complete.js +0 -154
- package/src/gates/g2-qa-planning.js +0 -156
- package/src/gates/g3-implementation.js +0 -215
- package/src/gates/g4-qa-implementation.js +0 -240
- package/src/gates/g5-deploy-ready.js +0 -181
- package/src/gates/gate-base.js +0 -185
- package/src/gates/index.js +0 -46
- package/src/health/auto-fix.js +0 -216
- package/src/health/engine.js +0 -246
- package/src/merger/semantic-merger.js +0 -292
- package/src/preview/detector.js +0 -238
- package/src/preview/index.js +0 -20
- package/src/preview/launcher.js +0 -235
- package/src/preview/log-buffer.js +0 -103
- package/src/quality/metrics-collector.js +0 -281
- package/src/quality/test-runner.js +0 -366
- package/src/tasks/executor.js +0 -195
- package/src/tasks/index.js +0 -4
- package/src/tasks/loader.js +0 -210
- package/src/tasks/router.js +0 -182
- package/src/utils/event-bus.js +0 -126
package/src/health/auto-fix.js
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Auto-fix for common health check issues.
|
|
3
|
-
*
|
|
4
|
-
* Automatically repairs stale locks, oversized error logs,
|
|
5
|
-
* missing directories, and other recoverable problems.
|
|
6
|
-
*
|
|
7
|
-
* Constitution Article XIV — Framework Registry Governance.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { existsSync, mkdirSync, rmSync, statSync, readdirSync, truncateSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Constants
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
/** Maximum error log size before truncation (1 MB). */
|
|
18
|
-
const MAX_ERROR_LOG_SIZE = 1_024 * 1_024;
|
|
19
|
-
|
|
20
|
-
/** Stale lock file age threshold (30 seconds). */
|
|
21
|
-
const STALE_LOCK_AGE_MS = 30_000;
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Public API
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @typedef {object} FixAction
|
|
29
|
-
* @property {string} action - Description of what was fixed
|
|
30
|
-
* @property {boolean} success - Whether the fix was applied
|
|
31
|
-
* @property {string} [error] - Error message if fix failed
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @typedef {object} AutoFixResult
|
|
36
|
-
* @property {number} fixed - Number of issues fixed
|
|
37
|
-
* @property {number} failed - Number of fixes that failed
|
|
38
|
-
* @property {FixAction[]} actions - Detailed action log
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Run auto-fix on a project directory.
|
|
43
|
-
*
|
|
44
|
-
* @param {string} projectDir
|
|
45
|
-
* @param {object[]} [checkResults=[]] - Results from health checks (for targeted fixes)
|
|
46
|
-
* @returns {AutoFixResult}
|
|
47
|
-
*/
|
|
48
|
-
export function autoFix(projectDir, _checkResults = []) {
|
|
49
|
-
const actions = [];
|
|
50
|
-
|
|
51
|
-
// Fix 1: Ensure required directories exist
|
|
52
|
-
actions.push(...fixMissingDirectories(projectDir));
|
|
53
|
-
|
|
54
|
-
// Fix 2: Remove stale .lock files
|
|
55
|
-
actions.push(...fixStaleLocks(projectDir));
|
|
56
|
-
|
|
57
|
-
// Fix 3: Truncate oversized error logs
|
|
58
|
-
actions.push(...fixOversizedLogs(projectDir));
|
|
59
|
-
|
|
60
|
-
// Fix 4: Clean up orphaned worktrees directory entries
|
|
61
|
-
actions.push(...fixOrphanedWorktrees(projectDir));
|
|
62
|
-
|
|
63
|
-
const fixed = actions.filter(a => a.success).length;
|
|
64
|
-
const failed = actions.filter(a => !a.success).length;
|
|
65
|
-
|
|
66
|
-
return { fixed, failed, actions };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// Individual Fixers
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Ensure required .chati/ subdirectories exist.
|
|
75
|
-
*
|
|
76
|
-
* @param {string} projectDir
|
|
77
|
-
* @returns {FixAction[]}
|
|
78
|
-
*/
|
|
79
|
-
function fixMissingDirectories(projectDir) {
|
|
80
|
-
const actions = [];
|
|
81
|
-
const requiredDirs = [
|
|
82
|
-
'.chati',
|
|
83
|
-
'.chati/memory',
|
|
84
|
-
'.chati/metrics',
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
for (const dir of requiredDirs) {
|
|
88
|
-
const fullPath = join(projectDir, dir);
|
|
89
|
-
if (!existsSync(fullPath)) {
|
|
90
|
-
try {
|
|
91
|
-
mkdirSync(fullPath, { recursive: true });
|
|
92
|
-
actions.push({ action: `Created missing directory: ${dir}`, success: true });
|
|
93
|
-
} catch (err) {
|
|
94
|
-
actions.push({ action: `Failed to create ${dir}`, success: false, error: err.message });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return actions;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Remove .lock files that are stale (old + no active PID).
|
|
104
|
-
*
|
|
105
|
-
* @param {string} projectDir
|
|
106
|
-
* @returns {FixAction[]}
|
|
107
|
-
*/
|
|
108
|
-
function fixStaleLocks(projectDir) {
|
|
109
|
-
const actions = [];
|
|
110
|
-
const chatiDir = join(projectDir, '.chati');
|
|
111
|
-
|
|
112
|
-
if (!existsSync(chatiDir)) return actions;
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const entries = readdirSync(chatiDir);
|
|
116
|
-
const lockFiles = entries.filter(e => e.endsWith('.lock'));
|
|
117
|
-
|
|
118
|
-
for (const lockFile of lockFiles) {
|
|
119
|
-
const lockPath = join(chatiDir, lockFile);
|
|
120
|
-
try {
|
|
121
|
-
const stat = statSync(lockPath);
|
|
122
|
-
const age = Date.now() - stat.mtimeMs;
|
|
123
|
-
|
|
124
|
-
if (age > STALE_LOCK_AGE_MS) {
|
|
125
|
-
rmSync(lockPath, { force: true });
|
|
126
|
-
actions.push({ action: `Removed stale lock: ${lockFile}`, success: true });
|
|
127
|
-
}
|
|
128
|
-
} catch (err) {
|
|
129
|
-
actions.push({ action: `Failed to check lock ${lockFile}`, success: false, error: err.message });
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
} catch { /* expected: path may already be removed */
|
|
133
|
-
// Directory read failed — skip
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return actions;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Truncate oversized error/gotcha logs.
|
|
141
|
-
*
|
|
142
|
-
* @param {string} projectDir
|
|
143
|
-
* @returns {FixAction[]}
|
|
144
|
-
*/
|
|
145
|
-
function fixOversizedLogs(projectDir) {
|
|
146
|
-
const actions = [];
|
|
147
|
-
const logFiles = [
|
|
148
|
-
join(projectDir, '.chati', 'memory', 'errors.json'),
|
|
149
|
-
join(projectDir, '.chati', 'memory', 'gotchas.json'),
|
|
150
|
-
];
|
|
151
|
-
|
|
152
|
-
for (const logPath of logFiles) {
|
|
153
|
-
if (!existsSync(logPath)) continue;
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
const stat = statSync(logPath);
|
|
157
|
-
if (stat.size > MAX_ERROR_LOG_SIZE) {
|
|
158
|
-
truncateSync(logPath, 0);
|
|
159
|
-
actions.push({
|
|
160
|
-
action: `Truncated oversized log: ${logPath.split('/').pop()} (${Math.round(stat.size / 1024)}KB)`,
|
|
161
|
-
success: true,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
} catch (err) {
|
|
165
|
-
actions.push({
|
|
166
|
-
action: `Failed to truncate ${logPath.split('/').pop()}`,
|
|
167
|
-
success: false,
|
|
168
|
-
error: err.message,
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return actions;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Clean up orphaned worktree directory entries (empty dirs).
|
|
178
|
-
*
|
|
179
|
-
* @param {string} projectDir
|
|
180
|
-
* @returns {FixAction[]}
|
|
181
|
-
*/
|
|
182
|
-
function fixOrphanedWorktrees(projectDir) {
|
|
183
|
-
const actions = [];
|
|
184
|
-
const worktreeDir = join(projectDir, '.chati', 'worktrees');
|
|
185
|
-
|
|
186
|
-
if (!existsSync(worktreeDir)) return actions;
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
const entries = readdirSync(worktreeDir);
|
|
190
|
-
|
|
191
|
-
for (const entry of entries) {
|
|
192
|
-
const entryPath = join(worktreeDir, entry);
|
|
193
|
-
try {
|
|
194
|
-
const stat = statSync(entryPath);
|
|
195
|
-
if (stat.isDirectory()) {
|
|
196
|
-
const contents = readdirSync(entryPath);
|
|
197
|
-
if (contents.length === 0) {
|
|
198
|
-
rmSync(entryPath, { recursive: true, force: true });
|
|
199
|
-
actions.push({ action: `Removed empty worktree dir: ${entry}`, success: true });
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
} catch { /* expected: directory may not exist */
|
|
203
|
-
// Skip entries we can't read
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
} catch { /* expected: directory may not exist */
|
|
207
|
-
// Directory read failed
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return actions;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Exported constants for testing.
|
|
215
|
-
*/
|
|
216
|
-
export { MAX_ERROR_LOG_SIZE, STALE_LOCK_AGE_MS };
|
package/src/health/engine.js
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Health check engine for chati.dev.
|
|
3
|
-
*
|
|
4
|
-
* Validates framework integrity, CLI availability, session state,
|
|
5
|
-
* hook health, dependencies, and provider authentication.
|
|
6
|
-
* Constitution Article XIV — Framework Registry Governance.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { existsSync, readFileSync } from 'fs';
|
|
10
|
-
import { join } from 'path';
|
|
11
|
-
import { isCommandAvailable, parseProviderConfig } from '../utils/config-parser.js';
|
|
12
|
-
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
13
|
-
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Check Result Types
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @typedef {object} CheckResult
|
|
20
|
-
* @property {string} name - Check name
|
|
21
|
-
* @property {'pass'|'warn'|'fail'} status - Check status
|
|
22
|
-
* @property {string} message - Human-readable result
|
|
23
|
-
* @property {number} duration - Execution time in ms
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @typedef {object} HealthReport
|
|
28
|
-
* @property {number} score - Overall score (0-100)
|
|
29
|
-
* @property {number} total - Total checks run
|
|
30
|
-
* @property {number} passed - Checks passed
|
|
31
|
-
* @property {number} warned - Checks with warnings
|
|
32
|
-
* @property {number} failed - Checks failed
|
|
33
|
-
* @property {CheckResult[]} checks - Individual check results
|
|
34
|
-
* @property {string} timestamp - ISO timestamp
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
// Individual Checks
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Check if configured CLI providers are available on the system.
|
|
43
|
-
*
|
|
44
|
-
* @param {string} projectDir
|
|
45
|
-
* @returns {Promise<CheckResult>}
|
|
46
|
-
*/
|
|
47
|
-
export async function checkCliAvailability(projectDir) {
|
|
48
|
-
const start = Date.now();
|
|
49
|
-
const configPath = join(projectDir, resolveFrameworkDir(projectDir), 'config.yaml');
|
|
50
|
-
|
|
51
|
-
if (!existsSync(configPath)) {
|
|
52
|
-
return { name: 'cli-availability', status: 'warn', message: 'No config.yaml found', duration: Date.now() - start };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const { enabled } = parseProviderConfig(projectDir);
|
|
56
|
-
const missing = [];
|
|
57
|
-
|
|
58
|
-
for (const provider of enabled) {
|
|
59
|
-
const available = await isCommandAvailable(provider);
|
|
60
|
-
if (!available) {
|
|
61
|
-
missing.push(provider);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (missing.length > 0) {
|
|
66
|
-
return { name: 'cli-availability', status: 'warn', message: `Missing CLIs: ${missing.join(', ')}`, duration: Date.now() - start };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return { name: 'cli-availability', status: 'pass', message: 'All enabled CLIs available', duration: Date.now() - start };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Validate framework file integrity against entity registry.
|
|
74
|
-
*
|
|
75
|
-
* @param {string} projectDir
|
|
76
|
-
* @returns {Promise<CheckResult>}
|
|
77
|
-
*/
|
|
78
|
-
export async function checkFrameworkIntegrity(projectDir) {
|
|
79
|
-
const start = Date.now();
|
|
80
|
-
const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'data', 'entity-registry.yaml');
|
|
81
|
-
|
|
82
|
-
if (!existsSync(registryPath)) {
|
|
83
|
-
return { name: 'framework-integrity', status: 'warn', message: 'No entity registry found', duration: Date.now() - start };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const raw = readFileSync(registryPath, 'utf-8');
|
|
87
|
-
const pathMatches = raw.match(/path:\s*["']?([^\s"']+)["']?/gm) || [];
|
|
88
|
-
let missing = 0;
|
|
89
|
-
|
|
90
|
-
for (const match of pathMatches) {
|
|
91
|
-
const filePath = match.replace(/path:\s*["']?/, '').replace(/["']?$/, '');
|
|
92
|
-
if (!existsSync(join(projectDir, filePath))) {
|
|
93
|
-
missing++;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (missing > 0) {
|
|
98
|
-
return { name: 'framework-integrity', status: 'warn', message: `${missing} registered entities missing from filesystem`, duration: Date.now() - start };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return { name: 'framework-integrity', status: 'pass', message: 'All registered entities exist', duration: Date.now() - start };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Validate session.yaml structure.
|
|
106
|
-
*
|
|
107
|
-
* @param {string} projectDir
|
|
108
|
-
* @returns {Promise<CheckResult>}
|
|
109
|
-
*/
|
|
110
|
-
export async function checkSessionState(projectDir) {
|
|
111
|
-
const start = Date.now();
|
|
112
|
-
const sessionPath = join(projectDir, '.chati', 'session.yaml');
|
|
113
|
-
|
|
114
|
-
if (!existsSync(sessionPath)) {
|
|
115
|
-
return { name: 'session-state', status: 'pass', message: 'No active session (expected for new projects)', duration: Date.now() - start };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const raw = readFileSync(sessionPath, 'utf-8');
|
|
119
|
-
const requiredFields = ['project', 'language'];
|
|
120
|
-
const missingFields = requiredFields.filter((f) => !raw.includes(`${f}:`));
|
|
121
|
-
|
|
122
|
-
if (missingFields.length > 0) {
|
|
123
|
-
return { name: 'session-state', status: 'warn', message: `Session missing fields: ${missingFields.join(', ')}`, duration: Date.now() - start };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return { name: 'session-state', status: 'pass', message: 'Session state valid', duration: Date.now() - start };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Verify that hooks are functional.
|
|
131
|
-
*
|
|
132
|
-
* @param {string} projectDir
|
|
133
|
-
* @returns {Promise<CheckResult>}
|
|
134
|
-
*/
|
|
135
|
-
export async function checkHooksHealth(projectDir) {
|
|
136
|
-
const start = Date.now();
|
|
137
|
-
const hooksDir = join(projectDir, resolveFrameworkDir(projectDir), 'hooks');
|
|
138
|
-
|
|
139
|
-
if (!existsSync(hooksDir)) {
|
|
140
|
-
return { name: 'hooks-health', status: 'fail', message: 'Hooks directory not found', duration: Date.now() - start };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const expectedHooks = ['prism-engine.js', 'model-governance.js', 'mode-governance.js', 'constitution-guard.js', 'read-protection.js', 'session-digest.js'];
|
|
144
|
-
const missing = expectedHooks.filter((h) => !existsSync(join(hooksDir, h)));
|
|
145
|
-
|
|
146
|
-
if (missing.length > 0) {
|
|
147
|
-
return { name: 'hooks-health', status: 'fail', message: `Missing hooks: ${missing.join(', ')}`, duration: Date.now() - start };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return { name: 'hooks-health', status: 'pass', message: 'All 6 hooks present', duration: Date.now() - start };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Verify system dependencies.
|
|
155
|
-
*
|
|
156
|
-
* @returns {Promise<CheckResult>}
|
|
157
|
-
*/
|
|
158
|
-
export async function checkDependencies() {
|
|
159
|
-
const start = Date.now();
|
|
160
|
-
const issues = [];
|
|
161
|
-
|
|
162
|
-
const { execSync } = await import('child_process');
|
|
163
|
-
|
|
164
|
-
// Check Node.js version
|
|
165
|
-
try {
|
|
166
|
-
const nodeVersion = execSync('node --version', { encoding: 'utf-8' }).trim();
|
|
167
|
-
const major = parseInt(nodeVersion.replace('v', '').split('.')[0], 10);
|
|
168
|
-
if (major < 20) {
|
|
169
|
-
issues.push(`Node.js ${nodeVersion} < required v20`);
|
|
170
|
-
}
|
|
171
|
-
} catch { /* expected: content may be malformed */
|
|
172
|
-
issues.push('Node.js not found');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Check git
|
|
176
|
-
try {
|
|
177
|
-
execSync('git --version', { stdio: 'ignore' });
|
|
178
|
-
} catch { /* expected: git command may fail */
|
|
179
|
-
issues.push('git not found');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (issues.length > 0) {
|
|
183
|
-
return { name: 'dependencies', status: 'fail', message: issues.join('; '), duration: Date.now() - start };
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return { name: 'dependencies', status: 'pass', message: 'All dependencies satisfied', duration: Date.now() - start };
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Check git repository status.
|
|
191
|
-
*
|
|
192
|
-
* @param {string} projectDir
|
|
193
|
-
* @returns {Promise<CheckResult>}
|
|
194
|
-
*/
|
|
195
|
-
export async function checkGitStatus(projectDir) {
|
|
196
|
-
const start = Date.now();
|
|
197
|
-
|
|
198
|
-
if (!existsSync(join(projectDir, '.git'))) {
|
|
199
|
-
return { name: 'git-status', status: 'warn', message: 'Not a git repository', duration: Date.now() - start };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return { name: 'git-status', status: 'pass', message: 'Git repository detected', duration: Date.now() - start };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// ---------------------------------------------------------------------------
|
|
206
|
-
// Health Engine
|
|
207
|
-
// ---------------------------------------------------------------------------
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Run all health checks and produce a report.
|
|
211
|
-
*
|
|
212
|
-
* @param {string} projectDir - Project root directory
|
|
213
|
-
* @returns {Promise<HealthReport>}
|
|
214
|
-
*/
|
|
215
|
-
export async function runHealthChecks(projectDir) {
|
|
216
|
-
const checks = [
|
|
217
|
-
checkCliAvailability(projectDir),
|
|
218
|
-
checkFrameworkIntegrity(projectDir),
|
|
219
|
-
checkSessionState(projectDir),
|
|
220
|
-
checkHooksHealth(projectDir),
|
|
221
|
-
checkDependencies(),
|
|
222
|
-
checkGitStatus(projectDir),
|
|
223
|
-
];
|
|
224
|
-
|
|
225
|
-
const results = await Promise.allSettled(checks);
|
|
226
|
-
const checkResults = results.map((r) => {
|
|
227
|
-
if (r.status === 'fulfilled') return r.value;
|
|
228
|
-
return { name: 'unknown', status: 'fail', message: r.reason?.message || 'Check failed', duration: 0 };
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
const passed = checkResults.filter((c) => c.status === 'pass').length;
|
|
232
|
-
const warned = checkResults.filter((c) => c.status === 'warn').length;
|
|
233
|
-
const failed = checkResults.filter((c) => c.status === 'fail').length;
|
|
234
|
-
const total = checkResults.length;
|
|
235
|
-
const score = Math.round((passed / total) * 100);
|
|
236
|
-
|
|
237
|
-
return {
|
|
238
|
-
score,
|
|
239
|
-
total,
|
|
240
|
-
passed,
|
|
241
|
-
warned,
|
|
242
|
-
failed,
|
|
243
|
-
checks: checkResults,
|
|
244
|
-
timestamp: new Date().toISOString(),
|
|
245
|
-
};
|
|
246
|
-
}
|