@polymorphism-tech/morph-spec 4.8.19 โ†’ 4.9.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 (137) hide show
  1. package/CLAUDE.md +21 -0
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +15 -56
  4. package/bin/task-manager.js +115 -14
  5. package/bin/validate.js +67 -33
  6. package/claude-plugin.json +1 -1
  7. package/docs/CHEATSHEET.md +201 -203
  8. package/docs/QUICKSTART.md +2 -2
  9. package/framework/CLAUDE.md +21 -0
  10. package/framework/agents.json +698 -176
  11. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
  12. package/framework/hooks/claude-code/post-tool-use/dispatch.js +2 -2
  13. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +155 -0
  14. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +1 -1
  15. package/framework/hooks/claude-code/session-start/inject-morph-context.js +71 -2
  16. package/framework/hooks/claude-code/statusline.py +76 -30
  17. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
  18. package/framework/hooks/shared/activity-logger.js +0 -24
  19. package/framework/hooks/shared/phase-utils.js +3 -0
  20. package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
  21. package/framework/hooks/shared/stale-task-reset.js +57 -0
  22. package/framework/hooks/shared/state-reader.js +2 -2
  23. package/framework/hooks/shared/worktree-helpers.js +53 -0
  24. package/framework/phases.json +40 -8
  25. package/framework/skills/level-0-meta/brainstorming/SKILL.md +1 -1
  26. package/framework/skills/level-0-meta/code-review/SKILL.md +1 -1
  27. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +163 -163
  28. package/framework/skills/level-0-meta/frontend-review/SKILL.md +5 -5
  29. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  30. package/framework/skills/level-0-meta/morph-init/SKILL.md +5 -5
  31. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
  32. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
  33. package/framework/skills/level-0-meta/post-implementation/SKILL.md +59 -12
  34. package/framework/skills/level-0-meta/simulation-checklist/SKILL.md +1 -1
  35. package/framework/skills/level-0-meta/terminal-title/SKILL.md +1 -1
  36. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +1 -1
  37. package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +6 -5
  38. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
  39. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +215 -189
  40. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +251 -251
  41. package/framework/skills/level-1-workflows/phase-design/SKILL.md +382 -365
  42. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +492 -450
  43. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +194 -190
  44. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +270 -270
  45. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +285 -285
  46. package/framework/standards/STANDARDS.json +640 -88
  47. package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
  48. package/framework/templates/REGISTRY.json +1825 -1909
  49. package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
  50. package/framework/templates/docs/onboarding.md +1 -5
  51. package/package.json +2 -6
  52. package/src/commands/agents/dispatch-agents.js +55 -4
  53. package/src/commands/project/doctor.js +16 -47
  54. package/src/commands/project/init.js +1 -1
  55. package/src/commands/project/status.js +2 -2
  56. package/src/commands/project/update.js +381 -365
  57. package/src/commands/project/worktree.js +154 -0
  58. package/src/commands/state/advance-phase.js +120 -30
  59. package/src/commands/state/approve.js +2 -2
  60. package/src/commands/state/index.js +7 -8
  61. package/src/commands/state/phase-runner.js +1 -1
  62. package/src/commands/state/state.js +61 -6
  63. package/src/commands/tasks/task.js +78 -99
  64. package/src/commands/templates/template-render.js +93 -173
  65. package/src/commands/trust/trust.js +26 -21
  66. package/src/core/paths/output-schema.js +15 -0
  67. package/src/core/state/state-manager.js +28 -54
  68. package/src/core/workflows/workflow-detector.js +9 -87
  69. package/src/lib/phase-chain/phase-validator.js +330 -0
  70. package/src/lib/stack/stack-profile.js +88 -0
  71. package/src/lib/tasks/task-classifier.js +16 -0
  72. package/src/lib/tasks/test-runner.js +77 -0
  73. package/src/lib/trust/trust-manager.js +32 -144
  74. package/src/lib/validators/spec-validator.js +58 -4
  75. package/src/lib/validators/validation-runner.js +23 -11
  76. package/src/scripts/setup-infra.js +240 -224
  77. package/src/utils/agents-installer.js +2 -2
  78. package/src/utils/banner.js +1 -1
  79. package/src/utils/claude-settings-manager.js +1 -1
  80. package/src/utils/file-copier.js +1 -0
  81. package/src/utils/hooks-installer.js +258 -8
  82. package/framework/hooks/dev/check-sync-health.js +0 -117
  83. package/framework/hooks/dev/guard-version-numbers.js +0 -57
  84. package/framework/hooks/dev/sync-standards-registry.js +0 -60
  85. package/framework/hooks/dev/sync-template-registry.js +0 -60
  86. package/framework/hooks/dev/validate-skill-format.js +0 -70
  87. package/framework/hooks/dev/validate-standard-format.js +0 -73
  88. package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  89. package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  90. package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  91. package/framework/workflows/configs/design-impl.json +0 -49
  92. package/framework/workflows/configs/express.json +0 -45
  93. package/framework/workflows/configs/fast-track.json +0 -42
  94. package/framework/workflows/configs/full-morph.json +0 -79
  95. package/framework/workflows/configs/fusion.json +0 -39
  96. package/framework/workflows/configs/long-running.json +0 -33
  97. package/framework/workflows/configs/spec-only.json +0 -43
  98. package/framework/workflows/configs/ui-refresh.json +0 -49
  99. package/framework/workflows/configs/zero-touch.json +0 -82
  100. package/src/commands/project/monitor.js +0 -295
  101. package/src/commands/project/tutorial.js +0 -115
  102. package/src/commands/state/validate-phase.js +0 -238
  103. package/src/commands/templates/generate-contracts.js +0 -445
  104. package/src/core/orchestrator.js +0 -171
  105. package/src/core/registry/command-registry.js +0 -28
  106. package/src/core/registry/index.js +0 -8
  107. package/src/core/registry/validator-registry.js +0 -204
  108. package/src/core/templates/template-validator.js +0 -296
  109. package/src/generator/config-generator.js +0 -206
  110. package/src/generator/templates/config.json.template +0 -40
  111. package/src/generator/templates/project.md.template +0 -67
  112. package/src/lib/agents/micro-agent-factory.js +0 -161
  113. package/src/lib/analysis/complexity-analyzer.js +0 -441
  114. package/src/lib/analysis/index.js +0 -7
  115. package/src/lib/analytics/analytics-engine.js +0 -345
  116. package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
  117. package/src/lib/checkpoints/index.js +0 -7
  118. package/src/lib/context/context-bundler.js +0 -241
  119. package/src/lib/context/context-optimizer.js +0 -212
  120. package/src/lib/context/context-tracker.js +0 -273
  121. package/src/lib/context/core-four-tracker.js +0 -201
  122. package/src/lib/context/mcp-optimizer.js +0 -200
  123. package/src/lib/execution/fusion-executor.js +0 -304
  124. package/src/lib/execution/parallel-executor.js +0 -270
  125. package/src/lib/hooks/stop-hook-executor.js +0 -286
  126. package/src/lib/hops/hop-composer.js +0 -221
  127. package/src/lib/phase-chain/eligibility-checker.js +0 -243
  128. package/src/lib/threads/thread-coordinator.js +0 -238
  129. package/src/lib/threads/thread-manager.js +0 -317
  130. package/src/lib/tracking/artifact-trail.js +0 -202
  131. package/src/scanner/project-scanner.js +0 -242
  132. package/src/ui/diff-display.js +0 -91
  133. package/src/ui/interactive-wizard.js +0 -96
  134. package/src/ui/user-review.js +0 -211
  135. package/src/ui/wizard-questions.js +0 -188
  136. package/src/utils/color-utils.js +0 -70
  137. package/src/utils/process-handler.js +0 -97
