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. **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
@@ -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
|