claude-code-workflow 6.3.22 → 6.3.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/issue-plan-agent.md +10 -5
- package/.claude/commands/issue/plan.md +1 -1
- package/.claude/skills/review-code/SKILL.md +170 -0
- package/.claude/skills/review-code/phases/actions/action-collect-context.md +139 -0
- package/.claude/skills/review-code/phases/actions/action-complete.md +115 -0
- package/.claude/skills/review-code/phases/actions/action-deep-review.md +302 -0
- package/.claude/skills/review-code/phases/actions/action-generate-report.md +263 -0
- package/.claude/skills/review-code/phases/actions/action-quick-scan.md +164 -0
- package/.claude/skills/review-code/phases/orchestrator.md +251 -0
- package/.claude/skills/review-code/phases/state-manager.md +752 -0
- package/.claude/skills/review-code/phases/state-schema.md +174 -0
- package/.claude/skills/review-code/specs/issue-classification.md +228 -0
- package/.claude/skills/review-code/specs/quality-standards.md +214 -0
- package/.claude/skills/review-code/specs/review-dimensions.md +337 -0
- package/.claude/skills/review-code/specs/rules/architecture-rules.json +63 -0
- package/.claude/skills/review-code/specs/rules/correctness-rules.json +60 -0
- package/.claude/skills/review-code/specs/rules/index.md +140 -0
- package/.claude/skills/review-code/specs/rules/performance-rules.json +59 -0
- package/.claude/skills/review-code/specs/rules/readability-rules.json +60 -0
- package/.claude/skills/review-code/specs/rules/security-rules.json +58 -0
- package/.claude/skills/review-code/specs/rules/testing-rules.json +59 -0
- package/.claude/skills/review-code/templates/issue-template.md +186 -0
- package/.claude/skills/review-code/templates/review-report.md +173 -0
- package/.claude/skills/skill-generator/SKILL.md +56 -17
- package/.claude/skills/skill-generator/templates/autonomous-orchestrator.md +10 -0
- package/.claude/skills/skill-generator/templates/sequential-phase.md +9 -0
- package/.claude/skills/skill-generator/templates/skill-md.md +84 -5
- package/.claude/workflows/cli-templates/schemas/solution-schema.json +3 -3
- package/ccw/src/templates/dashboard-js/views/issue-manager.js +8 -0
- package/package.json +1 -1
- package/.claude/skills/code-reviewer/README.md +0 -340
- package/.claude/skills/code-reviewer/SKILL.md +0 -308
- package/.claude/skills/code-reviewer/phases/01-code-discovery.md +0 -246
- package/.claude/skills/code-reviewer/phases/02-security-analysis.md +0 -442
- package/.claude/skills/code-reviewer/phases/03-best-practices-review.md +0 -36
- package/.claude/skills/code-reviewer/phases/04-report-generation.md +0 -278
- package/.claude/skills/code-reviewer/specs/best-practices-requirements.md +0 -346
- package/.claude/skills/code-reviewer/specs/quality-standards.md +0 -252
- package/.claude/skills/code-reviewer/specs/security-requirements.md +0 -243
- package/.claude/skills/code-reviewer/templates/best-practice-finding.md +0 -234
- package/.claude/skills/code-reviewer/templates/report-template.md +0 -316
- package/.claude/skills/code-reviewer/templates/security-finding.md +0 -161
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
# State Manager
|
|
2
|
+
|
|
3
|
+
Centralized state management module for Code Review workflow. Provides atomic operations, automatic backups, validation, and rollback capabilities.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This module solves the fragile state management problem by providing:
|
|
8
|
+
- **Atomic updates** - Write to temp file, then rename (prevents corruption)
|
|
9
|
+
- **Automatic backups** - Every update creates a backup first
|
|
10
|
+
- **Rollback capability** - Restore from backup on failure
|
|
11
|
+
- **Schema validation** - Ensure state structure integrity
|
|
12
|
+
- **Change history** - Track all state modifications
|
|
13
|
+
|
|
14
|
+
## File Structure
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
{workDir}/
|
|
18
|
+
state.json # Current state
|
|
19
|
+
state.backup.json # Latest backup
|
|
20
|
+
state-history.json # Change history log
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## API Reference
|
|
24
|
+
|
|
25
|
+
### initState(workDir)
|
|
26
|
+
|
|
27
|
+
Initialize a new state file with default values.
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
/**
|
|
31
|
+
* Initialize state file with default structure
|
|
32
|
+
* @param {string} workDir - Working directory path
|
|
33
|
+
* @returns {object} - Initial state object
|
|
34
|
+
*/
|
|
35
|
+
function initState(workDir) {
|
|
36
|
+
const now = new Date().toISOString();
|
|
37
|
+
|
|
38
|
+
const initialState = {
|
|
39
|
+
status: 'pending',
|
|
40
|
+
started_at: now,
|
|
41
|
+
updated_at: now,
|
|
42
|
+
context: null,
|
|
43
|
+
scan_completed: false,
|
|
44
|
+
scan_summary: null,
|
|
45
|
+
reviewed_dimensions: [],
|
|
46
|
+
current_dimension: null,
|
|
47
|
+
findings: {
|
|
48
|
+
correctness: [],
|
|
49
|
+
readability: [],
|
|
50
|
+
performance: [],
|
|
51
|
+
security: [],
|
|
52
|
+
testing: [],
|
|
53
|
+
architecture: []
|
|
54
|
+
},
|
|
55
|
+
report_generated: false,
|
|
56
|
+
report_path: null,
|
|
57
|
+
current_action: null,
|
|
58
|
+
completed_actions: [],
|
|
59
|
+
errors: [],
|
|
60
|
+
error_count: 0,
|
|
61
|
+
summary: null
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Write state file
|
|
65
|
+
const statePath = `${workDir}/state.json`;
|
|
66
|
+
Write(statePath, JSON.stringify(initialState, null, 2));
|
|
67
|
+
|
|
68
|
+
// Initialize history log
|
|
69
|
+
const historyPath = `${workDir}/state-history.json`;
|
|
70
|
+
const historyEntry = {
|
|
71
|
+
entries: [{
|
|
72
|
+
timestamp: now,
|
|
73
|
+
action: 'init',
|
|
74
|
+
changes: { type: 'initialize', status: 'pending' }
|
|
75
|
+
}]
|
|
76
|
+
};
|
|
77
|
+
Write(historyPath, JSON.stringify(historyEntry, null, 2));
|
|
78
|
+
|
|
79
|
+
console.log(`[StateManager] Initialized state at ${statePath}`);
|
|
80
|
+
return initialState;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### getState(workDir)
|
|
85
|
+
|
|
86
|
+
Read and parse current state from file.
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
/**
|
|
90
|
+
* Read current state from file
|
|
91
|
+
* @param {string} workDir - Working directory path
|
|
92
|
+
* @returns {object|null} - Current state or null if not found
|
|
93
|
+
*/
|
|
94
|
+
function getState(workDir) {
|
|
95
|
+
const statePath = `${workDir}/state.json`;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const content = Read(statePath);
|
|
99
|
+
const state = JSON.parse(content);
|
|
100
|
+
|
|
101
|
+
// Validate structure before returning
|
|
102
|
+
const validation = validateState(state);
|
|
103
|
+
if (!validation.valid) {
|
|
104
|
+
console.warn(`[StateManager] State validation warnings: ${validation.warnings.join(', ')}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return state;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error(`[StateManager] Failed to read state: ${error.message}`);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### updateState(workDir, updates)
|
|
116
|
+
|
|
117
|
+
Safely update state with atomic write and automatic backup.
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
/**
|
|
121
|
+
* Safely update state with atomic write
|
|
122
|
+
* @param {string} workDir - Working directory path
|
|
123
|
+
* @param {object} updates - Partial state updates to apply
|
|
124
|
+
* @returns {object} - Updated state object
|
|
125
|
+
* @throws {Error} - If update fails (automatically rolls back)
|
|
126
|
+
*/
|
|
127
|
+
function updateState(workDir, updates) {
|
|
128
|
+
const statePath = `${workDir}/state.json`;
|
|
129
|
+
const tempPath = `${workDir}/state.tmp.json`;
|
|
130
|
+
const backupPath = `${workDir}/state.backup.json`;
|
|
131
|
+
const historyPath = `${workDir}/state-history.json`;
|
|
132
|
+
|
|
133
|
+
// Step 1: Read current state
|
|
134
|
+
let currentState;
|
|
135
|
+
try {
|
|
136
|
+
currentState = JSON.parse(Read(statePath));
|
|
137
|
+
} catch (error) {
|
|
138
|
+
throw new Error(`Cannot read current state: ${error.message}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Step 2: Create backup before any modification
|
|
142
|
+
try {
|
|
143
|
+
Write(backupPath, JSON.stringify(currentState, null, 2));
|
|
144
|
+
} catch (error) {
|
|
145
|
+
throw new Error(`Cannot create backup: ${error.message}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Step 3: Merge updates
|
|
149
|
+
const now = new Date().toISOString();
|
|
150
|
+
const newState = deepMerge(currentState, {
|
|
151
|
+
...updates,
|
|
152
|
+
updated_at: now
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Step 4: Validate new state
|
|
156
|
+
const validation = validateState(newState);
|
|
157
|
+
if (!validation.valid && validation.errors.length > 0) {
|
|
158
|
+
throw new Error(`Invalid state after update: ${validation.errors.join(', ')}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Step 5: Write to temp file first (atomic preparation)
|
|
162
|
+
try {
|
|
163
|
+
Write(tempPath, JSON.stringify(newState, null, 2));
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new Error(`Cannot write temp state: ${error.message}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Step 6: Atomic rename (replace original with temp)
|
|
169
|
+
try {
|
|
170
|
+
// Read temp and write to original (simulating atomic rename)
|
|
171
|
+
const tempContent = Read(tempPath);
|
|
172
|
+
Write(statePath, tempContent);
|
|
173
|
+
|
|
174
|
+
// Clean up temp file
|
|
175
|
+
Bash(`rm -f "${tempPath}"`);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
// Rollback: restore from backup
|
|
178
|
+
console.error(`[StateManager] Update failed, rolling back: ${error.message}`);
|
|
179
|
+
try {
|
|
180
|
+
const backup = Read(backupPath);
|
|
181
|
+
Write(statePath, backup);
|
|
182
|
+
} catch (rollbackError) {
|
|
183
|
+
throw new Error(`Critical: Update failed and rollback failed: ${rollbackError.message}`);
|
|
184
|
+
}
|
|
185
|
+
throw new Error(`Update failed, rolled back: ${error.message}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Step 7: Record in history
|
|
189
|
+
try {
|
|
190
|
+
let history = { entries: [] };
|
|
191
|
+
try {
|
|
192
|
+
history = JSON.parse(Read(historyPath));
|
|
193
|
+
} catch (e) {
|
|
194
|
+
// History file may not exist, start fresh
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
history.entries.push({
|
|
198
|
+
timestamp: now,
|
|
199
|
+
action: 'update',
|
|
200
|
+
changes: summarizeChanges(currentState, newState, updates)
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Keep only last 100 entries
|
|
204
|
+
if (history.entries.length > 100) {
|
|
205
|
+
history.entries = history.entries.slice(-100);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
Write(historyPath, JSON.stringify(history, null, 2));
|
|
209
|
+
} catch (error) {
|
|
210
|
+
// History logging failure is non-critical
|
|
211
|
+
console.warn(`[StateManager] Failed to log history: ${error.message}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log(`[StateManager] State updated successfully`);
|
|
215
|
+
return newState;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Deep merge helper - merges nested objects
|
|
220
|
+
*/
|
|
221
|
+
function deepMerge(target, source) {
|
|
222
|
+
const result = { ...target };
|
|
223
|
+
|
|
224
|
+
for (const key of Object.keys(source)) {
|
|
225
|
+
if (source[key] === null || source[key] === undefined) {
|
|
226
|
+
result[key] = source[key];
|
|
227
|
+
} else if (Array.isArray(source[key])) {
|
|
228
|
+
result[key] = source[key];
|
|
229
|
+
} else if (typeof source[key] === 'object' && typeof target[key] === 'object') {
|
|
230
|
+
result[key] = deepMerge(target[key], source[key]);
|
|
231
|
+
} else {
|
|
232
|
+
result[key] = source[key];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Summarize changes for history logging
|
|
241
|
+
*/
|
|
242
|
+
function summarizeChanges(oldState, newState, updates) {
|
|
243
|
+
const changes = {};
|
|
244
|
+
|
|
245
|
+
for (const key of Object.keys(updates)) {
|
|
246
|
+
if (key === 'updated_at') continue;
|
|
247
|
+
|
|
248
|
+
const oldVal = oldState[key];
|
|
249
|
+
const newVal = newState[key];
|
|
250
|
+
|
|
251
|
+
if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
|
|
252
|
+
changes[key] = {
|
|
253
|
+
from: typeof oldVal === 'object' ? '[object]' : oldVal,
|
|
254
|
+
to: typeof newVal === 'object' ? '[object]' : newVal
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return changes;
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### validateState(state)
|
|
264
|
+
|
|
265
|
+
Validate state structure against schema.
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
/**
|
|
269
|
+
* Validate state structure
|
|
270
|
+
* @param {object} state - State object to validate
|
|
271
|
+
* @returns {object} - { valid: boolean, errors: string[], warnings: string[] }
|
|
272
|
+
*/
|
|
273
|
+
function validateState(state) {
|
|
274
|
+
const errors = [];
|
|
275
|
+
const warnings = [];
|
|
276
|
+
|
|
277
|
+
// Required fields
|
|
278
|
+
const requiredFields = ['status', 'started_at', 'updated_at'];
|
|
279
|
+
for (const field of requiredFields) {
|
|
280
|
+
if (state[field] === undefined) {
|
|
281
|
+
errors.push(`Missing required field: ${field}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Status validation
|
|
286
|
+
const validStatuses = ['pending', 'running', 'completed', 'failed', 'user_exit'];
|
|
287
|
+
if (state.status && !validStatuses.includes(state.status)) {
|
|
288
|
+
errors.push(`Invalid status: ${state.status}. Must be one of: ${validStatuses.join(', ')}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Timestamp format validation
|
|
292
|
+
const timestampFields = ['started_at', 'updated_at', 'completed_at'];
|
|
293
|
+
for (const field of timestampFields) {
|
|
294
|
+
if (state[field] && !isValidISOTimestamp(state[field])) {
|
|
295
|
+
warnings.push(`Invalid timestamp format for ${field}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Findings structure validation
|
|
300
|
+
if (state.findings) {
|
|
301
|
+
const expectedDimensions = ['correctness', 'readability', 'performance', 'security', 'testing', 'architecture'];
|
|
302
|
+
for (const dim of expectedDimensions) {
|
|
303
|
+
if (!Array.isArray(state.findings[dim])) {
|
|
304
|
+
warnings.push(`findings.${dim} should be an array`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Context validation (when present)
|
|
310
|
+
if (state.context !== null && state.context !== undefined) {
|
|
311
|
+
const contextFields = ['target_path', 'files', 'language', 'total_lines', 'file_count'];
|
|
312
|
+
for (const field of contextFields) {
|
|
313
|
+
if (state.context[field] === undefined) {
|
|
314
|
+
warnings.push(`context.${field} is missing`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Error count validation
|
|
320
|
+
if (typeof state.error_count !== 'number') {
|
|
321
|
+
warnings.push('error_count should be a number');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Array fields validation
|
|
325
|
+
const arrayFields = ['reviewed_dimensions', 'completed_actions', 'errors'];
|
|
326
|
+
for (const field of arrayFields) {
|
|
327
|
+
if (state[field] !== undefined && !Array.isArray(state[field])) {
|
|
328
|
+
errors.push(`${field} must be an array`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
valid: errors.length === 0,
|
|
334
|
+
errors,
|
|
335
|
+
warnings
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Check if string is valid ISO timestamp
|
|
341
|
+
*/
|
|
342
|
+
function isValidISOTimestamp(str) {
|
|
343
|
+
if (typeof str !== 'string') return false;
|
|
344
|
+
const date = new Date(str);
|
|
345
|
+
return !isNaN(date.getTime()) && str.includes('T');
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### backupState(workDir)
|
|
350
|
+
|
|
351
|
+
Create a manual backup of current state.
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
/**
|
|
355
|
+
* Create a manual backup of current state
|
|
356
|
+
* @param {string} workDir - Working directory path
|
|
357
|
+
* @param {string} [suffix] - Optional suffix for backup file name
|
|
358
|
+
* @returns {string} - Backup file path
|
|
359
|
+
*/
|
|
360
|
+
function backupState(workDir, suffix = null) {
|
|
361
|
+
const statePath = `${workDir}/state.json`;
|
|
362
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
363
|
+
const backupName = suffix
|
|
364
|
+
? `state.backup.${suffix}.json`
|
|
365
|
+
: `state.backup.${timestamp}.json`;
|
|
366
|
+
const backupPath = `${workDir}/${backupName}`;
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const content = Read(statePath);
|
|
370
|
+
Write(backupPath, content);
|
|
371
|
+
console.log(`[StateManager] Backup created: ${backupPath}`);
|
|
372
|
+
return backupPath;
|
|
373
|
+
} catch (error) {
|
|
374
|
+
throw new Error(`Failed to create backup: ${error.message}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### restoreState(workDir, backupPath)
|
|
380
|
+
|
|
381
|
+
Restore state from a backup file.
|
|
382
|
+
|
|
383
|
+
```javascript
|
|
384
|
+
/**
|
|
385
|
+
* Restore state from a backup file
|
|
386
|
+
* @param {string} workDir - Working directory path
|
|
387
|
+
* @param {string} [backupPath] - Path to backup file (default: latest backup)
|
|
388
|
+
* @returns {object} - Restored state object
|
|
389
|
+
*/
|
|
390
|
+
function restoreState(workDir, backupPath = null) {
|
|
391
|
+
const statePath = `${workDir}/state.json`;
|
|
392
|
+
const defaultBackup = `${workDir}/state.backup.json`;
|
|
393
|
+
const historyPath = `${workDir}/state-history.json`;
|
|
394
|
+
|
|
395
|
+
const sourcePath = backupPath || defaultBackup;
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
// Read backup
|
|
399
|
+
const backupContent = Read(sourcePath);
|
|
400
|
+
const backupState = JSON.parse(backupContent);
|
|
401
|
+
|
|
402
|
+
// Validate backup state
|
|
403
|
+
const validation = validateState(backupState);
|
|
404
|
+
if (!validation.valid) {
|
|
405
|
+
throw new Error(`Backup state is invalid: ${validation.errors.join(', ')}`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Create backup of current state before restore (for safety)
|
|
409
|
+
try {
|
|
410
|
+
const currentContent = Read(statePath);
|
|
411
|
+
Write(`${workDir}/state.pre-restore.json`, currentContent);
|
|
412
|
+
} catch (e) {
|
|
413
|
+
// Current state may not exist, that's okay
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Update timestamp
|
|
417
|
+
const now = new Date().toISOString();
|
|
418
|
+
backupState.updated_at = now;
|
|
419
|
+
|
|
420
|
+
// Write restored state
|
|
421
|
+
Write(statePath, JSON.stringify(backupState, null, 2));
|
|
422
|
+
|
|
423
|
+
// Log to history
|
|
424
|
+
try {
|
|
425
|
+
let history = { entries: [] };
|
|
426
|
+
try {
|
|
427
|
+
history = JSON.parse(Read(historyPath));
|
|
428
|
+
} catch (e) {}
|
|
429
|
+
|
|
430
|
+
history.entries.push({
|
|
431
|
+
timestamp: now,
|
|
432
|
+
action: 'restore',
|
|
433
|
+
changes: { source: sourcePath }
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
Write(historyPath, JSON.stringify(history, null, 2));
|
|
437
|
+
} catch (e) {
|
|
438
|
+
console.warn(`[StateManager] Failed to log restore to history`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
console.log(`[StateManager] State restored from ${sourcePath}`);
|
|
442
|
+
return backupState;
|
|
443
|
+
} catch (error) {
|
|
444
|
+
throw new Error(`Failed to restore state: ${error.message}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## Convenience Functions
|
|
450
|
+
|
|
451
|
+
### getNextDimension(state)
|
|
452
|
+
|
|
453
|
+
Get the next dimension to review based on current state.
|
|
454
|
+
|
|
455
|
+
```javascript
|
|
456
|
+
/**
|
|
457
|
+
* Get next dimension to review
|
|
458
|
+
* @param {object} state - Current state
|
|
459
|
+
* @returns {string|null} - Next dimension or null if all reviewed
|
|
460
|
+
*/
|
|
461
|
+
function getNextDimension(state) {
|
|
462
|
+
const dimensions = ['correctness', 'security', 'performance', 'readability', 'testing', 'architecture'];
|
|
463
|
+
const reviewed = state.reviewed_dimensions || [];
|
|
464
|
+
|
|
465
|
+
for (const dim of dimensions) {
|
|
466
|
+
if (!reviewed.includes(dim)) {
|
|
467
|
+
return dim;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### addFinding(workDir, finding)
|
|
476
|
+
|
|
477
|
+
Add a new finding to the state.
|
|
478
|
+
|
|
479
|
+
```javascript
|
|
480
|
+
/**
|
|
481
|
+
* Add a finding to the appropriate dimension
|
|
482
|
+
* @param {string} workDir - Working directory path
|
|
483
|
+
* @param {object} finding - Finding object (must include dimension field)
|
|
484
|
+
* @returns {object} - Updated state
|
|
485
|
+
*/
|
|
486
|
+
function addFinding(workDir, finding) {
|
|
487
|
+
if (!finding.dimension) {
|
|
488
|
+
throw new Error('Finding must have a dimension field');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const state = getState(workDir);
|
|
492
|
+
const dimension = finding.dimension;
|
|
493
|
+
|
|
494
|
+
// Generate ID if not provided
|
|
495
|
+
if (!finding.id) {
|
|
496
|
+
const prefixes = {
|
|
497
|
+
correctness: 'CORR',
|
|
498
|
+
readability: 'READ',
|
|
499
|
+
performance: 'PERF',
|
|
500
|
+
security: 'SEC',
|
|
501
|
+
testing: 'TEST',
|
|
502
|
+
architecture: 'ARCH'
|
|
503
|
+
};
|
|
504
|
+
const prefix = prefixes[dimension] || 'MISC';
|
|
505
|
+
const count = (state.findings[dimension]?.length || 0) + 1;
|
|
506
|
+
finding.id = `${prefix}-${String(count).padStart(3, '0')}`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const currentFindings = state.findings[dimension] || [];
|
|
510
|
+
|
|
511
|
+
return updateState(workDir, {
|
|
512
|
+
findings: {
|
|
513
|
+
...state.findings,
|
|
514
|
+
[dimension]: [...currentFindings, finding]
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### markDimensionComplete(workDir, dimension)
|
|
521
|
+
|
|
522
|
+
Mark a dimension as reviewed.
|
|
523
|
+
|
|
524
|
+
```javascript
|
|
525
|
+
/**
|
|
526
|
+
* Mark a dimension as reviewed
|
|
527
|
+
* @param {string} workDir - Working directory path
|
|
528
|
+
* @param {string} dimension - Dimension name
|
|
529
|
+
* @returns {object} - Updated state
|
|
530
|
+
*/
|
|
531
|
+
function markDimensionComplete(workDir, dimension) {
|
|
532
|
+
const state = getState(workDir);
|
|
533
|
+
const reviewed = state.reviewed_dimensions || [];
|
|
534
|
+
|
|
535
|
+
if (reviewed.includes(dimension)) {
|
|
536
|
+
console.warn(`[StateManager] Dimension ${dimension} already marked as reviewed`);
|
|
537
|
+
return state;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return updateState(workDir, {
|
|
541
|
+
reviewed_dimensions: [...reviewed, dimension],
|
|
542
|
+
current_dimension: null
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### recordError(workDir, action, message)
|
|
548
|
+
|
|
549
|
+
Record an error in state.
|
|
550
|
+
|
|
551
|
+
```javascript
|
|
552
|
+
/**
|
|
553
|
+
* Record an execution error
|
|
554
|
+
* @param {string} workDir - Working directory path
|
|
555
|
+
* @param {string} action - Action that failed
|
|
556
|
+
* @param {string} message - Error message
|
|
557
|
+
* @returns {object} - Updated state
|
|
558
|
+
*/
|
|
559
|
+
function recordError(workDir, action, message) {
|
|
560
|
+
const state = getState(workDir);
|
|
561
|
+
const errors = state.errors || [];
|
|
562
|
+
const errorCount = (state.error_count || 0) + 1;
|
|
563
|
+
|
|
564
|
+
const newError = {
|
|
565
|
+
action,
|
|
566
|
+
message,
|
|
567
|
+
timestamp: new Date().toISOString()
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
const newState = updateState(workDir, {
|
|
571
|
+
errors: [...errors, newError],
|
|
572
|
+
error_count: errorCount
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
// Auto-fail if error count exceeds threshold
|
|
576
|
+
if (errorCount >= 3) {
|
|
577
|
+
return updateState(workDir, {
|
|
578
|
+
status: 'failed'
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return newState;
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
## Usage Examples
|
|
587
|
+
|
|
588
|
+
### Initialize and Run Review
|
|
589
|
+
|
|
590
|
+
```javascript
|
|
591
|
+
// Initialize new review session
|
|
592
|
+
const workDir = '/path/to/review-session';
|
|
593
|
+
const state = initState(workDir);
|
|
594
|
+
|
|
595
|
+
// Update status to running
|
|
596
|
+
updateState(workDir, { status: 'running' });
|
|
597
|
+
|
|
598
|
+
// After collecting context
|
|
599
|
+
updateState(workDir, {
|
|
600
|
+
context: {
|
|
601
|
+
target_path: '/src/auth',
|
|
602
|
+
files: ['auth.ts', 'login.ts'],
|
|
603
|
+
language: 'typescript',
|
|
604
|
+
total_lines: 500,
|
|
605
|
+
file_count: 2
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
// After completing quick scan
|
|
610
|
+
updateState(workDir, {
|
|
611
|
+
scan_completed: true,
|
|
612
|
+
scan_summary: {
|
|
613
|
+
risk_areas: [{ file: 'auth.ts', reason: 'Complex logic', priority: 'high' }],
|
|
614
|
+
complexity_score: 7.5,
|
|
615
|
+
quick_issues: []
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Add Findings During Review
|
|
621
|
+
|
|
622
|
+
```javascript
|
|
623
|
+
// Add a security finding
|
|
624
|
+
addFinding(workDir, {
|
|
625
|
+
dimension: 'security',
|
|
626
|
+
severity: 'high',
|
|
627
|
+
category: 'injection',
|
|
628
|
+
file: 'auth.ts',
|
|
629
|
+
line: 45,
|
|
630
|
+
description: 'SQL injection vulnerability',
|
|
631
|
+
recommendation: 'Use parameterized queries'
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Mark dimension complete
|
|
635
|
+
markDimensionComplete(workDir, 'security');
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Error Handling with Rollback
|
|
639
|
+
|
|
640
|
+
```javascript
|
|
641
|
+
try {
|
|
642
|
+
updateState(workDir, {
|
|
643
|
+
status: 'running',
|
|
644
|
+
current_action: 'deep-review'
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// ... do review work ...
|
|
648
|
+
|
|
649
|
+
} catch (error) {
|
|
650
|
+
// Record error
|
|
651
|
+
recordError(workDir, 'deep-review', error.message);
|
|
652
|
+
|
|
653
|
+
// If needed, restore from backup
|
|
654
|
+
restoreState(workDir);
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### Check Review Progress
|
|
659
|
+
|
|
660
|
+
```javascript
|
|
661
|
+
const state = getState(workDir);
|
|
662
|
+
const nextDim = getNextDimension(state);
|
|
663
|
+
|
|
664
|
+
if (nextDim) {
|
|
665
|
+
console.log(`Next dimension to review: ${nextDim}`);
|
|
666
|
+
updateState(workDir, { current_dimension: nextDim });
|
|
667
|
+
} else {
|
|
668
|
+
console.log('All dimensions reviewed');
|
|
669
|
+
}
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
## Integration with Orchestrator
|
|
673
|
+
|
|
674
|
+
Update the orchestrator to use StateManager:
|
|
675
|
+
|
|
676
|
+
```javascript
|
|
677
|
+
// In orchestrator.md - Replace direct state operations with StateManager calls
|
|
678
|
+
|
|
679
|
+
// OLD:
|
|
680
|
+
const state = JSON.parse(Read(`${workDir}/state.json`));
|
|
681
|
+
|
|
682
|
+
// NEW:
|
|
683
|
+
const state = getState(workDir);
|
|
684
|
+
|
|
685
|
+
// OLD:
|
|
686
|
+
function updateState(updates) {
|
|
687
|
+
const state = JSON.parse(Read(`${workDir}/state.json`));
|
|
688
|
+
const newState = { ...state, ...updates, updated_at: new Date().toISOString() };
|
|
689
|
+
Write(`${workDir}/state.json`, JSON.stringify(newState, null, 2));
|
|
690
|
+
return newState;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// NEW:
|
|
694
|
+
// Import from state-manager.md
|
|
695
|
+
// updateState(workDir, updates) - handles atomic write, backup, validation
|
|
696
|
+
|
|
697
|
+
// Error handling - OLD:
|
|
698
|
+
updateState({
|
|
699
|
+
errors: [...(state.errors || []), { action: actionId, message: error.message, timestamp: new Date().toISOString() }],
|
|
700
|
+
error_count: (state.error_count || 0) + 1
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// Error handling - NEW:
|
|
704
|
+
recordError(workDir, actionId, error.message);
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
## State History Format
|
|
708
|
+
|
|
709
|
+
The `state-history.json` file tracks all state changes:
|
|
710
|
+
|
|
711
|
+
```json
|
|
712
|
+
{
|
|
713
|
+
"entries": [
|
|
714
|
+
{
|
|
715
|
+
"timestamp": "2024-01-01T10:00:00.000Z",
|
|
716
|
+
"action": "init",
|
|
717
|
+
"changes": { "type": "initialize", "status": "pending" }
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
"timestamp": "2024-01-01T10:01:00.000Z",
|
|
721
|
+
"action": "update",
|
|
722
|
+
"changes": {
|
|
723
|
+
"status": { "from": "pending", "to": "running" },
|
|
724
|
+
"current_action": { "from": null, "to": "action-collect-context" }
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
"timestamp": "2024-01-01T10:05:00.000Z",
|
|
729
|
+
"action": "restore",
|
|
730
|
+
"changes": { "source": "/path/state.backup.json" }
|
|
731
|
+
}
|
|
732
|
+
]
|
|
733
|
+
}
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
## Error Recovery Strategies
|
|
737
|
+
|
|
738
|
+
| Scenario | Strategy | Function |
|
|
739
|
+
|----------|----------|----------|
|
|
740
|
+
| State file corrupted | Restore from backup | `restoreState(workDir)` |
|
|
741
|
+
| Invalid state after update | Auto-rollback (built-in) | N/A (automatic) |
|
|
742
|
+
| Multiple errors | Auto-fail after 3 | `recordError()` |
|
|
743
|
+
| Need to retry from checkpoint | Restore specific backup | `restoreState(workDir, backupPath)` |
|
|
744
|
+
| Review interrupted | Resume from saved state | `getState(workDir)` |
|
|
745
|
+
|
|
746
|
+
## Best Practices
|
|
747
|
+
|
|
748
|
+
1. **Always use `updateState()`** - Never write directly to state.json
|
|
749
|
+
2. **Check validation warnings** - Warnings may indicate data issues
|
|
750
|
+
3. **Use convenience functions** - `addFinding()`, `markDimensionComplete()`, etc.
|
|
751
|
+
4. **Monitor history** - Check state-history.json for debugging
|
|
752
|
+
5. **Create named backups** - Before major operations: `backupState(workDir, 'pre-deep-review')`
|