chati-dev 4.3.0 → 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.
Files changed (156) hide show
  1. package/README.md +18 -13
  2. package/bin/chati.js +32 -4
  3. package/framework/agents/build/dev.md +9 -5
  4. package/framework/agents/discover/brief.md +2 -0
  5. package/framework/agents/discover/brownfield-wu.md +2 -0
  6. package/framework/agents/discover/greenfield-wu.md +18 -1
  7. package/framework/agents/plan/detail.md +2 -0
  8. package/framework/agents/plan/tasks.md +31 -0
  9. package/framework/agents/plan/ux-brand-architect.md +21 -2
  10. package/framework/agents/plan/ux-component-engineer.md +10 -0
  11. package/framework/agents/quality/qa-implementation.md +1 -1
  12. package/framework/agents/quality/qa-planning.md +1 -1
  13. package/framework/agents/quality/qa-visual.md +33 -21
  14. package/framework/config.yaml +5 -4
  15. package/framework/constitution.md +58 -11
  16. package/framework/context/governance.md +12 -10
  17. package/framework/context/root.md +2 -2
  18. package/framework/data/entity-registry.yaml +12 -4
  19. package/framework/data/qa-rubrics.yaml +335 -0
  20. package/framework/domains/constitution.yaml +3 -3
  21. package/framework/executors/json-validate.js +80 -0
  22. package/framework/executors/npm-script.js +72 -0
  23. package/framework/executors/path-check.js +67 -0
  24. package/framework/executors/registry.yaml +36 -0
  25. package/framework/hooks/advance-trigger.js +47 -3
  26. package/framework/hooks/constitution-guard.js +9 -4
  27. package/framework/hooks/git-push-authority.js +113 -0
  28. package/framework/hooks/license-guard.js +59 -9
  29. package/framework/hooks/model-governance.js +40 -17
  30. package/framework/hooks/prism-engine.js +78 -11
  31. package/framework/hooks/session-digest.js +12 -3
  32. package/framework/hooks/settings.json +7 -3
  33. package/framework/hooks/style-guard.js +52 -3
  34. package/framework/hooks/team-quality-gate.js +43 -5
  35. package/framework/i18n/en.yaml +3 -3
  36. package/framework/i18n/es.yaml +3 -3
  37. package/framework/i18n/fr.yaml +3 -3
  38. package/framework/i18n/pt.yaml +3 -3
  39. package/framework/intelligence/confidence.yaml +85 -6
  40. package/framework/intelligence/context-engine.md +9 -5
  41. package/framework/intelligence/decision-engine.md +1 -1
  42. package/framework/orchestrator/chati-router.js +59 -5
  43. package/framework/orchestrator/chati-update.md +19 -3
  44. package/framework/orchestrator/chati.md +20 -1
  45. package/framework/quality-gates/planning-gate.md +2 -0
  46. package/framework/rules/human-writing-style.md +47 -0
  47. package/framework/scaffold/motion-premium/README.md +12 -2
  48. package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +1 -1
  49. package/framework/scaffold/motion-premium/scaffold.yaml +6 -1
  50. package/framework/scaffold/motion-premium-3d/README.md +10 -0
  51. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
  52. package/framework/scaffold/motion-premium-3d/scaffold.yaml +3 -0
  53. package/framework/scaffold/saas-dashboard/README.md +58 -0
  54. package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
  55. package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
  56. package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
  57. package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
  58. package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
  59. package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
  60. package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
  61. package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
  62. package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
  63. package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
  64. package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
  65. package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
  66. package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
  67. package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
  68. package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
  69. package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
  70. package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
  71. package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
  72. package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
  73. package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
  74. package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
  75. package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
  76. package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
  77. package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
  78. package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
  79. package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
  80. package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
  81. package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
  82. package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
  83. package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
  84. package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
  85. package/framework/schemas/session.schema.json +5 -0
  86. package/framework/schemas/task.schema.json +9 -0
  87. package/framework/scripts/visual-qa.js +101 -3
  88. package/framework/tasks/orchestrator-health.md +4 -4
  89. package/framework/tasks/qa-impl-verdict.md +11 -5
  90. package/framework/templates/brandbook-html-tmpl.md +1 -1
  91. package/framework/templates/qa-gate-tmpl.yaml +24 -11
  92. package/package.json +4 -3
  93. package/src/config/context-file-generator.js +0 -6
  94. package/src/dashboard/renderer.js +0 -36
  95. package/src/executors/runner.js +204 -0
  96. package/src/installer/core.js +13 -3
  97. package/src/installer/templates.js +5 -6
  98. package/src/intelligence/registry-manager.js +1 -1
  99. package/src/license/client.js +9 -3
  100. package/src/license/commands.js +12 -2
  101. package/src/license/machine-id.js +42 -1
  102. package/src/memory/gotchas.js +58 -40
  103. package/src/memory/magic-docs.js +1 -1
  104. package/src/memory/session-digest.js +9 -4
  105. package/src/orchestrator/cli.js +186 -15
  106. package/src/orchestrator/doctor.js +98 -16
  107. package/src/orchestrator/pipeline-manager.js +44 -22
  108. package/src/orchestrator/session-manager.js +64 -8
  109. package/src/telemetry/sender.js +7 -19
  110. package/src/terminal/run-team.js +3 -3
  111. package/src/upgrade/tracked-files-detector.js +34 -16
  112. package/src/utils/feature-flags.js +1 -1
  113. package/src/utils/flatten-entities.js +4 -40
  114. package/src/utils/schema-validator.js +0 -14
  115. package/src/wizard/i18n.js +3 -3
  116. package/src/api/index.js +0 -120
  117. package/src/autonomy/autonomous-gate.js +0 -294
  118. package/src/autonomy/build-loop.js +0 -281
  119. package/src/autonomy/build-state.js +0 -286
  120. package/src/autonomy/cause-analyzer.js +0 -177
  121. package/src/autonomy/escalation.js +0 -214
  122. package/src/autonomy/index.js +0 -51
  123. package/src/autonomy/mode-manager.js +0 -225
  124. package/src/autonomy/mode-suggester.js +0 -283
  125. package/src/autonomy/progress-reporter.js +0 -275
  126. package/src/autonomy/safety-net.js +0 -370
  127. package/src/config/agent-customizer.js +0 -231
  128. package/src/decision/analyzer.js +0 -291
  129. package/src/decision/engine.js +0 -250
  130. package/src/decision/index.js +0 -38
  131. package/src/decision/registry-healer.js +0 -468
  132. package/src/decision/registry-updater.js +0 -339
  133. package/src/extensions/loader.js +0 -145
  134. package/src/extensions/registry.js +0 -134
  135. package/src/gates/circuit-breaker.js +0 -151
  136. package/src/gates/g1-planning-complete.js +0 -154
  137. package/src/gates/g2-qa-planning.js +0 -156
  138. package/src/gates/g3-implementation.js +0 -215
  139. package/src/gates/g4-qa-implementation.js +0 -240
  140. package/src/gates/g5-deploy-ready.js +0 -181
  141. package/src/gates/gate-base.js +0 -185
  142. package/src/gates/index.js +0 -46
  143. package/src/health/auto-fix.js +0 -216
  144. package/src/health/engine.js +0 -246
  145. package/src/merger/semantic-merger.js +0 -292
  146. package/src/preview/detector.js +0 -238
  147. package/src/preview/index.js +0 -20
  148. package/src/preview/launcher.js +0 -235
  149. package/src/preview/log-buffer.js +0 -103
  150. package/src/quality/metrics-collector.js +0 -281
  151. package/src/quality/test-runner.js +0 -366
  152. package/src/tasks/executor.js +0 -195
  153. package/src/tasks/index.js +0 -4
  154. package/src/tasks/loader.js +0 -210
  155. package/src/tasks/router.js +0 -182
  156. package/src/utils/event-bus.js +0 -126
@@ -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 };
@@ -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
- }