wogiflow 2.5.9 → 2.6.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.
@@ -411,30 +411,50 @@ After implementing all scenarios, BEFORE quality gates:
411
411
  | Remove deprecated API | "What in this file provides the same FUNCTIONALITY as the deprecated API?" | Wrapper functions, polyfills, compatibility shims, re-implementations |
412
412
  | Fix all raw JSON.parse | "What in this file deserializes JSON?" | Utility functions that call JSON.parse internally, library wrappers |
413
413
 
414
- 3. **Produce a numbered inventory** and display it to the user:
414
+ 3. **Trace data-providing imports one level (MANDATORY)**:
415
+
416
+ The semantic scan in step 2 catches inline instances but gives a free pass to imported values. Imported constants, configurations, and helpers can contain the exact thing you're looking for — hidden behind one level of indirection and a legitimate-sounding name (`DEFAULT_*`, `INITIAL_*`, `FALLBACK_*`, `BASE_*`).
417
+
418
+ **For every import statement in each scoped file**, classify it:
419
+
420
+ | Import Type | Example | Action |
421
+ |-------------|---------|--------|
422
+ | **Data-providing** | `import { RATE_OPTIONS } from './constants'` | MUST read the source file and apply the semantic question to its contents |
423
+ | **Utility/function** | `import { formatDate } from './utils'` | Skip — unless the function wraps or returns the target pattern |
424
+ | **Type/interface** | `import type { Customer } from './types'` | Skip — types don't contain runtime data |
425
+ | **Style/asset** | `import styles from './styles.module.css'` | Skip |
426
+ | **Component** | `import { Button } from './ui'` | Skip — unless it's a wrapper that embeds the target pattern |
427
+
428
+ **How to classify**: If the import provides a value that gets **rendered, displayed, logged, passed to an API, or used as configuration** — it's data-providing. Read its source.
429
+
430
+ **Anti-pattern — naming convention bias**: Constants named `DEFAULT_*`, `INITIAL_*`, `FALLBACK_*`, `CONFIG_*`, `BASE_*` look legitimate but are often hardcoded placeholders. The name is NOT evidence of legitimacy. Only the source is.
431
+
432
+ **Rule**: Any imported value that contributes to **user-visible output** and resolves to a hardcoded literal (not an API call, env var, or database query) is an instance of [X] — regardless of what it's named or which directory it lives in.
433
+
434
+ 4. **Produce a numbered inventory** and display it to the user:
415
435
  ```
416
436
  ━━━ PRE-IMPLEMENTATION INVENTORY ━━━
417
437
  Found N instances of [X] across M files:
418
438
 
419
- 1. [file:lines] — [description] [TYPE: syntactic|semantic]
420
- 2. [file:lines] — [description] [TYPE: syntactic|semantic]
439
+ 1. [file:lines] — [description] [TYPE: syntactic|semantic|import-traced]
440
+ 2. [file:lines] — [description] [TYPE: syntactic|semantic|import-traced]
421
441
  ...
422
442
 
423
443
  Total: N instances (S syntactic, M semantic)
424
444
  Confirm inventory is complete before proceeding? [Y/adjust]
425
445
  ```
426
446
 
427
- 4. **Wait for user confirmation** that the inventory is complete. If the user identifies missing items, add them. This step is CRITICAL — it commits the AI to a concrete scope that can be verified later.
447
+ 5. **Wait for user confirmation** that the inventory is complete. If the user identifies missing items, add them. This step is CRITICAL — it commits the AI to a concrete scope that can be verified later.
428
448
 
429
449
  #### Phase B: Implementation
430
450
 
431
- 5. Implement the removal/fix/replacement for EVERY item in the inventory. Each inventory item becomes a trackable unit of work.
451
+ 6. Implement the removal/fix/replacement for EVERY item in the inventory. Each inventory item becomes a trackable unit of work.
432
452
 
433
453
  #### Phase C: Post-Implementation Re-Inventory (AFTER all changes)
434
454
 
435
- 6. **Re-run the SAME semantic scan** from Phase A on the SAME set of files. Use the same questions — do NOT downgrade to pattern-only search.
455
+ 7. **Re-run the SAME semantic scan** from Phase A (including import tracing from step 3) on the SAME set of files. Do NOT downgrade to pattern-only search.
436
456
 
437
- 7. **Diff the inventories**:
457
+ 8. **Diff the inventories**:
438
458
  ```
439
459
  ━━━ POST-IMPLEMENTATION VERIFICATION ━━━
440
460
  Re-scanned M files for [X]:
@@ -447,9 +467,9 @@ After implementing all scenarios, BEFORE quality gates:
447
467
  Result: N/N removed (0 remaining)
448
468
  ```
449
469
 
450
- 8. **If ANY items remain** → task is NOT done. Fix the remaining items and re-verify. Do NOT proceed to quality gates with remaining items.
470
+ 9. **If ANY items remain** → task is NOT done. Fix the remaining items and re-verify. Do NOT proceed to quality gates with remaining items.
451
471
 
