wogiflow 2.4.4 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/workspace.js CHANGED
@@ -328,14 +328,24 @@ function generateWorkspaceConfig(workspaceName, members) {
328
328
  sync: {
329
329
  autoOnSessionStart: true,
330
330
  autoAfterTaskComplete: true
331
+ },
332
+ channels: {
333
+ enabled: true,
334
+ basePort: 8801,
335
+ members: {}
331
336
  }
332
337
  };
333
338
 
339
+ let port = config.channels.basePort;
340
+ if (port + members.length - 1 > 65535) {
341
+ throw new Error(`Channel port range ${port}-${port + members.length - 1} exceeds maximum port 65535. Reduce basePort or number of members.`);
342
+ }
334
343
  for (const member of members) {
335
344
  config.members[member.name] = {
336
345
  path: `./${member.name}`,
337
346
  role: member.role
338
347
  };
348
+ config.channels.members[member.name] = { port: port++ };
339
349
  }
340
350
 
341
351
  return config;
@@ -546,49 +556,43 @@ ${Object.entries(manifest.members).map(([name, m]) => {
546
556
  }).join('\n')}
547
557
  - **Both/all repos**: api contract, schema change, integration, full-stack, end-to-end
548
558
 
549
- ### Step 3: Execute
550
-
551
- **Single-repo task — spawn one sub-agent:**
552
- \`\`\`
553
- Use the Agent tool:
554
- prompt: "You are working in the <REPO> repository.
555
- Stack: <STACK>
556
- Task: <TASK DESCRIPTION>
559
+ ### Step 3: Dispatch via Channels
557
560
 
558
- Project rules (read from <REPO>/.workflow/state/decisions.md):
559
- <PASTE DECISIONS CONTENT>
561
+ Each worker repo runs a full Claude Code session with its own CLAUDE.md, hooks, and WogiFlow pipeline. You dispatch tasks by sending HTTP requests to their channel ports. **Do NOT use the Agent tool for implementation** — use channels for full enforcement.
560
562
 
561
- After completing: commit your changes with a descriptive message."
563
+ **Worker Channel Ports:**
564
+ ${Object.entries(config.channels?.members || {}).map(([name, ch]) => `- **${name}**: \`http://localhost:${ch.port}\``).join('\n')}
562
565
 