@@ -1,345 +0,0 @@
1
- /**
2
- * Analytics Engine โ€” Metrics collection and JSONL storage
3
- *
4
- * Records events to append-only JSONL files:
5
- * - .morph/analytics/threads-log.jsonl
6
- * - .morph/analytics/context-log.jsonl
7
- * - .morph/analytics/trust-log.jsonl
8
- *
9
- * Provides aggregation, ASCII chart generation, and 30-day dashboards.
10
- * Auto-prunes entries older than 90 days.
11
- */
12
-
13
- import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from 'fs';
14
- import { join } from 'path';
15
-
16
- const ANALYTICS_DIR = join(process.cwd(), '.morph/analytics');
17
- const THREADS_LOG = join(ANALYTICS_DIR, 'threads-log.jsonl');
18
- const CONTEXT_LOG = join(ANALYTICS_DIR, 'context-log.jsonl');
19
- const TRUST_LOG = join(ANALYTICS_DIR, 'trust-log.jsonl');
20
- const PRUNE_DAYS = 90;
21
- const DASHBOARD_DAYS = 30;
22
-
23
- // ============================================================================
24
- // Internal Helpers
25
- // ============================================================================
26
-
27
- function ensureAnalyticsDir() {
28
- if (!existsSync(ANALYTICS_DIR)) {
29
- mkdirSync(ANALYTICS_DIR, { recursive: true });
30
- }
31
- }
32
-
33
- function appendJSONL(filePath, record) {
34
- ensureAnalyticsDir();
35
- appendFileSync(filePath, JSON.stringify(record) + '\n', 'utf8');
36
- }
37
-
38
- function readJSONL(filePath) {
39
- if (!existsSync(filePath)) return [];
40
-
41
- const lines = readFileSync(filePath, 'utf8').trim().split('\n').filter(Boolean);
42
- const records = [];
43
-
44
- for (const line of lines) {
45
- try {
46
- records.push(JSON.parse(line));
47
- } catch {
48
- // Skip malformed lines
49
- }
50
- }
51
- return records;
52
- }
53
-
54
- function pruneJSONL(filePath) {
55
- if (!existsSync(filePath)) return;
56
-
57
- const cutoff = new Date();
58
- cutoff.setDate(cutoff.getDate() - PRUNE_DAYS);
59
-
60
- const records = readJSONL(filePath);
61
- const fresh = records.filter(r => {
62
- const ts = new Date(r.timestamp || r.createdAt || 0);
63
- return ts > cutoff;
64
- });
65
-
66
- if (fresh.length < records.length) {
67
- writeFileSync(filePath, fresh.map(r => JSON.stringify(r)).join('\n') + '\n', 'utf8');
68
- }
69
- }
70
-
71
- function recentRecords(records, days = DASHBOARD_DAYS) {
72
- const cutoff = new Date();
73
- cutoff.setDate(cutoff.getDate() - days);
74
- return records.filter(r => {
75
- const ts = new Date(r.timestamp || r.createdAt || 0);
76
- return ts > cutoff;
77
- });
78
- }
79
-
80
- // ============================================================================
81
- // Event Recording
82
- // ============================================================================
83
-
84
- /**
85
- * Record a thread analytics event
86
- * @param {Object} event
87
- * @param {string} event.type - Event type (thread_created|thread_completed|thread_failed|checkpoint_passed|etc.)
88
- * @param {string} event.feature - Feature name
89
- * @param {string} [event.threadId] - Thread ID
90
- * @param {string} [event.agent] - Agent name
91
- * @param {Object} [event.data] - Additional event data
92
- */
93
- export function recordEvent(event) {
94
- const record = {
95
- ...event,
96
- timestamp: new Date().toISOString()
97
- };
98
-
99
- // Route to appropriate log
100
- if (event.type?.startsWith('context_') || event.type === 'token_usage') {
101
- appendJSONL(CONTEXT_LOG, record);
102
- } else if (event.type?.startsWith('trust_')) {
103
- appendJSONL(TRUST_LOG, record);
104
- } else {
105
- appendJSONL(THREADS_LOG, record);
106
- }
107
- }
108
-
109
- /**
110
- * Record a context event (token usage, optimization, etc.)
111
- * @param {Object} event
112
- */
113
- export function recordContextEvent(event) {
114
- recordEvent({ ...event, type: event.type || 'context_usage' });
115
- }
116
-
117
- /**
118
- * Record a trust level change event
119
- * @param {string} feature
120
- * @param {string} level - new trust level
121
- * @param {string} reason
122
- */
123
- export function recordTrustEvent(feature, level, reason = '') {
124
- appendJSONL(TRUST_LOG, {
125
- timestamp: new Date().toISOString(),
126
- type: 'trust_level_changed',
127
- feature,
128
- level,
129
- reason
130
- });
131
- }
132
-
133
- /**
134
- * Record auto-approval event (gate approved by trust)
135
- * @param {string} feature
136
- * @param {string} gate - 'design' | 'tasks' | 'proposal'
137
- * @param {string} trustLevel
138
- * @param {number} passRate
139
- */
140
- export function recordAutoApproval(feature, gate, trustLevel, passRate) {
141
- appendJSONL(TRUST_LOG, {
142
- timestamp: new Date().toISOString(),
143
- type: 'trust_auto_approved',
144
- feature,
145
- gate,
146
- trustLevel,
147
- passRate,
148
- timeSavedMinutes: 5 // estimated minutes saved per auto-approval
149
- });
150
- }
151
-
152
- /**
153
- * Get trust progression for a feature
154
- * @param {string} featureName
155
- * @returns {Array} Trust level history
156
- */
157
- export function getTrustProgression(featureName) {
158
- const events = readJSONL(TRUST_LOG)
159
- .filter(e => e.feature === featureName && e.type === 'trust_level_changed')
160
- .sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
161
- return events;
162
- }
163
-
164
- // ============================================================================
165
- // Feature Analytics
166
- // ============================================================================
167
-
168
- /**
169
- * Get analytics for a specific feature
170
- * @param {string} feature - Feature name
171
- * @returns {Object} Feature analytics
172
- */
173
- export function getFeatureAnalytics(feature) {
174
- const allThreadEvents = readJSONL(THREADS_LOG).filter(r => r.feature === feature);
175
- const allContextEvents = readJSONL(CONTEXT_LOG).filter(r => r.feature === feature);
176
-
177
- const threadsByStatus = allThreadEvents
178
- .filter(e => e.type === 'thread_completed' || e.type === 'thread_failed')
179
- .reduce((acc, e) => {
180
- const status = e.type === 'thread_completed' ? 'completed' : 'failed';
181
- acc[status] = (acc[status] || 0) + 1;
182
- return acc;
183
- }, {});
184
-
185
- const checkpoints = allThreadEvents.filter(e =>
186
- e.type === 'checkpoint_passed' || e.type === 'checkpoint_failed'
187
- );
188
- const checkpointPassRate = checkpoints.length > 0
189
- ? Math.round(checkpoints.filter(e => e.type === 'checkpoint_passed').length / checkpoints.length * 100)
190
- : 100;
191
-
192
- const totalDuration = allThreadEvents
193
- .filter(e => e.type === 'thread_completed' && e.data?.duration)
194
- .reduce((sum, e) => sum + e.data.duration, 0);
195
-
196
- const toolCallEvents = allThreadEvents.filter(e => e.type === 'tool_call');
197
- const tokenEvents = allContextEvents.filter(e => e.data?.tokensUsed);
198
- const totalTokens = tokenEvents.reduce((sum, e) => sum + (e.data.tokensUsed || 0), 0);
199
-
200
- return {
201
- feature,
202
- threads: threadsByStatus,
203
- checkpointPassRate,
204
- totalDuration,
205
- totalToolCalls: toolCallEvents.length,
206
- totalTokensUsed: totalTokens,
207
- optimizationEvents: allContextEvents.filter(e => e.type === 'context_optimized').length
208
- };
209
- }
210
-
211
- /**
212
- * Get project-wide analytics dashboard (30-day summary)
213
- * @returns {Object} Project analytics
214
- */
215
- export function getProjectAnalytics() {
216
- const allThreadEvents = recentRecords(readJSONL(THREADS_LOG));
217
- const allContextEvents = recentRecords(readJSONL(CONTEXT_LOG));
218
- const allTrustEvents = recentRecords(readJSONL(TRUST_LOG));
219
-
220
- const features = [...new Set(allThreadEvents.map(e => e.feature).filter(Boolean))];
221
-
222
- const completedThreads = allThreadEvents.filter(e => e.type === 'thread_completed');
223
- const failedThreads = allThreadEvents.filter(e => e.type === 'thread_failed');
224
- const checkpoints = allThreadEvents.filter(e =>
225
- e.type === 'checkpoint_passed' || e.type === 'checkpoint_failed'
226
- );
227
-
228
- const parallelEvents = allThreadEvents.filter(e => e.data?.type === 'parallel');
229
- const avgParallel = parallelEvents.length > 0
230
- ? parallelEvents.reduce((sum, e) => sum + (e.data?.concurrency || 1), 0) / parallelEvents.length
231
- : 1;
232
-
233
- const autoApprovalEvents = allTrustEvents.filter(e => e.type === 'trust_auto_approved');
234
- const avgTokens = allContextEvents.filter(e => e.data?.tokensUsed).length > 0
235
- ? Math.round(allContextEvents.reduce((sum, e) => sum + (e.data?.tokensUsed || 0), 0) / allContextEvents.filter(e => e.data?.tokensUsed).length)
236
- : 0;
237
-
238
- return {
239
- period: `${DASHBOARD_DAYS} days`,
240
- features: features.length,
241
- threads: {
242
- total: completedThreads.length + failedThreads.length,
243
- completed: completedThreads.length,
244
- failed: failedThreads.length
245
- },
246
- checkpoints: {
247
- total: checkpoints.length,
248
- passed: checkpoints.filter(e => e.type === 'checkpoint_passed').length,
249
- passRate: checkpoints.length > 0
250
- ? Math.round(checkpoints.filter(e => e.type === 'checkpoint_passed').length / checkpoints.length * 100)
251
- : 100
252
- },
253
- parallelization: {
254
- avgConcurrency: Math.round(avgParallel * 10) / 10,
255
- parallelThreads: parallelEvents.length
256
- },
257
- trust: {
258
- autoApprovals: autoApprovalEvents.length,
259
- trustChanges: allTrustEvents.filter(e => e.type === 'trust_level_changed').length
260
- },
261
- context: {
262
- avgTokensPerSession: avgTokens,
263
- optimizations: allContextEvents.filter(e => e.type === 'context_optimized').length
264
- }
265
- };
266
- }
267
-
268
- // ============================================================================
269
- // ASCII Charts
270
- // ============================================================================
271
-
272
- /**
273
- * Generate an ASCII bar chart
274
- * @param {Object} data - { label: value }
275
- * @param {Object} [opts]
276
- * @param {number} [opts.width=40] - Chart width in chars
277
- * @param {string} [opts.title] - Chart title
278
- * @returns {string} ASCII chart string
279
- */
280
- export function generateAsciiChart(data, { width = 40, title = '' } = {}) {
281
- const entries = Object.entries(data);
282
- if (entries.length === 0) return ' (no data)';
283
-
284
- const maxValue = Math.max(...entries.map(([, v]) => v), 1);
285
- const maxLabelLen = Math.max(...entries.map(([k]) => k.length));
286
- const lines = [];
287
-
288
- if (title) {
289
- lines.push(` ${title}`);
290
- lines.push(' ' + 'โ”€'.repeat(width + maxLabelLen + 5));
291
- }
292
-
293
- for (const [label, value] of entries) {
294
- const barLen = Math.round((value / maxValue) * width);
295
- const bar = 'โ–ˆ'.repeat(barLen);
296
- const paddedLabel = label.padStart(maxLabelLen);
297
- lines.push(` ${paddedLabel} โ”‚${bar} ${value}`);
298
- }
299
-
300
- return lines.join('\n');
301
- }
302
-
303
- /**
304
- * Generate thread timeline ASCII chart
305
- * @param {Array} events - Array of thread events with timestamps
306
- * @param {string} label - Chart label
307
- * @returns {string} ASCII timeline
308
- */
309
- export function generateTimelineChart(events, label = 'Timeline') {
310
- if (events.length === 0) return ' (no data)';
311
-
312
- const now = new Date();
313
- const days = Array.from({ length: 7 }, (_, i) => {
314
- const d = new Date(now);
315
- d.setDate(d.getDate() - (6 - i));
316
- return d.toLocaleDateString('en-US', { weekday: 'short' });
317
- });
318
-
319
- const counts = new Array(7).fill(0);
320
- for (const event of events) {
321
- const ts = new Date(event.timestamp);
322
- const daysAgo = Math.floor((now - ts) / (1000 * 60 * 60 * 24));
323
- if (daysAgo < 7) {
324
- counts[6 - daysAgo]++;
325
- }
326
- }
327
-
328
- const data = {};
329
- days.forEach((d, i) => { data[d] = counts[i]; });
330
-
331
- return generateAsciiChart(data, { title: label });
332
- }
333
-
334
- // ============================================================================
335
- // Maintenance
336
- // ============================================================================
337
-
338
- /**
339
- * Prune all analytics logs (remove entries older than 90 days)
340
- */
341
- export function pruneAnalytics() {
342
- pruneJSONL(THREADS_LOG);
343
- pruneJSONL(CONTEXT_LOG);
344
- pruneJSONL(TRUST_LOG);
345
- }
@@ -1,298 +0,0 @@
1
- import { readFileSync, existsSync } from 'fs';
2
- import { join } from 'path';
3
- import { execSync } from 'child_process';
4
- import chalk from 'chalk';
5
- import { executeStopHooks, isMaxRetriesExceeded, incrementRetryCount } from '../hooks/stop-hook-executor.js';
6
- import { recordEvent } from '../analytics/analytics-engine.js';
7
- import { getFeature } from '../../core/state/state-manager.js';
8
-
9
- /**
10
- * Checkpoint Hooks - Automated validation orchestration
11
- *
12
- * Runs validators, tests, and linters at checkpoint milestones (every N tasks).
13
- * Blocks progress if critical validations fail.
14
- */
15
-
16
- /**
17
- * Load LLM interaction configuration
18
- * @returns {Object} Configuration object
19
- */
20
- function loadLLMInteractionConfig() {
21
- const configPath = join(process.cwd(), '.morph/config/llm-interaction.json');
22
-
23
- if (!existsSync(configPath)) {
24
- // Return defaults if config doesn't exist
25
- return {
26
- checkpoints: {
27
- frequency: 3,
28
- autoValidate: true,
29
- validators: {
30
- enabled: ['architecture', 'packages', 'design-system', 'security'],
31
- optional: []
32
- },
33
- hooks: {
34
- runTests: false,
35
- runLinters: true,
36
- buildCheck: false
37
- },
38
- onFailure: {
39
- blockProgress: true,
40
- maxRetries: 3,
41
- escalateAfter: 3
42
- }
43
- }
44
- };
45
- }
46
-
47
- return JSON.parse(readFileSync(configPath, 'utf8'));
48
- }
49
-
50
- /**
51
- * Run a single validator
52
- * @param {string} validatorName - Validator to run (architecture, packages, etc.)
53
- * @param {string} featureName - Feature being validated
54
- * @returns {Promise<Object>} Validation result
55
- */
56
- async function runValidator(validatorName, featureName) {
57
- try {
58
- // Use existing validate command
59
- const result = execSync(
60
- `node bin/validate.js ${validatorName} --feature=${featureName} --json`,
61
- { encoding: 'utf8', stdio: 'pipe' }
62
- );
63
-
64
- const parsed = JSON.parse(result);
65
- return {
66
- validator: validatorName,
67
- passed: parsed.errors === 0,
68
- errors: parsed.errors || 0,
69
- warnings: parsed.warnings || 0,
70
- details: parsed.issues || []
71
- };
72
- } catch (error) {
73
- return {
74
- validator: validatorName,
75
- passed: false,
76
- errors: 1,
77
- warnings: 0,
78
- details: [{ message: error.message, severity: 'error' }]
79
- };
80
- }
81
- }
82
-
83
- /**
84
- * Run tests if configured
85
- * @param {string} featureName - Feature being tested
86
- * @returns {Promise<Object>} Test result
87
- */
88
- async function runTests(featureName) {
89
- try {
90
- execSync('npm test --passWithNoTests', { encoding: 'utf8', stdio: 'pipe' });
91
- return {
92
- validator: 'tests',
93
- passed: true,
94
- errors: 0,
95
- warnings: 0,
96
- details: []
97
- };
98
- } catch (error) {
99
- return {
100
- validator: 'tests',
101
- passed: false,
102
- errors: 1,
103
- warnings: 0,
104
- details: [{ message: 'Test suite failed', severity: 'error' }]
105
- };
106
- }
107
- }
108
-
109
- /**
110
- * Run linters if configured
111
- * @param {string} featureName - Feature being linted
112
- * @returns {Promise<Object>} Linter result
113
- */
114
- async function runLinters(featureName) {
115
- try {
116
- // Check if eslint exists
117
- if (existsSync(join(process.cwd(), 'node_modules/.bin/eslint'))) {
118
- execSync('npm run lint --if-present', { encoding: 'utf8', stdio: 'pipe' });
119
- }
120
-
121
- return {
122
- validator: 'linters',
123
- passed: true,
124
- errors: 0,
125
- warnings: 0,
126
- details: []
127
- };
128
- } catch (error) {
129
- return {
130
- validator: 'linters',
131
- passed: false,
132
- errors: 0,
133
- warnings: 1,
134
- details: [{ message: 'Linting warnings detected', severity: 'warning' }]
135
- };
136
- }
137
- }
138
-
139
- /**
140
- * Run checkpoint hooks - orchestrates all validation
141
- * @param {string} featureName - Feature name
142
- * @param {number} checkpointNum - Checkpoint number (1, 2, 3, etc.)
143
- * @returns {Promise<Object>} Checkpoint result
144
- */
145
- export async function runCheckpointHooks(featureName, checkpointNum) {
146
- const config = loadLLMInteractionConfig();
147
- const checkpointConfig = config.checkpoints;
148
-
149
- if (!checkpointConfig.autoValidate) {
150
- return {
151
- passed: true,
152
- skipped: true,
153
- message: 'Auto-validation disabled in config'
154
- };
155
- }
156
-
157
- console.log(`\n๐Ÿ” Running CHECKPOINT ${checkpointNum} for feature: ${featureName}`);
158
- console.log('โ”'.repeat(60));
159
-
160
- const results = [];
161
-
162
- // 1. Run enabled validators
163
- console.log('\n๐Ÿ“‹ Running validators...');
164
- for (const validatorName of checkpointConfig.validators.enabled) {
165
- process.stdout.write(` โ€ข ${validatorName}... `);
166
- const result = await runValidator(validatorName, featureName);
167
- results.push(result);
168
-
169
- if (result.passed) {
170
- console.log('โœ… PASSED');
171
- } else {
172
- console.log(`โŒ FAILED (${result.errors} errors, ${result.warnings} warnings)`);
173
- }
174
- }
175
-
176
- // 2. Run tests (if configured)
177
- if (checkpointConfig.hooks.runTests) {
178
- console.log('\n๐Ÿงช Running tests...');
179
- process.stdout.write(' โ€ข test suite... ');
180
- const testResult = await runTests(featureName);
181
- results.push(testResult);
182
-
183
- if (testResult.passed) {
184
- console.log('โœ… PASSED');
185
- } else {
186
- console.log('โŒ FAILED');
187
- }
188
- }
189
-
190
- // 3. Run linters (if configured)
191
- if (checkpointConfig.hooks.runLinters) {
192
- console.log('\n๐ŸŽจ Running linters...');
193
- process.stdout.write(' โ€ข code style... ');
194
- const lintResult = await runLinters(featureName);
195
- results.push(lintResult);
196
-
197
- if (lintResult.passed) {
198
- console.log('โœ… PASSED');
199
- } else {
200
- console.log('โš ๏ธ WARNINGS');
201
- }
202
- }
203
-
204
- // Calculate overall pass/fail
205
- const errorCount = results.reduce((sum, r) => sum + r.errors, 0);
206
- const warningCount = results.reduce((sum, r) => sum + r.warnings, 0);
207
- const passed = errorCount === 0;
208
-
209
- console.log('\n' + 'โ”'.repeat(60));
210
- console.log(`\n๐Ÿ“Š Checkpoint ${checkpointNum} Summary:`);
211
- console.log(` Errors: ${errorCount}`);
212
- console.log(` Warnings: ${warningCount}`);
213
- console.log(` Status: ${passed ? 'โœ… PASSED' : 'โŒ FAILED'}`);
214
-
215
- if (!passed) {
216
- console.log('\nโš ๏ธ Checkpoint failed! Fix violations before proceeding.');
217
-
218
- // Show details of failures
219
- results.filter(r => r.errors > 0).forEach(r => {
220
- console.log(`\nโŒ ${r.validator} errors:`);
221
- r.details.forEach(d => {
222
- if (d.severity === 'error') {
223
- console.log(` - ${d.message}`);
224
- }
225
- });
226
- });
227
- }
228
-
229
- console.log('\n' + 'โ”'.repeat(60) + '\n');
230
-
231
- // Run stop hooks for L-Thread support
232
- let stopHookResult = null;
233
- if (featureName) {
234
- try {
235
- // Use feature name as pseudo thread ID for stop hook feedback
236
- const pseudoThreadId = `checkpoint-${featureName}-${checkpointNum}`;
237
- stopHookResult = await executeStopHooks(pseudoThreadId, 'on-stop', { feature: featureName });
238
-
239
- if (!stopHookResult.passed) {
240
- // Check retry count
241
- let retryCount = null;
242
- if (isMaxRetriesExceeded(pseudoThreadId)) {
243
- console.log(chalk.red('\nโŒ Stop hooks failed 5+ times. Escalating to user.'));
244
- } else {
245
- retryCount = incrementRetryCount(pseudoThreadId);
246
- console.log(chalk.yellow(`\nโš  Stop hooks failed (attempt ${retryCount}/5). Feedback saved.`));
247
- if (stopHookResult.feedback?.suggestions?.length > 0) {
248
- stopHookResult.feedback.suggestions.forEach(s => console.log(` โ†’ ${s}`));
249
- }
250
- }
251
-
252
- // Record to analytics
253
- recordEvent({
254
- type: 'stop_hook_failed_at_checkpoint',
255
- feature: featureName,
256
- data: { checkpointNum, retryCount }
257
- });
258
- }
259
- } catch {
260
- // Stop hooks are non-critical โ€” don't block checkpoint if they error
261
- }
262
- }
263
-
264
- const phase = featureName ? (getFeature(featureName)?.phase || null) : null;
265
-
266
- return {
267
- passed,
268
- checkpointNum,
269
- timestamp: new Date().toISOString(),
270
- phase,
271
- results,
272
- summary: {
273
- errors: errorCount,
274
- warnings: warningCount,
275
- validatorsRun: results.length
276
- }
277
- };
278
- }
279
-
280
- /**
281
- * Check if checkpoint should run based on task count
282
- * @param {number} tasksCompleted - Number of tasks completed
283
- * @param {number} frequency - Checkpoint frequency (default: 3)
284
- * @returns {boolean} True if checkpoint should run
285
- */
286
- export function shouldRunCheckpoint(tasksCompleted, frequency = 3) {
287
- return tasksCompleted > 0 && tasksCompleted % frequency === 0;
288
- }
289
-
290
- /**
291
- * Get checkpoint number from task count
292
- * @param {number} tasksCompleted - Number of tasks completed
293
- * @param {number} frequency - Checkpoint frequency (default: 3)
294
- * @returns {number} Checkpoint number
295
- */
296
- export function getCheckpointNumber(tasksCompleted, frequency = 3) {
297
- return Math.floor(tasksCompleted / frequency);
298
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * Checkpoint System
3
- *
4
- * Automatic validation hooks triggered at checkpoints during feature implementation.
5
- */
6
-
7
- export * from './checkpoint-hooks.js';