452
- 9. **If new instances are discovered** during re-scan that weren't in the original inventory → add them, fix them, and note them as "discovered during verification."
472
+ 10. **If new instances are discovered** during re-scan (including via import tracing) that weren't in the original inventory → add them, fix them, and note them as "discovered during verification."
453
473
 
454
474
  **Why this works**: The inventory creates a concrete, numbered checklist BEFORE implementation. The AI cannot claim "done" when the post-inventory shows items still present — the evidence is in the conversation. The pre/post diff is unfakeable.
455
475
 
@@ -270,8 +270,27 @@ function handleRequest(msg) {
270
270
  };
271
271
  const msgPath = require('node:path').join(messagesDir, `${msgId}.json`);
272
272
  fs.writeFileSync(msgPath, JSON.stringify(msgObj, null, 2));
273
+
274
+ // Also POST to manager's channel port for real-time notification
275
+ const managerPort = process.env.WOGI_MANAGER_PORT;
276
+ if (managerPort) {
277
+ try {
278
+ const buf = Buffer.from(message, 'utf-8');
279
+ const req = http.request({
280
+ hostname: '127.0.0.1',
281
+ port: parseInt(managerPort, 10),
282
+ path: '/',
283
+ method: 'POST',
284
+ headers: { 'Content-Type': 'text/plain', 'Content-Length': buf.byteLength, 'X-Wogi-From': REPO_NAME }
285
+ });
286
+ req.on('error', () => { /* best effort */ });
287
+ req.write(buf);
288
+ req.end();
289
+ } catch (_err) { /* fallback is file */ }
290
+ }
291
+
273
292
  sendResponse(msg.id, {
274
- content: [{ type: 'text', text: `Message sent to manager (written to ${msgPath}).` }]
293
+ content: [{ type: 'text', text: `Message sent to manager${managerPort ? ' (file + channel notification)' : ' (file only)'}.` }]
275
294
  });
276
295
  } catch (err) {
277
296
  sendResponse(msg.id, {
package/lib/workspace.js CHANGED
@@ -945,7 +945,8 @@ function generateMemberMcpConfigs(workspaceRoot, config) {
945
945
  WOGI_CHANNEL_PORT: String(channelConfig.port),
946
946
  WOGI_REPO_NAME: name,
947
947
  WOGI_PEERS: peers,
948
- WOGI_WORKSPACE_ROOT: workspaceRoot
948
+ WOGI_WORKSPACE_ROOT: workspaceRoot,
949
+ WOGI_MANAGER_PORT: String(config.channels.managerPort || (config.channels.basePort - 1))
949
950
  }
950
951
  }
951
952
  }
@@ -1039,6 +1040,50 @@ For everything else — just do the work and report results.
1039
1040
  fs.writeFileSync(path.join(rulesDir, 'worker-rules.md'), workerRule);
1040
1041
  console.log(` ✓ ${name}/.claude/rules/workspace/worker-rules.md`);
1041
1042
  }
1043
+
1044
+ // Generate .mcp.json at workspace root for the manager's channel server
1045
+ const managerPort = config.channels.managerPort || (config.channels.basePort - 1);
1046
+ let channelServerPath;
1047
+ try {
1048
+ channelServerPath = require.resolve('wogiflow/lib/workspace-channel-server.js');
1049
+ } catch (_err) {
1050
+ channelServerPath = path.join(__dirname, 'workspace-channel-server.js');
1051
+ }
1052
+
1053
+ const allMembers = memberNames.map(n => `${n}:${channelMembers[n].port}`).join(',');
1054
+ const managerMcpConfig = {
1055
+ mcpServers: {
1056
+ 'wogi-workspace-channel': {
1057
+ command: 'node',
1058
+ args: [channelServerPath],
1059
+ env: {
1060
+ WOGI_CHANNEL_PORT: String(managerPort),
1061
+ WOGI_REPO_NAME: 'manager',
1062
+ WOGI_PEERS: allMembers,
1063
+ WOGI_WORKSPACE_ROOT: workspaceRoot
1064
+ }
1065
+ }
1066
+ }
1067
+ };
1068
+
1069
+ // Merge with existing .mcp.json at workspace root
1070
+ const managerMcpPath = path.join(workspaceRoot, '.mcp.json');
1071
+ let existingManagerMcp = {};
1072
+ try {
1073
+ if (fs.existsSync(managerMcpPath)) {
1074
+ existingManagerMcp = JSON.parse(fs.readFileSync(managerMcpPath, 'utf-8'));
1075
+ }
1076
+ } catch (_err) { /* ignore */ }
1077
+
1078
+ const mergedManager = {
1079
+ ...existingManagerMcp,
1080
+ mcpServers: {
1081
+ ...(existingManagerMcp.mcpServers || {}),
1082
+ ...managerMcpConfig.mcpServers
1083
+ }
1084
+ };
1085
+ fs.writeFileSync(managerMcpPath, JSON.stringify(mergedManager, null, 2));
1086
+ console.log(` ✓ .mcp.json (manager channel port ${managerPort}, workers: ${allMembers})`);
1042
1087
  }
