@claude-flow/cli 3.63.6 → 3.63.7
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/.claude/agents/github/github-modes.md +3 -3
- package/.claude/agents/github/issue-tracker.md +6 -6
- package/.claude/agents/github/pr-manager.md +6 -6
- package/.claude/agents/github/release-manager.md +11 -11
- package/.claude/agents/github/repo-architect.md +9 -9
- package/.claude/agents/github/swarm-issue.md +4 -4
- package/.claude/agents/github/swarm-pr.md +5 -5
- package/.claude/agents/github/sync-coordinator.md +18 -18
- package/.claude/agents/github/workflow-automation.md +7 -7
- package/.claude/commands/agents/agent-spawning.md +1 -1
- package/.claude/commands/agents/spawn.md +1 -1
- package/.claude/commands/github/github-modes.md +3 -3
- package/.claude/commands/github/issue-tracker.md +6 -6
- package/.claude/commands/github/pr-manager.md +6 -6
- package/.claude/commands/github/release-manager.md +11 -11
- package/.claude/commands/github/repo-architect.md +9 -9
- package/.claude/commands/github/sync-coordinator.md +9 -9
- package/.claude/commands/sparc/orchestrator.md +1 -1
- package/.claude/commands/sparc/sparc-modes.md +1 -1
- package/.claude/helpers/helpers.manifest.json +2 -2
- package/.claude/skills/github-code-review/SKILL.md +3 -3
- package/.claude/skills/github-multi-repo/SKILL.md +6 -9
- package/.claude/skills/github-project-management/SKILL.md +7 -7
- package/.claude/skills/github-release-management/SKILL.md +12 -14
- package/.claude/skills/github-workflow-automation/SKILL.md +7 -7
- package/.claude/skills/sparc-methodology/SKILL.md +11 -11
- package/.claude/skills/swarm-advanced/SKILL.md +5 -5
- package/README.md +1 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/hooks.js +7 -0
- package/dist/src/mcp-tools/agent-tools.js +19 -5
- package/dist/src/mcp-tools/coordination-tools.js +10 -12
- package/dist/src/mcp-tools/hooks-tools.js +11 -2
- package/dist/src/mcp-tools/performance-tools.js +13 -23
- package/dist/src/mcp-tools/session-tools.js +25 -8
- package/dist/src/missions/schemas.d.ts +113 -113
- package/dist/src/mods/command-registry/schema.d.ts +87 -87
- package/dist/src/services/worker-daemon.js +11 -4
- package/package.json +1 -1
|
@@ -140,12 +140,12 @@ gh release create $(npm pkg get version) \
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// Spawn specialized agents
|
|
143
|
-
mcp__claude-flow__agent_spawn {
|
|
144
|
-
mcp__claude-flow__agent_spawn {
|
|
145
|
-
mcp__claude-flow__agent_spawn {
|
|
146
|
-
mcp__claude-flow__agent_spawn {
|
|
147
|
-
mcp__claude-flow__agent_spawn {
|
|
148
|
-
mcp__claude-flow__agent_spawn {
|
|
143
|
+
mcp__claude-flow__agent_spawn { agentType: "coordinator", name: "Release Director" }
|
|
144
|
+
mcp__claude-flow__agent_spawn { agentType: "coder", name: "Version Manager" }
|
|
145
|
+
mcp__claude-flow__agent_spawn { agentType: "tester", name: "QA Engineer" }
|
|
146
|
+
mcp__claude-flow__agent_spawn { agentType: "reviewer", name: "Release Reviewer" }
|
|
147
|
+
mcp__claude-flow__agent_spawn { agentType: "analyst", name: "Deployment Analyst" }
|
|
148
|
+
mcp__claude-flow__agent_spawn { agentType: "researcher", name: "Compatibility Checker" }
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
#### Coordinated Release Workflow
|
|
@@ -155,11 +155,10 @@ gh release create $(npm pkg get version) \
|
|
|
155
155
|
Bash("gh api repos/:owner/:repo/git/refs --method POST -f ref='refs/heads/release/v2.0.0' -f sha=$(gh api repos/:owner/:repo/git/refs/heads/main --jq '.object.sha')")
|
|
156
156
|
|
|
157
157
|
// Orchestrate release preparation
|
|
158
|
-
mcp__claude-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
priority: "critical"
|
|
162
|
-
maxAgents: 6
|
|
158
|
+
mcp__claude-flow__task_create {
|
|
159
|
+
type: "release",
|
|
160
|
+
description: "Prepare release v2.0.0 with comprehensive testing and validation",
|
|
161
|
+
priority: "critical"
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
// Update all release files
|
|
@@ -187,8 +186,7 @@ gh release create $(npm pkg get version) \
|
|
|
187
186
|
]}
|
|
188
187
|
|
|
189
188
|
// Store release state
|
|
190
|
-
mcp__claude-
|
|
191
|
-
action: "store",
|
|
189
|
+
mcp__claude-flow__memory_store {
|
|
192
190
|
key: "release/v2.0.0/status",
|
|
193
191
|
value: JSON.stringify({
|
|
194
192
|
version: "2.0.0",
|
|
@@ -391,7 +389,7 @@ npx @claude-flow/cli@latest github multi-release \
|
|
|
391
389
|
Bash("gh api repos/org/cli/dispatches --method POST -f event_type='release' -F client_payload[version]=v1.5.0")
|
|
392
390
|
|
|
393
391
|
// Monitor all releases
|
|
394
|
-
mcp__claude-
|
|
392
|
+
mcp__claude-flow__swarm_status {}
|
|
395
393
|
```
|
|
396
394
|
|
|
397
395
|
### Hotfix Emergency Procedures
|
|
@@ -525,15 +525,15 @@ mcp__claude-flow__swarm_init {
|
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
// Step 2: Spawn specialized agents
|
|
528
|
-
mcp__claude-flow__agent_spawn {
|
|
529
|
-
mcp__claude-flow__agent_spawn {
|
|
530
|
-
mcp__claude-flow__agent_spawn {
|
|
531
|
-
mcp__claude-flow__agent_spawn {
|
|
528
|
+
mcp__claude-flow__agent_spawn { agentType: "coordinator", name: "GitHub Coordinator" }
|
|
529
|
+
mcp__claude-flow__agent_spawn { agentType: "reviewer", name: "Code Reviewer" }
|
|
530
|
+
mcp__claude-flow__agent_spawn { agentType: "tester", name: "QA Agent" }
|
|
531
|
+
mcp__claude-flow__agent_spawn { agentType: "analyst", name: "Security Analyst" }
|
|
532
532
|
|
|
533
533
|
// Step 3: Orchestrate GitHub workflow
|
|
534
|
-
mcp__claude-
|
|
535
|
-
|
|
536
|
-
|
|
534
|
+
mcp__claude-flow__task_create {
|
|
535
|
+
type: "review",
|
|
536
|
+
description: "Complete PR review and merge workflow",
|
|
537
537
|
priority: "high"
|
|
538
538
|
}
|
|
539
539
|
```
|
|
@@ -455,7 +455,7 @@ mcp__claude-flow__swarm_init {
|
|
|
455
455
|
|
|
456
456
|
// Spawn specialized agents
|
|
457
457
|
mcp__claude-flow__agent_spawn {
|
|
458
|
-
|
|
458
|
+
agentType: "<agent-type>",
|
|
459
459
|
capabilities: ["<capability1>", "<capability2>"]
|
|
460
460
|
}
|
|
461
461
|
|
|
@@ -522,15 +522,15 @@ mcp__claude-flow__swarm_init {
|
|
|
522
522
|
|
|
523
523
|
// Spawn coordinator
|
|
524
524
|
mcp__claude-flow__agent_spawn {
|
|
525
|
-
|
|
525
|
+
agentType: "coordinator",
|
|
526
526
|
capabilities: ["planning", "delegation", "monitoring"]
|
|
527
527
|
}
|
|
528
528
|
|
|
529
529
|
// Spawn specialized workers
|
|
530
|
-
mcp__claude-flow__agent_spawn {
|
|
531
|
-
mcp__claude-flow__agent_spawn {
|
|
532
|
-
mcp__claude-flow__agent_spawn {
|
|
533
|
-
mcp__claude-flow__agent_spawn {
|
|
530
|
+
mcp__claude-flow__agent_spawn { agentType: "architect" }
|
|
531
|
+
mcp__claude-flow__agent_spawn { agentType: "coder" }
|
|
532
|
+
mcp__claude-flow__agent_spawn { agentType: "tester" }
|
|
533
|
+
mcp__claude-flow__agent_spawn { agentType: "reviewer" }
|
|
534
534
|
```
|
|
535
535
|
|
|
536
536
|
### Pattern 2: Mesh Coordination
|
|
@@ -703,14 +703,14 @@ mcp__claude-flow__memory_usage {
|
|
|
703
703
|
```javascript
|
|
704
704
|
// ✅ CORRECT: All operations together
|
|
705
705
|
[Single Message]:
|
|
706
|
-
mcp__claude-flow__agent_spawn {
|
|
707
|
-
mcp__claude-flow__agent_spawn {
|
|
708
|
-
mcp__claude-flow__agent_spawn {
|
|
706
|
+
mcp__claude-flow__agent_spawn { agentType: "researcher" }
|
|
707
|
+
mcp__claude-flow__agent_spawn { agentType: "coder" }
|
|
708
|
+
mcp__claude-flow__agent_spawn { agentType: "tester" }
|
|
709
709
|
TodoWrite { todos: [8-10 todos] }
|
|
710
710
|
|
|
711
711
|
// ❌ WRONG: Multiple messages
|
|
712
|
-
Message 1: mcp__claude-flow__agent_spawn {
|
|
713
|
-
Message 2: mcp__claude-flow__agent_spawn {
|
|
712
|
+
Message 1: mcp__claude-flow__agent_spawn { agentType: "researcher" }
|
|
713
|
+
Message 2: mcp__claude-flow__agent_spawn { agentType: "coder" }
|
|
714
714
|
Message 3: TodoWrite { todos: [...] }
|
|
715
715
|
```
|
|
716
716
|
|
|
@@ -25,7 +25,7 @@ claude mcp add claude-flow npx claude-flow@alpha mcp start
|
|
|
25
25
|
mcp__claude-flow__swarm_init({ topology: "mesh", maxAgents: 6 })
|
|
26
26
|
|
|
27
27
|
// 2. Spawn specialized agents
|
|
28
|
-
mcp__claude-flow__agent_spawn({
|
|
28
|
+
mcp__claude-flow__agent_spawn({ agentType: "researcher", name: "Agent 1" })
|
|
29
29
|
|
|
30
30
|
// 3. Orchestrate tasks
|
|
31
31
|
mcp__claude-flow__task_orchestrate({ task: "...", strategy: "parallel" })
|
|
@@ -108,7 +108,7 @@ const researchAgents = [
|
|
|
108
108
|
// Spawn all agents
|
|
109
109
|
researchAgents.forEach(agent => {
|
|
110
110
|
mcp__claude-flow__agent_spawn({
|
|
111
|
-
|
|
111
|
+
agentType: agent.type,
|
|
112
112
|
name: agent.name,
|
|
113
113
|
capabilities: agent.capabilities
|
|
114
114
|
})
|
|
@@ -274,7 +274,7 @@ const devTeam = [
|
|
|
274
274
|
// Spawn all team members
|
|
275
275
|
devTeam.forEach(member => {
|
|
276
276
|
mcp__claude-flow__agent_spawn({
|
|
277
|
-
|
|
277
|
+
agentType: member.type,
|
|
278
278
|
name: member.name,
|
|
279
279
|
capabilities: member.capabilities,
|
|
280
280
|
swarmId: "dev-swarm"
|
|
@@ -444,7 +444,7 @@ const testingTeam = [
|
|
|
444
444
|
// Spawn all testers
|
|
445
445
|
testingTeam.forEach(tester => {
|
|
446
446
|
mcp__claude-flow__agent_spawn({
|
|
447
|
-
|
|
447
|
+
agentType: tester.type,
|
|
448
448
|
name: tester.name,
|
|
449
449
|
capabilities: tester.capabilities,
|
|
450
450
|
swarmId: "testing-swarm"
|
|
@@ -634,7 +634,7 @@ const analysisTeam = [
|
|
|
634
634
|
// Spawn all analysts
|
|
635
635
|
analysisTeam.forEach(analyst => {
|
|
636
636
|
mcp__claude-flow__agent_spawn({
|
|
637
|
-
|
|
637
|
+
agentType: analyst.type,
|
|
638
638
|
name: analyst.name,
|
|
639
639
|
capabilities: analyst.capabilities
|
|
640
640
|
})
|
package/README.md
CHANGED
|
@@ -244,7 +244,7 @@ This adds slash commands and agent definitions. `ruflo-core` (installed above) d
|
|
|
244
244
|
| [**ruflo-browser**](plugins/ruflo-browser/README.md) | Automate browser testing with Playwright |
|
|
245
245
|
| [**ruflo-jujutsu**](plugins/ruflo-jujutsu/README.md) | Analyze git diffs, score risk, suggest reviewers |
|
|
246
246
|
| [**ruflo-docs**](plugins/ruflo-docs/README.md) | Generate and maintain documentation automatically |
|
|
247
|
-
| [**agentic-qe-fleet**](https://github.com/proffesor-for-testing/agentic-qe/tree/v3.15.
|
|
247
|
+
| [**agentic-qe-fleet**](https://github.com/proffesor-for-testing/agentic-qe/tree/v3.15.16/plugins/agentic-qe-fleet) | External, maintained by [Agentic QE](https://github.com/proffesor-for-testing/agentic-qe): 11 QE agents, test generation, coverage, chaos and TDD skills, the `agentic-qe` MCP server, and a guard for AQE learning data (`/plugin install agentic-qe-fleet@ruflo`). Install it from one marketplace only, `agentic-qe` or `ruflo`: enabled from both, its mod and commands load twice |
|
|
248
248
|
|
|
249
249
|
#### Security & Compliance
|
|
250
250
|
|
package/catalog-manifest.json
CHANGED
|
@@ -967,6 +967,13 @@ const buildAgentsCommand = {
|
|
|
967
967
|
format: configFormat,
|
|
968
968
|
includePretrained: true,
|
|
969
969
|
});
|
|
970
|
+
// #4558: the tool now rejects an invalid outputDir with { success: false, error }.
|
|
971
|
+
const failed = result;
|
|
972
|
+
if (failed.success === false) {
|
|
973
|
+
spinner.fail('Agent config generation failed');
|
|
974
|
+
output.printError(`Build agents error: ${failed.error}`);
|
|
975
|
+
return { success: false, exitCode: 1 };
|
|
976
|
+
}
|
|
970
977
|
spinner.succeed(`Generated ${result.agents.length} agent configs`);
|
|
971
978
|
if (ctx.flags.format === 'json') {
|
|
972
979
|
output.printJson(result);
|
|
@@ -214,7 +214,7 @@ export const agentTools = [
|
|
|
214
214
|
inputSchema: {
|
|
215
215
|
type: 'object',
|
|
216
216
|
properties: {
|
|
217
|
-
agentType: { type: 'string', description: 'Type of agent to spawn' },
|
|
217
|
+
agentType: { type: 'string', description: 'Type of agent to spawn (a legacy `type` key is accepted as an alias)' },
|
|
218
218
|
agentId: { type: 'string', description: 'Optional custom agent ID' },
|
|
219
219
|
name: { type: 'string', description: 'Optional display name for the agent (shown by agent_list, agent_status and ruflo-console)' },
|
|
220
220
|
// #2085 — accept swarmId so spawned agents register in the
|
|
@@ -234,7 +234,19 @@ export const agentTools = [
|
|
|
234
234
|
},
|
|
235
235
|
required: ['agentType'],
|
|
236
236
|
},
|
|
237
|
-
handler: async (
|
|
237
|
+
handler: async (rawInput) => {
|
|
238
|
+
// #4542: agent and command docs that `init` installed before 3.63.7 call
|
|
239
|
+
// agent_spawn with `type`. Accept it as an alias for `agentType`, and
|
|
240
|
+
// name the missing key ourselves: validateAgentSpawn maps agentType onto
|
|
241
|
+
// the security schema's `type` field and would report "type: Required".
|
|
242
|
+
let input = rawInput;
|
|
243
|
+
if (input.agentType === undefined && typeof input.type === 'string') {
|
|
244
|
+
const { type, ...rest } = input;
|
|
245
|
+
input = { ...rest, agentType: type };
|
|
246
|
+
}
|
|
247
|
+
if (typeof input.agentType !== 'string' || input.agentType === '') {
|
|
248
|
+
return { success: false, error: 'Input validation failed: agentType is required and must be a non-empty string (for example agentType: "coder")' };
|
|
249
|
+
}
|
|
238
250
|
// Validate user-provided input (#1425: wire security validators to runtime)
|
|
239
251
|
const validation = await validateAgentSpawn(input);
|
|
240
252
|
if (!validation.valid) {
|
|
@@ -606,7 +618,7 @@ export const agentTools = [
|
|
|
606
618
|
type: 'object',
|
|
607
619
|
properties: {
|
|
608
620
|
action: { type: 'string', enum: ['status', 'scale', 'drain', 'fill'], description: 'Pool action' },
|
|
609
|
-
targetSize: { type: 'number', description: 'Target pool size (
|
|
621
|
+
targetSize: { type: 'number', description: 'Target pool size (scale: grow or shrink to it; fill: grow to it, never shrink)' },
|
|
610
622
|
agentType: { type: 'string', description: 'Agent type filter' },
|
|
611
623
|
},
|
|
612
624
|
required: ['action'],
|
|
@@ -653,7 +665,9 @@ export const agentTools = [
|
|
|
653
665
|
avgHealth: agents.length > 0 ? agents.reduce((sum, a) => sum + a.health, 0) / agents.length : 0,
|
|
654
666
|
};
|
|
655
667
|
}
|
|
656
|
-
|
|
668
|
+
// #4555: fill was advertised in the enum but had no branch. It is scale
|
|
669
|
+
// without the shrink step: grow agentType's pool to targetSize, never terminate.
|
|
670
|
+
if (action === 'scale' || action === 'fill') {
|
|
657
671
|
const targetSize = input.targetSize === undefined ? 5 : input.targetSize;
|
|
658
672
|
if (typeof targetSize !== 'number' || !Number.isSafeInteger(targetSize) || targetSize < 0) {
|
|
659
673
|
return { action, error: 'targetSize must be a non-negative safe integer' };
|
|
@@ -678,7 +692,7 @@ export const agentTools = [
|
|
|
678
692
|
added.push(agentId);
|
|
679
693
|
}
|
|
680
694
|
}
|
|
681
|
-
else if (delta < 0) {
|
|
695
|
+
else if (delta < 0 && action === 'scale') {
|
|
682
696
|
const toRemove = agents.filter(a => a.agentType === agentType && a.status === 'idle').slice(0, -delta);
|
|
683
697
|
for (const agent of toRemove) {
|
|
684
698
|
store.agents[agent.agentId].status = 'terminated';
|
|
@@ -225,8 +225,8 @@ export const coordinationTools = [
|
|
|
225
225
|
inputSchema: {
|
|
226
226
|
type: 'object',
|
|
227
227
|
properties: {
|
|
228
|
-
action: { type: 'string', enum: ['status', 'trigger', 'resolve'], description: 'Action to perform' },
|
|
229
|
-
force: { type: 'boolean', description: '
|
|
228
|
+
action: { type: 'string', enum: ['status', 'trigger', 'resolve'], description: 'Action to perform. `trigger` is unavailable: nodes are local records with no sync backend, so it is refused (#4557)' },
|
|
229
|
+
force: { type: 'boolean', description: 'Unused: `trigger` is refused regardless (#4557)' },
|
|
230
230
|
conflictResolution: { type: 'string', enum: ['latest', 'merge', 'manual'], description: 'Conflict resolution strategy' },
|
|
231
231
|
},
|
|
232
232
|
},
|
|
@@ -243,18 +243,16 @@ export const coordinationTools = [
|
|
|
243
243
|
};
|
|
244
244
|
}
|
|
245
245
|
if (action === 'trigger') {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
//
|
|
250
|
-
await new Promise(resolve => setTimeout(resolve, 50));
|
|
251
|
-
saveCoordStore(store);
|
|
246
|
+
// #4557: this waited on a simulated 50ms "sync", bumped syncCount/lastSync
|
|
247
|
+
// and reported every node synchronized, but no state moves between
|
|
248
|
+
// nodes. Refuse without touching the stored sync record, as
|
|
249
|
+
// neural_compress quantize/prune do (#4195, #4423).
|
|
252
250
|
return {
|
|
253
|
-
success:
|
|
254
|
-
action: '
|
|
251
|
+
success: false,
|
|
252
|
+
action: 'trigger',
|
|
253
|
+
error: 'Synchronization is unavailable: coordination nodes are local records with no sync backend, so nothing would be transferred.',
|
|
255
254
|
syncCount: store.sync.syncCount,
|
|
256
|
-
|
|
257
|
-
nodesSync: Object.keys(store.nodes).length,
|
|
255
|
+
lastSync: store.sync.lastSync,
|
|
258
256
|
};
|
|
259
257
|
}
|
|
260
258
|
if (action === 'resolve') {
|
|
@@ -2212,7 +2212,14 @@ export const hooksBuildAgents = {
|
|
|
2212
2212
|
},
|
|
2213
2213
|
},
|
|
2214
2214
|
handler: async (params) => {
|
|
2215
|
-
|
|
2215
|
+
// #4558: outputDir was never validated and resolved against the process
|
|
2216
|
+
// cwd, so a default call from another directory overwrote files there.
|
|
2217
|
+
if (params.outputDir !== undefined) {
|
|
2218
|
+
const v = validatePath(params.outputDir, 'outputDir');
|
|
2219
|
+
if (!v.valid)
|
|
2220
|
+
return { success: false, error: v.error };
|
|
2221
|
+
}
|
|
2222
|
+
const outputDir = resolve(getProjectCwd(), params.outputDir || './agents');
|
|
2216
2223
|
const focus = params.focus || 'all';
|
|
2217
2224
|
const format = params.format || 'yaml';
|
|
2218
2225
|
const persist = params.persist !== false; // Default to true
|
|
@@ -2255,9 +2262,11 @@ export const hooksBuildAgents = {
|
|
|
2255
2262
|
agents: filteredAgents,
|
|
2256
2263
|
stats: {
|
|
2257
2264
|
configsGenerated: filteredAgents.length,
|
|
2258
|
-
|
|
2265
|
+
// #4558: was agents * 3; no pretrain data is read, the configs are templates.
|
|
2266
|
+
patternsApplied: 0,
|
|
2259
2267
|
optimizationsIncluded: filteredAgents.reduce((acc, a) => acc + a.optimizations.length, 0),
|
|
2260
2268
|
},
|
|
2269
|
+
_note: 'Configs are built-in templates; pretrain data is not read.',
|
|
2261
2270
|
};
|
|
2262
2271
|
},
|
|
2263
2272
|
};
|
|
@@ -612,6 +612,14 @@ export const performanceTools = [
|
|
|
612
612
|
};
|
|
613
613
|
const cpuStats = calcStats(cpuHistory, cpuPercent);
|
|
614
614
|
const memStats = calcStats(memHistory, memUsedMB);
|
|
615
|
+
// #4554: latency/throughput used to be fixed literals (45ms, 1250 ops/s).
|
|
616
|
+
// Report the samples performance_report records (ADR-093 F8), or say none exist.
|
|
617
|
+
const sampled = (values, unit) => {
|
|
618
|
+
const real = values.filter(n => Number.isFinite(n));
|
|
619
|
+
return real.length === 0
|
|
620
|
+
? { available: false, unit, samples: 0, _real: false, _note: 'No samples recorded yet: run performance_report to record one.' }
|
|
621
|
+
: { ...calcStats(real, real[real.length - 1]), unit, samples: real.length, _real: true };
|
|
622
|
+
};
|
|
615
623
|
const allMetrics = {
|
|
616
624
|
cpu: {
|
|
617
625
|
...cpuStats,
|
|
@@ -630,30 +638,12 @@ export const performanceTools = [
|
|
|
630
638
|
unit: 'MB',
|
|
631
639
|
_real: true,
|
|
632
640
|
},
|
|
633
|
-
latency:
|
|
634
|
-
|
|
635
|
-
avg: 52,
|
|
636
|
-
min: 15,
|
|
637
|
-
max: 250,
|
|
638
|
-
p50: 48,
|
|
639
|
-
p95: 150,
|
|
640
|
-
p99: 220,
|
|
641
|
-
unit: 'ms',
|
|
642
|
-
},
|
|
643
|
-
throughput: {
|
|
644
|
-
current: 1250,
|
|
645
|
-
avg: 1100,
|
|
646
|
-
min: 500,
|
|
647
|
-
max: 2000,
|
|
648
|
-
p50: 1050,
|
|
649
|
-
p95: 1800,
|
|
650
|
-
p99: 1950,
|
|
651
|
-
unit: 'ops/s',
|
|
652
|
-
},
|
|
641
|
+
latency: sampled(history.map(m => m.latency?.avg), 'ms'),
|
|
642
|
+
throughput: sampled(history.map(m => m.throughput?.operations), 'ops/s'),
|
|
653
643
|
};
|
|
654
644
|
if (metric === 'all') {
|
|
655
645
|
return {
|
|
656
|
-
_real:
|
|
646
|
+
_real: Object.values(allMetrics).every(m => m._real),
|
|
657
647
|
metrics: allMetrics,
|
|
658
648
|
aggregation,
|
|
659
649
|
historySize: history.length,
|
|
@@ -662,9 +652,9 @@ export const performanceTools = [
|
|
|
662
652
|
}
|
|
663
653
|
const selectedMetric = allMetrics[metric];
|
|
664
654
|
return {
|
|
665
|
-
_real:
|
|
655
|
+
_real: selectedMetric._real,
|
|
666
656
|
metric,
|
|
667
|
-
value: selectedMetric[aggregation],
|
|
657
|
+
value: selectedMetric[aggregation] ?? null,
|
|
668
658
|
unit: selectedMetric.unit,
|
|
669
659
|
details: selectedMetric,
|
|
670
660
|
timestamp: new Date().toISOString(),
|
|
@@ -54,6 +54,21 @@ function ensureSessionDir() {
|
|
|
54
54
|
mkdirRestricted(dir);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The session hook (`session.js` from init/helpers-generator.ts) writes its
|
|
59
|
+
* records into this same directory: `current.json` and `session-<n>.json`
|
|
60
|
+
* archives shaped `{ id, startedAt, ... }` rather than session_save's
|
|
61
|
+
* `{ sessionId, name, savedAt, stats }`. Read the id and time from either shape
|
|
62
|
+
* (ADR-093 F6), falling back to the file's own id.
|
|
63
|
+
*/
|
|
64
|
+
function sessionIdentity(session, fileId) {
|
|
65
|
+
const raw = session;
|
|
66
|
+
const pick = (...values) => values.find((v) => typeof v === 'string' && v !== '');
|
|
67
|
+
return {
|
|
68
|
+
sessionId: pick(raw.sessionId, raw.id) ?? fileId,
|
|
69
|
+
savedAt: pick(raw.savedAt, raw.startedAt) ?? '',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
57
72
|
function loadSession(sessionId) {
|
|
58
73
|
try {
|
|
59
74
|
const path = getSessionPath(sessionId);
|
|
@@ -583,12 +598,13 @@ export const sessionTools = [
|
|
|
583
598
|
if (session) {
|
|
584
599
|
const path = getSessionPath(sessionId);
|
|
585
600
|
const stat = statSync(path);
|
|
601
|
+
const identity = sessionIdentity(session, sessionId);
|
|
586
602
|
return {
|
|
587
|
-
sessionId:
|
|
588
|
-
name: session.name,
|
|
603
|
+
sessionId: identity.sessionId,
|
|
604
|
+
name: session.name ?? identity.sessionId,
|
|
589
605
|
description: session.description,
|
|
590
|
-
savedAt:
|
|
591
|
-
stats: session.stats,
|
|
606
|
+
savedAt: identity.savedAt,
|
|
607
|
+
stats: session.stats ?? null,
|
|
592
608
|
fileSize: stat.size,
|
|
593
609
|
path,
|
|
594
610
|
hasData: {
|
|
@@ -631,12 +647,13 @@ export const sessionTools = [
|
|
|
631
647
|
const session = loadSession(sessionId);
|
|
632
648
|
if (!session)
|
|
633
649
|
return { sessionId, status: 'unknown', startedAt: '', error: 'Session file unreadable' };
|
|
650
|
+
const identity = sessionIdentity(session, sessionId);
|
|
634
651
|
return {
|
|
635
|
-
sessionId:
|
|
636
|
-
name: session.name,
|
|
652
|
+
sessionId: identity.sessionId,
|
|
653
|
+
name: session.name ?? identity.sessionId,
|
|
637
654
|
status: 'active',
|
|
638
|
-
startedAt:
|
|
639
|
-
stats: session.stats,
|
|
655
|
+
startedAt: identity.savedAt,
|
|
656
|
+
stats: session.stats ?? null,
|
|
640
657
|
};
|
|
641
658
|
},
|
|
642
659
|
},
|