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,589 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Knowledge Sync
5
+ *
6
+ * Detects drift in knowledge files (stack.md, architecture.md, testing.md)
7
+ * by tracking hashes of project indicator files.
8
+ *
9
+ * Usage:
10
+ * flow knowledge-sync status Check sync status
11
+ * flow knowledge-sync check Check and report drift
12
+ * flow knowledge-sync regenerate Regenerate stale knowledge files
13
+ * flow knowledge-sync --json JSON output
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const crypto = require('crypto');
19
+
20
+ const {
21
+ PATHS,
22
+ PROJECT_ROOT,
23
+ fileExists,
24
+ dirExists,
25
+ parseFlags,
26
+ outputJson,
27
+ printHeader,
28
+ printSection,
29
+ color,
30
+ success,
31
+ warn,
32
+ error,
33
+ info,
34
+ getConfig,
35
+ isPathWithinProject,
36
+ safeJsonParse
37
+ } = require('./flow-utils');
38
+
39
+ // Files that indicate stack/architecture changes
40
+ const STACK_INDICATORS = [
41
+ 'package.json',
42
+ 'package-lock.json',
43
+ 'yarn.lock',
44
+ 'pnpm-lock.yaml',
45
+ 'requirements.txt',
46
+ 'Pipfile',
47
+ 'Gemfile',
48
+ 'go.mod',
49
+ 'Cargo.toml',
50
+ 'build.gradle',
51
+ 'pom.xml',
52
+ ];
53
+
54
+ // Files that indicate architecture changes
55
+ const ARCHITECTURE_INDICATORS = [
56
+ 'tsconfig.json',
57
+ 'tsconfig.*.json',
58
+ 'jsconfig.json',
59
+ '.eslintrc*',
60
+ '.prettierrc*',
61
+ 'webpack.config.*',
62
+ 'vite.config.*',
63
+ 'next.config.*',
64
+ 'nuxt.config.*',
65
+ 'angular.json',
66
+ 'nest-cli.json',
67
+ '.babelrc*',
68
+ 'rollup.config.*',
69
+ ];
70
+
71
+ // Files that indicate testing changes
72
+ const TESTING_INDICATORS = [
73
+ 'jest.config.*',
74
+ 'vitest.config.*',
75
+ 'cypress.config.*',
76
+ 'playwright.config.*',
77
+ '.mocharc*',
78
+ 'karma.conf.*',
79
+ 'pytest.ini',
80
+ 'setup.py',
81
+ 'tox.ini',
82
+ 'phpunit.xml',
83
+ ];
84
+
85
+ /**
86
+ * Compute MD5 hash of file content
87
+ * @param {string} filePath - Path to file
88
+ * @returns {{hash: string|null, error: string|null}} Hash result with error context
89
+ */
90
+ function hashFile(filePath) {
91
+ try {
92
+ if (!fs.existsSync(filePath)) {
93
+ return { hash: null, error: 'not_found' };
94
+ }
95
+ const content = fs.readFileSync(filePath, 'utf-8');
96
+ return { hash: crypto.createHash('md5').update(content).digest('hex'), error: null };
97
+ } catch (err) {
98
+ // Provide error context for debugging
99
+ const errorType = err.code === 'EACCES' ? 'permission_denied' :
100
+ err.code === 'EISDIR' ? 'is_directory' :
101
+ 'read_error';
102
+ return { hash: null, error: errorType };
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Escape all regex special characters except * which becomes .*
108
+ * @param {string} pattern - Glob pattern
109
+ * @returns {string} Regex-safe string
110
+ */
111
+ function escapeGlobToRegex(pattern) {
112
+ // Escape all regex special chars except *
113
+ return pattern
114
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape special chars
115
+ .replace(/\*/g, '[^/]*'); // Convert * to non-path-separator match
116
+ }
117
+
118
+ /**
119
+ * Validate pattern contains only safe characters
120
+ * @param {string} pattern - Pattern to validate
121
+ * @returns {boolean} True if safe
122
+ */
123
+ function isSafePattern(pattern) {
124
+ // Only allow alphanumeric, -, _, ., and *
125
+ return /^[a-zA-Z0-9._*-]+$/.test(pattern);
126
+ }
127
+
128
+ /**
129
+ * Find files matching glob patterns in project root
130
+ */
131
+ function findIndicatorFiles(patterns) {
132
+ const found = [];
133
+
134
+ for (const pattern of patterns) {
135
+ // Validate pattern is safe
136
+ if (!isSafePattern(pattern)) {
137
+ warn(`Skipping unsafe pattern: ${pattern}`);
138
+ continue;
139
+ }
140
+
141
+ // Simple glob matching - supports * wildcard
142
+ if (pattern.includes('*')) {
143
+ const regex = new RegExp('^' + escapeGlobToRegex(pattern) + '$');
144
+ try {
145
+ const files = fs.readdirSync(PROJECT_ROOT);
146
+ for (const file of files) {
147
+ // Skip hidden files and symlinks for safety
148
+ const fullPath = path.join(PROJECT_ROOT, file);
149
+ try {
150
+ const stat = fs.lstatSync(fullPath);
151
+ if (stat.isSymbolicLink()) continue;
152
+ } catch {
153
+ continue;
154
+ }
155
+
156
+ if (regex.test(file)) {
157
+ found.push(file);
158
+ }
159
+ }
160
+ } catch {
161
+ // Directory read error - silently skip
162
+ }
163
+ } else {
164
+ // Exact match
165
+ const fullPath = path.join(PROJECT_ROOT, pattern);
166
+
167
+ // Defense in depth: verify resolved path is within project
168
+ if (!isPathWithinProject(fullPath)) {
169
+ warn(`Path traversal blocked: ${pattern}`);
170
+ continue;
171
+ }
172
+
173
+ try {
174
+ // Check it exists and is not a symlink
175
+ const stat = fs.lstatSync(fullPath);
176
+ if (!stat.isSymbolicLink() && fs.existsSync(fullPath)) {
177
+ found.push(pattern);
178
+ }
179
+ } catch {
180
+ // File doesn't exist - skip
181
+ }
182
+ }
183
+ }
184
+
185
+ return found;
186
+ }
187
+
188
+ /**
189
+ * Compute hashes for a category of indicator files
190
+ */
191
+ function computeCategoryHashes(patterns) {
192
+ const files = findIndicatorFiles(patterns);
193
+ const hashes = {};
194
+ const errors = {};
195
+
196
+ for (const file of files) {
197
+ const fullPath = path.join(PROJECT_ROOT, file);
198
+
199
+ // Defense in depth: double-check path is within project
200
+ if (!isPathWithinProject(fullPath)) {
201
+ errors[file] = 'path_traversal';
202
+ continue;
203
+ }
204
+
205
+ const result = hashFile(fullPath);
206
+ if (result.hash) {
207
+ hashes[file] = result.hash;
208
+ } else if (result.error && result.error !== 'not_found') {
209
+ // Track non-trivial errors for debugging
210
+ errors[file] = result.error;
211
+ }
212
+ }
213
+
214
+ // Return combined hash of all files
215
+ const combined = Object.entries(hashes)
216
+ .sort(([a], [b]) => a.localeCompare(b))
217
+ .map(([file, hash]) => `${file}:${hash}`)
218
+ .join('|');
219
+
220
+ return {
221
+ files: Object.keys(hashes),
222
+ combinedHash: combined ? crypto.createHash('md5').update(combined).digest('hex') : null,
223
+ individualHashes: hashes,
224
+ errors: Object.keys(errors).length > 0 ? errors : null
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Validate sync state structure
230
+ * @param {Object} state - Parsed state object
231
+ * @returns {boolean} True if valid structure
232
+ */
233
+ function isValidSyncState(state) {
234
+ if (!state || typeof state !== 'object') {
235
+ return false;
236
+ }
237
+
238
+ // lastSync is optional but must be string if present
239
+ if (state.lastSync !== undefined && typeof state.lastSync !== 'string') {
240
+ return false;
241
+ }
242
+
243
+ // Validate each category if present
244
+ const categories = ['stack', 'architecture', 'testing'];
245
+ for (const cat of categories) {
246
+ if (state[cat] !== undefined) {
247
+ const catData = state[cat];
248
+ if (typeof catData !== 'object' || catData === null) {
249
+ return false;
250
+ }
251
+ // combinedHash should be string or null
252
+ if (catData.combinedHash !== undefined &&
253
+ catData.combinedHash !== null &&
254
+ typeof catData.combinedHash !== 'string') {
255
+ return false;
256
+ }
257
+ // individualHashes should be object if present
258
+ if (catData.individualHashes !== undefined &&
259
+ (typeof catData.individualHashes !== 'object' || catData.individualHashes === null)) {
260
+ return false;
261
+ }
262
+ }
263
+ }
264
+
265
+ return true;
266
+ }
267
+
268
+ /**
269
+ * Load current sync state
270
+ */
271
+ function loadSyncState() {
272
+ if (!fileExists(PATHS.knowledgeSync)) {
273
+ return null;
274
+ }
275
+
276
+ try {
277
+ const content = fs.readFileSync(PATHS.knowledgeSync, 'utf-8');
278
+ const state = safeJsonParse(content, null);
279
+
280
+ // Validate structure before returning
281
+ if (!isValidSyncState(state)) {
282
+ warn('Invalid sync state structure in knowledge-sync.json');
283
+ return null;
284
+ }
285
+
286
+ return state;
287
+ } catch (err) {
288
+ warn(`Failed to load sync state: ${err.message}`);
289
+ return null;
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Save sync state
295
+ */
296
+ function saveSyncState(state) {
297
+ fs.writeFileSync(PATHS.knowledgeSync, JSON.stringify(state, null, 2));
298
+ }
299
+
300
+ /**
301
+ * Check drift for a specific knowledge file category
302
+ */
303
+ function checkCategoryDrift(category, indicators, syncState) {
304
+ const current = computeCategoryHashes(indicators);
305
+ const stored = syncState?.[category];
306
+
307
+ if (!stored) {
308
+ return {
309
+ category,
310
+ status: 'missing',
311
+ reason: 'No sync state recorded',
312
+ needsRegeneration: true,
313
+ currentHash: current.combinedHash,
314
+ storedHash: null,
315
+ files: current.files
316
+ };
317
+ }
318
+
319
+ if (current.combinedHash !== stored.combinedHash) {
320
+ // Find which files changed
321
+ const changedFiles = [];
322
+ for (const [file, hash] of Object.entries(current.individualHashes)) {
323
+ if (stored.individualHashes?.[file] !== hash) {
324
+ changedFiles.push(file);
325
+ }
326
+ }
327
+ // Check for removed files
328
+ for (const file of Object.keys(stored.individualHashes || {})) {
329
+ if (!current.individualHashes[file]) {
330
+ changedFiles.push(`${file} (removed)`);
331
+ }
332
+ }
333
+
334
+ return {
335
+ category,
336
+ status: 'drifted',
337
+ reason: `Files changed: ${changedFiles.join(', ')}`,
338
+ needsRegeneration: true,
339
+ currentHash: current.combinedHash,
340
+ storedHash: stored.combinedHash,
341
+ changedFiles,
342
+ files: current.files
343
+ };
344
+ }
345
+
346
+ return {
347
+ category,
348
+ status: 'synced',
349
+ reason: 'Hashes match',
350
+ needsRegeneration: false,
351
+ currentHash: current.combinedHash,
352
+ storedHash: stored.combinedHash,
353
+ files: current.files
354
+ };
355
+ }
356
+
357
+ /**
358
+ * Check all knowledge file categories for drift
359
+ */
360
+ function checkAllDrift() {
361
+ const syncState = loadSyncState();
362
+
363
+ const results = {
364
+ stack: checkCategoryDrift('stack', STACK_INDICATORS, syncState),
365
+ architecture: checkCategoryDrift('architecture', ARCHITECTURE_INDICATORS, syncState),
366
+ testing: checkCategoryDrift('testing', TESTING_INDICATORS, syncState)
367
+ };
368
+
369
+ // Check if knowledge files exist
370
+ results.stack.fileExists = fileExists(PATHS.stackMd);
371
+ results.architecture.fileExists = fileExists(PATHS.architectureMd);
372
+ results.testing.fileExists = fileExists(PATHS.testingMd);
373
+
374
+ // Overall status
375
+ const anyDrift = Object.values(results).some(r => r.needsRegeneration);
376
+ const anyMissing = !results.stack.fileExists || !results.architecture.fileExists || !results.testing.fileExists;
377
+
378
+ return {
379
+ overall: anyDrift || anyMissing ? 'stale' : 'synced',
380
+ lastSync: syncState?.lastSync || null,
381
+ categories: results,
382
+ anyDrift,
383
+ anyMissing
384
+ };
385
+ }
386
+
387
+ /**
388
+ * Update sync state after regeneration
389
+ */
390
+ function markAsSynced() {
391
+ const state = {
392
+ lastSync: new Date().toISOString(),
393
+ stack: computeCategoryHashes(STACK_INDICATORS),
394
+ architecture: computeCategoryHashes(ARCHITECTURE_INDICATORS),
395
+ testing: computeCategoryHashes(TESTING_INDICATORS)
396
+ };
397
+
398
+ saveSyncState(state);
399
+ return state;
400
+ }
401
+
402
+ /**
403
+ * Print human-readable status
404
+ */
405
+ function printStatus(driftStatus) {
406
+ printHeader('KNOWLEDGE FILES SYNC STATUS');
407
+
408
+ if (driftStatus.lastSync) {
409
+ info(`Last synced: ${driftStatus.lastSync}`);
410
+ } else {
411
+ warn('Never synced - run "flow onboard" or "flow knowledge-sync regenerate"');
412
+ }
413
+
414
+ console.log('');
415
+
416
+ const categories = [
417
+ { key: 'stack', name: 'Stack (stack.md)', file: PATHS.stackMd },
418
+ { key: 'architecture', name: 'Architecture (architecture.md)', file: PATHS.architectureMd },
419
+ { key: 'testing', name: 'Testing (testing.md)', file: PATHS.testingMd }
420
+ ];
421
+
422
+ for (const { key, name, file } of categories) {
423
+ const status = driftStatus.categories[key];
424
+ printSection(name);
425
+
426
+ // File existence
427
+ if (status.fileExists) {
428
+ console.log(` ${color('green', '✓')} File exists`);
429
+ } else {
430
+ console.log(` ${color('red', '✗')} File missing`);
431
+ }
432
+
433
+ // Sync status
434
+ if (status.status === 'synced') {
435
+ console.log(` ${color('green', '✓')} In sync`);
436
+ } else if (status.status === 'drifted') {
437
+ console.log(` ${color('yellow', '⚠')} Drifted: ${status.reason}`);
438
+ } else {
439
+ console.log(` ${color('yellow', '○')} ${status.reason}`);
440
+ }
441
+
442
+ // Indicator files
443
+ if (status.files.length > 0) {
444
+ console.log(` Tracked files: ${status.files.join(', ')}`);
445
+ }
446
+
447
+ console.log('');
448
+ }
449
+
450
+ // Overall recommendation
451
+ printSection('📌 Recommendation');
452
+ if (driftStatus.overall === 'synced') {
453
+ console.log(` ${color('green', '✓')} All knowledge files are up to date`);
454
+ } else if (driftStatus.anyMissing) {
455
+ console.log(` Run: ${color('cyan', 'flow onboard')} to generate missing files`);
456
+ } else if (driftStatus.anyDrift) {
457
+ console.log(` Run: ${color('cyan', 'flow knowledge-sync regenerate')} to update drifted files`);
458
+ }
459
+
460
+ console.log('');
461
+ }
462
+
463
+ /**
464
+ * Regenerate knowledge files using onboard generators
465
+ * @param {string[]} categories - Categories to regenerate
466
+ * @returns {Promise<Object>} New sync state or null if failed
467
+ */
468
+ async function regenerateKnowledgeFiles(categories = ['stack', 'architecture', 'testing']) {
469
+ info('Regenerating knowledge files...');
470
+
471
+ const { spawn } = require('child_process');
472
+
473
+ return new Promise((resolve, reject) => {
474
+ // Use spawn with explicit args array and absolute path to prevent command injection
475
+ const scriptPath = path.join(PROJECT_ROOT, 'scripts', 'flow-onboard');
476
+ const child = spawn('node', [scriptPath, '--update-knowledge'], {
477
+ cwd: PROJECT_ROOT,
478
+ stdio: 'inherit'
479
+ });
480
+
481
+ child.on('error', (err) => {
482
+ // Spawn failed (e.g., node not found)
483
+ error(`Failed to spawn process: ${err.message}`);
484
+ warn('Run "flow onboard" manually to regenerate knowledge files');
485
+ resolve(null);
486
+ });
487
+
488
+ child.on('close', (code) => {
489
+ if (code === 0) {
490
+ // Success - update sync state
491
+ const state = markAsSynced();
492
+ success('Knowledge files regenerated and sync state updated');
493
+ resolve(state);
494
+ } else if (code === null) {
495
+ // Process was killed
496
+ warn('Regeneration process was terminated');
497
+ resolve(null);
498
+ } else {
499
+ // Non-zero exit - onboard command may not support --update-knowledge
500
+ warn(`Onboard exited with code ${code}`);
501
+ info('The --update-knowledge flag may not be supported yet.');
502
+ info('Options:');
503
+ info(' 1. Run "flow onboard" to regenerate all knowledge files');
504
+ info(' 2. Run "flow knowledge-sync mark-synced" to accept current state');
505
+ // Do NOT mark as synced - this would be misleading
506
+ resolve(null);
507
+ }
508
+ });
509
+ });
510
+ }
511
+
512
+ /**
513
+ * Main entry point
514
+ */
515
+ async function main() {
516
+ const { positional, flags } = parseFlags(process.argv.slice(2));
517
+ const command = positional[0] || 'status';
518
+
519
+ const driftStatus = checkAllDrift();
520
+
521
+ // JSON output
522
+ if (flags.json) {
523
+ outputJson({
524
+ success: true,
525
+ command,
526
+ ...driftStatus
527
+ });
528
+ return;
529
+ }
530
+
531
+ switch (command) {
532
+ case 'status':
533
+ case 'check':
534
+ printStatus(driftStatus);
535
+ // Exit with code 1 if stale (useful for CI)
536
+ process.exit(driftStatus.overall === 'stale' ? 1 : 0);
537
+ break;
538
+
539
+ case 'regenerate':
540
+ case 'sync':
541
+ case 'update':
542
+ if (driftStatus.overall === 'synced' && !flags.force) {
543
+ success('Knowledge files are already in sync');
544
+ info('Use --force to regenerate anyway');
545
+ return;
546
+ }
547
+ await regenerateKnowledgeFiles();
548
+ break;
549
+
550
+ case 'mark-synced':
551
+ // Manual mark as synced (for testing or after manual edits)
552
+ markAsSynced();
553
+ success('Sync state updated');
554
+ break;
555
+
556
+ default:
557
+ error(`Unknown command: ${command}`);
558
+ console.log('');
559
+ console.log('Usage:');
560
+ console.log(' flow knowledge-sync status Check sync status');
561
+ console.log(' flow knowledge-sync check Check and report drift');
562
+ console.log(' flow knowledge-sync regenerate Regenerate stale files');
563
+ console.log(' flow knowledge-sync mark-synced Mark current state as synced');
564
+ console.log('');
565
+ console.log('Options:');
566
+ console.log(' --json Output in JSON format');
567
+ console.log(' --force Force regeneration even if synced');
568
+ process.exit(1);
569
+ }
570
+ }
571
+
572
+ // Export for use by other scripts
573
+ module.exports = {
574
+ checkAllDrift,
575
+ checkCategoryDrift,
576
+ markAsSynced,
577
+ loadSyncState,
578
+ computeCategoryHashes,
579
+ STACK_INDICATORS,
580
+ ARCHITECTURE_INDICATORS,
581
+ TESTING_INDICATORS
582
+ };
583
+
584
+ if (require.main === module) {
585
+ main().catch(err => {
586
+ error(err.message);
587
+ process.exit(1);
588
+ });
589
+ }