563
- The sub-agent runs with the full codebase of that repo available.
564
- It follows that repo's own WogiFlow rules, decisions, and patterns.
566
+ **Check if workers are running:**
567
+ \`\`\`bash
568
+ ${Object.entries(config.channels?.members || {}).map(([name, ch]) => `curl -s http://localhost:${ch.port}/health`).join('\n')}
565
569
  \`\`\`
566
570
 
567
- **Cross-repo task sequential delegation:**
568
- 1. Read the contract from \`.workspace/contracts/\` (if exists)
569
- 2. If the API contract needs updating, update it first
570
- 3. Spawn provider sub-agent (${providers.join(' or ')}) — implement the API side
571
- 4. Read what the provider changed (from its git diff or commit message)
572
- 5. Write a message to \`.workspace/messages/\` describing the change
573
- 6. Spawn consumer sub-agent (${consumers.join(' or ')}) — implement the client side, include the message as context
574
- 7. Verify both sides work together
571
+ If a worker is down, tell the user: "Start the ${'{'}repo{'}'} worker: \`cd ${'{'}repo{'}'}/ && flow workspace start\`"
575
572
 
576
- **Bug investigation — parallel agents:**
577
- \`\`\`
578
- Spawn ${memberNames.length} agents IN PARALLEL (single message with multiple Agent tool calls):
573
+ **Single-repo task:**
574
+ 1. Create task in the repo's ready.json using \`decomposeToRepoTasks()\` or directly
575
+ 2. Dispatch: \`curl -s -X POST http://localhost:${'{'}port{'}'} -d "/wogi-start ${'{'}taskId${'}'}"\`
576
+ 3. The worker's full WogiFlow pipeline handles the rest (explore, spec, implement, verify)
577
+ 4. Monitor: check \`.workspace/messages/\` for a \`task-complete\` message
579
578
 
580
- Agent 1 (${memberNames[0]}):
581
- "Investigate this bug in ${memberNames[0]}: <BUG DESCRIPTION>
582
- Check: recent changes, error logs, relevant code.
583
- Report: Is the issue on YOUR side? What did you find?"
584
-
585
- ${memberNames.length > 1 ? `Agent 2 (${memberNames[1]}):
586
- "Investigate this bug in ${memberNames[1]}: <BUG DESCRIPTION>
587
- Check: recent changes, error logs, relevant code.
588
- Report: Is the issue on YOUR side? What did you find?"` : ''}
589
-
590
- Then synthesize the findings and route the fix to the correct repo.
579
+ **Cross-repo task:**
580
+ 1. Read the contract from \`.workspace/contracts/\` (if exists)
581
+ 2. If the API contract needs updating, update it first
582
+ 3. Create tasks in each repo's ready.json (provider gets P0 priority)
583
+ 4. Dispatch to provider first: \`curl -s -X POST http://localhost:${providers[0] ? config.channels?.members?.[providers[0]]?.port || '{port}' : '{port}'} -d "/wogi-start ${'{'}providerTaskId${'}'}"\`
584
+ 5. Wait for provider completion message in \`.workspace/messages/\`
585
+ 6. Then dispatch to consumer: \`curl -s -X POST http://localhost:${consumers[0] ? config.channels?.members?.[consumers[0]]?.port || '{port}' : '{port}'} -d "/wogi-start ${'{'}consumerTaskId${'}'}"\`
586
+ 7. Wait for consumer completion, then verify integration
587
+
588
+ **Bug investigation — dispatch to all workers:**
589
+ \`\`\`bash
590
+ # Send investigation request to all workers in parallel
591
+ ${Object.entries(config.channels?.members || {}).map(([name, ch]) =>
592
+ `curl -s -X POST http://localhost:${ch.port} -d "Investigate: <BUG_DESCRIPTION>. Check recent changes, error logs, relevant code. Report back via workspace message."`
593
+ ).join('\n')}
591
594
  \`\`\`
595
+ Then read responses from \`.workspace/messages/\` and synthesize findings.
592
596
 
593
597
  ## Reading Member State (What to Read, When)
594
598
 
@@ -637,6 +641,51 @@ When spawning a sub-agent, check for pending messages to that repo and include t
637
641
  Shared API contracts: \`.workspace/contracts/\`
638
642
  When a provider changes an endpoint, update the contract BEFORE the consumer implements.
639
643
 
644
+ ## Peer Communication (Worker ↔ Worker)
645
+
646
+ Workers can talk directly to each other — no manager bottleneck. Each worker knows its peers' channel ports.
647
+
648
+ A worker asking its peer a question:
649
+ \`\`\`bash
650
+ curl -s -X POST http://localhost:{peer_port} -H "X-Wogi-From: {my_repo}" -d "Is the POST /users endpoint ready? What's the expected payload shape?"
651
+ \`\`\`
652
+
653
+ The peer receives this as a channel event, reads its codebase to answer, and can reply back the same way. This is useful when:
654
+ - Frontend needs to know the exact API shape before implementing
655
+ - Backend wants to know which fields the frontend actually uses
656
+ - Any repo needs real-time coordination without going through the manager
657
+
658
+ Workers also have a \`workspace_send_message\` MCP tool that handles this automatically.
659
+
660
+ ## Command Routing (CRITICAL)
661
+
662
+ **You are an orchestrator — you do NOT execute code-level commands locally.** When the user invokes a WogiFlow command, route it based on this table:
663
+
664
+ | Command | Action | Why |
665
+ |---------|--------|-----|
666
+ | \`/wogi-review\` | **Dispatch to ALL workers** → aggregate findings + cross-repo analysis | Workers have the source code and full pipeline |
667
+ | \`/wogi-audit\` | **Dispatch to ALL workers** → aggregate audit results + cross-repo analysis | Same reason |
668
+ | \`/wogi-test\` | **Dispatch to ALL workers** → aggregate test results | Tests run in each repo's environment |
669
+ | \`/wogi-health\` | **Dispatch to ALL workers** → unified health report | Each repo has its own workflow state |
670
+ | \`/wogi-start "task"\` | **Analyze → decompose → dispatch** to correct worker(s) | Already designed for this |
671
+ | \`/wogi-status\` | Run **locally** — workspace-level overview | Reads workspace state files |
672
+ | \`/wogi-ready\` | Run **locally** — cross-repo task queue | Reads all repos' ready.json |
673
+ | \`/wogi-session-end\` | Run **locally** — end workspace session | Workspace-level action |
674
+
675
+ **How to dispatch a command to all workers:**
676
+ \`\`\`bash
677
+ ${Object.entries(config.channels?.members || {}).map(([name, ch]) =>
678
+ `curl -s -X POST http://localhost:${ch.port} -d "/wogi-review" # → ${name}`
679
+ ).join('\n')}
680
+ \`\`\`
681
+
682
+ **After all workers complete:** Run a cross-repo analysis that only you can do:
683
+ 1. Collect findings from \`.workspace/messages/\` (workers write results there)
684
+ 2. Check API contract alignment between provider and consumer
685
+ 3. Check for integration gaps (orphaned consumers, drifted contracts)
686
+ 4. Check shared decisions compliance (\`.workspace/state/decisions.md\`)
687
+ 5. Present unified report: per-repo findings + cross-repo findings
688
+
640
689
  ## Workspace Commands
641
690
 
642
691
  | Command | What it does |
@@ -645,6 +694,7 @@ When a provider changes an endpoint, update the contract BEFORE the consumer imp
645
694
  | \`flow workspace status\` | Show all repos, tasks, messages, contracts |
646
695
  | \`flow workspace add <path>\` | Add a new member repo |
647
696
  | \`flow workspace remove <name>\` | Remove a member repo |
697
+ | \`flow workspace start\` | Start a worker session (run from within a member repo) |
648
698
 
649
699
  ## File Reference
650
700
 
@@ -690,7 +740,13 @@ function generateWorkspaceSettings(memberNames) {
690
740
  'Bash(node --check *)',
691
741
  'Bash(node scripts/*)',
692
742
  'Bash(ls *)',
693
- 'Bash(cat *)'
743
+ 'Bash(cat *)',
744
+ 'Bash(curl http://localhost:*)',
745
+ 'Bash(curl http://127.0.0.1:*)',
746
+ 'Bash(curl -s http://localhost:*)',
747
+ 'Bash(curl -s http://127.0.0.1:*)',
748
+ 'Bash(curl -s -X POST http://localhost:*)',
749
+ 'Bash(curl -s -X POST http://127.0.0.1:*)'
694
750
  ];
695
751
 
696
752
  for (const name of (memberNames || [])) {
@@ -785,6 +841,95 @@ state/contract-versions.json
785
841
  }
786
842
  }
787
843
 
844
+ // ============================================================
845
+ // ============================================================
846
+ // Channel MCP Config Generation
847
+ // ============================================================
848
+
849
+ /**
850
+ * Generate .mcp.json for each member repo with channel server config.
851
+ * This allows workers to receive task dispatches from the manager
852
+ * and communicate with peer repos via channels.
853
+ *
854
+ * @param {string} workspaceRoot
855
+ * @param {Object} config — workspace config (with channels section)
856
+ */
857
+ function generateMemberMcpConfigs(workspaceRoot, config) {
858
+ const channelMembers = config.channels?.members || {};
859
+ const memberNames = Object.keys(channelMembers);
860
+
861
+ const VALID_NAME = /^[a-zA-Z0-9_-]{1,64}$/;
862
+
863
+ for (const [name, channelConfig] of Object.entries(channelMembers)) {
864
+ if (!VALID_NAME.test(name)) {
865
+ console.error(` ✗ ${name}: invalid member name (must match [a-zA-Z0-9_-]) — skipping`);
866
+ continue;
867
+ }
868
+
869
+ const memberPath = path.resolve(workspaceRoot, config.members[name]?.path || `./${name}`);
870
+
871
+ // Path traversal guard — ensure member path is inside workspace root
872
+ if (!memberPath.startsWith(workspaceRoot + path.sep) && memberPath !== workspaceRoot) {
873
+ console.error(` ✗ ${name}: path escapes workspace root (${config.members[name]?.path}) — skipping`);
874
+ continue;
875
+ }
876
+
877
+ const mcpJsonPath = path.join(memberPath, '.mcp.json');
878
+
879
+ // Build peer list (all other members)
880
+ const peers = memberNames
881
+ .filter(n => n !== name)
882
+ .map(n => `${n}:${channelMembers[n].port}`)
883
+ .join(',');
884
+
885
+ // Resolve channel server script path
886
+ // Use require.resolve to find the installed wogiflow package
887
+ let channelServerPath;
888
+ try {
889
+ channelServerPath = require.resolve('wogiflow/lib/workspace-channel-server.js');
890
+ } catch (_err) {
891
+ // Fallback: relative path from workspace root
892
+ channelServerPath = path.join(__dirname, 'workspace-channel-server.js');
893
+ }
894
+
895
+ const mcpConfig = {
896
+ mcpServers: {
897
+ 'wogi-workspace-channel': {
898
+ command: 'node',
899
+ args: [channelServerPath],
900
+ env: {
901
+ WOGI_CHANNEL_PORT: String(channelConfig.port),
902
+ WOGI_REPO_NAME: name,
903
+ WOGI_PEERS: peers,
904
+ WOGI_WORKSPACE_ROOT: workspaceRoot
905
+ }
906
+ }
907
+ }
908
+ };
909
+
910
+ // Merge with existing .mcp.json if present
911
+ let existingConfig = {};
912
+ try {
913
+ if (fs.existsSync(mcpJsonPath)) {
914
+ existingConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8'));
915
+ }
916
+ } catch (_err) {
917
+ // Ignore malformed existing config
918
+ }
919
+
920
+ const merged = {
921
+ ...existingConfig,
922
+ mcpServers: {
923
+ ...(existingConfig.mcpServers || {}),
924
+ ...mcpConfig.mcpServers
925
+ }
926
+ };
927
+
928
+ fs.writeFileSync(mcpJsonPath, JSON.stringify(merged, null, 2));
929
+ console.log(` ✓ ${name}/.mcp.json (channel port ${channelConfig.port}, peers: ${peers || 'none'})`);
930
+ }
931
+ }
932
+
788
933
  // ============================================================
789
934
  // Main Init Function
790
935
  // ============================================================
@@ -899,6 +1044,13 @@ async function initWorkspace(args) {
899
1044
  JSON.stringify(settings, null, 2)
900
1045
  );
901
1046
  console.log(' ✓ .claude/settings.json');
1047
+
1048
+ // Generate .mcp.json for each member repo (channel server config)
1049
+ if (config.channels?.enabled) {
1050
+ console.log('');
1051
+ console.log('── Setting up workspace channels ────────────\n');
1052
+ generateMemberMcpConfigs(workspaceRoot, config);
1053
+ }
902
1054
  console.log('');
903
1055
 
904
1056
  // Summary
@@ -981,6 +1133,122 @@ function generateIntegrationMap(manifest) {
981
1133
  return lines.join('\n');
982
1134
  }
983
1135
 
1136
+ // ============================================================
1137
+ // Worker Session Launcher
1138
+ // ============================================================
1139
+
1140
+ /**
1141
+ * Start a Claude Code worker session with the workspace channel enabled.
1142
+ * Must be run from within a member repo directory.
1143
+ *
1144
+ * @param {string} cwd — current working directory (should be a member repo)
1145
+ */
1146
+ function startWorkerSession(cwd) {
1147
+ const { execSync } = require('node:child_process');
1148
+
1149
+ // Find workspace root by walking up
1150
+ let workspaceRoot = null;
1151
+ let dir = cwd;
1152
+ while (dir !== path.dirname(dir)) {
1153
+ if (fs.existsSync(path.join(dir, WORKSPACE_CONFIG_FILE))) {
1154
+ workspaceRoot = dir;
1155
+ break;
1156
+ }
1157
+ dir = path.dirname(dir);
1158
+ }
1159
+
1160
+ if (!workspaceRoot) {
1161
+ console.error('Error: Not inside a workspace. Could not find wogi-workspace.json in any parent directory.');
1162
+ process.exit(1);
1163
+ }
1164
+
1165
+ // Read workspace config
1166
+ let config;
1167
+ try {
1168
+ config = JSON.parse(fs.readFileSync(path.join(workspaceRoot, WORKSPACE_CONFIG_FILE), 'utf-8'));
1169
+ } catch (err) {
1170
+ console.error(`Error reading workspace config: ${err.message}`);
1171
+ process.exit(1);
1172
+ }
1173
+
1174
+ if (!config.channels?.enabled) {
1175
+ console.error('Error: Channels are not enabled in this workspace. Run "flow workspace init" to set up channels.');
1176
+ process.exit(1);
1177
+ }
1178
+
1179
+ // Determine which member we are based on cwd
1180
+ const relativePath = path.relative(workspaceRoot, cwd);
1181
+ let memberName = null;
1182
+ let memberChannel = null;
1183
+
1184
+ for (const [name, memberConfig] of Object.entries(config.members)) {
1185
+ const memberRelPath = memberConfig.path.replace(/^\.\//, '');
1186
+ if (relativePath === memberRelPath || relativePath.startsWith(memberRelPath + path.sep)) {
1187
+ memberName = name;
1188
+ memberChannel = config.channels.members[name];
1189
+ break;
1190
+ }
1191
+ }
1192
+
1193
+ if (!memberName || !memberChannel) {
1194
+ console.error(`Error: Current directory "${relativePath}" does not match any workspace member.`);
1195
+ console.error('Members:', Object.keys(config.members).join(', '));
1196
+ process.exit(1);
1197
+ }
1198
+
1199
+ // Build peer list
1200
+ const peers = Object.entries(config.channels.members)
1201
+ .filter(([n]) => n !== memberName)
1202
+ .map(([n, ch]) => `${n}:${ch.port}`)
1203
+ .join(',');
1204
+
1205
+ // Verify .mcp.json exists with channel config
1206
+ const mcpJsonPath = path.join(cwd, '.mcp.json');
1207
+ let mcpConfigValid = false;
1208
+ try {
1209
+ if (fs.existsSync(mcpJsonPath)) {
1210
+ const mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8'));
1211
+ mcpConfigValid = !!mcpConfig?.mcpServers?.['wogi-workspace-channel'];
1212
+ }
1213
+ } catch (_err) {
1214
+ // Malformed .mcp.json
1215
+ }
1216
+
1217
+ if (!mcpConfigValid) {
1218
+ console.error(`Error: .mcp.json missing or does not contain wogi-workspace-channel config.`);
1219
+ console.error('Run "flow workspace init" from the workspace root to generate channel configs.');
1220
+ process.exit(1);
1221
+ }
1222
+
1223
+ console.log(`Starting worker session for "${memberName}" (port ${memberChannel.port})`);
1224
+ if (peers) console.log(`Peers: ${peers}`);
1225
+ console.log('');
1226
+
1227
+ // Set up environment for the channel server
1228
+ const env = {
1229
+ ...process.env,
1230
+ WOGI_CHANNEL_PORT: String(memberChannel.port),
1231
+ WOGI_REPO_NAME: memberName,
1232
+ WOGI_PEERS: peers,
1233
+ WOGI_WORKSPACE_ROOT: workspaceRoot
1234
+ };
1235
+
1236
+ // Launch Claude Code with the channel
1237
+ try {
1238
+ execSync('claude --dangerously-load-development-channels server:wogi-workspace-channel', {
1239
+ cwd,
1240
+ env,
1241
+ stdio: 'inherit'
1242
+ });
1243
+ } catch (err) {
1244
+ // Exit code 0 or SIGINT from user quit — that's fine
1245
+ if (err.status && err.status !== 0 && err.signal !== 'SIGINT') {
1246
+ console.error(`Worker session exited with error (code ${err.status}): ${err.message}`);
1247
+ process.exit(err.status);
1248
+ }
1249
+ }
1250
+ }
1251
+
984
1252
  // ============================================================
985
1253
  // CLI Router
986
1254
  // ============================================================
@@ -1033,6 +1301,10 @@ async function workspace(args) {
1033
1301
  console.log(`✓ Removed '${name}' from workspace`);
1034
1302
  break;
1035
1303
  }
1304
+ case 'start': {
1305
+ startWorkerSession(process.cwd());
1306
+ break;
1307
+ }
1036
1308
  default:
1037
1309
  console.log(`
