zigrix 0.1.0-alpha.8 → 0.1.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/LICENSE +1 -1
- package/README.md +159 -120
- package/dist/agents/registry.js +19 -2
- package/dist/agents/roles.d.ts +10 -0
- package/dist/agents/roles.js +83 -0
- package/dist/config/defaults.d.ts +88 -6
- package/dist/config/defaults.js +82 -50
- package/dist/config/load.d.ts +5 -3
- package/dist/config/load.js +69 -30
- package/dist/config/schema.d.ts +46 -4
- package/dist/config/schema.js +49 -3
- package/dist/configure.d.ts +2 -0
- package/dist/configure.js +37 -14
- package/dist/dashboard/.next/BUILD_ID +1 -1
- package/dist/dashboard/.next/app-build-manifest.json +13 -13
- package/dist/dashboard/.next/app-path-routes-manifest.json +3 -3
- package/dist/dashboard/.next/build-manifest.json +2 -2
- package/dist/dashboard/.next/prerender-manifest.json +6 -6
- package/dist/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/_not-found.html +1 -1
- package/dist/dashboard/.next/server/app/_not-found.rsc +1 -1
- package/dist/dashboard/.next/server/app/api/auth/login/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/logout/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/session/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/session/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/setup/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/setup/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/overview/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/stream/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/tasks/[taskId]/conversation/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/login.html +1 -1
- package/dist/dashboard/.next/server/app/login.rsc +1 -1
- package/dist/dashboard/.next/server/app/page.js +2 -2
- package/dist/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/setup.html +1 -1
- package/dist/dashboard/.next/server/app/setup.rsc +1 -1
- package/dist/dashboard/.next/server/app-paths-manifest.json +3 -3
- package/dist/dashboard/.next/server/chunks/972.js +1 -1
- package/dist/dashboard/.next/server/functions-config-manifest.json +3 -3
- package/dist/dashboard/.next/server/middleware.js +1 -1
- package/dist/dashboard/.next/server/pages/404.html +1 -1
- package/dist/dashboard/.next/server/pages/500.html +1 -1
- package/dist/dashboard/.next/static/chunks/app/page-0314989c31e18b4b.js +1 -0
- package/dist/dashboard/.next/static/css/{94d75aff24d0c077.css → c3a7306cb2ba3f6c.css} +1 -1
- package/dist/dashboard.js +47 -0
- package/dist/doctor.js +28 -5
- package/dist/index.js +175 -171
- package/dist/onboard.d.ts +76 -2
- package/dist/onboard.js +529 -25
- package/dist/orchestration/dispatch.d.ts +3 -1
- package/dist/orchestration/dispatch.js +173 -45
- package/dist/orchestration/evidence.js +31 -4
- package/dist/orchestration/finalize.d.ts +1 -0
- package/dist/orchestration/finalize.js +5 -3
- package/dist/orchestration/report.js +9 -1
- package/dist/orchestration/worker.d.ts +1 -1
- package/dist/orchestration/worker.js +58 -8
- package/dist/rules/templates.js +3 -6
- package/dist/state/tasks.d.ts +12 -0
- package/dist/state/tasks.js +7 -0
- package/package.json +23 -2
- package/rules/defaults/README.md +9 -9
- package/rules/defaults/{back-zig.md → backend-agent.md} +4 -4
- package/rules/defaults/{front-zig.md → frontend-agent.md} +4 -4
- package/rules/defaults/orchestrator-agent.md +261 -0
- package/rules/defaults/{qa-zig.md → qa-agent.md} +11 -11
- package/rules/defaults/{sec-zig.md → security-agent.md} +4 -4
- package/rules/defaults/{sys-zig.md → system-agent.md} +8 -9
- package/rules/defaults/worker-common.md +25 -19
- package/skills/zigrix-doctor/SKILL.md +4 -2
- package/skills/zigrix-evidence/SKILL.md +7 -3
- package/skills/zigrix-main-agent-guide/SKILL.md +128 -0
- package/skills/zigrix-shared/SKILL.md +27 -3
- package/skills/zigrix-task-create/SKILL.md +8 -2
- package/skills/zigrix-task-status/SKILL.md +5 -2
- package/skills/zigrix-worker/SKILL.md +12 -4
- package/dist/dashboard/.next/static/chunks/app/page-25f54e54e74fb3af.js +0 -1
- package/rules/defaults/pro-zig.md +0 -238
- /package/dist/dashboard/.next/static/{2a4glWei05xr4Jg0Ly6cp → PT4hYxzrqxj-Zq4ZjtKNg}/_buildManifest.js +0 -0
- /package/dist/dashboard/.next/static/{2a4glWei05xr4Jg0Ly6cp → PT4hYxzrqxj-Zq4ZjtKNg}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { ZigrixConfig } from '../config/schema.js';
|
|
1
2
|
import { type ZigrixPaths } from '../state/paths.js';
|
|
2
|
-
export declare function
|
|
3
|
+
export declare function resolveConfiguredProjectDir(config: ZigrixConfig, explicitProjectDir?: string): string | undefined;
|
|
4
|
+
export declare function dispatchTask(paths: ZigrixPaths, config: ZigrixConfig, params: {
|
|
3
5
|
title: string;
|
|
4
6
|
description: string;
|
|
5
7
|
scale: string;
|
|
@@ -1,18 +1,118 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { ROLE_HINTS } from '../agents/roles.js';
|
|
4
|
+
import { resolveAbsolutePath } from '../config/defaults.js';
|
|
3
5
|
import { appendEvent } from '../state/events.js';
|
|
4
|
-
import { ensureBaseState } from '../state/paths.js';
|
|
5
6
|
import { createTask, rebuildIndex, saveTask } from '../state/tasks.js';
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
import { ensureBaseState } from '../state/paths.js';
|
|
8
|
+
const BASELINE_REQUIRED_ROLES = ['orchestrator', 'qa'];
|
|
9
|
+
const CANDIDATE_ROLE_ORDER = ['frontend', 'backend', 'system', 'security'];
|
|
10
|
+
function unique(items) {
|
|
11
|
+
return [...new Set(items)];
|
|
12
|
+
}
|
|
13
|
+
function listEligibleAgentsByRole(config) {
|
|
14
|
+
const byRole = new Map();
|
|
15
|
+
const participants = new Set(config.agents.orchestration.participants);
|
|
16
|
+
const excluded = new Set(config.agents.orchestration.excluded);
|
|
17
|
+
const participantMode = participants.size > 0;
|
|
18
|
+
for (const [agentId, agent] of Object.entries(config.agents.registry).sort(([a], [b]) => a.localeCompare(b))) {
|
|
19
|
+
if (!agent.enabled)
|
|
20
|
+
continue;
|
|
21
|
+
if (excluded.has(agentId))
|
|
22
|
+
continue;
|
|
23
|
+
if (participantMode && !participants.has(agentId))
|
|
24
|
+
continue;
|
|
25
|
+
const role = agent.role;
|
|
26
|
+
const row = byRole.get(role) ?? [];
|
|
27
|
+
row.push(agentId);
|
|
28
|
+
byRole.set(role, row);
|
|
29
|
+
}
|
|
30
|
+
return byRole;
|
|
31
|
+
}
|
|
32
|
+
function pickRequiredAgentByRole(params) {
|
|
33
|
+
if (params.role === 'orchestrator') {
|
|
34
|
+
if (params.roleAgents.length === 0) {
|
|
35
|
+
throw new Error('dispatch validation failed: no eligible agent for required role "orchestrator"');
|
|
36
|
+
}
|
|
37
|
+
if (!params.roleAgents.includes(params.orchestratorId)) {
|
|
38
|
+
throw new Error(`dispatch validation failed: configured orchestratorId '${params.orchestratorId}' is not eligible (available: ${params.roleAgents.join(', ')})`);
|
|
39
|
+
}
|
|
40
|
+
return params.orchestratorId;
|
|
41
|
+
}
|
|
42
|
+
const picked = params.roleAgents[0];
|
|
43
|
+
if (!picked) {
|
|
44
|
+
throw new Error(`dispatch validation failed: no eligible agent for required role "${params.role}"`);
|
|
45
|
+
}
|
|
46
|
+
return picked;
|
|
47
|
+
}
|
|
48
|
+
function resolveAgentSelection(config, scale) {
|
|
49
|
+
const scalePolicy = config.rules.scales[scale];
|
|
50
|
+
if (!scalePolicy) {
|
|
51
|
+
throw new Error(`unknown scale: ${scale}`);
|
|
52
|
+
}
|
|
53
|
+
const requiredRoles = unique([...BASELINE_REQUIRED_ROLES, ...scalePolicy.requiredRoles]);
|
|
54
|
+
const optionalRoles = unique(scalePolicy.optionalRoles.filter((role) => !requiredRoles.includes(role)));
|
|
55
|
+
const eligibleByRole = listEligibleAgentsByRole(config);
|
|
56
|
+
const requiredAgents = [];
|
|
57
|
+
const roleAgentMap = {};
|
|
58
|
+
for (const role of requiredRoles) {
|
|
59
|
+
const roleAgents = eligibleByRole.get(role) ?? [];
|
|
60
|
+
roleAgentMap[role] = [...roleAgents];
|
|
61
|
+
const picked = pickRequiredAgentByRole({
|
|
62
|
+
role,
|
|
63
|
+
roleAgents,
|
|
64
|
+
orchestratorId: config.agents.orchestration.orchestratorId,
|
|
65
|
+
});
|
|
66
|
+
requiredAgents.push(picked);
|
|
67
|
+
}
|
|
68
|
+
const candidateRoles = unique([...optionalRoles, ...CANDIDATE_ROLE_ORDER.filter((role) => !requiredRoles.includes(role))]);
|
|
69
|
+
const candidateAgents = [];
|
|
70
|
+
for (const role of candidateRoles) {
|
|
71
|
+
const roleAgents = eligibleByRole.get(role) ?? [];
|
|
72
|
+
roleAgentMap[role] = [...roleAgents];
|
|
73
|
+
for (const agentId of roleAgents) {
|
|
74
|
+
if (requiredAgents.includes(agentId))
|
|
75
|
+
continue;
|
|
76
|
+
if (!candidateAgents.includes(agentId))
|
|
77
|
+
candidateAgents.push(agentId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const selectionHints = {};
|
|
81
|
+
for (const [role, agentIds] of Object.entries(roleAgentMap)) {
|
|
82
|
+
const hint = ROLE_HINTS[role] ?? 'role-based selection';
|
|
83
|
+
for (const agentId of agentIds) {
|
|
84
|
+
selectionHints[agentId] = `${hint} (role: ${role})`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const qaAgentId = requiredAgents.find((agentId) => {
|
|
88
|
+
const role = config.agents.registry[agentId]?.role;
|
|
89
|
+
return role === 'qa';
|
|
90
|
+
});
|
|
91
|
+
if (!qaAgentId) {
|
|
92
|
+
throw new Error('dispatch validation failed: no selected QA agent');
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
requiredRoles,
|
|
96
|
+
optionalRoles,
|
|
97
|
+
requiredAgents,
|
|
98
|
+
candidateAgents,
|
|
99
|
+
roleAgentMap,
|
|
100
|
+
selectionHints,
|
|
101
|
+
orchestratorId: config.agents.orchestration.orchestratorId,
|
|
102
|
+
qaAgentId,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export function resolveConfiguredProjectDir(config, explicitProjectDir) {
|
|
106
|
+
const explicit = explicitProjectDir?.trim();
|
|
107
|
+
if (explicit) {
|
|
108
|
+
return resolveAbsolutePath(explicit);
|
|
109
|
+
}
|
|
110
|
+
const configured = config.workspace.projectsBaseDir?.trim();
|
|
111
|
+
if (configured && configured.length > 0) {
|
|
112
|
+
return resolveAbsolutePath(configured);
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
16
116
|
function defaultWorkPackages(scale) {
|
|
17
117
|
return [
|
|
18
118
|
{ id: 'WP1', key: 'planning', title: 'planning', parallel: false },
|
|
@@ -21,33 +121,33 @@ function defaultWorkPackages(scale) {
|
|
|
21
121
|
{ id: 'WP4', key: 'release', title: 'release', parallel: false },
|
|
22
122
|
];
|
|
23
123
|
}
|
|
24
|
-
function defaultExecutionUnits(scale) {
|
|
124
|
+
function defaultExecutionUnits(scale, owners) {
|
|
25
125
|
if (['normal', 'risky', 'large'].includes(scale)) {
|
|
26
126
|
return [
|
|
27
|
-
{ id: 'U1', title: 'spec confirmation', kind: 'planning', owner:
|
|
28
|
-
{ id: 'U2', title: 'implementation planning / work package split', kind: 'planning', owner:
|
|
29
|
-
{ id: 'U3', title: 'implementation slices', kind: 'implementation', owner:
|
|
30
|
-
{ id: 'U4', title: 'qa / regression', kind: 'verification', owner:
|
|
31
|
-
{ id: 'U5', title: 'report / deploy / wrap-up', kind: 'reporting', owner:
|
|
127
|
+
{ id: 'U1', title: 'spec confirmation', kind: 'planning', owner: owners.orchestratorId, workPackage: 'planning', dependsOn: [], parallel: false, status: 'OPEN', dod: 'scope / constraints / edge cases fixed' },
|
|
128
|
+
{ id: 'U2', title: 'implementation planning / work package split', kind: 'planning', owner: owners.orchestratorId, workPackage: 'planning', dependsOn: ['U1'], parallel: false, status: 'OPEN', dod: 'execution units and work packages fixed' },
|
|
129
|
+
{ id: 'U3', title: 'implementation slices', kind: 'implementation', owner: owners.orchestratorId, workPackage: 'implementation', dependsOn: ['U2'], parallel: true, status: 'OPEN', dod: 'required work packages complete' },
|
|
130
|
+
{ id: 'U4', title: 'qa / regression', kind: 'verification', owner: owners.qaAgentId, workPackage: 'verification', dependsOn: ['U3'], parallel: false, status: 'OPEN', dod: 'qa evidence attached' },
|
|
131
|
+
{ id: 'U5', title: 'report / deploy / wrap-up', kind: 'reporting', owner: owners.orchestratorId, workPackage: 'release', dependsOn: ['U4'], parallel: false, status: 'OPEN', dod: 'final report prepared and deployment decision recorded' },
|
|
32
132
|
];
|
|
33
133
|
}
|
|
34
134
|
return [
|
|
35
|
-
{ id: 'U1', title: 'spec confirmation', kind: 'planning', owner:
|
|
36
|
-
{ id: 'U2', title: 'implementation slice', kind: 'implementation', owner:
|
|
37
|
-
{ id: 'U3', title: 'qa / regression', kind: 'verification', owner:
|
|
135
|
+
{ id: 'U1', title: 'spec confirmation', kind: 'planning', owner: owners.orchestratorId, workPackage: 'planning', dependsOn: [], parallel: false, status: 'OPEN', dod: 'scope / constraints / edge cases fixed' },
|
|
136
|
+
{ id: 'U2', title: 'implementation slice', kind: 'implementation', owner: owners.orchestratorId, workPackage: 'implementation', dependsOn: ['U1'], parallel: false, status: 'OPEN', dod: 'main implementation slice complete' },
|
|
137
|
+
{ id: 'U3', title: 'qa / regression', kind: 'verification', owner: owners.qaAgentId, workPackage: 'verification', dependsOn: ['U2'], parallel: false, status: 'OPEN', dod: 'qa evidence attached' },
|
|
38
138
|
];
|
|
39
139
|
}
|
|
40
|
-
|
|
41
|
-
function buildBootPrompt(task) {
|
|
140
|
+
function buildBootPrompt(task, options) {
|
|
42
141
|
return `## Orchestration Task Boot: ${task.taskId}
|
|
43
142
|
- **Title:** ${task.title}
|
|
44
143
|
- **Scale:** ${task.scale}
|
|
144
|
+
- **Orchestrator:** ${options.orchestratorId}
|
|
45
145
|
|
|
46
146
|
---
|
|
47
147
|
|
|
48
|
-
## ⚠️ 절대 규칙:
|
|
148
|
+
## ⚠️ 절대 규칙: QA 역할 워커 호출 필수
|
|
49
149
|
|
|
50
|
-
|
|
150
|
+
**이 태스크는 QA 역할(${options.qaAgentId}) 워커 완료가 필수다.**
|
|
51
151
|
|
|
52
152
|
---
|
|
53
153
|
|
|
@@ -78,24 +178,32 @@ zigrix task finalize ${task.taskId} --json
|
|
|
78
178
|
\`\`\`
|
|
79
179
|
`;
|
|
80
180
|
}
|
|
81
|
-
|
|
82
|
-
export function dispatchTask(paths, params) {
|
|
181
|
+
export function dispatchTask(paths, config, params) {
|
|
83
182
|
ensureBaseState(paths);
|
|
84
|
-
|
|
183
|
+
const selection = resolveAgentSelection(config, params.scale);
|
|
184
|
+
const projectDir = resolveConfiguredProjectDir(config, params.projectDir);
|
|
85
185
|
const task = createTask(paths, {
|
|
86
186
|
title: params.title,
|
|
87
187
|
description: params.description,
|
|
88
188
|
scale: params.scale,
|
|
89
|
-
requiredAgents: [...
|
|
90
|
-
projectDir
|
|
189
|
+
requiredAgents: [...selection.requiredAgents],
|
|
190
|
+
projectDir,
|
|
91
191
|
requestedBy: params.requestedBy,
|
|
92
192
|
});
|
|
93
|
-
|
|
94
|
-
task.selectedAgents = [...BASELINE_REQUIRED];
|
|
193
|
+
task.selectedAgents = [...selection.requiredAgents];
|
|
95
194
|
task.workPackages = defaultWorkPackages(params.scale);
|
|
96
|
-
task.executionUnits = defaultExecutionUnits(params.scale
|
|
195
|
+
task.executionUnits = defaultExecutionUnits(params.scale, {
|
|
196
|
+
orchestratorId: selection.orchestratorId,
|
|
197
|
+
qaAgentId: selection.qaAgentId,
|
|
198
|
+
});
|
|
199
|
+
task.baselineRequiredAgents = [...selection.requiredAgents];
|
|
200
|
+
task.candidateAgents = [...selection.candidateAgents];
|
|
201
|
+
task.requiredRoles = [...selection.requiredRoles];
|
|
202
|
+
task.optionalRoles = [...selection.optionalRoles];
|
|
203
|
+
task.roleAgentMap = selection.roleAgentMap;
|
|
204
|
+
task.orchestratorId = selection.orchestratorId;
|
|
205
|
+
task.qaAgentId = selection.qaAgentId;
|
|
97
206
|
saveTask(paths, task);
|
|
98
|
-
// Write dispatch prompt
|
|
99
207
|
const promptPath = path.join(paths.promptsDir, `${task.taskId}-dispatch.md`);
|
|
100
208
|
const dispatchPrompt = [
|
|
101
209
|
`## Orchestration Task: ${task.taskId}`,
|
|
@@ -103,19 +211,30 @@ export function dispatchTask(paths, params) {
|
|
|
103
211
|
'### 기본 정보',
|
|
104
212
|
`- **Title:** ${task.title}`,
|
|
105
213
|
`- **Scale:** ${task.scale}`,
|
|
106
|
-
`- **
|
|
107
|
-
`- **
|
|
108
|
-
|
|
214
|
+
`- **Orchestrator:** ${selection.orchestratorId}`,
|
|
215
|
+
`- **Baseline Required Agents:** ${selection.requiredAgents.join(', ')}`,
|
|
216
|
+
`- **Candidate Agents:** ${selection.candidateAgents.length > 0 ? selection.candidateAgents.join(', ') : '(none)'}`,
|
|
217
|
+
`- **Required Roles:** ${selection.requiredRoles.join(', ')}`,
|
|
218
|
+
`- **Optional Roles:** ${selection.optionalRoles.length > 0 ? selection.optionalRoles.join(', ') : '(none)'}`,
|
|
219
|
+
projectDir ? `- **Project Dir:** ${projectDir}` : '',
|
|
109
220
|
'',
|
|
110
221
|
'### 요청 내용',
|
|
111
222
|
params.description,
|
|
112
223
|
params.constraints ? `\n### 제약사항\n${params.constraints}` : '',
|
|
113
224
|
'',
|
|
225
|
+
'### 역할 매핑',
|
|
226
|
+
...Object.entries(selection.roleAgentMap)
|
|
227
|
+
.filter(([, agentIds]) => agentIds.length > 0)
|
|
228
|
+
.map(([role, agentIds]) => `- ${role}: ${agentIds.join(', ')}`),
|
|
229
|
+
'',
|
|
114
230
|
'### 선택 규칙',
|
|
115
|
-
...Object.entries(
|
|
231
|
+
...Object.entries(selection.selectionHints).map(([agentId, hint]) => `- ${agentId}: ${hint}`),
|
|
116
232
|
].filter(Boolean).join('\n');
|
|
117
233
|
fs.writeFileSync(promptPath, `${dispatchPrompt}\n`, 'utf8');
|
|
118
|
-
const
|
|
234
|
+
const orchestratorPrompt = buildBootPrompt(task, {
|
|
235
|
+
orchestratorId: selection.orchestratorId,
|
|
236
|
+
qaAgentId: selection.qaAgentId,
|
|
237
|
+
});
|
|
119
238
|
appendEvent(paths.eventsFile, {
|
|
120
239
|
event: 'task_dispatched',
|
|
121
240
|
taskId: task.taskId,
|
|
@@ -124,9 +243,13 @@ export function dispatchTask(paths, params) {
|
|
|
124
243
|
status: 'OPEN',
|
|
125
244
|
payload: {
|
|
126
245
|
scale: task.scale,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
246
|
+
orchestratorId: selection.orchestratorId,
|
|
247
|
+
baselineRequiredAgents: selection.requiredAgents,
|
|
248
|
+
candidateAgents: selection.candidateAgents,
|
|
249
|
+
requiredRoles: selection.requiredRoles,
|
|
250
|
+
optionalRoles: selection.optionalRoles,
|
|
251
|
+
roleAgentMap: selection.roleAgentMap,
|
|
252
|
+
projectDir: projectDir ?? null,
|
|
130
253
|
},
|
|
131
254
|
});
|
|
132
255
|
rebuildIndex(paths);
|
|
@@ -135,12 +258,17 @@ export function dispatchTask(paths, params) {
|
|
|
135
258
|
taskId: task.taskId,
|
|
136
259
|
title: task.title,
|
|
137
260
|
scale: task.scale,
|
|
138
|
-
|
|
139
|
-
|
|
261
|
+
orchestratorId: selection.orchestratorId,
|
|
262
|
+
qaAgentId: selection.qaAgentId,
|
|
263
|
+
baselineRequiredAgents: selection.requiredAgents,
|
|
264
|
+
candidateAgents: selection.candidateAgents,
|
|
265
|
+
requiredRoles: selection.requiredRoles,
|
|
266
|
+
optionalRoles: selection.optionalRoles,
|
|
267
|
+
roleAgentMap: selection.roleAgentMap,
|
|
140
268
|
specPath: path.join(paths.tasksDir, `${task.taskId}.md`),
|
|
141
269
|
metaPath: path.join(paths.tasksDir, `${task.taskId}.meta.json`),
|
|
142
270
|
promptPath,
|
|
143
|
-
|
|
144
|
-
projectDir:
|
|
271
|
+
orchestratorPrompt,
|
|
272
|
+
projectDir: projectDir ?? null,
|
|
145
273
|
};
|
|
146
274
|
}
|
|
@@ -32,6 +32,31 @@ function extractEvidence(rows) {
|
|
|
32
32
|
}
|
|
33
33
|
return { lastAssistant, toolResults: toolResults.slice(-3) };
|
|
34
34
|
}
|
|
35
|
+
function resolveQaAgentId(task) {
|
|
36
|
+
if (typeof task.qaAgentId === 'string' && task.qaAgentId.trim().length > 0) {
|
|
37
|
+
return task.qaAgentId.trim();
|
|
38
|
+
}
|
|
39
|
+
const roleMap = task.roleAgentMap;
|
|
40
|
+
if (roleMap && typeof roleMap === 'object') {
|
|
41
|
+
const qaAgents = roleMap.qa;
|
|
42
|
+
if (Array.isArray(qaAgents) && qaAgents.length > 0) {
|
|
43
|
+
const fromRoleMap = String(qaAgents[0]).trim();
|
|
44
|
+
if (fromRoleMap.length > 0)
|
|
45
|
+
return fromRoleMap;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const required = Array.isArray(task.requiredAgents) ? task.requiredAgents : [];
|
|
49
|
+
if (required.length === 1) {
|
|
50
|
+
const only = String(required[0]).trim();
|
|
51
|
+
if (only.length > 0)
|
|
52
|
+
return only;
|
|
53
|
+
}
|
|
54
|
+
const orchestratorId = typeof task.orchestratorId === 'string' && task.orchestratorId.trim().length > 0
|
|
55
|
+
? task.orchestratorId.trim()
|
|
56
|
+
: null;
|
|
57
|
+
const fallback = required.find((agentId) => String(agentId).trim().length > 0 && String(agentId).trim() !== orchestratorId);
|
|
58
|
+
return fallback ? String(fallback).trim() : null;
|
|
59
|
+
}
|
|
35
60
|
export function collectEvidence(paths, params) {
|
|
36
61
|
ensureBaseState(paths);
|
|
37
62
|
const task = loadTask(paths, params.taskId);
|
|
@@ -83,9 +108,11 @@ export function mergeEvidence(paths, params) {
|
|
|
83
108
|
const presentAgents = [...new Set(items.map((item) => String(item.agentId)))].sort();
|
|
84
109
|
const requiredAgents = [...(params.requiredAgents?.length ? params.requiredAgents : resolveRequiredAgents(task))];
|
|
85
110
|
const missingAgents = requiredAgents.filter((agentId) => !presentAgents.includes(agentId));
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
111
|
+
const qaAgentId = resolveQaAgentId(task);
|
|
112
|
+
const qaPresent = qaAgentId ? presentAgents.includes(qaAgentId) : false;
|
|
113
|
+
const qaRequiredSatisfied = !(params.requireQa ?? false) || (qaAgentId !== null && qaPresent);
|
|
114
|
+
const complete = missingAgents.length === 0 && qaRequiredSatisfied;
|
|
115
|
+
const merged = { ts: nowIso(), taskId: params.taskId, requiredAgents, presentAgents, missingAgents, qaAgentId, qaPresent, complete, items };
|
|
89
116
|
const outPath = path.join(taskDir, '_merged.json');
|
|
90
117
|
fs.writeFileSync(outPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
|
|
91
118
|
appendEvent(paths.eventsFile, {
|
|
@@ -93,5 +120,5 @@ export function mergeEvidence(paths, params) {
|
|
|
93
120
|
payload: { requiredAgents, missingAgents, complete, mergedPath: outPath, qaPresent },
|
|
94
121
|
});
|
|
95
122
|
rebuildIndex(paths);
|
|
96
|
-
return { ok: true, taskId: params.taskId, complete, missingAgents, mergedPath: outPath };
|
|
123
|
+
return { ok: true, taskId: params.taskId, complete, missingAgents, qaAgentId, qaPresent, mergedPath: outPath };
|
|
97
124
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ZigrixPaths } from '../state/paths.js';
|
|
2
2
|
export declare function finalizeTask(paths: ZigrixPaths, params: {
|
|
3
3
|
taskId: string;
|
|
4
|
+
/** Auto-transition to REPORTED when complete. Defaults to true. */
|
|
4
5
|
autoReport?: boolean;
|
|
5
6
|
secIssues?: boolean;
|
|
6
7
|
qaIssues?: boolean;
|
|
@@ -15,8 +15,10 @@ function autoCloseCompletedUnits(task) {
|
|
|
15
15
|
if (s.status === 'done')
|
|
16
16
|
doneAgents.add(agentId);
|
|
17
17
|
}
|
|
18
|
-
//
|
|
19
|
-
|
|
18
|
+
// orchestrator is always "done" at finalize time when identifiable
|
|
19
|
+
if (typeof task.orchestratorId === 'string' && task.orchestratorId.trim().length > 0) {
|
|
20
|
+
doneAgents.add(task.orchestratorId.trim());
|
|
21
|
+
}
|
|
20
22
|
let changed = false;
|
|
21
23
|
for (const unit of units) {
|
|
22
24
|
if (['OPEN', 'IN_PROGRESS'].includes(unit.status.toUpperCase()) && doneAgents.has(unit.owner)) {
|
|
@@ -130,7 +132,7 @@ export function finalizeTask(paths, params) {
|
|
|
130
132
|
steps,
|
|
131
133
|
};
|
|
132
134
|
if (complete) {
|
|
133
|
-
result.nextAction = '
|
|
135
|
+
result.nextAction = 'send final task report to the requesting channel/thread';
|
|
134
136
|
}
|
|
135
137
|
return result;
|
|
136
138
|
}
|
|
@@ -44,7 +44,15 @@ function collectRisks(merged) {
|
|
|
44
44
|
}
|
|
45
45
|
function qaLine(merged) {
|
|
46
46
|
const present = new Set(Array.isArray(merged.presentAgents) ? merged.presentAgents.map(String) : []);
|
|
47
|
-
|
|
47
|
+
const qaAgentId = typeof merged.qaAgentId === 'string' && merged.qaAgentId.trim().length > 0
|
|
48
|
+
? merged.qaAgentId
|
|
49
|
+
: null;
|
|
50
|
+
if (!qaAgentId) {
|
|
51
|
+
return '- QA 역할 에이전트가 식별되지 않음 (config.agents.registry role=qa 확인 필요)';
|
|
52
|
+
}
|
|
53
|
+
return present.has(qaAgentId)
|
|
54
|
+
? `- ${qaAgentId} evidence 존재, QA 수행됨`
|
|
55
|
+
: `- ${qaAgentId} evidence 없음 또는 별도 QA 미실행`;
|
|
48
56
|
}
|
|
49
57
|
export function renderReport(paths, params) {
|
|
50
58
|
const task = loadTask(paths, params.taskId);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ZigrixPaths } from '../state/paths.js';
|
|
2
2
|
import { type ZigrixTask } from '../state/tasks.js';
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const DEFAULT_REQUIRED_ROLES: readonly ["orchestrator", "qa"];
|
|
4
4
|
export declare function resolveRequiredAgents(task: Partial<ZigrixTask> & Record<string, unknown>): string[];
|
|
5
5
|
export declare function prepareWorker(paths: ZigrixPaths, params: {
|
|
6
6
|
taskId: string;
|
|
@@ -2,8 +2,38 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { appendEvent } from '../state/events.js';
|
|
4
4
|
import { ensureBaseState } from '../state/paths.js';
|
|
5
|
-
import { loadTask, saveTask } from '../state/tasks.js';
|
|
6
|
-
export const
|
|
5
|
+
import { loadTask, resolveTaskPaths, saveTask } from '../state/tasks.js';
|
|
6
|
+
export const DEFAULT_REQUIRED_ROLES = ['orchestrator', 'qa'];
|
|
7
|
+
function firstNonEmpty(...values) {
|
|
8
|
+
for (const value of values) {
|
|
9
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
10
|
+
return value.trim();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
function roleFallback(task, role) {
|
|
16
|
+
const roleMap = task.roleAgentMap;
|
|
17
|
+
if (roleMap && typeof roleMap === 'object') {
|
|
18
|
+
const mapped = roleMap[role];
|
|
19
|
+
if (Array.isArray(mapped) && mapped.length > 0) {
|
|
20
|
+
const first = firstNonEmpty(mapped[0]);
|
|
21
|
+
if (first)
|
|
22
|
+
return first;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function resolveDefaultRequiredAgents(task) {
|
|
28
|
+
const resolvedByRole = {
|
|
29
|
+
orchestrator: firstNonEmpty(task.orchestratorId) ?? roleFallback(task, 'orchestrator'),
|
|
30
|
+
qa: firstNonEmpty(task.qaAgentId) ?? roleFallback(task, 'qa'),
|
|
31
|
+
};
|
|
32
|
+
const resolved = DEFAULT_REQUIRED_ROLES
|
|
33
|
+
.map((role) => resolvedByRole[role])
|
|
34
|
+
.filter((item) => Boolean(item && item.length > 0));
|
|
35
|
+
return [...new Set(resolved)];
|
|
36
|
+
}
|
|
7
37
|
export function resolveRequiredAgents(task) {
|
|
8
38
|
for (const key of ['requiredAgents', 'selectedAgents', 'baselineRequiredAgents']) {
|
|
9
39
|
const value = task[key];
|
|
@@ -15,7 +45,7 @@ export function resolveRequiredAgents(task) {
|
|
|
15
45
|
if (workers && typeof workers === 'object' && Object.keys(workers).length > 0) {
|
|
16
46
|
return Object.keys(workers).sort();
|
|
17
47
|
}
|
|
18
|
-
return
|
|
48
|
+
return resolveDefaultRequiredAgents(task);
|
|
19
49
|
}
|
|
20
50
|
function renderPrompt(params) {
|
|
21
51
|
const sections = [
|
|
@@ -39,7 +69,7 @@ function renderPrompt(params) {
|
|
|
39
69
|
if (params.unitId || params.workPackage) {
|
|
40
70
|
sections.push('', '### Execution Context', `- unitId: ${params.unitId ?? 'N/A'}`, `- workPackage: ${params.workPackage ?? 'N/A'}`);
|
|
41
71
|
}
|
|
42
|
-
sections.push('', '### Completion', '작업 완료 후 결과와 근거를 명확히 보고하라.');
|
|
72
|
+
sections.push('', '### Completion', '작업 완료 후 다음 순서를 반드시 따르라:', '', '1. **증적(evidence) 수집** — 작업 결과 증적을 먼저 기록한다:', ' ```bash', ` zigrix evidence collect --task-id ${params.task.taskId} --agent-id ${params.agentId} --summary "<작업 결과 요약>"`, ' ```', '2. **결과 보고** — 증적 수집 완료 후 결과와 근거를 명확히 보고하라.', '', '⚠️ 증적 없이 완료하면 finalize에서 incomplete 판정된다.');
|
|
43
73
|
return sections.join('\n');
|
|
44
74
|
}
|
|
45
75
|
export function prepareWorker(paths, params) {
|
|
@@ -73,18 +103,38 @@ export function prepareWorker(paths, params) {
|
|
|
73
103
|
workPackage: params.workPackage,
|
|
74
104
|
payload: { agentId: params.agentId, description: params.description, constraints: params.constraints ?? '', dod: params.dod ?? '', promptPath },
|
|
75
105
|
});
|
|
76
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
ok: true,
|
|
108
|
+
taskId: params.taskId,
|
|
109
|
+
agentId: params.agentId,
|
|
110
|
+
promptPath,
|
|
111
|
+
prompt,
|
|
112
|
+
unitId: params.unitId,
|
|
113
|
+
workPackage: params.workPackage,
|
|
114
|
+
projectDir: projectDir ?? null,
|
|
115
|
+
...resolveTaskPaths(paths, params.taskId),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Extract sessionId from a sessionKey of the form `agent:<agentId>:subagent:<sessionId>`.
|
|
120
|
+
* Returns null if the sessionKey does not match the expected pattern.
|
|
121
|
+
*/
|
|
122
|
+
function parseSessionIdFromKey(sessionKey) {
|
|
123
|
+
const matched = sessionKey.match(/^agent:[^:]+:subagent:([^:\s]+)$/);
|
|
124
|
+
return matched?.[1] ?? null;
|
|
77
125
|
}
|
|
78
126
|
export function registerWorker(paths, params) {
|
|
79
127
|
const task = loadTask(paths, params.taskId);
|
|
80
128
|
if (!task)
|
|
81
129
|
return null;
|
|
130
|
+
// Resolve sessionId: use provided value, or fall back to parsing it from sessionKey
|
|
131
|
+
const resolvedSessionId = params.sessionId || parseSessionIdFromKey(params.sessionKey) || null;
|
|
82
132
|
task.workerSessions[params.agentId] = {
|
|
83
133
|
...(task.workerSessions[params.agentId] ?? {}),
|
|
84
134
|
status: 'dispatched',
|
|
85
135
|
sessionKey: params.sessionKey,
|
|
86
136
|
runId: params.runId ?? '',
|
|
87
|
-
sessionId:
|
|
137
|
+
sessionId: resolvedSessionId,
|
|
88
138
|
unitId: params.unitId,
|
|
89
139
|
workPackage: params.workPackage,
|
|
90
140
|
reason: params.reason ?? '',
|
|
@@ -96,10 +146,10 @@ export function registerWorker(paths, params) {
|
|
|
96
146
|
saveTask(paths, task);
|
|
97
147
|
appendEvent(paths.eventsFile, {
|
|
98
148
|
event: 'worker_dispatched', taskId: params.taskId, phase: 'execution', actor: 'zigrix', targetAgent: params.agentId, status: 'IN_PROGRESS',
|
|
99
|
-
sessionKey: params.sessionKey, sessionId:
|
|
149
|
+
sessionKey: params.sessionKey, sessionId: resolvedSessionId, unitId: params.unitId, workPackage: params.workPackage,
|
|
100
150
|
payload: { agentId: params.agentId, runId: params.runId ?? '', reason: params.reason ?? '' },
|
|
101
151
|
});
|
|
102
|
-
return { ok: true, taskId: params.taskId, agentId: params.agentId, sessionKey: params.sessionKey, runId: params.runId ?? '', sessionId:
|
|
152
|
+
return { ok: true, taskId: params.taskId, agentId: params.agentId, sessionKey: params.sessionKey, runId: params.runId ?? '', sessionId: resolvedSessionId, unitId: params.unitId, workPackage: params.workPackage, status: 'dispatched' };
|
|
103
153
|
}
|
|
104
154
|
export function completeWorker(paths, params) {
|
|
105
155
|
const task = loadTask(paths, params.taskId);
|
package/dist/rules/templates.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { STANDARD_AGENT_ROLES } from '../agents/roles.js';
|
|
1
2
|
export const TEMPLATE_PLACEHOLDERS = {
|
|
2
3
|
workerPrompt: ['taskId', 'title', 'scale', 'agentId', 'description', 'constraints', 'requiredRoles', 'workPackage', 'unitId'],
|
|
3
4
|
finalReport: ['taskId', 'title', 'scale', 'status', 'summary', 'missingAgents'],
|
|
@@ -36,12 +37,8 @@ export function validateTemplate(kind, body) {
|
|
|
36
37
|
}
|
|
37
38
|
export function validateRules(config) {
|
|
38
39
|
const knownRoles = new Set(Object.values(config.agents.registry).map((agent) => agent.role));
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
knownRoles.add('frontend');
|
|
42
|
-
knownRoles.add('backend');
|
|
43
|
-
knownRoles.add('security');
|
|
44
|
-
knownRoles.add('infra');
|
|
40
|
+
for (const role of STANDARD_AGENT_ROLES)
|
|
41
|
+
knownRoles.add(role);
|
|
45
42
|
const invalidRoles = new Set();
|
|
46
43
|
for (const scale of Object.values(config.rules.scales)) {
|
|
47
44
|
for (const role of [...scale.requiredRoles, ...scale.optionalRoles]) {
|
package/dist/state/tasks.d.ts
CHANGED
|
@@ -31,9 +31,21 @@ export type ZigrixTask = {
|
|
|
31
31
|
selectedAgents?: string[];
|
|
32
32
|
workPackages?: WorkPackage[];
|
|
33
33
|
executionUnits?: ExecutionUnit[];
|
|
34
|
+
baselineRequiredAgents?: string[];
|
|
35
|
+
candidateAgents?: string[];
|
|
36
|
+
requiredRoles?: string[];
|
|
37
|
+
optionalRoles?: string[];
|
|
38
|
+
roleAgentMap?: Record<string, string[]>;
|
|
39
|
+
orchestratorId?: string;
|
|
40
|
+
qaAgentId?: string;
|
|
34
41
|
orchestratorSessionKey?: string;
|
|
35
42
|
orchestratorSessionId?: string;
|
|
36
43
|
};
|
|
44
|
+
export declare function resolveTaskPaths(paths: ZigrixPaths, taskId: string): {
|
|
45
|
+
specPath: string;
|
|
46
|
+
metaPath: string;
|
|
47
|
+
legacyPath: string;
|
|
48
|
+
};
|
|
37
49
|
export declare function nextTaskId(paths: ZigrixPaths, prefix?: string): string;
|
|
38
50
|
export declare function saveTask(paths: ZigrixPaths, task: ZigrixTask): ZigrixTask;
|
|
39
51
|
export declare function loadTask(paths: ZigrixPaths, taskId: string): ZigrixTask | null;
|
package/dist/state/tasks.js
CHANGED
|
@@ -14,6 +14,13 @@ function specPath(paths, taskId) {
|
|
|
14
14
|
function legacyPath(paths, taskId) {
|
|
15
15
|
return path.join(paths.tasksDir, `${taskId}.json`);
|
|
16
16
|
}
|
|
17
|
+
export function resolveTaskPaths(paths, taskId) {
|
|
18
|
+
return {
|
|
19
|
+
specPath: specPath(paths, taskId),
|
|
20
|
+
metaPath: metaPath(paths, taskId),
|
|
21
|
+
legacyPath: legacyPath(paths, taskId),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
17
24
|
// ─── Read helpers ───────────────────────────────────────────────────────────
|
|
18
25
|
function readJson(filePath) {
|
|
19
26
|
try {
|
package/package.json
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zigrix",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Multi-project parallel task orchestration CLI for agent-assisted development",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/owen-ever/zigrix.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/owen-ever/zigrix",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/owen-ever/zigrix/issues"
|
|
14
|
+
},
|
|
15
|
+
"author": "Chanho Jeon",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"zigrix",
|
|
18
|
+
"cli",
|
|
19
|
+
"openclaw",
|
|
20
|
+
"multi-agent",
|
|
21
|
+
"agent-orchestration",
|
|
22
|
+
"task-orchestration",
|
|
23
|
+
"agent-matrix",
|
|
24
|
+
"task-matrix",
|
|
25
|
+
"workflow"
|
|
26
|
+
],
|
|
7
27
|
"bin": {
|
|
8
28
|
"zigrix": "./dist/index.js"
|
|
9
29
|
},
|
|
@@ -23,7 +43,8 @@
|
|
|
23
43
|
"prebuild": "npm run clean",
|
|
24
44
|
"prepack": "npm run build && npm run build:dashboard",
|
|
25
45
|
"publish:check": "npm run build && npm run build:dashboard && npm run test && npm run smoke && bash scripts/release-smoke.sh && npm pack --dry-run",
|
|
26
|
-
"
|
|
46
|
+
"release": "tsx scripts/release.ts",
|
|
47
|
+
"smoke": "node dist/index.js --version",
|
|
27
48
|
"pretest": "npm run build",
|
|
28
49
|
"test": "vitest run",
|
|
29
50
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|