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.
Files changed (86) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +159 -120
  3. package/dist/agents/registry.js +19 -2
  4. package/dist/agents/roles.d.ts +10 -0
  5. package/dist/agents/roles.js +83 -0
  6. package/dist/config/defaults.d.ts +88 -6
  7. package/dist/config/defaults.js +82 -50
  8. package/dist/config/load.d.ts +5 -3
  9. package/dist/config/load.js +69 -30
  10. package/dist/config/schema.d.ts +46 -4
  11. package/dist/config/schema.js +49 -3
  12. package/dist/configure.d.ts +2 -0
  13. package/dist/configure.js +37 -14
  14. package/dist/dashboard/.next/BUILD_ID +1 -1
  15. package/dist/dashboard/.next/app-build-manifest.json +13 -13
  16. package/dist/dashboard/.next/app-path-routes-manifest.json +3 -3
  17. package/dist/dashboard/.next/build-manifest.json +2 -2
  18. package/dist/dashboard/.next/prerender-manifest.json +6 -6
  19. package/dist/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/dist/dashboard/.next/server/app/_not-found.html +1 -1
  21. package/dist/dashboard/.next/server/app/_not-found.rsc +1 -1
  22. package/dist/dashboard/.next/server/app/api/auth/login/route.js +1 -1
  23. package/dist/dashboard/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  24. package/dist/dashboard/.next/server/app/api/auth/logout/route.js +1 -1
  25. package/dist/dashboard/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  26. package/dist/dashboard/.next/server/app/api/auth/session/route.js +1 -1
  27. package/dist/dashboard/.next/server/app/api/auth/session/route_client-reference-manifest.js +1 -1
  28. package/dist/dashboard/.next/server/app/api/auth/setup/route.js +1 -1
  29. package/dist/dashboard/.next/server/app/api/auth/setup/route_client-reference-manifest.js +1 -1
  30. package/dist/dashboard/.next/server/app/api/overview/route_client-reference-manifest.js +1 -1
  31. package/dist/dashboard/.next/server/app/api/stream/route_client-reference-manifest.js +1 -1
  32. package/dist/dashboard/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  33. package/dist/dashboard/.next/server/app/api/tasks/[taskId]/conversation/route_client-reference-manifest.js +1 -1
  34. package/dist/dashboard/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  35. package/dist/dashboard/.next/server/app/login/page_client-reference-manifest.js +1 -1
  36. package/dist/dashboard/.next/server/app/login.html +1 -1
  37. package/dist/dashboard/.next/server/app/login.rsc +1 -1
  38. package/dist/dashboard/.next/server/app/page.js +2 -2
  39. package/dist/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/dist/dashboard/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  41. package/dist/dashboard/.next/server/app/setup.html +1 -1
  42. package/dist/dashboard/.next/server/app/setup.rsc +1 -1
  43. package/dist/dashboard/.next/server/app-paths-manifest.json +3 -3
  44. package/dist/dashboard/.next/server/chunks/972.js +1 -1
  45. package/dist/dashboard/.next/server/functions-config-manifest.json +3 -3
  46. package/dist/dashboard/.next/server/middleware.js +1 -1
  47. package/dist/dashboard/.next/server/pages/404.html +1 -1
  48. package/dist/dashboard/.next/server/pages/500.html +1 -1
  49. package/dist/dashboard/.next/static/chunks/app/page-0314989c31e18b4b.js +1 -0
  50. package/dist/dashboard/.next/static/css/{94d75aff24d0c077.css → c3a7306cb2ba3f6c.css} +1 -1
  51. package/dist/dashboard.js +47 -0
  52. package/dist/doctor.js +28 -5
  53. package/dist/index.js +175 -171
  54. package/dist/onboard.d.ts +76 -2
  55. package/dist/onboard.js +529 -25
  56. package/dist/orchestration/dispatch.d.ts +3 -1
  57. package/dist/orchestration/dispatch.js +173 -45
  58. package/dist/orchestration/evidence.js +31 -4
  59. package/dist/orchestration/finalize.d.ts +1 -0
  60. package/dist/orchestration/finalize.js +5 -3
  61. package/dist/orchestration/report.js +9 -1
  62. package/dist/orchestration/worker.d.ts +1 -1
  63. package/dist/orchestration/worker.js +58 -8
  64. package/dist/rules/templates.js +3 -6
  65. package/dist/state/tasks.d.ts +12 -0
  66. package/dist/state/tasks.js +7 -0
  67. package/package.json +23 -2
  68. package/rules/defaults/README.md +9 -9
  69. package/rules/defaults/{back-zig.md → backend-agent.md} +4 -4
  70. package/rules/defaults/{front-zig.md → frontend-agent.md} +4 -4
  71. package/rules/defaults/orchestrator-agent.md +261 -0
  72. package/rules/defaults/{qa-zig.md → qa-agent.md} +11 -11
  73. package/rules/defaults/{sec-zig.md → security-agent.md} +4 -4
  74. package/rules/defaults/{sys-zig.md → system-agent.md} +8 -9
  75. package/rules/defaults/worker-common.md +25 -19
  76. package/skills/zigrix-doctor/SKILL.md +4 -2
  77. package/skills/zigrix-evidence/SKILL.md +7 -3
  78. package/skills/zigrix-main-agent-guide/SKILL.md +128 -0
  79. package/skills/zigrix-shared/SKILL.md +27 -3
  80. package/skills/zigrix-task-create/SKILL.md +8 -2
  81. package/skills/zigrix-task-status/SKILL.md +5 -2
  82. package/skills/zigrix-worker/SKILL.md +12 -4
  83. package/dist/dashboard/.next/static/chunks/app/page-25f54e54e74fb3af.js +0 -1
  84. package/rules/defaults/pro-zig.md +0 -238
  85. /package/dist/dashboard/.next/static/{2a4glWei05xr4Jg0Ly6cp → PT4hYxzrqxj-Zq4ZjtKNg}/_buildManifest.js +0 -0
  86. /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 dispatchTask(paths: ZigrixPaths, params: {
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
- // ─── Constants ──────────────────────────────────────────────────────────────
7
- const BASELINE_REQUIRED = ['pro-zig', 'qa-zig'];
8
- const CANDIDATE_AGENTS = ['front-zig', 'back-zig', 'sys-zig', 'sec-zig'];
9
- const SELECTION_HINTS = {
10
- 'front-zig': 'UI / styling / client-side integration when present',
11
- 'back-zig': 'API / DB / server-side logic when present',
12
- 'sys-zig': 'architecture / infra / system-wide change when present',
13
- 'sec-zig': 'security-sensitive scope or risky changes when present',
14
- };
15
- // ─── Execution unit skeletons ───────────────────────────────────────────────
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: 'pro-zig', workPackage: 'planning', dependsOn: [], parallel: false, status: 'OPEN', dod: 'scope / constraints / edge cases fixed' },
28
- { id: 'U2', title: 'implementation planning / work package split', kind: 'planning', owner: 'pro-zig', workPackage: 'planning', dependsOn: ['U1'], parallel: false, status: 'OPEN', dod: 'execution units and work packages fixed' },
29
- { id: 'U3', title: 'implementation slices', kind: 'implementation', owner: 'pro-zig', workPackage: 'implementation', dependsOn: ['U2'], parallel: true, status: 'OPEN', dod: 'required work packages complete' },
30
- { id: 'U4', title: 'qa / regression', kind: 'verification', owner: 'qa-zig', workPackage: 'verification', dependsOn: ['U3'], parallel: false, status: 'OPEN', dod: 'qa evidence attached' },
31
- { id: 'U5', title: 'report / deploy / wrap-up', kind: 'reporting', owner: 'pro-zig', workPackage: 'release', dependsOn: ['U4'], parallel: false, status: 'OPEN', dod: 'final report prepared and deployment decision recorded' },
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: 'pro-zig', workPackage: 'planning', dependsOn: [], parallel: false, status: 'OPEN', dod: 'scope / constraints / edge cases fixed' },
36
- { id: 'U2', title: 'implementation slice', kind: 'implementation', owner: 'pro-zig', workPackage: 'implementation', dependsOn: ['U1'], parallel: false, status: 'OPEN', dod: 'main implementation slice complete' },
37
- { id: 'U3', title: 'qa / regression', kind: 'verification', owner: 'qa-zig', workPackage: 'verification', dependsOn: ['U2'], parallel: false, status: 'OPEN', dod: 'qa evidence attached' },
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
- // ─── Boot prompt ────────────────────────────────────────────────────────────
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
- ## ⚠️ 절대 규칙: qa-zig 호출 필수 (모든 스케일)
148
+ ## ⚠️ 절대 규칙: QA 역할 워커 호출 필수
49
149
 
