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.
Files changed (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. 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
+ }