wogiflow 2.4.2 → 2.4.4

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 (210) hide show
  1. package/.claude/commands/wogi-start.md +124 -0
  2. package/.claude/docs/claude-code-compatibility.md +51 -0
  3. package/.claude/docs/explore-agents.md +11 -0
  4. package/.claude/settings.json +12 -1
  5. package/.workflow/models/registry.json +1 -1
  6. package/bin/flow +11 -1
  7. package/lib/workspace-contracts.js +599 -0
  8. package/lib/workspace-intelligence.js +600 -0
  9. package/lib/workspace-messages.js +441 -0
  10. package/lib/workspace-routing.js +485 -0
  11. package/lib/workspace-sync.js +339 -0
  12. package/lib/workspace.js +1073 -0
  13. package/package.json +4 -4
  14. package/scripts/MEMORY-ARCHITECTURE.md +1 -1
  15. package/scripts/base-workflow-step.js +136 -0
  16. package/scripts/flow-adaptive-learning.js +8 -9
  17. package/scripts/flow-aggregate.js +11 -6
  18. package/scripts/flow-api-index.js +4 -6
  19. package/scripts/flow-assumption-detector.js +0 -2
  20. package/scripts/flow-audit.js +15 -2
  21. package/scripts/flow-auto-context.js +8 -12
  22. package/scripts/flow-auto-learn.js +49 -49
  23. package/scripts/flow-background.js +5 -6
  24. package/scripts/flow-bridge-state.js +8 -10
  25. package/scripts/flow-bulk-loop.js +1 -3
  26. package/scripts/flow-bulk-orchestrator.js +1 -3
  27. package/scripts/flow-cascade-completion.js +0 -2
  28. package/scripts/flow-cascade.js +4 -4
  29. package/scripts/flow-checkpoint.js +10 -13
  30. package/scripts/flow-code-intelligence.js +10 -12
  31. package/scripts/flow-community-sync.js +4 -4
  32. package/scripts/flow-community.js +12 -20
  33. package/scripts/flow-config-defaults.js +28 -2
  34. package/scripts/flow-config-interactive.js +9 -5
  35. package/scripts/flow-config-loader.js +49 -92
  36. package/scripts/flow-config-substitution.js +0 -2
  37. package/scripts/flow-context-estimator.js +4 -4
  38. package/scripts/flow-context-init.js +10 -12
  39. package/scripts/flow-context-manager.js +0 -2
  40. package/scripts/flow-context-scoring.js +2 -2
  41. package/scripts/flow-contract-scan.js +6 -9
  42. package/scripts/flow-correct.js +29 -27
  43. package/scripts/flow-correction-detector.js +5 -1
  44. package/scripts/flow-damage-control.js +47 -54
  45. package/scripts/flow-decisions-merge.js +4 -14
  46. package/scripts/flow-diff.js +5 -8
  47. package/scripts/flow-done-gates.js +786 -0
  48. package/scripts/flow-done-report.js +123 -0
  49. package/scripts/flow-done.js +71 -717
  50. package/scripts/flow-entropy-monitor.js +1 -3
  51. package/scripts/flow-eval-calibration.js +257 -0
  52. package/scripts/flow-eval-judge.js +10 -1
  53. package/scripts/flow-eval.js +14 -5
  54. package/scripts/flow-extraction-review.js +1 -0
  55. package/scripts/flow-failure-categories.js +0 -2
  56. package/scripts/flow-figma-confirm.js +5 -9
  57. package/scripts/flow-figma-generate.js +8 -10
  58. package/scripts/flow-figma-index.js +8 -10
  59. package/scripts/flow-figma-match.js +3 -5
  60. package/scripts/flow-figma-mcp-server.js +2 -4
  61. package/scripts/flow-figma-orchestrator.js +2 -3
  62. package/scripts/flow-figma-registry.js +2 -3
  63. package/scripts/flow-framework-resolver.js +0 -2
  64. package/scripts/flow-function-index.js +4 -6
  65. package/scripts/flow-gate-confidence.js +2 -2
  66. package/scripts/flow-gitignore.js +0 -2
  67. package/scripts/flow-guided-edit.js +5 -6
  68. package/scripts/flow-health.js +5 -6
  69. package/scripts/flow-hook-errors.js +6 -0
  70. package/scripts/flow-hook-status.js +263 -0
  71. package/scripts/flow-hooks.js +17 -29
  72. package/scripts/flow-http-client.js +9 -8
  73. package/scripts/flow-hybrid-interactive.js +7 -12
  74. package/scripts/flow-hybrid-test.js +12 -13
  75. package/scripts/flow-instruction-richness.js +1 -1
  76. package/scripts/flow-io.js +21 -4
  77. package/scripts/flow-knowledge-router.js +9 -3
  78. package/scripts/flow-learning-orchestrator.js +318 -13
  79. package/scripts/flow-links.js +5 -7
  80. package/scripts/flow-long-input-association.js +275 -0
  81. package/scripts/flow-long-input-chunking.js +1 -0
  82. package/scripts/flow-long-input-cli.js +0 -2
  83. package/scripts/flow-long-input-complexity.js +0 -2
  84. package/scripts/flow-long-input-constants.js +0 -2
  85. package/scripts/flow-long-input-contradictions.js +351 -0
  86. package/scripts/flow-long-input-detection.js +0 -2
  87. package/scripts/flow-long-input-passes.js +885 -0
  88. package/scripts/flow-long-input-stories.js +1 -1
  89. package/scripts/flow-long-input-voice.js +0 -2
  90. package/scripts/flow-long-input.js +425 -3005
  91. package/scripts/flow-loop-retry-learning.js +2 -3
  92. package/scripts/flow-lsp.js +3 -3
  93. package/scripts/flow-mcp-docs.js +3 -4
  94. package/scripts/flow-memory-db.js +6 -8
  95. package/scripts/flow-memory-sync.js +18 -11
  96. package/scripts/flow-metrics.js +1 -2
  97. package/scripts/flow-model-adapter.js +2 -3
  98. package/scripts/flow-model-config.js +72 -104
  99. package/scripts/flow-model-router.js +2 -2
  100. package/scripts/flow-model-types.js +0 -2
  101. package/scripts/flow-multi-approach.js +5 -6
  102. package/scripts/flow-orchestrate-context.js +3 -7
  103. package/scripts/flow-orchestrate-rollback.js +3 -8
  104. package/scripts/flow-orchestrate-state.js +8 -14
  105. package/scripts/flow-orchestrate-templates.js +2 -6
  106. package/scripts/flow-orchestrate-validator.js +5 -9
  107. package/scripts/flow-orchestrate.js +126 -103
  108. package/scripts/flow-output.js +0 -2
  109. package/scripts/flow-parallel.js +1 -1
  110. package/scripts/flow-paths.js +23 -2
  111. package/scripts/flow-pattern-enforcer.js +30 -28
  112. package/scripts/flow-pattern-extractor.js +3 -4
  113. package/scripts/flow-pending.js +0 -2
  114. package/scripts/flow-permissions.js +2 -3
  115. package/scripts/flow-plugin-registry.js +10 -12
  116. package/scripts/flow-prd-manager.js +1 -1
  117. package/scripts/flow-progress.js +7 -9
  118. package/scripts/flow-prompt-composer.js +3 -3
  119. package/scripts/flow-prompt-template.js +2 -2
  120. package/scripts/flow-providers.js +7 -4
  121. package/scripts/flow-registry-manager.js +7 -12
  122. package/scripts/flow-regression.js +9 -11
  123. package/scripts/flow-roadmap.js +2 -2
  124. package/scripts/flow-run-trace.js +16 -15
  125. package/scripts/flow-safety.js +2 -5
  126. package/scripts/flow-scanner-base.js +5 -7
  127. package/scripts/flow-scenario-engine.js +1 -5
  128. package/scripts/flow-security.js +29 -0
  129. package/scripts/flow-session-end.js +32 -41
  130. package/scripts/flow-session-learning.js +53 -49
  131. package/scripts/flow-setup-hooks.js +2 -3
  132. package/scripts/flow-skill-create.js +7 -12
  133. package/scripts/flow-skill-generator.js +12 -16
  134. package/scripts/flow-skill-learn.js +17 -8
  135. package/scripts/flow-skill-matcher.js +1 -2
  136. package/scripts/flow-spec-generator.js +2 -4
  137. package/scripts/flow-stack-wizard.js +5 -7
  138. package/scripts/flow-standards-learner.js +35 -16
  139. package/scripts/flow-start.js +2 -0
  140. package/scripts/flow-stats-collector.js +2 -2
  141. package/scripts/flow-status.js +10 -10
  142. package/scripts/flow-statusline-setup.js +2 -2
  143. package/scripts/flow-step-changelog.js +2 -3
  144. package/scripts/flow-step-comments.js +66 -81
  145. package/scripts/flow-step-complexity.js +50 -70
  146. package/scripts/flow-step-coverage.js +3 -5
  147. package/scripts/flow-step-knowledge.js +2 -3
  148. package/scripts/flow-step-pr-tests.js +64 -74
  149. package/scripts/flow-step-regression.js +3 -5
  150. package/scripts/flow-step-review.js +86 -103
  151. package/scripts/flow-step-security.js +111 -121
  152. package/scripts/flow-step-silent-failures.js +56 -83
  153. package/scripts/flow-step-simplifier.js +52 -70
  154. package/scripts/flow-story.js +4 -7
  155. package/scripts/flow-strict-adherence.js +3 -4
  156. package/scripts/flow-task-checkpoint.js +36 -5
  157. package/scripts/flow-task-enforcer.js +2 -24
  158. package/scripts/flow-tech-debt.js +1 -1
  159. package/scripts/flow-template-extractor.js +1 -0
  160. package/scripts/flow-templates.js +11 -13
  161. package/scripts/flow-test-api.js +9 -13
  162. package/scripts/flow-test-discovery.js +1 -1
  163. package/scripts/flow-test-generate.js +5 -9
  164. package/scripts/flow-test-integrity.js +3 -7
  165. package/scripts/flow-test-ui.js +5 -9
  166. package/scripts/flow-testing-deps.js +1 -3
  167. package/scripts/flow-tiered-learning.js +4 -4
  168. package/scripts/flow-todowrite-sync.js +1 -1
  169. package/scripts/flow-tokens.js +0 -2
  170. package/scripts/flow-verification-profile.js +6 -10
  171. package/scripts/flow-verify.js +12 -16
  172. package/scripts/flow-version-check.js +4 -12
  173. package/scripts/flow-webmcp-generator.js +3 -5
  174. package/scripts/flow-workflow-steps.js +0 -2
  175. package/scripts/flow-workflow.js +9 -11
  176. package/scripts/hooks/adapters/claude-code.js +31 -0
  177. package/scripts/hooks/core/config-change.js +1 -0
  178. package/scripts/hooks/core/extension-registry.js +0 -2
  179. package/scripts/hooks/core/instructions-loaded.js +1 -1
  180. package/scripts/hooks/core/observation-capture.js +5 -5
  181. package/scripts/hooks/core/phase-gate.js +5 -0
  182. package/scripts/hooks/core/post-compact.js +1 -12
  183. package/scripts/hooks/core/research-gate.js +2 -12
  184. package/scripts/hooks/core/routing-gate.js +6 -0
  185. package/scripts/hooks/core/task-completed.js +12 -0
  186. package/scripts/hooks/core/task-created.js +83 -0
  187. package/scripts/hooks/core/worktree-lifecycle.js +1 -1
  188. package/scripts/hooks/entry/claude-code/config-change.js +6 -29
  189. package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
  190. package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
  191. package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
  192. package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
  193. package/scripts/hooks/entry/claude-code/session-end.js +4 -28
  194. package/scripts/hooks/entry/claude-code/session-start.js +205 -243
  195. package/scripts/hooks/entry/claude-code/setup.js +8 -49
  196. package/scripts/hooks/entry/claude-code/stop.js +40 -72
  197. package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
  198. package/scripts/hooks/entry/claude-code/task-created.js +15 -0
  199. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
  200. package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
  201. package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
  202. package/scripts/hooks/entry/shared/hook-runner.js +99 -0
  203. package/scripts/hooks/entry/shared/read-stdin.js +0 -2
  204. package/scripts/postinstall.js +2 -0
  205. package/scripts/registries/api-registry.js +0 -2
  206. package/scripts/registries/component-registry.js +5 -9
  207. package/scripts/registries/contract-scanner.js +2 -9
  208. package/scripts/registries/function-registry.js +0 -2
  209. package/scripts/registries/schema-registry.js +14 -18
  210. package/scripts/registries/service-registry.js +23 -27
