agentic-qe 3.7.20 → 3.7.21
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/v3/qe-deployment-advisor.md +14 -0
- package/.claude/agents/v3/qe-gap-detector.md +8 -0
- package/.claude/agents/v3/qe-impact-analyzer.md +11 -0
- package/.claude/agents/v3/qe-queen-coordinator.md +45 -0
- package/.claude/agents/v3/qe-root-cause-analyzer.md +11 -0
- package/.claude/agents/v3/qe-security-scanner.md +25 -16
- package/.claude/helpers/brain-checkpoint.cjs +3 -3
- package/.claude/helpers/statusline-v3.cjs +4 -3
- package/.claude/skills/skills-manifest.json +1 -1
- package/CHANGELOG.md +20 -0
- package/assets/agents/v3/qe-deployment-advisor.md +14 -0
- package/assets/agents/v3/qe-gap-detector.md +8 -0
- package/assets/agents/v3/qe-impact-analyzer.md +11 -0
- package/assets/agents/v3/qe-queen-coordinator.md +45 -0
- package/assets/agents/v3/qe-root-cause-analyzer.md +11 -0
- package/assets/agents/v3/qe-security-scanner.md +25 -16
- package/assets/helpers/statusline-v3.cjs +4 -3
- package/dist/adapters/claude-flow/model-router-bridge.d.ts +0 -6
- package/dist/adapters/claude-flow/model-router-bridge.js +4 -17
- package/dist/adapters/claude-flow/pretrain-bridge.d.ts +0 -6
- package/dist/adapters/claude-flow/pretrain-bridge.js +6 -19
- package/dist/adapters/claude-flow/trajectory-bridge.d.ts +0 -6
- package/dist/adapters/claude-flow/trajectory-bridge.js +21 -23
- package/dist/cli/bundle.js +1814 -981
- package/dist/coordination/protocols/security-audit.d.ts +3 -6
- package/dist/coordination/protocols/security-audit.js +8 -88
- package/dist/coordination/queen-coordinator.d.ts +13 -0
- package/dist/coordination/queen-coordinator.js +76 -0
- package/dist/coordination/queen-task-management.d.ts +2 -0
- package/dist/coordination/queen-task-management.js +10 -0
- package/dist/coordination/queen-types.d.ts +3 -0
- package/dist/coordination/task-executor.js +7 -5
- package/dist/domains/security-compliance/services/scanners/sast-scanner.d.ts +25 -1
- package/dist/domains/security-compliance/services/scanners/sast-scanner.js +140 -11
- package/dist/domains/security-compliance/services/scanners/scanner-types.d.ts +2 -0
- package/dist/domains/security-compliance/services/scanners/scanner-types.js +1 -0
- package/dist/domains/test-execution/services/mincut-test-optimizer.js +2 -0
- package/dist/init/agents-installer.d.ts +2 -0
- package/dist/init/agents-installer.js +13 -0
- package/dist/init/enhancements/claude-flow-adapter.js +51 -24
- package/dist/init/init-wizard.js +1 -1
- package/dist/init/phases/07-hooks.js +6 -6
- package/dist/integrations/ruvector/brain-rvf-exporter.js +14 -2
- package/dist/learning/experience-capture-middleware.js +3 -1
- package/dist/learning/qe-reasoning-bank.js +3 -3
- package/dist/learning/sqlite-persistence.js +16 -0
- package/dist/learning/token-tracker.js +4 -2
- package/dist/mcp/bundle.js +1179 -500
- package/dist/routing/agent-dependency-graph.d.ts +77 -0
- package/dist/routing/agent-dependency-graph.js +359 -0
- package/dist/routing/co-execution-repository.d.ts +68 -0
- package/dist/routing/co-execution-repository.js +184 -0
- package/dist/routing/index.d.ts +6 -0
- package/dist/routing/index.js +6 -0
- package/dist/routing/qe-task-router.d.ts +7 -0
- package/dist/routing/qe-task-router.js +63 -1
- package/dist/routing/signal-merger.d.ts +81 -0
- package/dist/routing/signal-merger.js +136 -0
- package/dist/routing/types.d.ts +1 -0
- package/dist/shared/llm/providers/azure-openai.js +3 -2
- package/dist/shared/llm/providers/bedrock.js +3 -2
- package/dist/shared/llm/providers/claude.js +3 -2
- package/dist/shared/llm/providers/gemini.js +3 -2
- package/dist/shared/llm/providers/openai.js +3 -2
- package/dist/shared/llm/providers/openrouter.js +3 -2
- package/dist/shared/llm/retry.d.ts +10 -0
- package/dist/shared/llm/retry.js +16 -0
- package/dist/shared/llm/router/agent-router-config.d.ts +2 -1
- package/dist/shared/llm/router/agent-router-config.js +38 -88
- package/dist/validation/index.d.ts +2 -0
- package/dist/validation/index.js +4 -0
- package/dist/validation/steps/agent-mcp-validator.d.ts +88 -0
- package/dist/validation/steps/agent-mcp-validator.js +254 -0
- package/package.json +1 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Dependency Graph (Issue #342, Item 2)
|
|
3
|
+
*
|
|
4
|
+
* Parses agent .md frontmatter for structured dependency declarations
|
|
5
|
+
* and builds a dependency graph for spawn ordering and validation.
|
|
6
|
+
*
|
|
7
|
+
* Dependency types (from Skillsmith pattern):
|
|
8
|
+
* - hard: Required. Warn if missing but don't block (advisory philosophy).
|
|
9
|
+
* - soft: Nice-to-have. Advisory warning only.
|
|
10
|
+
* - peer: Works alongside. Informational.
|
|
11
|
+
*/
|
|
12
|
+
export type DependencyType = 'hard' | 'soft' | 'peer';
|
|
13
|
+
export interface AgentDependency {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly type: DependencyType;
|
|
16
|
+
readonly reason: string;
|
|
17
|
+
}
|
|
18
|
+
export interface McpServerDependency {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly required: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface ModelRequirement {
|
|
23
|
+
readonly minimum?: string;
|
|
24
|
+
readonly capabilities?: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface AgentDependencies {
|
|
27
|
+
readonly agents?: AgentDependency[];
|
|
28
|
+
readonly mcpServers?: McpServerDependency[];
|
|
29
|
+
readonly models?: ModelRequirement;
|
|
30
|
+
}
|
|
31
|
+
export interface DependencyNode {
|
|
32
|
+
readonly agentName: string;
|
|
33
|
+
readonly hardDeps: string[];
|
|
34
|
+
readonly softDeps: string[];
|
|
35
|
+
readonly peerDeps: string[];
|
|
36
|
+
readonly dependencies: AgentDependencies;
|
|
37
|
+
}
|
|
38
|
+
export interface DependencyGraphResult {
|
|
39
|
+
readonly nodes: Map<string, DependencyNode>;
|
|
40
|
+
readonly spawnOrder: string[];
|
|
41
|
+
readonly cycles: string[][];
|
|
42
|
+
readonly warnings: string[];
|
|
43
|
+
}
|
|
44
|
+
export interface SpawnPlan {
|
|
45
|
+
readonly phases: string[][];
|
|
46
|
+
readonly unsatisfiedHardDeps: Array<{
|
|
47
|
+
agent: string;
|
|
48
|
+
missing: string[];
|
|
49
|
+
}>;
|
|
50
|
+
readonly warnings: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Parse structured dependency declarations from agent frontmatter.
|
|
54
|
+
* Uses a state-machine: reads lines, tracks current section, builds objects.
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseDependenciesFromFrontmatter(content: string): AgentDependencies | null;
|
|
57
|
+
/**
|
|
58
|
+
* Build a dependency graph from all qe-*.md agent files in a directory.
|
|
59
|
+
* Reads frontmatter, parses dependencies, detects cycles, computes topo sort.
|
|
60
|
+
*/
|
|
61
|
+
export declare function buildDependencyGraph(agentsDir: string): DependencyGraphResult;
|
|
62
|
+
/**
|
|
63
|
+
* Create a spawn plan for a set of agents, respecting dependencies.
|
|
64
|
+
* Groups into ordered phases (parallel within each phase).
|
|
65
|
+
* Advisory philosophy: unsatisfied hard deps warn but don't block.
|
|
66
|
+
*/
|
|
67
|
+
export declare function createSpawnPlan(agentNames: string[], graph: DependencyGraphResult): SpawnPlan;
|
|
68
|
+
/**
|
|
69
|
+
* Get all transitive dependencies for a given agent.
|
|
70
|
+
* Follows the graph recursively, collecting hard, soft, and peer deps.
|
|
71
|
+
*/
|
|
72
|
+
export declare function getAgentDependencies(agentName: string, graph: DependencyGraphResult): {
|
|
73
|
+
hardDeps: string[];
|
|
74
|
+
softDeps: string[];
|
|
75
|
+
peerDeps: string[];
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=agent-dependency-graph.d.ts.map
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Dependency Graph (Issue #342, Item 2)
|
|
3
|
+
*
|
|
4
|
+
* Parses agent .md frontmatter for structured dependency declarations
|
|
5
|
+
* and builds a dependency graph for spawn ordering and validation.
|
|
6
|
+
*
|
|
7
|
+
* Dependency types (from Skillsmith pattern):
|
|
8
|
+
* - hard: Required. Warn if missing but don't block (advisory philosophy).
|
|
9
|
+
* - soft: Nice-to-have. Advisory warning only.
|
|
10
|
+
* - peer: Works alongside. Informational.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
13
|
+
import { join, basename } from 'path';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Frontmatter Parsing
|
|
16
|
+
// ============================================================================
|
|
17
|
+
/** Extract YAML frontmatter content between --- markers. */
|
|
18
|
+
function extractFrontmatter(content) {
|
|
19
|
+
const lines = content.split('\n');
|
|
20
|
+
if (lines.length < 2 || lines[0].trim() !== '---')
|
|
21
|
+
return null;
|
|
22
|
+
for (let i = 1; i < lines.length; i++) {
|
|
23
|
+
if (lines[i].trim() === '---')
|
|
24
|
+
return lines.slice(1, i).join('\n');
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/** Parse a simple YAML value, stripping surrounding quotes. */
|
|
29
|
+
function parseYamlValue(raw) {
|
|
30
|
+
const t = raw.trim();
|
|
31
|
+
if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) {
|
|
32
|
+
return t.slice(1, -1);
|
|
33
|
+
}
|
|
34
|
+
return t;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Parse structured dependency declarations from agent frontmatter.
|
|
38
|
+
* Uses a state-machine: reads lines, tracks current section, builds objects.
|
|
39
|
+
*/
|
|
40
|
+
export function parseDependenciesFromFrontmatter(content) {
|
|
41
|
+
const frontmatter = extractFrontmatter(content);
|
|
42
|
+
if (!frontmatter)
|
|
43
|
+
return null;
|
|
44
|
+
const lines = frontmatter.split('\n');
|
|
45
|
+
let depsStart = -1;
|
|
46
|
+
for (let i = 0; i < lines.length; i++) {
|
|
47
|
+
if (/^dependencies:\s*$/.test(lines[i])) {
|
|
48
|
+
depsStart = i + 1;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (depsStart === -1)
|
|
53
|
+
return null;
|
|
54
|
+
const depsLines = [];
|
|
55
|
+
for (let i = depsStart; i < lines.length; i++) {
|
|
56
|
+
const line = lines[i];
|
|
57
|
+
if (line.trim() === '')
|
|
58
|
+
continue;
|
|
59
|
+
if (line.length > 0 && line[0] !== ' ' && line[0] !== '\t')
|
|
60
|
+
break;
|
|
61
|
+
depsLines.push(line);
|
|
62
|
+
}
|
|
63
|
+
if (depsLines.length === 0)
|
|
64
|
+
return null;
|
|
65
|
+
const result = {};
|
|
66
|
+
const st = { section: 'none', item: {}, inList: false };
|
|
67
|
+
const flushAgent = () => {
|
|
68
|
+
if (!st.item.name)
|
|
69
|
+
return;
|
|
70
|
+
if (!result.agents)
|
|
71
|
+
result.agents = [];
|
|
72
|
+
result.agents.push({ name: st.item.name, type: st.item.type || 'hard', reason: st.item.reason || '' });
|
|
73
|
+
st.item = {};
|
|
74
|
+
};
|
|
75
|
+
const flushMcp = () => {
|
|
76
|
+
if (!st.item.name)
|
|
77
|
+
return;
|
|
78
|
+
if (!result.mcpServers)
|
|
79
|
+
result.mcpServers = [];
|
|
80
|
+
result.mcpServers.push({ name: st.item.name, required: st.item.required === 'true' });
|
|
81
|
+
st.item = {};
|
|
82
|
+
};
|
|
83
|
+
for (const line of depsLines) {
|
|
84
|
+
const trimmed = line.trim();
|
|
85
|
+
if (trimmed === '')
|
|
86
|
+
continue;
|
|
87
|
+
const secMatch = trimmed.match(/^(agents|mcp_servers|models):\s*$/);
|
|
88
|
+
if (secMatch) {
|
|
89
|
+
if (st.section === 'agents' && st.inList)
|
|
90
|
+
flushAgent();
|
|
91
|
+
if (st.section === 'mcp_servers' && st.inList)
|
|
92
|
+
flushMcp();
|
|
93
|
+
st.inList = false;
|
|
94
|
+
st.section = secMatch[1];
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (st.section === 'agents' || st.section === 'mcp_servers') {
|
|
98
|
+
const listStart = trimmed.match(/^-\s+(\w+):\s*(.*)$/);
|
|
99
|
+
if (listStart) {
|
|
100
|
+
if (st.inList) {
|
|
101
|
+
st.section === 'agents' ? flushAgent() : flushMcp();
|
|
102
|
+
}
|
|
103
|
+
st.inList = true;
|
|
104
|
+
st.item = { [listStart[1]]: parseYamlValue(listStart[2]) };
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (st.inList) {
|
|
108
|
+
const kv = trimmed.match(/^(\w+):\s*(.*)$/);
|
|
109
|
+
if (kv) {
|
|
110
|
+
st.item[kv[1]] = parseYamlValue(kv[2]);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (st.section === 'models') {
|
|
116
|
+
if (!result.models)
|
|
117
|
+
result.models = {};
|
|
118
|
+
const kv = trimmed.match(/^(\w+):\s*(.*)$/);
|
|
119
|
+
if (kv) {
|
|
120
|
+
const val = parseYamlValue(kv[2]);
|
|
121
|
+
if (kv[1] === 'minimum')
|
|
122
|
+
result.models = { ...result.models, minimum: val };
|
|
123
|
+
else if (kv[1] === 'capabilities') {
|
|
124
|
+
const arr = val.match(/^\[(.+)]$/);
|
|
125
|
+
if (arr)
|
|
126
|
+
result.models = { ...result.models, capabilities: arr[1].split(',').map(s => parseYamlValue(s.trim())) };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const li = trimmed.match(/^-\s+(.+)$/);
|
|
130
|
+
if (li && result.models) {
|
|
131
|
+
const caps = result.models.capabilities || [];
|
|
132
|
+
result.models = { ...result.models, capabilities: [...caps, parseYamlValue(li[1])] };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (st.section === 'agents' && st.inList)
|
|
137
|
+
flushAgent();
|
|
138
|
+
if (st.section === 'mcp_servers' && st.inList)
|
|
139
|
+
flushMcp();
|
|
140
|
+
if (!result.agents && !result.mcpServers && !result.models)
|
|
141
|
+
return null;
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// Graph Building
|
|
146
|
+
// ============================================================================
|
|
147
|
+
function extractAgentName(content) {
|
|
148
|
+
const fm = extractFrontmatter(content);
|
|
149
|
+
if (!fm)
|
|
150
|
+
return null;
|
|
151
|
+
for (const line of fm.split('\n')) {
|
|
152
|
+
const m = line.match(/^name:\s*(.+)$/);
|
|
153
|
+
if (m)
|
|
154
|
+
return parseYamlValue(m[1]);
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build a dependency graph from all qe-*.md agent files in a directory.
|
|
160
|
+
* Reads frontmatter, parses dependencies, detects cycles, computes topo sort.
|
|
161
|
+
*/
|
|
162
|
+
export function buildDependencyGraph(agentsDir) {
|
|
163
|
+
const nodes = new Map();
|
|
164
|
+
const warnings = [];
|
|
165
|
+
if (!existsSync(agentsDir)) {
|
|
166
|
+
warnings.push(`Agents directory not found: ${agentsDir}`);
|
|
167
|
+
return { nodes, spawnOrder: [], cycles: [], warnings };
|
|
168
|
+
}
|
|
169
|
+
const files = readdirSync(agentsDir).filter(f => f.startsWith('qe-') && f.endsWith('.md'));
|
|
170
|
+
for (const file of files) {
|
|
171
|
+
let content;
|
|
172
|
+
try {
|
|
173
|
+
content = readFileSync(join(agentsDir, file), 'utf-8');
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
warnings.push(`Failed to read agent file: ${file}`);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const agentName = extractAgentName(content) || basename(file, '.md');
|
|
180
|
+
const deps = parseDependenciesFromFrontmatter(content);
|
|
181
|
+
const hardDeps = [], softDeps = [], peerDeps = [];
|
|
182
|
+
if (deps?.agents) {
|
|
183
|
+
for (const dep of deps.agents) {
|
|
184
|
+
if (dep.type === 'hard')
|
|
185
|
+
hardDeps.push(dep.name);
|
|
186
|
+
else if (dep.type === 'soft')
|
|
187
|
+
softDeps.push(dep.name);
|
|
188
|
+
else if (dep.type === 'peer')
|
|
189
|
+
peerDeps.push(dep.name);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
nodes.set(agentName, { agentName, hardDeps, softDeps, peerDeps, dependencies: deps || {} });
|
|
193
|
+
}
|
|
194
|
+
// Warn about missing hard deps
|
|
195
|
+
const allNames = new Set(nodes.keys());
|
|
196
|
+
for (const [name, node] of nodes) {
|
|
197
|
+
for (const dep of node.hardDeps) {
|
|
198
|
+
if (!allNames.has(dep)) {
|
|
199
|
+
warnings.push(`Agent "${name}" declares hard dependency on "${dep}" which is not in the agent directory`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const cycles = detectCycles(nodes);
|
|
204
|
+
for (const cycle of cycles)
|
|
205
|
+
warnings.push(`Dependency cycle detected: ${cycle.join(' -> ')}`);
|
|
206
|
+
return { nodes, spawnOrder: topologicalSort(nodes, cycles), cycles, warnings };
|
|
207
|
+
}
|
|
208
|
+
/** Detect cycles in the hard-dependency graph using DFS. */
|
|
209
|
+
function detectCycles(nodes) {
|
|
210
|
+
const cycles = [];
|
|
211
|
+
const visited = new Set();
|
|
212
|
+
const stack = new Set();
|
|
213
|
+
const path = [];
|
|
214
|
+
function dfs(name) {
|
|
215
|
+
visited.add(name);
|
|
216
|
+
stack.add(name);
|
|
217
|
+
path.push(name);
|
|
218
|
+
const node = nodes.get(name);
|
|
219
|
+
if (node) {
|
|
220
|
+
for (const dep of node.hardDeps) {
|
|
221
|
+
if (!visited.has(dep) && nodes.has(dep))
|
|
222
|
+
dfs(dep);
|
|
223
|
+
else if (stack.has(dep)) {
|
|
224
|
+
const start = path.indexOf(dep);
|
|
225
|
+
if (start !== -1)
|
|
226
|
+
cycles.push([...path.slice(start), dep]);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
path.pop();
|
|
231
|
+
stack.delete(name);
|
|
232
|
+
}
|
|
233
|
+
for (const name of nodes.keys()) {
|
|
234
|
+
if (!visited.has(name))
|
|
235
|
+
dfs(name);
|
|
236
|
+
}
|
|
237
|
+
return cycles;
|
|
238
|
+
}
|
|
239
|
+
/** Topological sort respecting hard deps. Cycle agents placed at end. */
|
|
240
|
+
function topologicalSort(nodes, cycles) {
|
|
241
|
+
const cycleAgents = new Set(cycles.flat());
|
|
242
|
+
const sorted = [];
|
|
243
|
+
const visited = new Set();
|
|
244
|
+
const temp = new Set();
|
|
245
|
+
function visit(name) {
|
|
246
|
+
if (visited.has(name) || cycleAgents.has(name) || temp.has(name))
|
|
247
|
+
return;
|
|
248
|
+
temp.add(name);
|
|
249
|
+
const node = nodes.get(name);
|
|
250
|
+
if (node) {
|
|
251
|
+
for (const dep of node.hardDeps) {
|
|
252
|
+
if (nodes.has(dep))
|
|
253
|
+
visit(dep);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
temp.delete(name);
|
|
257
|
+
visited.add(name);
|
|
258
|
+
sorted.push(name);
|
|
259
|
+
}
|
|
260
|
+
for (const name of nodes.keys())
|
|
261
|
+
visit(name);
|
|
262
|
+
for (const agent of cycleAgents) {
|
|
263
|
+
if (!sorted.includes(agent))
|
|
264
|
+
sorted.push(agent);
|
|
265
|
+
}
|
|
266
|
+
return sorted;
|
|
267
|
+
}
|
|
268
|
+
// ============================================================================
|
|
269
|
+
// Spawn Planning
|
|
270
|
+
// ============================================================================
|
|
271
|
+
/**
|
|
272
|
+
* Create a spawn plan for a set of agents, respecting dependencies.
|
|
273
|
+
* Groups into ordered phases (parallel within each phase).
|
|
274
|
+
* Advisory philosophy: unsatisfied hard deps warn but don't block.
|
|
275
|
+
*/
|
|
276
|
+
export function createSpawnPlan(agentNames, graph) {
|
|
277
|
+
const requested = new Set(agentNames);
|
|
278
|
+
const warnings = [];
|
|
279
|
+
const unsatisfiedHardDeps = [];
|
|
280
|
+
for (const name of agentNames) {
|
|
281
|
+
const node = graph.nodes.get(name);
|
|
282
|
+
if (!node)
|
|
283
|
+
continue;
|
|
284
|
+
const missing = node.hardDeps.filter(d => !requested.has(d));
|
|
285
|
+
if (missing.length > 0) {
|
|
286
|
+
unsatisfiedHardDeps.push({ agent: name, missing });
|
|
287
|
+
warnings.push(`Advisory: "${name}" has unsatisfied hard dependencies: ${missing.join(', ')}. Proceeding anyway.`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const phases = [];
|
|
291
|
+
const placed = new Set();
|
|
292
|
+
let remaining = new Set(agentNames);
|
|
293
|
+
while (remaining.size > 0) {
|
|
294
|
+
const phase = [];
|
|
295
|
+
for (const name of remaining) {
|
|
296
|
+
const node = graph.nodes.get(name);
|
|
297
|
+
if (!node) {
|
|
298
|
+
phase.push(name);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const inScopeHard = node.hardDeps.filter(d => requested.has(d));
|
|
302
|
+
if (inScopeHard.every(d => placed.has(d)))
|
|
303
|
+
phase.push(name);
|
|
304
|
+
}
|
|
305
|
+
if (phase.length === 0) {
|
|
306
|
+
warnings.push(`Could not resolve dependency ordering for: ${[...remaining].join(', ')}. Placing in final phase.`);
|
|
307
|
+
phases.push([...remaining]);
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
phases.push(phase);
|
|
311
|
+
for (const a of phase) {
|
|
312
|
+
placed.add(a);
|
|
313
|
+
remaining.delete(a);
|
|
314
|
+
}
|
|
315
|
+
remaining = new Set([...remaining]);
|
|
316
|
+
}
|
|
317
|
+
return { phases, unsatisfiedHardDeps, warnings };
|
|
318
|
+
}
|
|
319
|
+
// ============================================================================
|
|
320
|
+
// Transitive Dependency Resolution
|
|
321
|
+
// ============================================================================
|
|
322
|
+
/**
|
|
323
|
+
* Get all transitive dependencies for a given agent.
|
|
324
|
+
* Follows the graph recursively, collecting hard, soft, and peer deps.
|
|
325
|
+
*/
|
|
326
|
+
export function getAgentDependencies(agentName, graph) {
|
|
327
|
+
const hard = new Set();
|
|
328
|
+
const soft = new Set();
|
|
329
|
+
const peer = new Set();
|
|
330
|
+
const visited = new Set();
|
|
331
|
+
function collect(name, transitive) {
|
|
332
|
+
if (visited.has(name))
|
|
333
|
+
return;
|
|
334
|
+
visited.add(name);
|
|
335
|
+
const node = graph.nodes.get(name);
|
|
336
|
+
if (!node)
|
|
337
|
+
return;
|
|
338
|
+
for (const dep of node.hardDeps) {
|
|
339
|
+
if (dep !== agentName) {
|
|
340
|
+
hard.add(dep);
|
|
341
|
+
collect(dep, true);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const dep of node.softDeps) {
|
|
345
|
+
if (dep !== agentName && !hard.has(dep)) {
|
|
346
|
+
soft.add(dep);
|
|
347
|
+
if (!transitive)
|
|
348
|
+
collect(dep, true);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
for (const dep of node.peerDeps) {
|
|
352
|
+
if (dep !== agentName && !hard.has(dep) && !soft.has(dep))
|
|
353
|
+
peer.add(dep);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
collect(agentName, false);
|
|
357
|
+
return { hardDeps: [...hard], softDeps: [...soft], peerDeps: [...peer] };
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=agent-dependency-graph.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Co-Execution Repository (Issue #342, Item 3)
|
|
3
|
+
*
|
|
4
|
+
* Tracks which agent combinations succeed together on similar tasks.
|
|
5
|
+
* Behavioral confidence ramps linearly: min(1.0, success_count / 20).
|
|
6
|
+
* A single co-execution registers at 0.05; twenty successes reach full confidence.
|
|
7
|
+
*/
|
|
8
|
+
import type { Database as DatabaseType } from 'better-sqlite3';
|
|
9
|
+
/** A record of two agents executing together on a task */
|
|
10
|
+
export interface CoExecutionRecord {
|
|
11
|
+
/** First agent ID */
|
|
12
|
+
readonly agentA: string;
|
|
13
|
+
/** Second agent ID */
|
|
14
|
+
readonly agentB: string;
|
|
15
|
+
/** Task domain */
|
|
16
|
+
readonly domain: string;
|
|
17
|
+
/** Whether the co-execution was successful */
|
|
18
|
+
readonly success: boolean;
|
|
19
|
+
/** Optional task description (truncated) */
|
|
20
|
+
readonly taskDescription?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Co-execution statistics for an agent pair */
|
|
23
|
+
export interface CoExecutionStats {
|
|
24
|
+
/** First agent */
|
|
25
|
+
readonly agentA: string;
|
|
26
|
+
/** Second agent */
|
|
27
|
+
readonly agentB: string;
|
|
28
|
+
/** Total co-executions */
|
|
29
|
+
readonly totalExecutions: number;
|
|
30
|
+
/** Successful co-executions */
|
|
31
|
+
readonly successCount: number;
|
|
32
|
+
/** Success rate */
|
|
33
|
+
readonly successRate: number;
|
|
34
|
+
/** Behavioral confidence: min(1.0, successCount / 20) */
|
|
35
|
+
readonly behavioralConfidence: number;
|
|
36
|
+
}
|
|
37
|
+
export declare class CoExecutionRepository {
|
|
38
|
+
private db;
|
|
39
|
+
private initialized;
|
|
40
|
+
/**
|
|
41
|
+
* Initialize with a database connection.
|
|
42
|
+
* Creates the co-execution table if it doesn't exist.
|
|
43
|
+
*/
|
|
44
|
+
initialize(db: DatabaseType): void;
|
|
45
|
+
/**
|
|
46
|
+
* Record a co-execution event between two agents.
|
|
47
|
+
* Agent order is normalized (sorted) so A-B and B-A are the same pair.
|
|
48
|
+
*/
|
|
49
|
+
recordCoExecution(record: CoExecutionRecord): void;
|
|
50
|
+
/**
|
|
51
|
+
* Get co-execution stats for a specific agent pair.
|
|
52
|
+
* Returns behavioral confidence using linear ramp: min(1.0, successCount / 20).
|
|
53
|
+
*/
|
|
54
|
+
getCoExecutionStats(agentA: string, agentB: string): CoExecutionStats | null;
|
|
55
|
+
/**
|
|
56
|
+
* Get all agents that have successfully co-executed with the given agent.
|
|
57
|
+
* Returns them sorted by behavioral confidence (descending).
|
|
58
|
+
*/
|
|
59
|
+
getCoExecutionPartners(agentId: string, limit?: number): CoExecutionStats[];
|
|
60
|
+
/**
|
|
61
|
+
* Record a batch of co-executions from a swarm task.
|
|
62
|
+
* Given a list of agents that participated in a task together,
|
|
63
|
+
* records co-execution for every unique pair.
|
|
64
|
+
*/
|
|
65
|
+
recordSwarmCoExecution(agentIds: string[], domain: string, success: boolean, taskDescription?: string): void;
|
|
66
|
+
}
|
|
67
|
+
export declare function getCoExecutionRepository(): CoExecutionRepository;
|
|
68
|
+
//# sourceMappingURL=co-execution-repository.d.ts.map
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Co-Execution Repository (Issue #342, Item 3)
|
|
3
|
+
*
|
|
4
|
+
* Tracks which agent combinations succeed together on similar tasks.
|
|
5
|
+
* Behavioral confidence ramps linearly: min(1.0, success_count / 20).
|
|
6
|
+
* A single co-execution registers at 0.05; twenty successes reach full confidence.
|
|
7
|
+
*/
|
|
8
|
+
import { toErrorMessage } from '../shared/error-utils.js';
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Constants
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/** Number of successes needed for full behavioral confidence */
|
|
13
|
+
const CONFIDENCE_RAMP_THRESHOLD = 20;
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Repository
|
|
16
|
+
// ============================================================================
|
|
17
|
+
export class CoExecutionRepository {
|
|
18
|
+
db = null;
|
|
19
|
+
initialized = false;
|
|
20
|
+
/**
|
|
21
|
+
* Initialize with a database connection.
|
|
22
|
+
* Creates the co-execution table if it doesn't exist.
|
|
23
|
+
*/
|
|
24
|
+
initialize(db) {
|
|
25
|
+
if (this.initialized)
|
|
26
|
+
return;
|
|
27
|
+
this.db = db;
|
|
28
|
+
try {
|
|
29
|
+
this.db.exec(`
|
|
30
|
+
CREATE TABLE IF NOT EXISTS qe_agent_co_execution (
|
|
31
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
32
|
+
agent_a TEXT NOT NULL,
|
|
33
|
+
agent_b TEXT NOT NULL,
|
|
34
|
+
domain TEXT NOT NULL,
|
|
35
|
+
success INTEGER NOT NULL DEFAULT 0,
|
|
36
|
+
task_description TEXT,
|
|
37
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_co_exec_agents
|
|
41
|
+
ON qe_agent_co_execution(agent_a, agent_b);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_co_exec_domain
|
|
43
|
+
ON qe_agent_co_execution(domain);
|
|
44
|
+
`);
|
|
45
|
+
this.initialized = true;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error(`[CoExecutionRepository] Init error: ${toErrorMessage(error)}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Record a co-execution event between two agents.
|
|
53
|
+
* Agent order is normalized (sorted) so A-B and B-A are the same pair.
|
|
54
|
+
*/
|
|
55
|
+
recordCoExecution(record) {
|
|
56
|
+
if (!this.db)
|
|
57
|
+
return;
|
|
58
|
+
const [a, b] = [record.agentA, record.agentB].sort();
|
|
59
|
+
try {
|
|
60
|
+
this.db.prepare(`
|
|
61
|
+
INSERT INTO qe_agent_co_execution (agent_a, agent_b, domain, success, task_description)
|
|
62
|
+
VALUES (?, ?, ?, ?, ?)
|
|
63
|
+
`).run(a, b, record.domain, record.success ? 1 : 0, record.taskDescription?.slice(0, 500) || null);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.debug(`[CoExecutionRepository] Record error: ${toErrorMessage(error)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get co-execution stats for a specific agent pair.
|
|
71
|
+
* Returns behavioral confidence using linear ramp: min(1.0, successCount / 20).
|
|
72
|
+
*/
|
|
73
|
+
getCoExecutionStats(agentA, agentB) {
|
|
74
|
+
if (!this.db)
|
|
75
|
+
return null;
|
|
76
|
+
const [a, b] = [agentA, agentB].sort();
|
|
77
|
+
try {
|
|
78
|
+
const row = this.db.prepare(`
|
|
79
|
+
SELECT
|
|
80
|
+
agent_a,
|
|
81
|
+
agent_b,
|
|
82
|
+
COUNT(*) as total_executions,
|
|
83
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as success_count
|
|
84
|
+
FROM qe_agent_co_execution
|
|
85
|
+
WHERE agent_a = ? AND agent_b = ?
|
|
86
|
+
GROUP BY agent_a, agent_b
|
|
87
|
+
`).get(a, b);
|
|
88
|
+
if (!row)
|
|
89
|
+
return null;
|
|
90
|
+
const successRate = row.total_executions > 0
|
|
91
|
+
? row.success_count / row.total_executions
|
|
92
|
+
: 0;
|
|
93
|
+
// Linear ramp: min(1.0, successCount / 20)
|
|
94
|
+
const behavioralConfidence = Math.min(1.0, row.success_count / CONFIDENCE_RAMP_THRESHOLD);
|
|
95
|
+
return {
|
|
96
|
+
agentA: row.agent_a,
|
|
97
|
+
agentB: row.agent_b,
|
|
98
|
+
totalExecutions: row.total_executions,
|
|
99
|
+
successCount: row.success_count,
|
|
100
|
+
successRate,
|
|
101
|
+
behavioralConfidence,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.debug(`[CoExecutionRepository] Stats error: ${toErrorMessage(error)}`);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get all agents that have successfully co-executed with the given agent.
|
|
111
|
+
* Returns them sorted by behavioral confidence (descending).
|
|
112
|
+
*/
|
|
113
|
+
getCoExecutionPartners(agentId, limit = 10) {
|
|
114
|
+
if (!this.db)
|
|
115
|
+
return [];
|
|
116
|
+
try {
|
|
117
|
+
const rows = this.db.prepare(`
|
|
118
|
+
SELECT
|
|
119
|
+
agent_a,
|
|
120
|
+
agent_b,
|
|
121
|
+
COUNT(*) as total_executions,
|
|
122
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as success_count
|
|
123
|
+
FROM qe_agent_co_execution
|
|
124
|
+
WHERE agent_a = ? OR agent_b = ?
|
|
125
|
+
GROUP BY agent_a, agent_b
|
|
126
|
+
ORDER BY success_count DESC
|
|
127
|
+
LIMIT ?
|
|
128
|
+
`).all(agentId, agentId, limit);
|
|
129
|
+
return rows.map(row => {
|
|
130
|
+
const successRate = row.total_executions > 0
|
|
131
|
+
? row.success_count / row.total_executions
|
|
132
|
+
: 0;
|
|
133
|
+
return {
|
|
134
|
+
agentA: row.agent_a,
|
|
135
|
+
agentB: row.agent_b,
|
|
136
|
+
totalExecutions: row.total_executions,
|
|
137
|
+
successCount: row.success_count,
|
|
138
|
+
successRate,
|
|
139
|
+
behavioralConfidence: Math.min(1.0, row.success_count / CONFIDENCE_RAMP_THRESHOLD),
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.debug(`[CoExecutionRepository] Partners error: ${toErrorMessage(error)}`);
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Record a batch of co-executions from a swarm task.
|
|
150
|
+
* Given a list of agents that participated in a task together,
|
|
151
|
+
* records co-execution for every unique pair.
|
|
152
|
+
*/
|
|
153
|
+
recordSwarmCoExecution(agentIds, domain, success, taskDescription) {
|
|
154
|
+
if (!this.db || agentIds.length < 2)
|
|
155
|
+
return;
|
|
156
|
+
try {
|
|
157
|
+
const insert = this.db.prepare(`
|
|
158
|
+
INSERT INTO qe_agent_co_execution (agent_a, agent_b, domain, success, task_description)
|
|
159
|
+
VALUES (?, ?, ?, ?, ?)
|
|
160
|
+
`);
|
|
161
|
+
const transaction = this.db.transaction(() => {
|
|
162
|
+
for (let i = 0; i < agentIds.length; i++) {
|
|
163
|
+
for (let j = i + 1; j < agentIds.length; j++) {
|
|
164
|
+
const [a, b] = [agentIds[i], agentIds[j]].sort();
|
|
165
|
+
insert.run(a, b, domain, success ? 1 : 0, taskDescription?.slice(0, 500) || null);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
transaction();
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
console.debug(`[CoExecutionRepository] Swarm record error: ${toErrorMessage(error)}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** Singleton instance */
|
|
177
|
+
let _instance = null;
|
|
178
|
+
export function getCoExecutionRepository() {
|
|
179
|
+
if (!_instance) {
|
|
180
|
+
_instance = new CoExecutionRepository();
|
|
181
|
+
}
|
|
182
|
+
return _instance;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=co-execution-repository.js.map
|