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,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Resume Command
|
|
5
|
+
*
|
|
6
|
+
* Resume a suspended task.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* flow resume # Resume if condition met
|
|
10
|
+
* flow resume --force # Force resume regardless
|
|
11
|
+
* flow resume --approve # Approve human review
|
|
12
|
+
* flow resume --status # Check suspension status
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
loadDurableSession,
|
|
17
|
+
getSuspensionStatus,
|
|
18
|
+
checkResumeCondition,
|
|
19
|
+
resumeSession,
|
|
20
|
+
canResumeFromStep,
|
|
21
|
+
getRemainingSteps,
|
|
22
|
+
isSuspended,
|
|
23
|
+
RESUME_CONDITION
|
|
24
|
+
} = require('./flow-durable-session');
|
|
25
|
+
const { color, getConfig } = require('./flow-utils');
|
|
26
|
+
|
|
27
|
+
function parseArgs() {
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const options = {
|
|
30
|
+
force: false,
|
|
31
|
+
approve: false,
|
|
32
|
+
status: false,
|
|
33
|
+
approvedBy: 'user'
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < args.length; i++) {
|
|
37
|
+
const arg = args[i];
|
|
38
|
+
const nextArg = args[i + 1];
|
|
39
|
+
|
|
40
|
+
switch (arg) {
|
|
41
|
+
case '--force':
|
|
42
|
+
case '-f':
|
|
43
|
+
options.force = true;
|
|
44
|
+
break;
|
|
45
|
+
|
|
46
|
+
case '--approve':
|
|
47
|
+
case '-a':
|
|
48
|
+
options.approve = true;
|
|
49
|
+
break;
|
|
50
|
+
|
|
51
|
+
case '--approved-by':
|
|
52
|
+
options.approvedBy = nextArg;
|
|
53
|
+
i++;
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
case '--status':
|
|
57
|
+
case '-s':
|
|
58
|
+
options.status = true;
|
|
59
|
+
break;
|
|
60
|
+
|
|
61
|
+
case '--help':
|
|
62
|
+
case '-h':
|
|
63
|
+
printHelp();
|
|
64
|
+
process.exit(0);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return options;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function printHelp() {
|
|
73
|
+
console.log(`
|
|
74
|
+
Wogi Flow - Resume Command
|
|
75
|
+
|
|
76
|
+
Resume a suspended task.
|
|
77
|
+
|
|
78
|
+
Usage:
|
|
79
|
+
flow resume [options]
|
|
80
|
+
|
|
81
|
+
Options:
|
|
82
|
+
--force, -f Force resume regardless of conditions
|
|
83
|
+
--approve, -a Approve human review condition
|
|
84
|
+
--approved-by <name> Specify who approved (for audit)
|
|
85
|
+
--status, -s Show suspension status only
|
|
86
|
+
--help, -h Show this help
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
flow resume # Resume if condition met
|
|
90
|
+
flow resume --status # Check current status
|
|
91
|
+
flow resume --approve # Approve pending review
|
|
92
|
+
flow resume --force # Override and resume
|
|
93
|
+
`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function showStatus() {
|
|
97
|
+
const session = loadDurableSession();
|
|
98
|
+
if (!session) {
|
|
99
|
+
console.log(color('yellow', 'No active task session.'));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log(color('cyan', '📊 Task Session Status'));
|
|
105
|
+
console.log(color('cyan', '─'.repeat(50)));
|
|
106
|
+
console.log(`Task: ${session.taskId}`);
|
|
107
|
+
console.log(`Type: ${session.taskType}`);
|
|
108
|
+
console.log(`Started: ${session.startedAt}`);
|
|
109
|
+
console.log('');
|
|
110
|
+
|
|
111
|
+
if (!isSuspended()) {
|
|
112
|
+
console.log(color('green', '▶️ Status: ACTIVE'));
|
|
113
|
+
const resumeInfo = canResumeFromStep(session);
|
|
114
|
+
if (resumeInfo.canResume) {
|
|
115
|
+
console.log(`Progress: ${resumeInfo.completedCount}/${resumeInfo.totalSteps} steps`);
|
|
116
|
+
console.log(`Next step: ${resumeInfo.fromStep?.description?.substring(0, 60) || resumeInfo.fromStep?.id}...`);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
const status = getSuspensionStatus();
|
|
120
|
+
console.log(color('yellow', '⏸️ Status: SUSPENDED'));
|
|
121
|
+
console.log('');
|
|
122
|
+
console.log(`Type: ${status.type}`);
|
|
123
|
+
console.log(`Reason: ${status.reason}`);
|
|
124
|
+
console.log(`Since: ${status.suspendedAt}`);
|
|
125
|
+
|
|
126
|
+
if (status.suspendedAtStep) {
|
|
127
|
+
console.log(`At step: ${status.suspendedAtStep}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log('');
|
|
131
|
+
console.log(color('cyan', '📋 Resume Condition:'));
|
|
132
|
+
|
|
133
|
+
const conditionCheck = checkResumeCondition(session.suspension);
|
|
134
|
+
|
|
135
|
+
if (conditionCheck.canResume) {
|
|
136
|
+
console.log(color('green', ` ✓ Condition met: ${conditionCheck.reason}`));
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log(`Run ${color('cyan', 'flow resume')} to continue.`);
|
|
139
|
+
} else {
|
|
140
|
+
console.log(color('red', ` ✗ Waiting: ${conditionCheck.reason}`));
|
|
141
|
+
|
|
142
|
+
// Show specific waiting info
|
|
143
|
+
switch (session.suspension?.resumeCondition?.type) {
|
|
144
|
+
case RESUME_CONDITION.TIME:
|
|
145
|
+
console.log(` Resume at: ${conditionCheck.resumeAt}`);
|
|
146
|
+
if (conditionCheck.remainingSeconds) {
|
|
147
|
+
const mins = Math.floor(conditionCheck.remainingSeconds / 60);
|
|
148
|
+
const secs = conditionCheck.remainingSeconds % 60;
|
|
149
|
+
console.log(` Remaining: ${mins}m ${secs}s`);
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
|
|
153
|
+
case RESUME_CONDITION.POLL:
|
|
154
|
+
console.log(` Expected: ${conditionCheck.expectedValue}`);
|
|
155
|
+
console.log(` Current: ${conditionCheck.currentValue || 'N/A'}`);
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
case RESUME_CONDITION.MANUAL:
|
|
159
|
+
console.log(` Approval needed: ${session.suspension.resumeCondition.manual.prompt}`);
|
|
160
|
+
console.log('');
|
|
161
|
+
console.log(` Run ${color('cyan', 'flow resume --approve')} to approve.`);
|
|
162
|
+
break;
|
|
163
|
+
|
|
164
|
+
case RESUME_CONDITION.FILE:
|
|
165
|
+
console.log(` Watching: ${session.suspension.resumeCondition.file.watchPath}`);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log('');
|
|
170
|
+
console.log(`To force resume: ${color('cyan', 'flow resume --force')}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(color('cyan', '─'.repeat(50)));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function main() {
|
|
178
|
+
// Handle help first (before any other checks)
|
|
179
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
180
|
+
printHelp();
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const config = getConfig();
|
|
185
|
+
|
|
186
|
+
// Check if durable steps are enabled
|
|
187
|
+
if (config.durableSteps?.enabled === false) {
|
|
188
|
+
console.log(color('red', 'Durable steps are disabled. Enable in config.json to use suspend/resume.'));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const options = parseArgs();
|
|
193
|
+
|
|
194
|
+
// Status only?
|
|
195
|
+
if (options.status) {
|
|
196
|
+
showStatus();
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check for active session
|
|
201
|
+
const session = loadDurableSession();
|
|
202
|
+
if (!session) {
|
|
203
|
+
console.log(color('red', 'No active task session.'));
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check suspension state from loaded session (avoids race condition from re-reading)
|
|
208
|
+
if (!session.suspension) {
|
|
209
|
+
console.log(color('green', 'Task is not suspended. Continue working!'));
|
|
210
|
+
const resumeInfo = canResumeFromStep(session);
|
|
211
|
+
if (resumeInfo.canResume && resumeInfo.fromStep) {
|
|
212
|
+
console.log('');
|
|
213
|
+
console.log(`Next step: ${resumeInfo.fromStep.description?.substring(0, 80) || resumeInfo.fromStep.id}`);
|
|
214
|
+
}
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Try to resume (resumeSession re-loads session internally for atomicity)
|
|
219
|
+
const result = resumeSession({
|
|
220
|
+
force: options.force,
|
|
221
|
+
approve: options.approve,
|
|
222
|
+
approvedBy: options.approvedBy
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Check result
|
|
226
|
+
if (result.error) {
|
|
227
|
+
console.log('');
|
|
228
|
+
console.log(color('red', '⚠️ Cannot Resume Yet'));
|
|
229
|
+
console.log(color('red', '─'.repeat(50)));
|
|
230
|
+
console.log(`Reason: ${result.reason}`);
|
|
231
|
+
|
|
232
|
+
// Show helpful info based on condition type
|
|
233
|
+
const status = getSuspensionStatus();
|
|
234
|
+
switch (result.reason) {
|
|
235
|
+
case 'waiting-for-time':
|
|
236
|
+
console.log(`Resume at: ${result.resumeAt}`);
|
|
237
|
+
if (result.remainingSeconds) {
|
|
238
|
+
const mins = Math.floor(result.remainingSeconds / 60);
|
|
239
|
+
const secs = result.remainingSeconds % 60;
|
|
240
|
+
console.log(`Remaining: ${mins}m ${secs}s`);
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
|
|
244
|
+
case 'poll-condition-not-met':
|
|
245
|
+
console.log(`Expected: ${result.expectedValue}`);
|
|
246
|
+
console.log(`Current: ${result.currentValue}`);
|
|
247
|
+
break;
|
|
248
|
+
|
|
249
|
+
case 'awaiting-approval':
|
|
250
|
+
console.log(`Approval needed: ${result.prompt}`);
|
|
251
|
+
console.log('');
|
|
252
|
+
console.log(`Run ${color('cyan', 'flow resume --approve')} to approve.`);
|
|
253
|
+
break;
|
|
254
|
+
|
|
255
|
+
case 'file-not-found':
|
|
256
|
+
console.log(`Waiting for: ${result.watchPath}`);
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log(`To force resume: ${color('cyan', 'flow resume --force')}`);
|
|
262
|
+
console.log(color('red', '─'.repeat(50)));
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Success!
|
|
267
|
+
console.log('');
|
|
268
|
+
console.log(color('green', '▶️ Task Resumed'));
|
|
269
|
+
console.log(color('green', '─'.repeat(50)));
|
|
270
|
+
console.log(`Task: ${session.taskId}`);
|
|
271
|
+
|
|
272
|
+
// Show remaining work (defensive null check for session)
|
|
273
|
+
const remaining = result ? getRemainingSteps(result) : [];
|
|
274
|
+
if (remaining.length > 0) {
|
|
275
|
+
console.log('');
|
|
276
|
+
console.log(`Remaining steps: ${remaining.length}`);
|
|
277
|
+
console.log(`Next: ${remaining[0].description?.substring(0, 60) || remaining[0].id}...`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
console.log('');
|
|
281
|
+
console.log(color('green', '─'.repeat(50)));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
main();
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Rules Sync
|
|
5
|
+
*
|
|
6
|
+
* Syncs decisions.md (source of truth) to .claude/rules/ (Claude Code native format)
|
|
7
|
+
* Each ## section in decisions.md becomes a separate rule file with optional path scoping.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/flow-rules-sync.js # Sync decisions.md to .claude/rules/
|
|
11
|
+
* node scripts/flow-rules-sync.js --json # Output JSON result
|
|
12
|
+
* node scripts/flow-rules-sync.js --dry-run # Preview without writing
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const {
|
|
18
|
+
PATHS,
|
|
19
|
+
PROJECT_ROOT,
|
|
20
|
+
readFile,
|
|
21
|
+
writeFile,
|
|
22
|
+
dirExists,
|
|
23
|
+
success,
|
|
24
|
+
warn,
|
|
25
|
+
info,
|
|
26
|
+
error,
|
|
27
|
+
parseFlags,
|
|
28
|
+
outputJson
|
|
29
|
+
} = require('./flow-utils');
|
|
30
|
+
|
|
31
|
+
// ============================================================
|
|
32
|
+
// Configuration
|
|
33
|
+
// ============================================================
|
|
34
|
+
|
|
35
|
+
const RULES_DIR = path.join(PROJECT_ROOT, '.claude', 'rules');
|
|
36
|
+
|
|
37
|
+
// Keywords that indicate a rule should ALWAYS be applied (not agent_requested)
|
|
38
|
+
const ALWAYS_APPLY_KEYWORDS = [
|
|
39
|
+
'general', 'always', 'project', 'naming', 'coding', 'standard',
|
|
40
|
+
'convention', 'must', 'never', 'critical', 'security'
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// Path scoping based on section title keywords
|
|
44
|
+
// These help Claude Code load rules only when working on relevant files
|
|
45
|
+
const PATH_SCOPE_MAPPING = {
|
|
46
|
+
// Component-related rules
|
|
47
|
+
'component': 'src/components/**/*',
|
|
48
|
+
'ui': 'src/components/**/*',
|
|
49
|
+
'design': 'src/components/**/*',
|
|
50
|
+
|
|
51
|
+
// API/Backend rules
|
|
52
|
+
'api': 'src/api/**/*',
|
|
53
|
+
'backend': 'src/api/**/*',
|
|
54
|
+
'endpoint': 'src/api/**/*',
|
|
55
|
+
'controller': 'src/**/*.controller.*',
|
|
56
|
+
'service': 'src/**/*.service.*',
|
|
57
|
+
|
|
58
|
+
// Testing rules
|
|
59
|
+
'test': '**/*.{test,spec}.*',
|
|
60
|
+
'testing': '**/*.{test,spec}.*',
|
|
61
|
+
|
|
62
|
+
// Style rules
|
|
63
|
+
'style': '**/*.{css,scss,sass,less}',
|
|
64
|
+
'css': '**/*.{css,scss,sass,less}',
|
|
65
|
+
|
|
66
|
+
// Database rules
|
|
67
|
+
'database': 'src/**/*.{entity,model,migration,schema}.*',
|
|
68
|
+
'entity': 'src/**/*.entity.*',
|
|
69
|
+
'model': 'src/**/*.model.*',
|
|
70
|
+
|
|
71
|
+
// Config rules
|
|
72
|
+
'config': '*.config.*',
|
|
73
|
+
'configuration': '*.config.*'
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// ============================================================
|
|
77
|
+
// Section Parsing
|
|
78
|
+
// ============================================================
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parse decisions.md into sections
|
|
82
|
+
* @param {string} content - File content
|
|
83
|
+
* @returns {Array<{title: string, content: string, level: number}>}
|
|
84
|
+
*/
|
|
85
|
+
function parseMarkdownSections(content) {
|
|
86
|
+
const sections = [];
|
|
87
|
+
const lines = content.split('\n');
|
|
88
|
+
|
|
89
|
+
let currentSection = null;
|
|
90
|
+
let currentContent = [];
|
|
91
|
+
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
// Match ## or ### headers (level 2 or 3)
|
|
94
|
+
const headerMatch = line.match(/^(#{2,3})\s+(.+)$/);
|
|
95
|
+
|
|
96
|
+
if (headerMatch) {
|
|
97
|
+
// Save previous section
|
|
98
|
+
if (currentSection) {
|
|
99
|
+
const trimmedContent = currentContent.join('\n').trim();
|
|
100
|
+
// Only include sections with actual content (not just placeholders)
|
|
101
|
+
if (trimmedContent && !trimmedContent.startsWith('<!--')) {
|
|
102
|
+
sections.push({
|
|
103
|
+
title: currentSection.title,
|
|
104
|
+
content: trimmedContent,
|
|
105
|
+
level: currentSection.level
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Start new section
|
|
111
|
+
currentSection = {
|
|
112
|
+
title: headerMatch[2].trim(),
|
|
113
|
+
level: headerMatch[1].length
|
|
114
|
+
};
|
|
115
|
+
currentContent = [];
|
|
116
|
+
} else if (currentSection) {
|
|
117
|
+
// Skip section separator lines
|
|
118
|
+
if (line.trim() !== '---') {
|
|
119
|
+
currentContent.push(line);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Save last section
|
|
125
|
+
if (currentSection) {
|
|
126
|
+
const trimmedContent = currentContent.join('\n').trim();
|
|
127
|
+
if (trimmedContent && !trimmedContent.startsWith('<!--')) {
|
|
128
|
+
sections.push({
|
|
129
|
+
title: currentSection.title,
|
|
130
|
+
content: trimmedContent,
|
|
131
|
+
level: currentSection.level
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return sections;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Convert title to filename-safe slug
|
|
141
|
+
* @param {string} title - Section title
|
|
142
|
+
* @returns {string} - Slugified filename
|
|
143
|
+
*/
|
|
144
|
+
function slugify(title) {
|
|
145
|
+
return title
|
|
146
|
+
.toLowerCase()
|
|
147
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
148
|
+
.replace(/^-+|-+$/g, '')
|
|
149
|
+
.substring(0, 50); // Limit length
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Determine path scope for a section based on title keywords
|
|
154
|
+
* @param {string} title - Section title
|
|
155
|
+
* @returns {string|null} - Glob pattern or null for no scoping
|
|
156
|
+
*/
|
|
157
|
+
function getPathScope(title) {
|
|
158
|
+
const lowerTitle = title.toLowerCase();
|
|
159
|
+
|
|
160
|
+
for (const [keyword, pathPattern] of Object.entries(PATH_SCOPE_MAPPING)) {
|
|
161
|
+
if (lowerTitle.includes(keyword)) {
|
|
162
|
+
return pathPattern;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return null; // No scoping - rule applies everywhere
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Check if a rule should always be applied based on title keywords
|
|
171
|
+
* @param {string} title - Section title
|
|
172
|
+
* @returns {boolean} - True if rule should always apply
|
|
173
|
+
*/
|
|
174
|
+
function shouldAlwaysApply(title) {
|
|
175
|
+
const lowerTitle = title.toLowerCase();
|
|
176
|
+
return ALWAYS_APPLY_KEYWORDS.some(keyword => lowerTitle.includes(keyword));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Extract the first sentence from content for description
|
|
181
|
+
* @param {string} content - Section content
|
|
182
|
+
* @returns {string} - First sentence (max 100 chars)
|
|
183
|
+
*/
|
|
184
|
+
function extractFirstSentence(content) {
|
|
185
|
+
// Remove markdown formatting
|
|
186
|
+
const cleaned = content
|
|
187
|
+
.replace(/^[-*]\s+/gm, '') // Remove list markers
|
|
188
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1') // Remove bold
|
|
189
|
+
.replace(/`([^`]+)`/g, '$1') // Remove code
|
|
190
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Remove links
|
|
191
|
+
.trim();
|
|
192
|
+
|
|
193
|
+
// Find first sentence
|
|
194
|
+
const sentenceMatch = cleaned.match(/^[^.!?\n]+[.!?]?/);
|
|
195
|
+
if (sentenceMatch) {
|
|
196
|
+
let sentence = sentenceMatch[0].trim();
|
|
197
|
+
if (sentence.length > 100) {
|
|
198
|
+
sentence = sentence.substring(0, 97) + '...';
|
|
199
|
+
}
|
|
200
|
+
return sentence;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Fallback to first line
|
|
204
|
+
const firstLine = cleaned.split('\n')[0].trim();
|
|
205
|
+
if (firstLine.length > 100) {
|
|
206
|
+
return firstLine.substring(0, 97) + '...';
|
|
207
|
+
}
|
|
208
|
+
return firstLine || 'Project rule';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Generate rule file content with frontmatter
|
|
213
|
+
* @param {Object} section - Section object
|
|
214
|
+
* @returns {string} - Rule file content
|
|
215
|
+
*/
|
|
216
|
+
function generateRuleFile(section) {
|
|
217
|
+
const { title, content } = section;
|
|
218
|
+
const pathScope = getPathScope(title);
|
|
219
|
+
const alwaysApply = shouldAlwaysApply(title);
|
|
220
|
+
const description = extractFirstSentence(content);
|
|
221
|
+
|
|
222
|
+
// Always add frontmatter with type and description
|
|
223
|
+
let output = '---\n';
|
|
224
|
+
if (pathScope) {
|
|
225
|
+
output += `globs: ${pathScope}\n`;
|
|
226
|
+
}
|
|
227
|
+
output += `alwaysApply: ${alwaysApply}\n`;
|
|
228
|
+
output += `description: "${title} - ${description.replace(/"/g, '\\"')}"\n`;
|
|
229
|
+
output += '---\n\n';
|
|
230
|
+
|
|
231
|
+
// Add header and content
|
|
232
|
+
output += `# ${title}\n\n`;
|
|
233
|
+
output += content;
|
|
234
|
+
output += '\n';
|
|
235
|
+
|
|
236
|
+
return output;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ============================================================
|
|
240
|
+
// Sync Logic
|
|
241
|
+
// ============================================================
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Sync decisions.md to .claude/rules/
|
|
245
|
+
* @param {Object} options - { dryRun: boolean }
|
|
246
|
+
* @returns {Object} - { success: boolean, files: string[], errors: string[] }
|
|
247
|
+
*/
|
|
248
|
+
function syncDecisionsToRules(options = {}) {
|
|
249
|
+
const { dryRun = false } = options;
|
|
250
|
+
const result = {
|
|
251
|
+
success: true,
|
|
252
|
+
filesCreated: [],
|
|
253
|
+
filesDeleted: [],
|
|
254
|
+
errors: [],
|
|
255
|
+
skipped: []
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Read decisions.md
|
|
259
|
+
const decisionsPath = PATHS.decisions;
|
|
260
|
+
if (!fs.existsSync(decisionsPath)) {
|
|
261
|
+
result.errors.push(`decisions.md not found at ${decisionsPath}`);
|
|
262
|
+
result.success = false;
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const decisionsContent = readFile(decisionsPath);
|
|
267
|
+
const sections = parseMarkdownSections(decisionsContent);
|
|
268
|
+
|
|
269
|
+
if (sections.length === 0) {
|
|
270
|
+
result.skipped.push('No sections with content found in decisions.md');
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Create rules directory if needed
|
|
275
|
+
if (!dryRun) {
|
|
276
|
+
if (!dirExists(RULES_DIR)) {
|
|
277
|
+
fs.mkdirSync(RULES_DIR, { recursive: true });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Clean existing generated rules
|
|
281
|
+
try {
|
|
282
|
+
const existingFiles = fs.readdirSync(RULES_DIR);
|
|
283
|
+
for (const file of existingFiles) {
|
|
284
|
+
if (file.endsWith('.md') && file !== 'README.md') {
|
|
285
|
+
const filePath = path.join(RULES_DIR, file);
|
|
286
|
+
fs.unlinkSync(filePath);
|
|
287
|
+
result.filesDeleted.push(file);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} catch (err) {
|
|
291
|
+
result.errors.push(`Error cleaning rules directory: ${err.message}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Generate rule files
|
|
296
|
+
for (const section of sections) {
|
|
297
|
+
const filename = slugify(section.title) + '.md';
|
|
298
|
+
const filePath = path.join(RULES_DIR, filename);
|
|
299
|
+
const content = generateRuleFile(section);
|
|
300
|
+
|
|
301
|
+
if (!dryRun) {
|
|
302
|
+
try {
|
|
303
|
+
writeFile(filePath, content);
|
|
304
|
+
result.filesCreated.push(filename);
|
|
305
|
+
} catch (err) {
|
|
306
|
+
result.errors.push(`Error writing ${filename}: ${err.message}`);
|
|
307
|
+
result.success = false;
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
result.filesCreated.push(filename);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Create/update README
|
|
315
|
+
if (!dryRun) {
|
|
316
|
+
const readmePath = path.join(RULES_DIR, 'README.md');
|
|
317
|
+
const readmeContent = `# Auto-Generated Rules
|
|
318
|
+
|
|
319
|
+
This directory is auto-generated from \`.workflow/state/decisions.md\`.
|
|
320
|
+
|
|
321
|
+
**DO NOT EDIT THESE FILES DIRECTLY.**
|
|
322
|
+
|
|
323
|
+
Edit \`decisions.md\` instead, then run:
|
|
324
|
+
\`\`\`bash
|
|
325
|
+
node scripts/flow-rules-sync.js
|
|
326
|
+
\`\`\`
|
|
327
|
+
|
|
328
|
+
Or rules will auto-sync when decisions.md is updated.
|
|
329
|
+
|
|
330
|
+
## How It Works
|
|
331
|
+
|
|
332
|
+
- Each section in decisions.md becomes a separate rule file
|
|
333
|
+
- Rules are path-scoped based on section keywords (e.g., "component" rules only load for component files)
|
|
334
|
+
- Claude Code automatically loads these rules for context-aware guidance
|
|
335
|
+
|
|
336
|
+
## Rule Types
|
|
337
|
+
|
|
338
|
+
Rules have \`alwaysApply\` frontmatter that determines loading behavior:
|
|
339
|
+
|
|
340
|
+
- **\`alwaysApply: true\`** - Always loaded (rules with: general, always, project, naming, coding, standard, convention, must, never, critical, security in title)
|
|
341
|
+
- **\`alwaysApply: false\`** - Agent-requested: Claude decides whether to load based on description relevance to current task
|
|
342
|
+
|
|
343
|
+
This saves tokens by not loading React rules when working on backend code, etc.
|
|
344
|
+
|
|
345
|
+
## Files
|
|
346
|
+
|
|
347
|
+
${result.filesCreated.map(f => `- ${f}`).join('\n')}
|
|
348
|
+
|
|
349
|
+
Last synced: ${new Date().toISOString()}
|
|
350
|
+
`;
|
|
351
|
+
writeFile(readmePath, readmeContent);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ============================================================
|
|
358
|
+
// Main
|
|
359
|
+
// ============================================================
|
|
360
|
+
|
|
361
|
+
function main() {
|
|
362
|
+
const { flags } = parseFlags(process.argv.slice(2));
|
|
363
|
+
|
|
364
|
+
if (flags.help) {
|
|
365
|
+
console.log(`
|
|
366
|
+
Usage: node scripts/flow-rules-sync.js [options]
|
|
367
|
+
|
|
368
|
+
Sync decisions.md to .claude/rules/ for Claude Code integration.
|
|
369
|
+
|
|
370
|
+
Options:
|
|
371
|
+
--dry-run Preview changes without writing files
|
|
372
|
+
--json Output result as JSON
|
|
373
|
+
--help Show this help message
|
|
374
|
+
|
|
375
|
+
Examples:
|
|
376
|
+
node scripts/flow-rules-sync.js # Sync rules
|
|
377
|
+
node scripts/flow-rules-sync.js --dry-run # Preview sync
|
|
378
|
+
`);
|
|
379
|
+
process.exit(0);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const result = syncDecisionsToRules({ dryRun: flags.dryRun || flags['dry-run'] });
|
|
383
|
+
|
|
384
|
+
if (flags.json) {
|
|
385
|
+
outputJson(result);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Human-readable output
|
|
390
|
+
if (flags.dryRun || flags['dry-run']) {
|
|
391
|
+
info('Dry run - no files written');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (result.filesDeleted.length > 0) {
|
|
395
|
+
info(`Cleaned ${result.filesDeleted.length} existing rule files`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (result.filesCreated.length > 0) {
|
|
399
|
+
success(`Generated ${result.filesCreated.length} rule files:`);
|
|
400
|
+
for (const file of result.filesCreated) {
|
|
401
|
+
console.log(` - ${file}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (result.skipped.length > 0) {
|
|
406
|
+
for (const msg of result.skipped) {
|
|
407
|
+
warn(msg);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (result.errors.length > 0) {
|
|
412
|
+
for (const err of result.errors) {
|
|
413
|
+
error(err);
|
|
414
|
+
}
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (result.success) {
|
|
419
|
+
success('Rules synced to .claude/rules/');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Export for use by other scripts
|
|
424
|
+
module.exports = {
|
|
425
|
+
syncDecisionsToRules,
|
|
426
|
+
parseMarkdownSections,
|
|
427
|
+
slugify,
|
|
428
|
+
getPathScope,
|
|
429
|
+
shouldAlwaysApply,
|
|
430
|
+
extractFirstSentence,
|
|
431
|
+
generateRuleFile,
|
|
432
|
+
RULES_DIR,
|
|
433
|
+
ALWAYS_APPLY_KEYWORDS
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// Run if called directly
|
|
437
|
+
if (require.main === module) {
|
|
438
|
+
main();
|
|
439
|
+
}
|