@@ -0,0 +1,600 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Workspace — Cross-Repo Intelligence + N-Repo Scaling + Cloud Prep
5
+ *
6
+ * Stories 5-8 (wf-bbc47dc1, wf-d2e01566, wf-868ba5a5):
7
+ * Contract drift detection, blame router, shared decisions, API changelog,
8
+ * cross-repo ready queue, integration testing gate, graph-based integration map,
9
+ * library support, cascading propagation, workspace health, and cloud interfaces.
10
+ */
11
+
12
+ const fs = require('node:fs');
13
+ const path = require('node:path');
14
+
15
+ // ============================================================
16
+ // S5: Contract Drift Detection (Criterion 1)
17
+ // ============================================================
18
+
19
+ /**
20
+ * Compare actual implementation against the contract spec.
21
+ * Scans provider api-maps and compares with contracts/.
22
+ *
23
+ * @param {string} workspaceRoot
24
+ * @param {Object} manifest
25
+ * @returns {Array<Object>} drift entries
26
+ */
27
+ function detectContractDrift(workspaceRoot, manifest) {
28
+ const drifts = [];
29
+ const contractsDir = path.join(workspaceRoot, '.workspace', 'contracts');
30
+ if (!fs.existsSync(contractsDir)) return drifts;
31
+
32
+ // Load all contracts
33
+ const contractEndpoints = new Set();
34
+ const files = fs.readdirSync(contractsDir).filter(f => f.endsWith('.json'));
35
+
36
+ for (const file of files) {
37
+ try {
38
+ const spec = JSON.parse(fs.readFileSync(path.join(contractsDir, file), 'utf-8'));
39
+ if (spec.paths) {
40
+ for (const [routePath, methods] of Object.entries(spec.paths)) {
41
+ for (const method of Object.keys(methods)) {
42
+ if (['get', 'post', 'put', 'patch', 'delete'].includes(method)) {
43
+ contractEndpoints.add(`${method.toUpperCase()} ${routePath}`);
44
+ }
45
+ }
46
+ }
47
+ }
48
+ } catch (_err) {
49
+ // Skip malformed contracts
50
+ }
51
+ }
52
+
53
+ if (contractEndpoints.size === 0) return drifts;
54
+
55
+ // Compare each provider's actual endpoints against contract
56
+ for (const [name, member] of Object.entries(manifest.members)) {
57
+ if (member.role !== 'provider' && member.role !== 'both') continue;
58
+
59
+ const actualEndpoints = new Set(member.provides || []);
60
+
61
+ // Endpoints in contract but not in actual implementation
62
+ for (const contractEp of contractEndpoints) {
63
+ const hasMatch = [...actualEndpoints].some(actual => {
64
+ const normActual = normalizeForDrift(actual);
65
+ const normContract = normalizeForDrift(contractEp);
66
+ return normActual === normContract;
67
+ });
68
+
69
+ if (!hasMatch) {
70
+ drifts.push({
71
+ type: 'missing-implementation',
72
+ member: name,
73
+ endpoint: contractEp,
74
+ severity: 'high',
75
+ message: `Contract defines ${contractEp} but ${name} does not implement it`
76
+ });
77
+ }
78
+ }
79
+
80
+ // Endpoints in implementation but not in contract
81
+ for (const actualEp of actualEndpoints) {
82
+ const hasMatch = [...contractEndpoints].some(contractEp => {
83
+ return normalizeForDrift(actualEp) === normalizeForDrift(contractEp);
84
+ });
85
+
86
+ if (!hasMatch) {
87
+ drifts.push({
88
+ type: 'undocumented-endpoint',
89
+ member: name,
90
+ endpoint: actualEp,
91
+ severity: 'medium',
92
+ message: `${name} implements ${actualEp} but it's not in any contract`
93
+ });
94
+ }
95
+ }
96
+ }
97
+
98
+ return drifts;
99
+ }
100
+
101
+ function normalizeForDrift(ep) {
102
+ const parts = ep.trim().split(/\s+/);
103
+ const method = (parts[0] || 'GET').toUpperCase();
104
+ let urlPath = parts.slice(1).join(' ');
105
+ urlPath = urlPath.replace(/\{[^}]+\}/g, ':param');
106
+ urlPath = urlPath.replace(/:\w+/g, ':param');
107
+ urlPath = urlPath.replace(/\/\d+/g, '/:param');
108
+ return `${method} ${urlPath}`;
109
+ }
110
+
111
+ // ============================================================
112
+ // S5: Blame Router (Criterion 2)
113
+ // ============================================================
114
+
115
+ /**
116
+ * Analyze a bug report and determine the most likely repo to blame.
117
+ * Uses recent git changes + contract + error analysis.
118
+ *
119
+ * @param {string} workspaceRoot
120
+ * @param {string} bugDescription
121
+ * @param {Object} manifest
122
+ * @returns {Object} blame analysis
123
+ */
124
+ function routeBlame(workspaceRoot, bugDescription, manifest) {
125
+ const desc = bugDescription.toLowerCase();
126
+ const scores = {};
127
+ const evidence = {};
128
+
129
+ for (const [name, member] of Object.entries(manifest.members)) {
130
+ scores[name] = 0;
131
+ evidence[name] = [];
132
+
133
+ // Check if bug mentions endpoints this repo owns
134
+ for (const ep of (member.provides || [])) {
135
+ const epPath = ep.split(' ').slice(1).join(' ').toLowerCase();
136
+ if (desc.includes(epPath)) {
137
+ scores[name] += 3;
138
+ evidence[name].push(`Bug mentions endpoint ${ep} which ${name} provides`);
139
+ }
140
+ }
141
+
142
+ // Check if bug mentions components/pages (consumer keywords)
143
+ if (member.role === 'consumer' || member.role === 'both') {
144
+ const uiKeywords = ['page', 'screen', 'component', 'form', 'button', 'blank', 'not showing', 'not loading', 'ui'];
145
+ for (const kw of uiKeywords) {
146
+ if (desc.includes(kw)) {
147
+ scores[name] += 1;
148
+ evidence[name].push(`Bug mentions UI keyword: "${kw}"`);
149
+ break;
150
+ }
151
+ }
152
+ }
153
+
154
+ // Check for error codes (5xx = likely backend, 4xx = could be either)
155
+ if (member.role === 'provider' || member.role === 'both') {
156
+ if (desc.includes('500') || desc.includes('502') || desc.includes('503') || desc.includes('server error')) {
157
+ scores[name] += 3;
158
+ evidence[name].push('Server error (5xx) suggests backend issue');
159
+ }
160
+ if (desc.includes('database') || desc.includes('query') || desc.includes('migration')) {
161
+ scores[name] += 2;
162
+ evidence[name].push('Database-related keywords');
163
+ }
164
+ }
165
+
166
+ // Check recent git changes (if accessible)
167
+ try {
168
+ const memberPath = path.resolve(workspaceRoot, member.path || `./${name}`);
169
+ const { execFileSync } = require('node:child_process');
170
+ const recentChanges = execFileSync('git', ['log', '--oneline', '-5', '--since=24 hours ago'], {
171
+ cwd: memberPath, encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
172
+ }).trim();
173
+
174
+ if (recentChanges) {
175
+ const changeCount = recentChanges.split('\n').filter(Boolean).length;
176
+ scores[name] += changeCount; // More recent changes = more likely to have introduced a bug
177
+ evidence[name].push(`${changeCount} commit(s) in the last 24 hours`);
178
+ }
179
+ } catch (_err) {
180
+ // Git not available or timeout — skip
181
+ }
182
+ }
183
+
184
+ // Sort by score
185
+ const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]);
186
+ const primarySuspect = sorted[0]?.[0];
187
+ const confidence = sorted[0]?.[1] > 0
188
+ ? sorted[0][1] > (sorted[1]?.[1] || 0) * 2 ? 'high' : 'medium'
189
+ : 'low';
190
+
191
+ return {
192
+ primarySuspect,
193
+ confidence,
194
+ scores,
195
+ evidence,
196
+ recommendation: confidence === 'high'
197
+ ? `Start investigation in ${primarySuspect}`
198
+ : `Investigate both repos in parallel — evidence is inconclusive`
199
+ };
200
+ }
201
+
202
+ // ============================================================
203
+ // S5: Shared Decision Layer (Criterion 3)
204
+ // ============================================================
205
+
206
+ /**
207
+ * Read workspace-level shared decisions
208
+ * @param {string} workspaceRoot
209
+ * @returns {string} decisions content
210
+ */
211
+ function getSharedDecisions(workspaceRoot) {
212
+ const decisionsPath = path.join(workspaceRoot, '.workspace', 'state', 'decisions.md');
213
+ try {
214
+ if (fs.existsSync(decisionsPath)) {
215
+ return fs.readFileSync(decisionsPath, 'utf-8');
216
+ }
217
+ } catch (_err) {
218
+ // Non-critical
219
+ }
220
+ return '';
221
+ }
222
+
223
+ /**
224
+ * Add a shared decision that applies across all repos
225
+ * @param {string} workspaceRoot
226
+ * @param {string} title
227
+ * @param {string} content
228
+ */
229
+ function addSharedDecision(workspaceRoot, title, content) {
230
+ const decisionsPath = path.join(workspaceRoot, '.workspace', 'state', 'decisions.md');
231
+ let existing = '';
232
+ try {
233
+ if (fs.existsSync(decisionsPath)) {
234
+ existing = fs.readFileSync(decisionsPath, 'utf-8');
235
+ }
236
+ } catch (_err) {
237
+ if (!fs.existsSync(decisionsPath)) {
238
+ existing = '# Workspace Decisions\n\nCross-repo rules that apply to all member repositories.\n';
239
+ } else {
240
+ // File exists but can't be read — don't risk overwriting
241
+ return;
242
+ }
243
+ }
244
+
245
+ const entry = `\n### ${title}\n\n${content}\n\n*Added: ${new Date().toISOString().split('T')[0]}*\n`;
246
+ fs.writeFileSync(decisionsPath, existing + entry);
247
+ }
248
+
249
+ // ============================================================
250
+ // S5: API Changelog (Criterion 4) — delegates to workspace-contracts
251
+ // S5: Cross-Repo Ready Queue (Criterion 5) — workspace state/ready.json
252
+ // S5: Integration Testing Gate (Criterion 6) — part of routing verify step
253
+ // ============================================================
254
+
255
+ // ============================================================
256
+ // S7: Graph-Based Integration Map (Criterion 1)
257
+ // ============================================================
258
+
259
+ /**
260
+ * Build a dependency graph across N repos.
261
+ * Nodes = repos, edges = endpoint dependencies.
262
+ *
263
+ * @param {Object} manifest
264
+ * @returns {Object} graph { nodes, edges, adjacency }
265
+ */
266
+ function buildDependencyGraph(manifest) {
267
+ const graph = {
268
+ nodes: [],
269
+ edges: [],
270
+ adjacency: {} // memberName → [dependsOn]
271
+ };
272
+
273
+ for (const [name, member] of Object.entries(manifest.members)) {
274
+ graph.nodes.push({
275
+ name,
276
+ role: member.role,
277
+ endpointCount: (member.provides || []).length + (member.consumes || []).length
278
+ });
279
+ graph.adjacency[name] = [];
280
+ }
281
+
282
+ // Build edges from matched integrations
283
+ const matched = manifest.integrations?.matched || [];
284
+ for (const m of matched) {
285
+ for (const consumer of (m.consumers || [])) {
286
+ for (const provider of (m.providers || [])) {
287
+ if (consumer !== provider) {
288
+ graph.edges.push({
289
+ from: consumer,
290
+ to: provider,
291
+ endpoint: m.endpoint,
292
+ type: 'consumes'
293
+ });
294
+ if (!graph.adjacency[consumer].includes(provider)) {
295
+ graph.adjacency[consumer].push(provider);
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ return graph;
303
+ }
304
+
305
+ // ============================================================
306
+ // S7: Library Repo Support (Criterion 3)
307
+ // ============================================================
308
+
309
+ /**
310
+ * Find all repos that depend on a library repo.
311
+ * Used for cascading change propagation.
312
+ *
313
+ * @param {string} libraryName
314
+ * @param {Object} manifest
315
+ * @returns {string[]} consumer repo names
316
+ */
317
+ function getLibraryConsumers(libraryName, manifest) {
318
+ const consumers = [];
319
+
320
+ for (const [name, member] of Object.entries(manifest.members)) {
321
+ if (name === libraryName) continue;
322
+
323
+ // Check if this repo imports from the library
324
+ // (Would need to check package.json dependencies in real implementation)
325
+ // For now, all non-library repos are potential consumers
326
+ if (member.role !== 'library') {
327
+ consumers.push(name);
328
+ }
329
+ }
330
+
331
+ return consumers;
332
+ }
333
+
334
+ // ============================================================
335
+ // S7: Cascading Change Propagation (Criterion 4)
336
+ // ============================================================
337
+
338
+ /**
339
+ * When a repo changes, determine which other repos need notification.
340
+ *
341
+ * @param {string} changedRepo
342
+ * @param {Object} manifest
343
+ * @param {Object} graph — from buildDependencyGraph()
344
+ * @returns {string[]} repos that need notification
345
+ */
346
+ function getCascadeTargets(changedRepo, manifest, graph) {
347
+ const targets = new Set();
348
+
349
+ // Direct dependents (repos that consume from changedRepo)
350
+ for (const edge of graph.edges) {
351
+ if (edge.to === changedRepo) {
352
+ targets.add(edge.from);
353
+ }
354
+ }
355
+
356
+ // If changedRepo is a library, all non-library repos are potential targets
357
+ const member = manifest.members[changedRepo];
358
+ if (member?.role === 'library') {
359
+ for (const name of getLibraryConsumers(changedRepo, manifest)) {
360
+ targets.add(name);
361
+ }
362
+ }
363
+
364
+ return [...targets];
365
+ }
366
+
367
+ // ============================================================
368
+ // S7: Workspace Health Check (Criterion 5)
369
+ // ============================================================
370
+
371
+ /**
372
+ * Check workspace health — stale manifests, broken contracts, unsynced repos.
373
+ *
374
+ * @param {string} workspaceRoot
375
+ * @param {Object} manifest
376
+ * @returns {Object} health report
377
+ */
378
+ function checkWorkspaceHealth(workspaceRoot, manifest) {
379
+ const issues = [];
380
+ const checks = { total: 0, passed: 0, failed: 0, warnings: 0 };
381
+
382
+ // Check 1: All member repos still exist
383
+ for (const [name, member] of Object.entries(manifest.members)) {
384
+ checks.total++;
385
+ const memberPath = path.resolve(workspaceRoot, member.path || `./${name}`);
386
+ if (!fs.existsSync(memberPath)) {
387
+ issues.push({ severity: 'error', check: 'member-exists', message: `Member '${name}' path does not exist: ${path.relative(workspaceRoot, memberPath)}` });
388
+ checks.failed++;
389
+ } else {
390
+ checks.passed++;
391
+ }
392
+ }
393
+
394
+ // Check 2: All members have .workflow/
395
+ for (const [name, member] of Object.entries(manifest.members)) {
396
+ checks.total++;
397
+ const workflowPath = path.join(path.resolve(workspaceRoot, member.path || `./${name}`), '.workflow');
398
+ if (!fs.existsSync(workflowPath)) {
399
+ issues.push({ severity: 'warning', check: 'workflow-exists', message: `Member '${name}' has no .workflow/ directory` });
400
+ checks.warnings++;
401
+ } else {
402
+ checks.passed++;
403
+ }
404
+ }
405
+
406
+ // Check 3: Manifest freshness
407
+ checks.total++;
408
+ const manifestPath = path.join(workspaceRoot, '.workspace', 'state', 'workspace-manifest.json');
409
+ if (fs.existsSync(manifestPath)) {
410
+ const stat = fs.statSync(manifestPath);
411
+ const ageHours = (Date.now() - stat.mtime.getTime()) / (1000 * 60 * 60);
412
+ if (ageHours > 24) {
413
+ issues.push({ severity: 'warning', check: 'manifest-fresh', message: `Manifest is ${Math.floor(ageHours)}h old — run 'flow workspace sync'` });
414
+ checks.warnings++;
415
+ } else {
416
+ checks.passed++;
417
+ }
418
+ }
419
+
420
+ // Check 4: Contract drift
421
+ checks.total++;
422
+ const drifts = detectContractDrift(workspaceRoot, manifest);
423
+ if (drifts.length > 0) {
424
+ const highDrifts = drifts.filter(d => d.severity === 'high');
425
+ if (highDrifts.length > 0) {
426
+ issues.push({ severity: 'error', check: 'contract-drift', message: `${highDrifts.length} high-severity contract drift(s) detected` });
427
+ checks.failed++;
428
+ } else {
429
+ issues.push({ severity: 'warning', check: 'contract-drift', message: `${drifts.length} contract drift(s) detected` });
430
+ checks.warnings++;
431
+ }
432
+ } else {
433
+ checks.passed++;
434
+ }
435
+
436
+ // Check 5: Unread messages
437
+ checks.total++;
438
+ try {
439
+ const { readMessages } = require('./workspace-messages');
440
+ const pending = readMessages(workspaceRoot, { status: 'pending' });
441
+ if (pending.length > 5) {
442
+ issues.push({ severity: 'warning', check: 'pending-messages', message: `${pending.length} unread messages — review with 'show messages'` });
443
+ checks.warnings++;
444
+ } else {
445
+ checks.passed++;
446
+ }
447
+ } catch (_err) {
448
+ checks.passed++; // No messages module = no issue
449
+ }
450
+
451
+ // Check 6: Orphaned consumers
452
+ checks.total++;
453
+ const orphanedC = manifest.integrations?.orphanedConsumers?.length || 0;
454
+ if (orphanedC > 0) {
455
+ issues.push({ severity: 'warning', check: 'orphaned-consumers', message: `${orphanedC} consumer(s) calling endpoints with no provider` });
456
+ checks.warnings++;
457
+ } else {
458
+ checks.passed++;
459
+ }
460
+
461
+ return {
462
+ healthy: checks.failed === 0,
463
+ checks,
464
+ issues,
465
+ summary: checks.failed > 0
466
+ ? `Unhealthy: ${checks.failed} error(s), ${checks.warnings} warning(s)`
467
+ : checks.warnings > 0
468
+ ? `OK with ${checks.warnings} warning(s)`
469
+ : 'All checks passed'
470
+ };
471
+ }
472
+
473
+ // ============================================================
474
+ // S8: Cloud Preparation — Extension Points & Interfaces
475
+ // ============================================================
476
+
477
+ /**
478
+ * Export workspace state in a format suitable for cloud dashboard consumption.
479
+ * This is the interface contract between OSS and cloud.
480
+ *
481
+ * @param {string} workspaceRoot
482
+ * @param {Object} manifest
483
+ * @returns {Object} dashboard-ready workspace state
484
+ */
485
+ function exportForDashboard(workspaceRoot, manifest) {
486
+ const { readMessages } = require('./workspace-messages');
487
+
488
+ return {
489
+ version: '1.0.0',
490
+ exportedAt: new Date().toISOString(),
491
+ workspace: {
492
+ name: manifest.workspace,
493
+ memberCount: Object.keys(manifest.members).length,
494
+ members: Object.entries(manifest.members).map(([name, m]) => ({
495
+ name,
496
+ role: m.role,
497
+ stack: m.stack,
498
+ endpointsProvided: (m.provides || []).length,
499
+ endpointsConsumed: (m.consumes || []).length
500
+ }))
501
+ },
502
+ integrations: {
503
+ matchedCount: manifest.integrations?.matched?.length || 0,
504
+ orphanedConsumers: manifest.integrations?.orphanedConsumers?.length || 0,
505
+ orphanedProviders: manifest.integrations?.orphanedProviders?.length || 0,
506
+ typeDrifts: manifest.integrations?.typeDrift?.length || 0,
507
+ contractDrifts: detectContractDrift(workspaceRoot, manifest).length
508
+ },
509
+ messages: (() => {
510
+ const allMessages = readMessages(workspaceRoot);
511
+ return {
512
+ total: allMessages.length,
513
+ pending: allMessages.filter(m => m.status === 'pending').length
514
+ };
515
+ })(),
516
+ health: checkWorkspaceHealth(workspaceRoot, manifest)
517
+ };
518
+ }
519
+
520
+ /**
521
+ * Message bus abstraction — provides the interface that cloud can swap for HTTP/WebSocket.
522
+ * OSS uses file-based transport. Cloud overrides with network transport.
523
+ *
524
+ * @returns {Object} transport interface
525
+ */
526
+ function getMessageTransport() {
527
+ return {
528
+ type: 'file',
529
+ send: (workspaceRoot, message) => {
530
+ const { saveMessage } = require('./workspace-messages');
531
+ return saveMessage(workspaceRoot, message);
532
+ },
533
+ receive: (workspaceRoot, filter) => {
534
+ const { readMessages } = require('./workspace-messages');
535
+ return readMessages(workspaceRoot, filter);
536
+ },
537
+ acknowledge: (workspaceRoot, messageId) => {
538
+ const { updateMessageStatus } = require('./workspace-messages');
539
+ return updateMessageStatus(workspaceRoot, messageId, 'acknowledged');
540
+ }
541
+ };
542
+ }
543
+
544
+ /**
545
+ * Multi-user data model schema — defines the structure for when cloud adds
546
+ * multi-user support. No implementation — just the interface contract.
547
+ *
548
+ * @returns {Object} schema definition
549
+ */
550
+ function getMultiUserSchema() {
551
+ return {
552
+ workspace: {
553
+ id: 'string (UUID)',
554
+ name: 'string',
555
+ createdBy: 'string (userId)',
556
+ members: 'WorkspaceMember[]',
557
+ teamId: 'string (from wogiflow-cloud)',
558
+ createdAt: 'ISO 8601',
559
+ updatedAt: 'ISO 8601'
560
+ },
561
+ workspaceMember: {
562
+ userId: 'string',
563
+ role: 'owner | admin | member | viewer',
564
+ repos: 'string[] (which repos they can access)',
565
+ lastActiveAt: 'ISO 8601'
566
+ },
567
+ agentSession: {
568
+ id: 'string (UUID)',
569
+ workspaceId: 'string',
570
+ userId: 'string',
571
+ repoName: 'string',
572
+ status: 'active | idle | disconnected',
573
+ startedAt: 'ISO 8601',
574
+ lastHeartbeat: 'ISO 8601'
575
+ }
576
+ };
577
+ }
578
+
579
+ // ============================================================
580
+ // Exports
581
+ // ============================================================
582
+
583
+ module.exports = {
584
+ // S5: Cross-Repo Intelligence
585
+ detectContractDrift,
586
+ routeBlame,
587
+ getSharedDecisions,
588
+ addSharedDecision,
589
+
590
+ // S7: N-Repo Scaling
591
+ buildDependencyGraph,
592
+ getLibraryConsumers,
593
+ getCascadeTargets,
594
+ checkWorkspaceHealth,
595
+
596
+ // S8: Cloud Preparation
597
+ exportForDashboard,
598
+ getMessageTransport,
599
+ getMultiUserSchema
600
+ };