zigrix 0.1.0-alpha.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 (58) hide show
  1. package/LICENSE +184 -0
  2. package/README.md +128 -0
  3. package/dist/agents/registry.d.ts +28 -0
  4. package/dist/agents/registry.js +98 -0
  5. package/dist/config/defaults.d.ts +66 -0
  6. package/dist/config/defaults.js +58 -0
  7. package/dist/config/load.d.ts +13 -0
  8. package/dist/config/load.js +96 -0
  9. package/dist/config/mutate.d.ts +10 -0
  10. package/dist/config/mutate.js +61 -0
  11. package/dist/config/schema.d.ts +132 -0
  12. package/dist/config/schema.js +123 -0
  13. package/dist/configure.d.ts +24 -0
  14. package/dist/configure.js +164 -0
  15. package/dist/doctor.d.ts +4 -0
  16. package/dist/doctor.js +99 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +791 -0
  19. package/dist/onboard.d.ts +99 -0
  20. package/dist/onboard.js +490 -0
  21. package/dist/orchestration/dispatch.d.ts +9 -0
  22. package/dist/orchestration/dispatch.js +146 -0
  23. package/dist/orchestration/evidence.d.ts +19 -0
  24. package/dist/orchestration/evidence.js +97 -0
  25. package/dist/orchestration/finalize.d.ts +7 -0
  26. package/dist/orchestration/finalize.js +136 -0
  27. package/dist/orchestration/pipeline.d.ts +11 -0
  28. package/dist/orchestration/pipeline.js +26 -0
  29. package/dist/orchestration/report.d.ts +5 -0
  30. package/dist/orchestration/report.js +92 -0
  31. package/dist/orchestration/worker.d.ts +34 -0
  32. package/dist/orchestration/worker.js +132 -0
  33. package/dist/rules/templates.d.ts +24 -0
  34. package/dist/rules/templates.js +73 -0
  35. package/dist/runner/run.d.ts +10 -0
  36. package/dist/runner/run.js +91 -0
  37. package/dist/runner/schema.d.ts +33 -0
  38. package/dist/runner/schema.js +10 -0
  39. package/dist/runner/store.d.ts +5 -0
  40. package/dist/runner/store.js +19 -0
  41. package/dist/state/events.d.ts +3 -0
  42. package/dist/state/events.js +53 -0
  43. package/dist/state/paths.d.ts +15 -0
  44. package/dist/state/paths.js +23 -0
  45. package/dist/state/tasks.d.ts +61 -0
  46. package/dist/state/tasks.js +247 -0
  47. package/dist/state/verify.d.ts +2 -0
  48. package/dist/state/verify.js +65 -0
  49. package/examples/hello-workflow.json +13 -0
  50. package/package.json +44 -0
  51. package/skills/zigrix-doctor/SKILL.md +20 -0
  52. package/skills/zigrix-evidence/SKILL.md +21 -0
  53. package/skills/zigrix-init/SKILL.md +23 -0
  54. package/skills/zigrix-report/SKILL.md +20 -0
  55. package/skills/zigrix-shared/SKILL.md +31 -0
  56. package/skills/zigrix-task-create/SKILL.md +34 -0
  57. package/skills/zigrix-task-status/SKILL.md +27 -0
  58. package/skills/zigrix-worker/SKILL.md +22 -0
