wogiflow 1.0.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.
- package/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- package/templates/trace.md +69 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Wogi Flow - Generate Changelog
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
WORKFLOW_DIR=".workflow"
|
|
8
|
+
REQUEST_LOG="$WORKFLOW_DIR/state/request-log.md"
|
|
9
|
+
OUTPUT="${1:-CHANGELOG.md}"
|
|
10
|
+
|
|
11
|
+
# Colors
|
|
12
|
+
GREEN='\033[0;32m'
|
|
13
|
+
CYAN='\033[0;36m'
|
|
14
|
+
NC='\033[0m'
|
|
15
|
+
|
|
16
|
+
echo -e "${CYAN}Generating changelog from request-log...${NC}"
|
|
17
|
+
echo ""
|
|
18
|
+
|
|
19
|
+
if [ ! -f "$REQUEST_LOG" ]; then
|
|
20
|
+
echo "No request-log found"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Generate changelog
|
|
25
|
+
cat > "$OUTPUT" << 'HEADER'
|
|
26
|
+
# Changelog
|
|
27
|
+
|
|
28
|
+
All notable changes to this project, generated from the request log.
|
|
29
|
+
|
|
30
|
+
HEADER
|
|
31
|
+
|
|
32
|
+
python3 << EOF >> "$OUTPUT"
|
|
33
|
+
import re
|
|
34
|
+
from collections import defaultdict
|
|
35
|
+
from datetime import datetime
|
|
36
|
+
|
|
37
|
+
# Parse request log
|
|
38
|
+
entries = defaultdict(list)
|
|
39
|
+
|
|
40
|
+
with open('$REQUEST_LOG') as f:
|
|
41
|
+
content = f.read()
|
|
42
|
+
|
|
43
|
+
# Match entries
|
|
44
|
+
pattern = r'### (R-\d+) \| ([\d-]+ [\d:]+).*?\n\*\*Type\*\*: (\w+).*?\n\*\*Tags\*\*: ([^\n]+).*?\n\*\*Request\*\*: "([^"]+)".*?\n\*\*Result\*\*: ([^\n]+)'
|
|
45
|
+
|
|
46
|
+
for match in re.finditer(pattern, content, re.DOTALL):
|
|
47
|
+
entry_id, timestamp, entry_type, tags, request, result = match.groups()
|
|
48
|
+
|
|
49
|
+
# Parse date
|
|
50
|
+
try:
|
|
51
|
+
date = timestamp.split()[0]
|
|
52
|
+
except:
|
|
53
|
+
date = "Unknown"
|
|
54
|
+
|
|
55
|
+
entries[entry_type].append({
|
|
56
|
+
'id': entry_id,
|
|
57
|
+
'date': date,
|
|
58
|
+
'request': request,
|
|
59
|
+
'result': result,
|
|
60
|
+
'tags': tags
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
# Output by type
|
|
64
|
+
type_headers = {
|
|
65
|
+
'new': 'Added',
|
|
66
|
+
'change': 'Changed',
|
|
67
|
+
'fix': 'Fixed',
|
|
68
|
+
'refactor': 'Refactored'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
print("## [Unreleased]")
|
|
72
|
+
print()
|
|
73
|
+
|
|
74
|
+
for entry_type, header in type_headers.items():
|
|
75
|
+
if entries[entry_type]:
|
|
76
|
+
print(f"### {header}")
|
|
77
|
+
for entry in entries[entry_type]:
|
|
78
|
+
tags = entry['tags'].replace('#', '').replace(':', '/').strip()
|
|
79
|
+
print(f"- {entry['result']} ({entry['id']})")
|
|
80
|
+
print()
|
|
81
|
+
EOF
|
|
82
|
+
|
|
83
|
+
echo -e "${GREEN}✓${NC} Generated: $OUTPUT"
|
|
84
|
+
echo ""
|
|
85
|
+
echo "Review and edit as needed."
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Auto-Checkpoint System
|
|
5
|
+
*
|
|
6
|
+
* Creates periodic checkpoints during execution:
|
|
7
|
+
* - Git commits at configurable intervals
|
|
8
|
+
* - State snapshots for rollback
|
|
9
|
+
* - Checkpoint metadata tracking
|
|
10
|
+
*
|
|
11
|
+
* Usage as module:
|
|
12
|
+
* const { Checkpoint, createCheckpoint, rollback } = require('./flow-checkpoint');
|
|
13
|
+
* const cp = new Checkpoint(config);
|
|
14
|
+
* cp.maybeCreate(stepCount);
|
|
15
|
+
*
|
|
16
|
+
* Usage as CLI:
|
|
17
|
+
* flow checkpoint create [message] # Create manual checkpoint
|
|
18
|
+
* flow checkpoint list # List checkpoints
|
|
19
|
+
* flow checkpoint rollback [id] # Rollback to checkpoint
|
|
20
|
+
* flow checkpoint cleanup # Remove old checkpoints
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const { execSync, spawnSync } = require('child_process');
|
|
26
|
+
const { getProjectRoot, getConfig, colors: c } = require('./flow-utils');
|
|
27
|
+
|
|
28
|
+
const PROJECT_ROOT = getProjectRoot();
|
|
29
|
+
const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
|
|
30
|
+
const CHECKPOINTS_DIR = path.join(WORKFLOW_DIR, 'checkpoints');
|
|
31
|
+
const CHECKPOINT_LOG = path.join(CHECKPOINTS_DIR, 'checkpoint-log.json');
|
|
32
|
+
|
|
33
|
+
// Alias getConfig as loadConfig for minimal code changes
|
|
34
|
+
const loadConfig = getConfig;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Default checkpoint configuration
|
|
38
|
+
*/
|
|
39
|
+
const DEFAULT_CHECKPOINT_CONFIG = {
|
|
40
|
+
enabled: true,
|
|
41
|
+
interval: 5,
|
|
42
|
+
maxCheckpoints: 20,
|
|
43
|
+
autoCommit: true,
|
|
44
|
+
commitPrefix: '[checkpoint]',
|
|
45
|
+
includeStateFiles: true,
|
|
46
|
+
excludePatterns: ['node_modules', '.git', 'dist', 'build']
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Checkpoint class for managing automatic checkpoints
|
|
51
|
+
*/
|
|
52
|
+
class Checkpoint {
|
|
53
|
+
constructor(config = {}) {
|
|
54
|
+
const cpConfig = config.checkpoint || config.safety?.limits || {};
|
|
55
|
+
this.config = { ...DEFAULT_CHECKPOINT_CONFIG, ...cpConfig };
|
|
56
|
+
this.stepCount = 0;
|
|
57
|
+
this.lastCheckpointStep = 0;
|
|
58
|
+
this.checkpoints = [];
|
|
59
|
+
|
|
60
|
+
this.ensureDir();
|
|
61
|
+
this.loadCheckpoints();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Ensure checkpoint directory exists
|
|
66
|
+
*/
|
|
67
|
+
ensureDir() {
|
|
68
|
+
if (!fs.existsSync(CHECKPOINTS_DIR)) {
|
|
69
|
+
fs.mkdirSync(CHECKPOINTS_DIR, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Load existing checkpoints
|
|
75
|
+
*/
|
|
76
|
+
loadCheckpoints() {
|
|
77
|
+
if (fs.existsSync(CHECKPOINT_LOG)) {
|
|
78
|
+
try {
|
|
79
|
+
this.checkpoints = JSON.parse(fs.readFileSync(CHECKPOINT_LOG, 'utf-8'));
|
|
80
|
+
} catch {
|
|
81
|
+
this.checkpoints = [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Save checkpoint log
|
|
88
|
+
*/
|
|
89
|
+
saveCheckpoints() {
|
|
90
|
+
fs.writeFileSync(CHECKPOINT_LOG, JSON.stringify(this.checkpoints, null, 2));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if checkpoint is needed
|
|
95
|
+
*/
|
|
96
|
+
needsCheckpoint() {
|
|
97
|
+
if (!this.config.enabled) return false;
|
|
98
|
+
const interval = this.config.interval || 5;
|
|
99
|
+
return this.stepCount > 0 && (this.stepCount - this.lastCheckpointStep) >= interval;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Increment step counter and maybe create checkpoint
|
|
104
|
+
*/
|
|
105
|
+
recordStep() {
|
|
106
|
+
this.stepCount++;
|
|
107
|
+
|
|
108
|
+
if (this.needsCheckpoint()) {
|
|
109
|
+
return this.create(`Auto checkpoint at step ${this.stepCount}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create a checkpoint
|
|
117
|
+
*/
|
|
118
|
+
create(message = null) {
|
|
119
|
+
const timestamp = new Date().toISOString();
|
|
120
|
+
const id = `cp-${timestamp.replace(/[:.]/g, '-').slice(0, 19)}`;
|
|
121
|
+
|
|
122
|
+
const checkpoint = {
|
|
123
|
+
id,
|
|
124
|
+
timestamp,
|
|
125
|
+
step: this.stepCount,
|
|
126
|
+
message: message || `Checkpoint at step ${this.stepCount}`,
|
|
127
|
+
gitCommit: null,
|
|
128
|
+
stateSnapshot: null,
|
|
129
|
+
filesTracked: []
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Snapshot state files
|
|
133
|
+
if (this.config.includeStateFiles) {
|
|
134
|
+
checkpoint.stateSnapshot = this.snapshotState(id);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Git commit if enabled
|
|
138
|
+
if (this.config.autoCommit && this.hasGitChanges()) {
|
|
139
|
+
checkpoint.gitCommit = this.createGitCommit(checkpoint.message);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.checkpoints.push(checkpoint);
|
|
143
|
+
this.lastCheckpointStep = this.stepCount;
|
|
144
|
+
this.saveCheckpoints();
|
|
145
|
+
|
|
146
|
+
// Cleanup old checkpoints
|
|
147
|
+
this.cleanup();
|
|
148
|
+
|
|
149
|
+
return checkpoint;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Snapshot workflow state files
|
|
154
|
+
*/
|
|
155
|
+
snapshotState(id) {
|
|
156
|
+
const snapshotDir = path.join(CHECKPOINTS_DIR, id);
|
|
157
|
+
if (!fs.existsSync(snapshotDir)) {
|
|
158
|
+
fs.mkdirSync(snapshotDir, { recursive: true });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const stateFiles = [
|
|
162
|
+
'state/ready.json',
|
|
163
|
+
'state/request-log.md',
|
|
164
|
+
'state/app-map.md',
|
|
165
|
+
'state/decisions.md',
|
|
166
|
+
'state/progress.md',
|
|
167
|
+
'state/durable-session.json',
|
|
168
|
+
'config.json'
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
const snapshots = {};
|
|
172
|
+
|
|
173
|
+
for (const relPath of stateFiles) {
|
|
174
|
+
const srcPath = path.join(WORKFLOW_DIR, relPath);
|
|
175
|
+
if (fs.existsSync(srcPath)) {
|
|
176
|
+
const content = fs.readFileSync(srcPath, 'utf-8');
|
|
177
|
+
const destPath = path.join(snapshotDir, relPath.replace(/\//g, '_'));
|
|
178
|
+
fs.writeFileSync(destPath, content);
|
|
179
|
+
snapshots[relPath] = destPath;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return snapshots;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Check if there are git changes to commit
|
|
188
|
+
*/
|
|
189
|
+
hasGitChanges() {
|
|
190
|
+
try {
|
|
191
|
+
const result = spawnSync('git', ['status', '--porcelain'], {
|
|
192
|
+
cwd: PROJECT_ROOT,
|
|
193
|
+
encoding: 'utf-8'
|
|
194
|
+
});
|
|
195
|
+
return result.stdout && result.stdout.trim().length > 0;
|
|
196
|
+
} catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Create a git commit
|
|
203
|
+
*/
|
|
204
|
+
createGitCommit(message) {
|
|
205
|
+
try {
|
|
206
|
+
// Stage all changes
|
|
207
|
+
spawnSync('git', ['add', '-A'], { cwd: PROJECT_ROOT });
|
|
208
|
+
|
|
209
|
+
// Create commit
|
|
210
|
+
const commitMessage = `${this.config.commitPrefix} ${message}`;
|
|
211
|
+
const result = spawnSync('git', ['commit', '-m', commitMessage], {
|
|
212
|
+
cwd: PROJECT_ROOT,
|
|
213
|
+
encoding: 'utf-8'
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (result.status === 0) {
|
|
217
|
+
// Get commit hash
|
|
218
|
+
const hashResult = spawnSync('git', ['rev-parse', '--short', 'HEAD'], {
|
|
219
|
+
cwd: PROJECT_ROOT,
|
|
220
|
+
encoding: 'utf-8'
|
|
221
|
+
});
|
|
222
|
+
return hashResult.stdout.trim();
|
|
223
|
+
}
|
|
224
|
+
} catch {
|
|
225
|
+
// Git commit failed
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Rollback to a checkpoint
|
|
232
|
+
*/
|
|
233
|
+
rollback(checkpointId) {
|
|
234
|
+
const checkpoint = this.checkpoints.find(cp => cp.id === checkpointId);
|
|
235
|
+
if (!checkpoint) {
|
|
236
|
+
throw new Error(`Checkpoint not found: ${checkpointId}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const results = {
|
|
240
|
+
stateRestored: false,
|
|
241
|
+
gitRestored: false,
|
|
242
|
+
errors: []
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Restore state files
|
|
246
|
+
if (checkpoint.stateSnapshot) {
|
|
247
|
+
try {
|
|
248
|
+
for (const [relPath, snapshotPath] of Object.entries(checkpoint.stateSnapshot)) {
|
|
249
|
+
if (fs.existsSync(snapshotPath)) {
|
|
250
|
+
const destPath = path.join(WORKFLOW_DIR, relPath);
|
|
251
|
+
const content = fs.readFileSync(snapshotPath, 'utf-8');
|
|
252
|
+
fs.writeFileSync(destPath, content);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
results.stateRestored = true;
|
|
256
|
+
} catch (err) {
|
|
257
|
+
results.errors.push(`Failed to restore state: ${err.message}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Rollback git if commit exists
|
|
262
|
+
if (checkpoint.gitCommit) {
|
|
263
|
+
try {
|
|
264
|
+
// Find commits since checkpoint
|
|
265
|
+
const logResult = spawnSync('git', [
|
|
266
|
+
'log', '--oneline', `${checkpoint.gitCommit}..HEAD`
|
|
267
|
+
], { cwd: PROJECT_ROOT, encoding: 'utf-8' });
|
|
268
|
+
|
|
269
|
+
const commitsSince = logResult.stdout.trim().split('\n').filter(l => l).length;
|
|
270
|
+
|
|
271
|
+
if (commitsSince > 0) {
|
|
272
|
+
// Soft reset to checkpoint
|
|
273
|
+
spawnSync('git', ['reset', '--soft', checkpoint.gitCommit], {
|
|
274
|
+
cwd: PROJECT_ROOT
|
|
275
|
+
});
|
|
276
|
+
results.gitRestored = true;
|
|
277
|
+
}
|
|
278
|
+
} catch (err) {
|
|
279
|
+
results.errors.push(`Failed to rollback git: ${err.message}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return results;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Cleanup old checkpoints
|
|
288
|
+
*/
|
|
289
|
+
cleanup() {
|
|
290
|
+
const max = this.config.maxCheckpoints || 20;
|
|
291
|
+
|
|
292
|
+
while (this.checkpoints.length > max) {
|
|
293
|
+
const oldest = this.checkpoints.shift();
|
|
294
|
+
|
|
295
|
+
// Remove snapshot directory
|
|
296
|
+
if (oldest.stateSnapshot) {
|
|
297
|
+
const snapshotDir = path.join(CHECKPOINTS_DIR, oldest.id);
|
|
298
|
+
if (fs.existsSync(snapshotDir)) {
|
|
299
|
+
fs.rmSync(snapshotDir, { recursive: true });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
this.saveCheckpoints();
|
|
305
|
+
return this.checkpoints.length;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get checkpoint list
|
|
310
|
+
*/
|
|
311
|
+
list() {
|
|
312
|
+
return this.checkpoints.map(cp => ({
|
|
313
|
+
id: cp.id,
|
|
314
|
+
timestamp: cp.timestamp,
|
|
315
|
+
step: cp.step,
|
|
316
|
+
message: cp.message,
|
|
317
|
+
hasGitCommit: !!cp.gitCommit,
|
|
318
|
+
hasStateSnapshot: !!cp.stateSnapshot
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get current status
|
|
324
|
+
*/
|
|
325
|
+
getStatus() {
|
|
326
|
+
return {
|
|
327
|
+
enabled: this.config.enabled,
|
|
328
|
+
interval: this.config.interval,
|
|
329
|
+
stepCount: this.stepCount,
|
|
330
|
+
lastCheckpointStep: this.lastCheckpointStep,
|
|
331
|
+
totalCheckpoints: this.checkpoints.length,
|
|
332
|
+
nextCheckpointAt: this.lastCheckpointStep + this.config.interval
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Format checkpoint list for display
|
|
340
|
+
*/
|
|
341
|
+
function formatCheckpointList(checkpoints) {
|
|
342
|
+
if (checkpoints.length === 0) {
|
|
343
|
+
return `${c.dim}No checkpoints found${c.reset}`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
let output = `${c.cyan}${c.bold}Checkpoints${c.reset}\n`;
|
|
347
|
+
output += `${'─'.repeat(60)}\n\n`;
|
|
348
|
+
|
|
349
|
+
for (const cp of checkpoints.reverse()) {
|
|
350
|
+
const date = new Date(cp.timestamp);
|
|
351
|
+
const timeStr = date.toLocaleString();
|
|
352
|
+
|
|
353
|
+
output += `${c.bold}${cp.id}${c.reset}\n`;
|
|
354
|
+
output += ` ${c.dim}Time:${c.reset} ${timeStr}\n`;
|
|
355
|
+
output += ` ${c.dim}Step:${c.reset} ${cp.step}\n`;
|
|
356
|
+
output += ` ${c.dim}Message:${c.reset} ${cp.message}\n`;
|
|
357
|
+
|
|
358
|
+
const features = [];
|
|
359
|
+
if (cp.hasGitCommit) features.push('git');
|
|
360
|
+
if (cp.hasStateSnapshot) features.push('state');
|
|
361
|
+
output += ` ${c.dim}Includes:${c.reset} ${features.join(', ') || 'none'}\n`;
|
|
362
|
+
output += '\n';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return output;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Module exports
|
|
369
|
+
module.exports = {
|
|
370
|
+
Checkpoint,
|
|
371
|
+
DEFAULT_CHECKPOINT_CONFIG,
|
|
372
|
+
loadConfig
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// CLI Handler
|
|
376
|
+
if (require.main === module) {
|
|
377
|
+
const args = process.argv.slice(2);
|
|
378
|
+
const command = args[0];
|
|
379
|
+
|
|
380
|
+
const config = loadConfig();
|
|
381
|
+
const cp = new Checkpoint(config);
|
|
382
|
+
|
|
383
|
+
switch (command) {
|
|
384
|
+
case 'create': {
|
|
385
|
+
const message = args.slice(1).join(' ') || 'Manual checkpoint';
|
|
386
|
+
console.log(`${c.cyan}Creating checkpoint...${c.reset}`);
|
|
387
|
+
const checkpoint = cp.create(message);
|
|
388
|
+
console.log(`${c.green}✅ Checkpoint created: ${checkpoint.id}${c.reset}`);
|
|
389
|
+
if (checkpoint.gitCommit) {
|
|
390
|
+
console.log(` Git commit: ${checkpoint.gitCommit}`);
|
|
391
|
+
}
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
case 'list': {
|
|
396
|
+
const checkpoints = cp.list();
|
|
397
|
+
console.log(formatCheckpointList(checkpoints));
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
case 'rollback': {
|
|
402
|
+
const checkpointId = args[1];
|
|
403
|
+
if (!checkpointId) {
|
|
404
|
+
console.error(`${c.red}Error: Checkpoint ID required${c.reset}`);
|
|
405
|
+
console.log(`${c.dim}Usage: flow checkpoint rollback <id>${c.reset}`);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
console.log(`${c.yellow}Rolling back to ${checkpointId}...${c.reset}`);
|
|
410
|
+
try {
|
|
411
|
+
const results = cp.rollback(checkpointId);
|
|
412
|
+
|
|
413
|
+
if (results.stateRestored) {
|
|
414
|
+
console.log(`${c.green}✅ State files restored${c.reset}`);
|
|
415
|
+
}
|
|
416
|
+
if (results.gitRestored) {
|
|
417
|
+
console.log(`${c.green}✅ Git rolled back${c.reset}`);
|
|
418
|
+
}
|
|
419
|
+
if (results.errors.length > 0) {
|
|
420
|
+
for (const err of results.errors) {
|
|
421
|
+
console.log(`${c.yellow}⚠ ${err}${c.reset}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
} catch (err) {
|
|
425
|
+
console.error(`${c.red}Error: ${err.message}${c.reset}`);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
case 'cleanup': {
|
|
432
|
+
const remaining = cp.cleanup();
|
|
433
|
+
console.log(`${c.green}✅ Cleanup complete. ${remaining} checkpoints remaining${c.reset}`);
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
case 'status': {
|
|
438
|
+
const status = cp.getStatus();
|
|
439
|
+
console.log(`${c.cyan}Checkpoint Status${c.reset}`);
|
|
440
|
+
console.log(`${'─'.repeat(40)}`);
|
|
441
|
+
console.log(` Enabled: ${status.enabled ? c.green + 'Yes' : c.yellow + 'No'}${c.reset}`);
|
|
442
|
+
console.log(` Interval: Every ${status.interval} steps`);
|
|
443
|
+
console.log(` Current step: ${status.stepCount}`);
|
|
444
|
+
console.log(` Last checkpoint: Step ${status.lastCheckpointStep}`);
|
|
445
|
+
console.log(` Total checkpoints: ${status.totalCheckpoints}`);
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
default: {
|
|
450
|
+
console.log(`
|
|
451
|
+
${c.cyan}Wogi Flow - Auto-Checkpoint System${c.reset}
|
|
452
|
+
|
|
453
|
+
${c.bold}Usage:${c.reset}
|
|
454
|
+
flow checkpoint create [message] Create manual checkpoint
|
|
455
|
+
flow checkpoint list List all checkpoints
|
|
456
|
+
flow checkpoint rollback <id> Rollback to checkpoint
|
|
457
|
+
flow checkpoint cleanup Remove old checkpoints
|
|
458
|
+
flow checkpoint status Show checkpoint status
|
|
459
|
+
|
|
460
|
+
${c.bold}Configuration:${c.reset}
|
|
461
|
+
Add to .workflow/config.json:
|
|
462
|
+
{
|
|
463
|
+
"checkpoint": {
|
|
464
|
+
"enabled": true,
|
|
465
|
+
"interval": 5,
|
|
466
|
+
"maxCheckpoints": 20,
|
|
467
|
+
"autoCommit": true,
|
|
468
|
+
"commitPrefix": "[checkpoint]"
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
Or in safety.limits:
|
|
473
|
+
{
|
|
474
|
+
"safety": {
|
|
475
|
+
"limits": {
|
|
476
|
+
"checkpointInterval": 5
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|