1043
1088
 
1044
1089
  // ============================================================
@@ -1375,13 +1420,50 @@ function startWorkerSession(cwd) {
1375
1420
  }
1376
1421
  }
1377
1422
 
1423
+ // Manager mode: CWD is the workspace root itself (not a member)
1378
1424
  if (!memberName || !memberChannel) {
1425
+ if (relativePath === '' || relativePath === '.') {
1426
+ // Start as workspace manager
1427
+ const managerPort = config.channels.managerPort || (config.channels.basePort - 1);
1428
+ const allMembers = Object.entries(config.channels.members)
1429
+ .map(([n, ch]) => `${n}:${ch.port}`)
1430
+ .join(',');
1431
+
1432
+ console.log(`Starting manager session (port ${managerPort})`);
1433
+ console.log(`Workers: ${allMembers}`);
1434
+ console.log('');
1435
+
1436
+ const env = {
1437
+ ...process.env,
1438
+ WOGI_CHANNEL_PORT: String(managerPort),
1439
+ WOGI_REPO_NAME: 'manager',
1440
+ WOGI_PEERS: allMembers,
1441
+ WOGI_WORKSPACE_ROOT: workspaceRoot
1442
+ };
1443
+
1444
+ try {
1445
+ execSync('claude --dangerously-skip-permissions --dangerously-load-development-channels server:wogi-workspace-channel', {
1446
+ cwd,
1447
+ env,
1448
+ stdio: 'inherit'
1449
+ });
1450
+ } catch (err) {
1451
+ if (err.status && err.status !== 0 && err.signal !== 'SIGINT') {
1452
+ console.error(`Manager session exited with error (code ${err.status}): ${err.message}`);
1453
+ process.exit(err.status);
1454
+ }
1455
+ }
1456
+ return;
1457
+ }
1458
+
1379
1459
  console.error(`Error: Current directory "${relativePath}" does not match any workspace member.`);
1380
1460
  console.error('Members:', Object.keys(config.members).join(', '));
1461
+ console.error('Or run from the workspace root to start as manager.');
1381
1462
  process.exit(1);
1382
1463
  }
1383
1464
 
1384
- // Build peer list
1465
+ // Build peer list (include manager port)
1466
+ const managerPort = config.channels.managerPort || (config.channels.basePort - 1);
1385
1467
  const peers = Object.entries(config.channels.members)
1386
1468
  .filter(([n]) => n !== memberName)
1387
1469
  .map(([n, ch]) => `${n}:${ch.port}`)
@@ -1415,6 +1497,7 @@ function startWorkerSession(cwd) {
1415
1497
  WOGI_CHANNEL_PORT: String(memberChannel.port),
1416
1498
  WOGI_REPO_NAME: memberName,
1417
1499
  WOGI_PEERS: peers,
1500
+ WOGI_MANAGER_PORT: String(managerPort),
1418
1501
  WOGI_WORKSPACE_ROOT: workspaceRoot
1419
1502
  };
1420
1503
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wogiflow",
3
- "version": "2.5.9",
3
+ "version": "2.6.0",
4
4
  "description": "AI-powered development workflow management system with multi-model support",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -134,10 +134,35 @@ runHook('Stop', async ({ parsedInput }) => {
134
134
  status: 'pending'
135
135
  };
136
136
 
137
+ // Write to file (fallback / persistent record)
137
138
  fs.writeFileSync(path.join(messagesDir, `${msgId}.json`), JSON.stringify(message, null, 2));
138
139
 
140
+ // Also HTTP POST to manager's channel port for real-time notification
141
+ // This makes the message appear as a prompt in the manager's session immediately
142
+ const managerPort = process.env.WOGI_MANAGER_PORT;
143
+ if (managerPort && repoName !== 'manager') {
144
+ try {
145
+ const http = require('node:http');
146
+ const body = Buffer.from(message.body, 'utf-8');
147
+ const req = http.request({
148
+ hostname: '127.0.0.1',
149
+ port: parseInt(managerPort, 10),
150
+ path: '/',
151
+ method: 'POST',
152
+ headers: {
153
+ 'Content-Type': 'text/plain',
154
+ 'Content-Length': body.byteLength,
155
+ 'X-Wogi-From': repoName
156
+ }
157
+ });
158
+ req.on('error', () => { /* best effort — file is the fallback */ });
159
+ req.write(body);
160
+ req.end();
161
+ } catch (_err) { /* non-critical */ }
162
+ }
163
+
139
164
  if (process.env.DEBUG) {
140
- console.error(`[Stop] Workspace message written: ${msgId}`);
165
+ console.error(`[Stop] Workspace message written: ${msgId}${managerPort ? ` + HTTP to manager:${managerPort}` : ''}`);
141
166
  }
142
167
  } catch (err) {
143
168
  // Non-critical — best effort