@@ -0,0 +1,146 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { appendEvent } from '../state/events.js';
4
+ import { ensureBaseState } from '../state/paths.js';
5
+ 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 ───────────────────────────────────────────────
16
+ function defaultWorkPackages(scale) {
17
+ return [
18
+ { id: 'WP1', key: 'planning', title: 'planning', parallel: false },
19
+ { id: 'WP2', key: 'implementation', title: 'implementation', parallel: ['normal', 'risky', 'large'].includes(scale) },
20
+ { id: 'WP3', key: 'verification', title: 'verification', parallel: false },
21
+ { id: 'WP4', key: 'release', title: 'release', parallel: false },
22
+ ];
23
+ }
24
+ function defaultExecutionUnits(scale) {
25
+ if (['normal', 'risky', 'large'].includes(scale)) {
26
+ 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' },
32
+ ];
33
+ }
34
+ 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' },
38
+ ];
39
+ }
40
+ // ─── Boot prompt ────────────────────────────────────────────────────────────
41
+ function buildBootPrompt(task) {
42
+ return `## Orchestration Task Boot: ${task.taskId}
43
+ - **Title:** ${task.title}
44
+ - **Scale:** ${task.scale}
45
+
46
+ ---
47
+
48
+ ## ⚠️ 절대 규칙: qa-zig 호출 필수 (모든 스케일)
49
+
50
+ **scale이 simple이든 normal이든 risky든 large든, qa-zig 워커는 반드시 호출해야 한다.**
51
+
52
+ ---
53
+
54
+ ## ⚡ 필수 첫 단계 (건너뛰기 금지)
55
+
56
+ 아래 명령을 **가장 먼저** 실행하라:
57
+
58
+ \`\`\`bash
59
+ zigrix task start ${task.taskId} --json
60
+ \`\`\`
61
+
62
+ 그 후 태스크 메타를 확인하라:
63
+
64
+ \`\`\`bash
65
+ zigrix task status ${task.taskId} --json
66
+ \`\`\`
67
+
68
+ 워커 호출 시:
69
+ \`\`\`bash
70
+ zigrix worker prepare --task-id ${task.taskId} --agent-id <workerId> --description "..." --json
71
+ zigrix worker register --task-id ${task.taskId} --agent-id <workerId> --session-key <key> --json
72
+ zigrix worker complete --task-id ${task.taskId} --agent-id <workerId> --session-key <key> --run-id <rid> --json
73
+ \`\`\`
74
+
75
+ 최종 완료:
76
+ \`\`\`bash
77
+ zigrix task finalize ${task.taskId} --json
78
+ \`\`\`
79
+ `;
80
+ }
81
+ // ─── Dispatch ───────────────────────────────────────────────────────────────
82
+ export function dispatchTask(paths, params) {
83
+ ensureBaseState(paths);
84
+ // Create the task
85
+ const task = createTask(paths, {
86
+ title: params.title,
87
+ description: params.description,
88
+ scale: params.scale,
89
+ requiredAgents: [...BASELINE_REQUIRED],
90
+ projectDir: params.projectDir,
91
+ requestedBy: params.requestedBy,
92
+ });
93
+ // Enrich with orchestration metadata
94
+ task.selectedAgents = [...BASELINE_REQUIRED];
95
+ task.workPackages = defaultWorkPackages(params.scale);
96
+ task.executionUnits = defaultExecutionUnits(params.scale);
97
+ saveTask(paths, task);
98
+ // Write dispatch prompt
99
+ const promptPath = path.join(paths.promptsDir, `${task.taskId}-dispatch.md`);
100
+ const dispatchPrompt = [
101
+ `## Orchestration Task: ${task.taskId}`,
102
+ '',
103
+ '### 기본 정보',
104
+ `- **Title:** ${task.title}`,
105
+ `- **Scale:** ${task.scale}`,
106
+ `- **Baseline Required Agents:** ${BASELINE_REQUIRED.join(', ')}`,
107
+ `- **Candidate Agents:** ${CANDIDATE_AGENTS.join(', ')}`,
108
+ params.projectDir ? `- **Project Dir:** ${params.projectDir}` : '',
109
+ '',
110
+ '### 요청 내용',
111
+ params.description,
112
+ params.constraints ? `\n### 제약사항\n${params.constraints}` : '',
113
+ '',
114
+ '### 선택 규칙',
115
+ ...Object.entries(SELECTION_HINTS).map(([k, v]) => `- ${k}: ${v}`),
116
+ ].filter(Boolean).join('\n');
117
+ fs.writeFileSync(promptPath, `${dispatchPrompt}\n`, 'utf8');
118
+ const bootPrompt = buildBootPrompt(task);
119
+ appendEvent(paths.eventsFile, {
120
+ event: 'task_dispatched',
121
+ taskId: task.taskId,
122
+ phase: 'dispatch',
123
+ actor: 'zigrix',
124
+ status: 'OPEN',
125
+ payload: {
126
+ scale: task.scale,
127
+ baselineRequiredAgents: BASELINE_REQUIRED,
128
+ candidateAgents: CANDIDATE_AGENTS,
129
+ projectDir: params.projectDir ?? null,
130
+ },
131
+ });
132
+ rebuildIndex(paths);
133
+ return {
134
+ ok: true,
135
+ taskId: task.taskId,
136
+ title: task.title,
137
+ scale: task.scale,
138
+ baselineRequiredAgents: BASELINE_REQUIRED,
139
+ candidateAgents: CANDIDATE_AGENTS,
140
+ specPath: path.join(paths.tasksDir, `${task.taskId}.md`),
141
+ metaPath: path.join(paths.tasksDir, `${task.taskId}.meta.json`),
142
+ promptPath,
143
+ proZigPrompt: bootPrompt,
144
+ projectDir: params.projectDir ?? null,
145
+ };
146
+ }
@@ -0,0 +1,19 @@
1
+ import { type ZigrixPaths } from '../state/paths.js';
2
+ export declare function collectEvidence(paths: ZigrixPaths, params: {
3
+ taskId: string;
4
+ agentId: string;
5
+ runId?: string;
6
+ unitId?: string;
7
+ sessionKey?: string;
8
+ sessionId?: string;
9
+ transcript?: string;
10
+ summary?: string;
11
+ toolResults?: string[];
12
+ notes?: string;
13
+ limit?: number;
14
+ }): Record<string, unknown> | null;
15
+ export declare function mergeEvidence(paths: ZigrixPaths, params: {
16
+ taskId: string;
17
+ requiredAgents?: string[];
18
+ requireQa?: boolean;
19
+ }): Record<string, unknown> | null;
@@ -0,0 +1,97 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { appendEvent, nowIso } from '../state/events.js';
4
+ import { ensureBaseState } from '../state/paths.js';
5
+ import { loadTask, rebuildIndex } from '../state/tasks.js';
6
+ import { resolveRequiredAgents } from './worker.js';
7
+ function readTranscript(transcriptPath, limit = 40) {
8
+ if (!transcriptPath || !fs.existsSync(transcriptPath))
9
+ return [];
10
+ return fs.readFileSync(transcriptPath, 'utf8')
11
+ .split(/\r?\n/)
12
+ .filter(Boolean)
13
+ .slice(-limit)
14
+ .flatMap((line) => {
15
+ try {
16
+ const parsed = JSON.parse(line);
17
+ return [parsed];
18
+ }
19
+ catch {
20
+ return [];
21
+ }
22
+ });
23
+ }
24
+ function extractEvidence(rows) {
25
+ let lastAssistant = null;
26
+ const toolResults = [];
27
+ for (const row of rows) {
28
+ if (row.role === 'assistant' && row.content)
29
+ lastAssistant = row.content;
30
+ if (row.role === 'toolResult')
31
+ toolResults.push(row.content);
32
+ }
33
+ return { lastAssistant, toolResults: toolResults.slice(-3) };
34
+ }
35
+ export function collectEvidence(paths, params) {
36
+ ensureBaseState(paths);
37
+ const task = loadTask(paths, params.taskId);
38
+ if (!task)
39
+ return null;
40
+ const transcriptRows = params.transcript ? readTranscript(path.resolve(params.transcript), params.limit ?? 40) : [];
41
+ const extracted = extractEvidence(transcriptRows);
42
+ if (params.summary) {
43
+ extracted.summary = params.summary;
44
+ extracted.lastAssistant = params.summary;
45
+ }
46
+ if (params.toolResults?.length)
47
+ extracted.toolResults = [...params.toolResults];
48
+ if (params.notes)
49
+ extracted.notes = params.notes;
50
+ const outDir = path.join(paths.evidenceDir, params.taskId);
51
+ fs.mkdirSync(outDir, { recursive: true });
52
+ const outPath = path.join(outDir, `${params.agentId}.json`);
53
+ const payload = {
54
+ ts: nowIso(), taskId: params.taskId, agentId: params.agentId, unitId: params.unitId ?? null,
55
+ runId: params.runId ?? '', sessionKey: params.sessionKey ?? null, sessionId: params.sessionId ?? null,
56
+ transcriptPath: params.transcript ? path.resolve(params.transcript) : null, evidence: extracted,
57
+ };
58
+ fs.writeFileSync(outPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
59
+ appendEvent(paths.eventsFile, {
60
+ event: 'evidence_collected', taskId: params.taskId, phase: 'verification', actor: params.agentId, status: 'IN_PROGRESS', unitId: params.unitId ?? null,
61
+ sessionKey: params.sessionKey ?? null, payload: { agentId: params.agentId, runId: params.runId ?? '', evidencePath: outPath },
62
+ });
63
+ rebuildIndex(paths);
64
+ return { ok: true, taskId: params.taskId, agentId: params.agentId, evidencePath: outPath, sessionId: params.sessionId ?? null, unitId: params.unitId ?? null };
65
+ }
66
+ export function mergeEvidence(paths, params) {
67
+ const task = loadTask(paths, params.taskId);
68
+ if (!task)
69
+ return null;
70
+ const taskDir = path.join(paths.evidenceDir, params.taskId);
71
+ fs.mkdirSync(taskDir, { recursive: true });
72
+ const files = fs.readdirSync(taskDir).filter((name) => name.endsWith('.json') && name !== '_merged.json').sort();
73
+ const items = files.flatMap((file) => {
74
+ try {
75
+ const data = JSON.parse(fs.readFileSync(path.join(taskDir, file), 'utf8'));
76
+ const agentId = String(data.agentId ?? path.basename(file, '.json'));
77
+ return [{ agentId, unitId: data.unitId, runId: data.runId, sessionKey: data.sessionKey, sessionId: data.sessionId, transcriptPath: data.transcriptPath, evidence: data.evidence ?? {} }];
78
+ }
79
+ catch {
80
+ return [];
81
+ }
82
+ });
83
+ const presentAgents = [...new Set(items.map((item) => String(item.agentId)))].sort();
84
+ const requiredAgents = [...(params.requiredAgents?.length ? params.requiredAgents : resolveRequiredAgents(task))];
85
+ 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 };
89
+ const outPath = path.join(taskDir, '_merged.json');
90
+ fs.writeFileSync(outPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
91
+ appendEvent(paths.eventsFile, {
92
+ event: 'evidence_merged', taskId: params.taskId, phase: 'verification', actor: 'zigrix', status: complete ? 'DONE_PENDING_REPORT' : 'IN_PROGRESS',
93
+ payload: { requiredAgents, missingAgents, complete, mergedPath: outPath, qaPresent },
94
+ });
95
+ rebuildIndex(paths);
96
+ return { ok: true, taskId: params.taskId, complete, missingAgents, mergedPath: outPath };
97
+ }
@@ -0,0 +1,7 @@
1
+ import { type ZigrixPaths } from '../state/paths.js';
2
+ export declare function finalizeTask(paths: ZigrixPaths, params: {
3
+ taskId: string;
4
+ autoReport?: boolean;
5
+ secIssues?: boolean;
6
+ qaIssues?: boolean;
7
+ }): Record<string, unknown> | null;
@@ -0,0 +1,136 @@
1
+ import { appendEvent } from '../state/events.js';
2
+ import { loadTask, rebuildIndex, saveTask } from '../state/tasks.js';
3
+ import { mergeEvidence } from './evidence.js';
4
+ import { renderReport } from './report.js';
5
+ import { resolveRequiredAgents } from './worker.js';
6
+ // ─── Unit completeness ─────────────────────────────────────────────────────
7
+ function autoCloseCompletedUnits(task) {
8
+ const units = task.executionUnits;
9
+ if (!units?.length)
10
+ return false;
11
+ // Build set of agents that have completed their work
12
+ const doneAgents = new Set();
13
+ for (const [agentId, session] of Object.entries(task.workerSessions)) {
14
+ const s = session;
15
+ if (s.status === 'done')
16
+ doneAgents.add(agentId);
17
+ }
18
+ // pro-zig is always "done" at finalize time
19
+ doneAgents.add('pro-zig');
20
+ let changed = false;
21
+ for (const unit of units) {
22
+ if (['OPEN', 'IN_PROGRESS'].includes(unit.status.toUpperCase()) && doneAgents.has(unit.owner)) {
23
+ unit.status = 'DONE';
24
+ changed = true;
25
+ }
26
+ }
27
+ return changed;
28
+ }
29
+ function summarizeUnits(task) {
30
+ const units = task.executionUnits;
31
+ if (!units?.length) {
32
+ return { hasUnits: false, complete: true, missingUnits: [], missingWorkPackages: [] };
33
+ }
34
+ const missing = [];
35
+ const missingWp = new Set();
36
+ for (const unit of units) {
37
+ if (unit.status.toUpperCase() !== 'DONE') {
38
+ missing.push({ id: unit.id, title: unit.title, status: unit.status, owner: unit.owner ?? null });
39
+ if (unit.workPackage)
40
+ missingWp.add(unit.workPackage);
41
+ }
42
+ }
43
+ return {
44
+ hasUnits: true,
45
+ complete: missing.length === 0,
46
+ missingUnits: missing,
47
+ missingWorkPackages: [...missingWp].sort(),
48
+ };
49
+ }
50
+ // ─── Finalize ───────────────────────────────────────────────────────────────
51
+ export function finalizeTask(paths, params) {
52
+ const task = loadTask(paths, params.taskId);
53
+ if (!task)
54
+ return null;
55
+ const steps = [];
56
+ const reqAgents = resolveRequiredAgents(task);
57
+ // Auto-close units whose owners have completed
58
+ if (autoCloseCompletedUnits(task)) {
59
+ saveTask(paths, task);
60
+ }
61
+ // Merge evidence
62
+ const merged = mergeEvidence(paths, { taskId: params.taskId, requiredAgents: reqAgents, requireQa: true });
63
+ steps.push({ step: 'merge_evidence', result: merged });
64
+ const agentComplete = Boolean(merged?.complete);
65
+ const missingAgents = Array.isArray(merged?.missingAgents) ? merged.missingAgents : [];
66
+ // Unit completeness
67
+ const unitSummary = summarizeUnits(task);
68
+ steps.push({
69
+ step: 'unit_completeness',
70
+ hasUnits: unitSummary.hasUnits,
71
+ complete: unitSummary.complete,
72
+ missingUnits: unitSummary.missingUnits,
73
+ missingWorkPackages: unitSummary.missingWorkPackages,
74
+ });
75
+ const complete = agentComplete && unitSummary.complete;
76
+ if (params.autoReport && complete) {
77
+ if (params.secIssues || params.qaIssues) {
78
+ // Needs owner confirmation
79
+ task.status = 'DONE_PENDING_REPORT';
80
+ saveTask(paths, task);
81
+ appendEvent(paths.eventsFile, {
82
+ event: 'owner_confirmation_required',
83
+ taskId: params.taskId,
84
+ phase: 'reporting',
85
+ actor: 'zigrix',
86
+ status: 'DONE_PENDING_REPORT',
87
+ payload: { securityIssues: params.secIssues, qaIssues: params.qaIssues },
88
+ });
89
+ steps.push({ step: 'owner_confirmation_required', ok: true });
90
+ }
91
+ else {
92
+ // Auto report
93
+ const report = renderReport(paths, { taskId: params.taskId, recordEvents: true });
94
+ steps.push({ step: 'report_render', result: report });
95
+ task.status = 'REPORTED';
96
+ saveTask(paths, task);
97
+ appendEvent(paths.eventsFile, {
98
+ event: 'reported',
99
+ taskId: params.taskId,
100
+ phase: 'reporting',
101
+ actor: 'zigrix',
102
+ status: 'REPORTED',
103
+ payload: { note: 'auto_report by finalize', requiredAgents: reqAgents },
104
+ });
105
+ steps.push({ step: 'reported', ok: true });
106
+ }
107
+ }
108
+ else if (!complete) {
109
+ steps.push({
110
+ step: 'incomplete',
111
+ missingAgents,
112
+ missingUnits: unitSummary.missingUnits,
113
+ missingWorkPackages: unitSummary.missingWorkPackages,
114
+ });
115
+ }
116
+ else {
117
+ // Complete but no auto-report
118
+ task.status = 'DONE_PENDING_REPORT';
119
+ saveTask(paths, task);
120
+ }
121
+ rebuildIndex(paths);
122
+ const result = {
123
+ ok: true,
124
+ taskId: params.taskId,
125
+ requiredAgents: reqAgents,
126
+ complete,
127
+ missingAgents,
128
+ missingUnits: unitSummary.missingUnits,
129
+ missingWorkPackages: unitSummary.missingWorkPackages,
130
+ steps,
131
+ };
132
+ if (complete) {
133
+ result.nextAction = 'sessions_send(sessionKey: "agent:main:main", message: "<taskId> 완료: <요약>")';
134
+ }
135
+ return result;
136
+ }
@@ -0,0 +1,11 @@
1
+ import { type ZigrixPaths } from '../state/paths.js';
2
+ export declare function runPipeline(paths: ZigrixPaths, params: {
3
+ title: string;
4
+ description: string;
5
+ scale?: string;
6
+ requiredAgents?: string[];
7
+ evidenceSummaries?: string[];
8
+ requireQa?: boolean;
9
+ autoReport?: boolean;
10
+ recordFeedback?: boolean;
11
+ }): Record<string, unknown>;
@@ -0,0 +1,26 @@
1
+ import { collectEvidence, mergeEvidence } from './evidence.js';
2
+ import { renderReport } from './report.js';
3
+ import { createTask, updateTaskStatus } from '../state/tasks.js';
4
+ export function runPipeline(paths, params) {
5
+ const steps = [];
6
+ const task = createTask(paths, { title: params.title, description: params.description, scale: params.scale ?? 'normal', requiredAgents: params.requiredAgents ?? [] });
7
+ const taskId = task.taskId;
8
+ steps.push({ step: 'task_create', result: task });
9
+ steps.push({ step: 'task_start', result: updateTaskStatus(paths, taskId, 'IN_PROGRESS') });
10
+ for (const raw of params.evidenceSummaries ?? []) {
11
+ if (!raw.includes('='))
12
+ throw new Error(`invalid evidence summary format: ${raw} (expected agentId=summary)`);
13
+ const [agentId, summary] = raw.split(/=(.*)/s, 2);
14
+ steps.push({ step: 'evidence_collect', agentId: agentId.trim(), result: collectEvidence(paths, { taskId, agentId: agentId.trim(), summary: summary.trim() }) });
15
+ }
16
+ const merged = mergeEvidence(paths, { taskId, requiredAgents: params.requiredAgents, requireQa: params.requireQa });
17
+ steps.push({ step: 'evidence_merge', result: merged });
18
+ if (merged?.complete) {
19
+ steps.push({ step: 'task_finalize', result: updateTaskStatus(paths, taskId, 'DONE_PENDING_REPORT') });
20
+ if (params.autoReport) {
21
+ steps.push({ step: 'report_render', result: renderReport(paths, { taskId, recordEvents: params.recordFeedback }) });
22
+ steps.push({ step: 'task_report', result: updateTaskStatus(paths, taskId, 'REPORTED') });
23
+ }
24
+ }
25
+ return { ok: true, taskId, complete: Boolean(merged?.complete), missingAgents: Array.isArray(merged?.missingAgents) ? merged.missingAgents : [], steps };
26
+ }
@@ -0,0 +1,5 @@
1
+ import { type ZigrixPaths } from '../state/paths.js';
2
+ export declare function renderReport(paths: ZigrixPaths, params: {
3
+ taskId: string;
4
+ recordEvents?: boolean;
5
+ }): Record<string, unknown> | null;
@@ -0,0 +1,92 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { appendEvent } from '../state/events.js';
4
+ import { loadTask, rebuildIndex } from '../state/tasks.js';
5
+ function readJson(filePath) {
6
+ try {
7
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
8
+ }
9
+ catch {
10
+ return {};
11
+ }
12
+ }
13
+ function summarizeAgents(merged) {
14
+ const items = Array.isArray(merged.items) ? merged.items : [];
15
+ const fromItems = items.flatMap((item) => typeof item === 'object' && item && 'agentId' in item ? [String(item.agentId)] : []);
16
+ if (fromItems.length > 0)
17
+ return fromItems;
18
+ return Array.isArray(merged.presentAgents) ? merged.presentAgents.map(String) : [];
19
+ }
20
+ function collectAgentLines(merged) {
21
+ const items = Array.isArray(merged.items) ? merged.items : [];
22
+ const lines = items.flatMap((item) => {
23
+ if (!item || typeof item !== 'object')
24
+ return [];
25
+ const row = item;
26
+ const evidence = (row.evidence && typeof row.evidence === 'object' ? row.evidence : {});
27
+ const summary = evidence.summary ?? evidence.lastAssistant ?? evidence.verdict ?? '수행 기록 있음';
28
+ return [`- ${String(row.agentId ?? 'unknown')}: ${String(summary)}`];
29
+ });
30
+ return lines.length > 0 ? lines : ['- 참여 에이전트 기록 없음'];
31
+ }
32
+ function collectRisks(merged) {
33
+ const risks = new Set();
34
+ const items = Array.isArray(merged.items) ? merged.items : [];
35
+ for (const item of items) {
36
+ if (!item || typeof item !== 'object')
37
+ continue;
38
+ const evidence = (item.evidence ?? {});
39
+ const entries = Array.isArray(evidence.risks) ? evidence.risks : [];
40
+ for (const risk of entries)
41
+ risks.add(String(risk));
42
+ }
43
+ return [...risks];
44
+ }
45
+ function qaLine(merged) {
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 미실행';
48
+ }
49
+ export function renderReport(paths, params) {
50
+ const task = loadTask(paths, params.taskId);
51
+ if (!task)
52
+ return null;
53
+ const merged = readJson(path.join(paths.evidenceDir, params.taskId, '_merged.json'));
54
+ const title = String(task.title ?? params.taskId);
55
+ const scale = String(task.scale ?? 'unknown');
56
+ const agents = summarizeAgents(merged);
57
+ const agentLines = collectAgentLines(merged);
58
+ const risks = collectRisks(merged);
59
+ const missing = Array.isArray(merged.missingAgents) ? merged.missingAgents.map(String) : [];
60
+ const complete = Boolean(merged.complete ?? false);
61
+ const finalState = complete ? '완료(REPORTED)' : '부분완료/추가확인필요';
62
+ const summaryLines = [`- 태스크: \`${params.taskId}\` / ${title}`, `- 상태: ${finalState}`];
63
+ if (agents.length > 0)
64
+ summaryLines.push(`- 참여 에이전트: ${agents.join(', ')}`);
65
+ const riskLines = [...(missing.length > 0 ? [`- 누락 에이전트: ${missing.join(', ')}`] : []), ...risks.map((risk) => `- ${risk}`)];
66
+ if (riskLines.length === 0)
67
+ riskLines.push('- 특이 리스크 없음');
68
+ const report = [
69
+ `작업유형: ${scale}`,
70
+ '',
71
+ '진행 요약',
72
+ ...summaryLines,
73
+ '',
74
+ '에이전트별 수행 내역',
75
+ ...agentLines,
76
+ '',
77
+ 'QA 결과',
78
+ qaLine(merged),
79
+ '',
80
+ '남은 리스크 / 후속 액션',
81
+ ...riskLines,
82
+ '',
83
+ '피드백 요청',
84
+ '- 만족도(1~5), 좋았던 점, 개선할 점 있으면 짧게 주세요.',
85
+ ].join('\n');
86
+ if (params.recordEvents) {
87
+ appendEvent(paths.eventsFile, { event: 'user_report_prepared', taskId: params.taskId, phase: 'reporting', actor: 'zigrix', payload: { preview: report.slice(0, 300) } });
88
+ appendEvent(paths.eventsFile, { event: 'feedback_requested', taskId: params.taskId, phase: 'reporting', actor: 'zigrix', payload: { questions: ['만족도(1~5)는?', '좋았던 점은?', '개선할 점은?'] } });
89
+ rebuildIndex(paths);
90
+ }
91
+ return { ok: true, taskId: params.taskId, complete, missingAgents: missing, report };
92
+ }
@@ -0,0 +1,34 @@
1
+ import { type ZigrixPaths } from '../state/paths.js';
2
+ import { type ZigrixTask } from '../state/tasks.js';
3
+ export declare const DEFAULT_REQUIRED_AGENTS: string[];
4
+ export declare function resolveRequiredAgents(task: Partial<ZigrixTask> & Record<string, unknown>): string[];
5
+ export declare function prepareWorker(paths: ZigrixPaths, params: {
6
+ taskId: string;
7
+ agentId: string;
8
+ description: string;
9
+ constraints?: string;
10
+ unitId?: string;
11
+ workPackage?: string;
12
+ dod?: string;
13
+ projectDir?: string;
14
+ }): Record<string, unknown> | null;
15
+ export declare function registerWorker(paths: ZigrixPaths, params: {
16
+ taskId: string;
17
+ agentId: string;
18
+ sessionKey: string;
19
+ runId?: string;
20
+ sessionId?: string;
21
+ unitId?: string;
22
+ workPackage?: string;
23
+ reason?: string;
24
+ }): Record<string, unknown> | null;
25
+ export declare function completeWorker(paths: ZigrixPaths, params: {
26
+ taskId: string;
27
+ agentId: string;
28
+ sessionKey: string;
29
+ runId: string;
30
+ result?: 'done' | 'blocked' | 'skipped';
31
+ sessionId?: string;
32
+ unitId?: string;
33
+ workPackage?: string;
34
+ }): Record<string, unknown> | null;