1038
1310
  Wogi Workspace — Multi-Repo Orchestration
@@ -1045,11 +1317,13 @@ Commands:
1045
1317
  status Show unified workspace status
1046
1318
  add Add a member repo to the workspace
1047
1319
  remove Remove a member repo from the workspace
1320
+ start Start a worker session with channel (run from a member repo)
1048
1321
 
1049
1322
  Examples:
1050
1323
  flow workspace init # Create workspace from subdirectories
1051
1324
  flow workspace sync # Refresh after external changes
1052
1325
  flow workspace status # Show all repos, tasks, contracts
1326
+ cd frontend/ && flow workspace start # Start worker session
1053
1327
  `);
1054
1328
  }
1055
1329
  }
@@ -1069,5 +1343,7 @@ module.exports = {
1069
1343
  createWorkspaceStructure,
1070
1344
  WORKSPACE_CONFIG_FILE,
1071
1345
  WORKSPACE_DIR,
1072
- MEMBER_ROLES
1346
+ MEMBER_ROLES,
1347
+ generateMemberMcpConfigs,
1348
+ startWorkerSession
1073
1349
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wogiflow",
3
- "version": "2.4.4",
3
+ "version": "2.5.0",
4
4
  "description": "AI-powered development workflow management system with multi-model support",
5
5
  "main": "lib/index.js",
6
6
  "bin": {