@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,317 +0,0 @@
1
- /**
2
- * Thread Manager — Core thread lifecycle management
3
- *
4
- * Manages the full lifecycle of MORPH-SPEC threads:
5
- * - Types: base, parallel, fusion, long-running, zero-touch
6
- * - Status transitions: pending → running → completed | failed | killed
7
- * - Persists thread state to state.json threads{} section
8
- */
9
-
10
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
11
- import { join } from 'path';
12
- import { randomUUID } from 'crypto';
13
-
14
- const STATE_PATH = join(process.cwd(), '.morph/state.json');
15
-
16
- // ============================================================================
17
- // Thread Types and Status
18
- // ============================================================================
19
-
20
- export const THREAD_TYPES = {
21
- BASE: 'base',
22
- PARALLEL: 'parallel',
23
- FUSION: 'fusion',
24
- LONG_RUNNING: 'long-running',
25
- ZERO_TOUCH: 'zero-touch'
26
- };
27
-
28
- export const THREAD_STATUS = {
29
- PENDING: 'pending',
30
- RUNNING: 'running',
31
- COMPLETED: 'completed',
32
- FAILED: 'failed',
33
- KILLED: 'killed'
34
- };
35
-
36
- // ============================================================================
37
- // State I/O
38
- // ============================================================================
39
-
40
- function loadState() {
41
- if (!existsSync(STATE_PATH)) {
42
- throw new Error(`State file not found: ${STATE_PATH}`);
43
- }
44
- return JSON.parse(readFileSync(STATE_PATH, 'utf8'));
45
- }
46
-
47
- function saveState(state) {
48
- state.metadata = state.metadata || {};
49
- state.metadata.lastUpdated = new Date().toISOString();
50
- writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), 'utf8');
51
- }
52
-
53
- function ensureThreadsSection(state) {
54
- if (!state.threads) {
55
- state.threads = {};
56
- }
57
- return state;
58
- }
59
-
60
- // ============================================================================
61
- // Thread CRUD
62
- // ============================================================================
63
-
64
- /**
65
- * Create a new thread
66
- * @param {Object} opts
67
- * @param {string} opts.feature - Feature name this thread belongs to
68
- * @param {string} opts.type - Thread type (base|parallel|fusion|long-running|zero-touch)
69
- * @param {string} [opts.agent] - Agent assigned to this thread
70
- * @param {string} [opts.mission] - Thread mission description
71
- * @param {Object} [opts.meta] - Additional metadata
72
- * @returns {Object} Created thread object
73
- */
74
- export function createThread({ feature, type = THREAD_TYPES.BASE, agent = null, mission = '', meta = {} }) {
75
- const state = ensureThreadsSection(loadState());
76
- const id = randomUUID();
77
- const now = new Date().toISOString();
78
-
79
- const thread = {
80
- id,
81
- feature,
82
- type,
83
- agent,
84
- mission,
85
- status: THREAD_STATUS.PENDING,
86
- createdAt: now,
87
- startedAt: null,
88
- completedAt: null,
89
- duration: null,
90
- events: [],
91
- metrics: {
92
- toolCalls: 0,
93
- tokensUsed: 0,
94
- checkpointsPassed: 0,
95
- checkpointsFailed: 0
96
- },
97
- meta
98
- };
99
-
100
- state.threads[id] = thread;
101
- saveState(state);
102
- return thread;
103
- }
104
-
105
- /**
106
- * Update thread fields
107
- * @param {string} id - Thread ID
108
- * @param {Object} updates - Fields to update
109
- * @returns {Object} Updated thread
110
- */
111
- export function updateThread(id, updates) {
112
- const state = ensureThreadsSection(loadState());
113
-
114
- if (!state.threads[id]) {
115
- throw new Error(`Thread not found: ${id}`);
116
- }
117
-
118
- state.threads[id] = { ...state.threads[id], ...updates };
119
- saveState(state);
120
- return state.threads[id];
121
- }
122
-
123
- /**
124
- * Get a thread by ID
125
- * @param {string} id - Thread ID
126
- * @returns {Object|null} Thread or null
127
- */
128
- export function getThread(id) {
129
- const state = ensureThreadsSection(loadState());
130
- return state.threads[id] || null;
131
- }
132
-
133
- /**
134
- * List threads, optionally filtered
135
- * @param {Object} [filter]
136
- * @param {string} [filter.feature] - Filter by feature name
137
- * @param {string} [filter.status] - Filter by status
138
- * @param {string} [filter.type] - Filter by type
139
- * @returns {Array} Array of thread objects
140
- */
141
- export function listThreads({ feature, status, type } = {}) {
142
- const state = ensureThreadsSection(loadState());
143
- let threads = Object.values(state.threads);
144
-
145
- if (feature) threads = threads.filter(t => t.feature === feature);
146
- if (status) threads = threads.filter(t => t.status === status);
147
- if (type) threads = threads.filter(t => t.type === type);
148
-
149
- return threads.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
150
- }
151
-
152
- /**
153
- * Start a thread (pending → running)
154
- * @param {string} id - Thread ID
155
- * @returns {Object} Updated thread
156
- */
157
- export function startThread(id) {
158
- const now = new Date().toISOString();
159
- return updateThread(id, {
160
- status: THREAD_STATUS.RUNNING,
161
- startedAt: now
162
- });
163
- }
164
-
165
- /**
166
- * Complete a thread (running → completed)
167
- * @param {string} id - Thread ID
168
- * @param {Object} [result] - Optional result metadata
169
- * @returns {Object} Updated thread
170
- */
171
- export function completeThread(id, result = {}) {
172
- const thread = getThread(id);
173
- if (!thread) throw new Error(`Thread not found: ${id}`);
174
-
175
- const now = new Date();
176
- const startedAt = thread.startedAt ? new Date(thread.startedAt) : now;
177
- const duration = Math.round((now - startedAt) / 1000); // seconds
178
-
179
- return updateThread(id, {
180
- status: THREAD_STATUS.COMPLETED,
181
- completedAt: now.toISOString(),
182
- duration,
183
- result
184
- });
185
- }
186
-
187
- /**
188
- * Mark a thread as failed
189
- * @param {string} id - Thread ID
190
- * @param {string} reason - Failure reason
191
- * @returns {Object} Updated thread
192
- */
193
- export function failThread(id, reason = '') {
194
- const thread = getThread(id);
195
- if (!thread) throw new Error(`Thread not found: ${id}`);
196
-
197
- const now = new Date();
198
- const startedAt = thread.startedAt ? new Date(thread.startedAt) : now;
199
- const duration = Math.round((now - startedAt) / 1000);
200
-
201
- return updateThread(id, {
202
- status: THREAD_STATUS.FAILED,
203
- completedAt: now.toISOString(),
204
- duration,
205
- failureReason: reason
206
- });
207
- }
208
-
209
- /**
210
- * Kill (terminate) a running thread
211
- * @param {string} id - Thread ID
212
- * @returns {Object} Updated thread
213
- */
214
- export function killThread(id) {
215
- const thread = getThread(id);
216
- if (!thread) throw new Error(`Thread not found: ${id}`);
217
- if (thread.status === THREAD_STATUS.COMPLETED || thread.status === THREAD_STATUS.KILLED) {
218
- throw new Error(`Cannot kill thread in status: ${thread.status}`);
219
- }
220
-
221
- const now = new Date();
222
- const startedAt = thread.startedAt ? new Date(thread.startedAt) : now;
223
- const duration = Math.round((now - startedAt) / 1000);
224
-
225
- return updateThread(id, {
226
- status: THREAD_STATUS.KILLED,
227
- completedAt: now.toISOString(),
228
- duration
229
- });
230
- }
231
-
232
- /**
233
- * Add an event to a thread's event log
234
- * @param {string} id - Thread ID
235
- * @param {string} type - Event type
236
- * @param {Object} [data] - Event data
237
- */
238
- export function addThreadEvent(id, type, data = {}) {
239
- const state = ensureThreadsSection(loadState());
240
- const thread = state.threads[id];
241
- if (!thread) throw new Error(`Thread not found: ${id}`);
242
-
243
- thread.events = thread.events || [];
244
- thread.events.push({
245
- type,
246
- timestamp: new Date().toISOString(),
247
- data
248
- });
249
-
250
- saveState(state);
251
- }
252
-
253
- /**
254
- * Increment thread metrics counter
255
- * @param {string} id - Thread ID
256
- * @param {string} metric - Metric name (toolCalls|tokensUsed|checkpointsPassed|checkpointsFailed)
257
- * @param {number} [amount=1] - Amount to increment
258
- */
259
- export function incrementThreadMetric(id, metric, amount = 1) {
260
- const state = ensureThreadsSection(loadState());
261
- const thread = state.threads[id];
262
- if (!thread) throw new Error(`Thread not found: ${id}`);
263
-
264
- thread.metrics = thread.metrics || {};
265
- thread.metrics[metric] = (thread.metrics[metric] || 0) + amount;
266
- saveState(state);
267
- }
268
-
269
- // ============================================================================
270
- // Analytics
271
- // ============================================================================
272
-
273
- /**
274
- * Get analytics for threads belonging to a feature
275
- * @param {string} feature - Feature name
276
- * @returns {Object} Analytics summary
277
- */
278
- export function getThreadAnalytics(feature) {
279
- const threads = listThreads({ feature });
280
-
281
- const byStatus = threads.reduce((acc, t) => {
282
- acc[t.status] = (acc[t.status] || 0) + 1;
283
- return acc;
284
- }, {});
285
-
286
- const byType = threads.reduce((acc, t) => {
287
- acc[t.type] = (acc[t.type] || 0) + 1;
288
- return acc;
289
- }, {});
290
-
291
- const completed = threads.filter(t => t.status === THREAD_STATUS.COMPLETED);
292
- const avgDuration = completed.length > 0
293
- ? Math.round(completed.reduce((sum, t) => sum + (t.duration || 0), 0) / completed.length)
294
- : 0;
295
-
296
- const totalToolCalls = threads.reduce((sum, t) => sum + (t.metrics?.toolCalls || 0), 0);
297
- const parallelThreads = threads.filter(t => t.type === THREAD_TYPES.PARALLEL);
298
- const avgParallel = parallelThreads.length;
299
-
300
- return {
301
- feature,
302
- total: threads.length,
303
- byStatus,
304
- byType,
305
- avgDuration,
306
- totalToolCalls,
307
- parallelEfficiency: {
308
- avgParallel,
309
- maxParallel: Math.max(...threads.map(() => parallelThreads.length), 0)
310
- },
311
- checkpointPassRate: threads.reduce((sum, t) => {
312
- const p = t.metrics?.checkpointsPassed || 0;
313
- const f = t.metrics?.checkpointsFailed || 0;
314
- return p + f > 0 ? sum + (p / (p + f)) : sum;
315
- }, 0) / Math.max(threads.filter(t => (t.metrics?.checkpointsPassed || 0) + (t.metrics?.checkpointsFailed || 0) > 0).length, 1)
316
- };
317
- }
@@ -1,202 +0,0 @@
1
- /**
2
- * Artifact Trail — Debugging trail system
3
- *
4
- * Creates checkpoint artifact directories and saves:
5
- * - Intermediate files (spec drafts, contract drafts)
6
- * - Agent reasoning logs
7
- * - Validator output logs
8
- * - Failure artifacts on checkpoint failure
9
- *
10
- * Structure:
11
- * .morph/features/{feature}/artifacts/
12
- * checkpoint-{n}/
13
- * spec-draft.md
14
- * contracts-draft.cs
15
- * agent-reasoning.json
16
- * validator-output.json
17
- * checkpoint-{n}/failures/
18
- * failure-detail.json
19
- */
20
-
21
- import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
22
- import { join } from 'path';
23
-
24
- const BASE_DIR = join(process.cwd(), '.morph/features');
25
-
26
- // ============================================================================
27
- // Path Helpers
28
- // ============================================================================
29
-
30
- function artifactsDir(feature) {
31
- return join(BASE_DIR, feature, 'artifacts');
32
- }
33
-
34
- function checkpointDir(feature, checkpointNum) {
35
- return join(artifactsDir(feature), `checkpoint-${checkpointNum}`);
36
- }
37
-
38
- function failuresDir(feature, checkpointNum) {
39
- return join(checkpointDir(feature, checkpointNum), 'failures');
40
- }
41
-
42
- function ensureDir(dir) {
43
- if (!existsSync(dir)) {
44
- mkdirSync(dir, { recursive: true });
45
- }
46
- return dir;
47
- }
48
-
49
- // ============================================================================
50
- // Checkpoint Artifacts
51
- // ============================================================================
52
-
53
- /**
54
- * Initialize a checkpoint artifact directory
55
- * @param {string} feature - Feature name
56
- * @param {number} checkpointNum - Checkpoint number
57
- * @returns {string} Path to checkpoint directory
58
- */
59
- export function initCheckpointArtifacts(feature, checkpointNum) {
60
- const dir = checkpointDir(feature, checkpointNum);
61
- ensureDir(dir);
62
-
63
- const manifest = {
64
- feature,
65
- checkpointNum,
66
- createdAt: new Date().toISOString(),
67
- files: []
68
- };
69
-
70
- writeFileSync(join(dir, 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
71
- return dir;
72
- }
73
-
74
- /**
75
- * Save an intermediate artifact file to a checkpoint directory
76
- * @param {string} feature - Feature name
77
- * @param {number} checkpointNum - Checkpoint number
78
- * @param {string} filename - File name (e.g., 'spec-draft.md')
79
- * @param {string} content - File content
80
- * @returns {string} Full path to saved file
81
- */
82
- export function saveArtifact(feature, checkpointNum, filename, content) {
83
- const dir = ensureDir(checkpointDir(feature, checkpointNum));
84
- const filePath = join(dir, filename);
85
-
86
- writeFileSync(filePath, content, 'utf8');
87
-
88
- // Update manifest
89
- const manifestPath = join(dir, 'manifest.json');
90
- if (existsSync(manifestPath)) {
91
- const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
92
- if (!manifest.files.includes(filename)) {
93
- manifest.files.push(filename);
94
- manifest.updatedAt = new Date().toISOString();
95
- writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
96
- }
97
- }
98
-
99
- return filePath;
100
- }
101
-
102
- /**
103
- * Save agent reasoning log
104
- * @param {string} feature - Feature name
105
- * @param {number} checkpointNum - Checkpoint number
106
- * @param {Object} reasoning - Agent reasoning data
107
- */
108
- export function saveAgentReasoning(feature, checkpointNum, reasoning) {
109
- const entry = {
110
- timestamp: new Date().toISOString(),
111
- ...reasoning
112
- };
113
- saveArtifact(feature, checkpointNum, 'agent-reasoning.json', JSON.stringify(entry, null, 2));
114
- }
115
-
116
- /**
117
- * Save validator output log
118
- * @param {string} feature - Feature name
119
- * @param {number} checkpointNum - Checkpoint number
120
- * @param {Object} validatorOutput - Validator results
121
- */
122
- export function saveValidatorOutput(feature, checkpointNum, validatorOutput) {
123
- const entry = {
124
- timestamp: new Date().toISOString(),
125
- ...validatorOutput
126
- };
127
- saveArtifact(feature, checkpointNum, 'validator-output.json', JSON.stringify(entry, null, 2));
128
- }
129
-
130
- // ============================================================================
131
- // Failure Artifacts
132
- // ============================================================================
133
-
134
- /**
135
- * Save failure artifacts when a checkpoint fails
136
- * @param {string} feature - Feature name
137
- * @param {number} checkpointNum - Checkpoint number
138
- * @param {Object} failure - Failure details
139
- * @param {string} failure.reason - Failure reason
140
- * @param {Array} failure.errors - Array of error objects
141
- * @param {number} failure.attemptNumber - Which retry attempt (1-3)
142
- */
143
- export function saveFailureArtifacts(feature, checkpointNum, failure) {
144
- const dir = ensureDir(failuresDir(feature, checkpointNum));
145
- const filename = `failure-attempt-${failure.attemptNumber || 1}.json`;
146
-
147
- const entry = {
148
- feature,
149
- checkpointNum,
150
- timestamp: new Date().toISOString(),
151
- ...failure
152
- };
153
-
154
- writeFileSync(join(dir, filename), JSON.stringify(entry, null, 2), 'utf8');
155
- return join(dir, filename);
156
- }
157
-
158
- /**
159
- * Get all failure artifacts for a checkpoint
160
- * @param {string} feature - Feature name
161
- * @param {number} checkpointNum - Checkpoint number
162
- * @returns {Array} Array of failure records
163
- */
164
- export function getFailureArtifacts(feature, checkpointNum) {
165
- const dir = failuresDir(feature, checkpointNum);
166
- if (!existsSync(dir)) return [];
167
-
168
- const files = readdirSync(dir).filter(f => f.endsWith('.json'));
169
-
170
- return files.map(f => {
171
- try {
172
- return JSON.parse(readFileSync(join(dir, f), 'utf8'));
173
- } catch {
174
- return null;
175
- }
176
- }).filter(Boolean);
177
- }
178
-
179
- /**
180
- * List all checkpoints for a feature
181
- * @param {string} feature - Feature name
182
- * @returns {Array} Array of checkpoint info objects
183
- */
184
- export function listCheckpointArtifacts(feature) {
185
- const dir = artifactsDir(feature);
186
- if (!existsSync(dir)) return [];
187
-
188
- try {
189
- return readdirSync(dir, { withFileTypes: true })
190
- .filter(d => d.isDirectory() && d.name.startsWith('checkpoint-'))
191
- .map(d => {
192
- const checkpointPath = join(dir, d.name);
193
- const manifestPath = join(checkpointPath, 'manifest.json');
194
- if (existsSync(manifestPath)) {
195
- return JSON.parse(readFileSync(manifestPath, 'utf8'));
196
- }
197
- return { name: d.name, path: checkpointPath };
198
- });
199
- } catch {
200
- return [];
201
- }
202
- }