chati-dev 4.3.1 → 4.4.1

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