50
- **scale이 simple이든 normal이든 risky든 large든, qa-zig 워커는 반드시 호출해야 한다.**
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
- // ─── Dispatch ───────────────────────────────────────────────────────────────
82
- export function dispatchTask(paths, params) {
181
+ export function dispatchTask(paths, config, params) {
83
182
  ensureBaseState(paths);
84
- // Create the task
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: [...BASELINE_REQUIRED],
90
- projectDir: params.projectDir,
189
+ requiredAgents: [...selection.requiredAgents],
190
+ projectDir,
91
191
  requestedBy: params.requestedBy,
92
192
  });
93
- // Enrich with orchestration metadata
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
- `- **Baseline Required Agents:** ${BASELINE_REQUIRED.join(', ')}`,
107
- `- **Candidate Agents:** ${CANDIDATE_AGENTS.join(', ')}`,
108
- params.projectDir ? `- **Project Dir:** ${params.projectDir}` : '',
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(SELECTION_HINTS).map(([k, v]) => `- ${k}: ${v}`),
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 bootPrompt = buildBootPrompt(task);
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
- baselineRequiredAgents: BASELINE_REQUIRED,
128
- candidateAgents: CANDIDATE_AGENTS,
129
- projectDir: params.projectDir ?? null,
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
- baselineRequiredAgents: BASELINE_REQUIRED,
139
- candidateAgents: CANDIDATE_AGENTS,
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
- proZigPrompt: bootPrompt,
144
- projectDir: params.projectDir ?? null,
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 qaPresent = presentAgents.includes('qa-zig');
87
- const complete = missingAgents.length === 0 && (!(params.requireQa ?? false) || qaPresent);
88
- const merged = { ts: nowIso(), taskId: params.taskId, requiredAgents, presentAgents, missingAgents, qaPresent, complete, items };
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
- // pro-zig is always "done" at finalize time
19
- doneAgents.add('pro-zig');
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 = 'sessions_send(sessionKey: "agent:main:main", message: "<taskId> 완료: <요약>")';
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
- return present.has('qa-zig') ? '- qa-zig evidence 존재, QA 수행됨' : '- qa-zig evidence 없음 또는 별도 QA 미실행';
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 DEFAULT_REQUIRED_AGENTS: string[];
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 DEFAULT_REQUIRED_AGENTS = ['pro-zig', 'qa-zig'];
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 [...DEFAULT_REQUIRED_AGENTS];
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 { ok: true, taskId: params.taskId, agentId: params.agentId, promptPath, prompt, unitId: params.unitId, workPackage: params.workPackage };
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: params.sessionId ?? null,
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: params.sessionId ?? null, unitId: params.unitId, workPackage: params.workPackage,
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: params.sessionId ?? null, unitId: params.unitId, workPackage: params.workPackage, status: 'dispatched' };
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);
@@ -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
- knownRoles.add('orchestrator');
40
- knownRoles.add('qa');
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]) {
@@ -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;
@@ -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-alpha.8",
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
- "smoke": "node dist/index.js --version && node dist/index.js config validate --json",
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"