opencodekit 0.16.4 β 0.16.6
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/dist/index.js +1 -1
- package/dist/template/.opencode/AGENTS.md +106 -384
- package/dist/template/.opencode/README.md +170 -104
- package/dist/template/.opencode/agent/build.md +39 -32
- package/dist/template/.opencode/agent/explore.md +2 -0
- package/dist/template/.opencode/agent/review.md +3 -0
- package/dist/template/.opencode/agent/scout.md +22 -11
- package/dist/template/.opencode/command/create.md +164 -106
- package/dist/template/.opencode/command/design.md +5 -1
- package/dist/template/.opencode/command/handoff.md +6 -4
- package/dist/template/.opencode/command/init.md +1 -1
- package/dist/template/.opencode/command/plan.md +26 -23
- package/dist/template/.opencode/command/research.md +13 -6
- package/dist/template/.opencode/command/resume.md +8 -6
- package/dist/template/.opencode/command/ship.md +1 -1
- package/dist/template/.opencode/command/start.md +30 -25
- package/dist/template/.opencode/command/status.md +9 -42
- package/dist/template/.opencode/command/verify.md +11 -11
- package/dist/template/.opencode/memory/README.md +67 -37
- package/dist/template/.opencode/memory/_templates/prd.md +102 -18
- package/dist/template/.opencode/memory/project/gotchas.md +31 -0
- package/dist/template/.opencode/memory.db +0 -0
- package/dist/template/.opencode/memory.db-shm +0 -0
- package/dist/template/.opencode/memory.db-wal +0 -0
- package/dist/template/.opencode/opencode.json +0 -10
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/skill/beads/SKILL.md +164 -380
- package/dist/template/.opencode/skill/beads/references/BOUNDARIES.md +23 -22
- package/dist/template/.opencode/skill/beads/references/DEPENDENCIES.md +23 -29
- package/dist/template/.opencode/skill/beads/references/RESUMABILITY.md +5 -8
- package/dist/template/.opencode/skill/beads/references/WORKFLOWS.md +43 -39
- package/dist/template/.opencode/skill/beads-bridge/SKILL.md +80 -53
- package/dist/template/.opencode/skill/brainstorming/SKILL.md +19 -5
- package/dist/template/.opencode/skill/context-engineering/SKILL.md +30 -63
- package/dist/template/.opencode/skill/context-management/SKILL.md +115 -0
- package/dist/template/.opencode/skill/deep-research/SKILL.md +4 -4
- package/dist/template/.opencode/skill/development-lifecycle/SKILL.md +305 -0
- package/dist/template/.opencode/skill/memory-system/SKILL.md +3 -3
- package/dist/template/.opencode/skill/prd/SKILL.md +47 -122
- package/dist/template/.opencode/skill/prd-task/SKILL.md +48 -4
- package/dist/template/.opencode/skill/prd-task/references/prd-schema.json +120 -24
- package/dist/template/.opencode/skill/swarm-coordination/SKILL.md +79 -61
- package/dist/template/.opencode/skill/tool-priority/SKILL.md +31 -22
- package/dist/template/.opencode/tool/context7.ts +183 -0
- package/dist/template/.opencode/tool/memory-admin.ts +445 -0
- package/dist/template/.opencode/tool/swarm.ts +572 -0
- package/package.json +1 -1
- package/dist/template/.opencode/memory/_templates/spec.md +0 -66
- package/dist/template/.opencode/tool/beads-sync.ts +0 -657
- package/dist/template/.opencode/tool/context7-query-docs.ts +0 -89
- package/dist/template/.opencode/tool/context7-resolve-library-id.ts +0 -113
- package/dist/template/.opencode/tool/memory-maintain.ts +0 -167
- package/dist/template/.opencode/tool/memory-migrate.ts +0 -319
- package/dist/template/.opencode/tool/swarm-delegate.ts +0 -180
- package/dist/template/.opencode/tool/swarm-monitor.ts +0 -388
- package/dist/template/.opencode/tool/swarm-plan.ts +0 -697
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { tool } from "@opencode-ai/plugin";
|
|
4
|
-
|
|
5
|
-
function isSafePathSegment(value: string): boolean {
|
|
6
|
-
if (!value) return false;
|
|
7
|
-
if (value.includes("..")) return false;
|
|
8
|
-
if (value.includes("/")) return false;
|
|
9
|
-
if (value.includes("\\")) return false;
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function sanitizeFilename(value: string): string {
|
|
14
|
-
const trimmed = value.trim();
|
|
15
|
-
if (!trimmed) return "delegation.md";
|
|
16
|
-
if (!isSafePathSegment(trimmed)) return "delegation.md";
|
|
17
|
-
return trimmed;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function splitList(input?: string): string[] {
|
|
21
|
-
if (!input) return [];
|
|
22
|
-
const parts = input
|
|
23
|
-
.split(/\r?\n|,/g)
|
|
24
|
-
.map((s) => s.trim())
|
|
25
|
-
.filter(Boolean);
|
|
26
|
-
return [...new Set(parts)];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function formatBullets(items: string[]): string {
|
|
30
|
-
if (items.length === 0) return "- (none)";
|
|
31
|
-
return items.map((i) => `- ${i}`).join("\n");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function fileExists(filePath: string): Promise<boolean> {
|
|
35
|
-
try {
|
|
36
|
-
await fs.access(filePath);
|
|
37
|
-
return true;
|
|
38
|
-
} catch {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default tool({
|
|
44
|
-
description: `Generate a delegation packet for Task tool workers.
|
|
45
|
-
|
|
46
|
-
Purpose:
|
|
47
|
-
- Creates a consistent delegation envelope for parallel Task execution
|
|
48
|
-
- Optionally appends it to .beads/artifacts/<id>/delegation.md
|
|
49
|
-
|
|
50
|
-
Recommended skills for workers:
|
|
51
|
-
- verification-before-completion: Must verify before claiming done
|
|
52
|
-
- test-driven-development: Write tests first, watch fail, implement, verify pass
|
|
53
|
-
- tool-priority: Use LSP before edits, grep for patterns, read before edit
|
|
54
|
-
|
|
55
|
-
Example:
|
|
56
|
-
swarm-delegate({
|
|
57
|
-
bead_id: "<bead-id>",
|
|
58
|
-
title: "<task-title>",
|
|
59
|
-
expected_outcome: "<specific outcome>",
|
|
60
|
-
required_tools: "read, grep, lsp, edit, bash",
|
|
61
|
-
must_do: "LSP before edits, run tests, verify before claiming done",
|
|
62
|
-
must_not_do: "no new deps, don't edit files outside assignment",
|
|
63
|
-
acceptance_checks: "typecheck: npm run typecheck, lint: npm run lint",
|
|
64
|
-
context: "Files: <files>, Purpose: <reason>",
|
|
65
|
-
write: true
|
|
66
|
-
})`,
|
|
67
|
-
args: {
|
|
68
|
-
bead_id: tool.schema
|
|
69
|
-
.string()
|
|
70
|
-
.describe("Beads issue id (e.g., opencodekit-template-abc)"),
|
|
71
|
-
title: tool.schema
|
|
72
|
-
.string()
|
|
73
|
-
.optional()
|
|
74
|
-
.describe("Optional title (defaults to Beads id only)"),
|
|
75
|
-
expected_outcome: tool.schema
|
|
76
|
-
.string()
|
|
77
|
-
.describe("Measurable end state for this task"),
|
|
78
|
-
required_tools: tool.schema
|
|
79
|
-
.string()
|
|
80
|
-
.optional()
|
|
81
|
-
.describe("Comma/newline-separated list of tools"),
|
|
82
|
-
must_do: tool.schema
|
|
83
|
-
.string()
|
|
84
|
-
.optional()
|
|
85
|
-
.describe("Comma/newline-separated MUST DO list"),
|
|
86
|
-
must_not_do: tool.schema
|
|
87
|
-
.string()
|
|
88
|
-
.optional()
|
|
89
|
-
.describe("Comma/newline-separated MUST NOT DO list"),
|
|
90
|
-
acceptance_checks: tool.schema
|
|
91
|
-
.string()
|
|
92
|
-
.optional()
|
|
93
|
-
.describe(
|
|
94
|
-
"Comma/newline-separated checks (prefer 'typecheck: <command>', 'lint: <command>', 'test: <command>')",
|
|
95
|
-
),
|
|
96
|
-
context: tool.schema
|
|
97
|
-
.string()
|
|
98
|
-
.optional()
|
|
99
|
-
.describe("Extra context pointers (files/links/notes)"),
|
|
100
|
-
write: tool.schema
|
|
101
|
-
.boolean()
|
|
102
|
-
.optional()
|
|
103
|
-
.describe("When true, append packet to task artifact file"),
|
|
104
|
-
output_file: tool.schema
|
|
105
|
-
.string()
|
|
106
|
-
.optional()
|
|
107
|
-
.describe(
|
|
108
|
-
"Artifact filename under .beads/artifacts/<id>/ (default: delegation.md)",
|
|
109
|
-
),
|
|
110
|
-
},
|
|
111
|
-
execute: async (args: {
|
|
112
|
-
bead_id: string;
|
|
113
|
-
title?: string;
|
|
114
|
-
expected_outcome: string;
|
|
115
|
-
required_tools?: string;
|
|
116
|
-
must_do?: string;
|
|
117
|
-
must_not_do?: string;
|
|
118
|
-
acceptance_checks?: string;
|
|
119
|
-
context?: string;
|
|
120
|
-
write?: boolean;
|
|
121
|
-
output_file?: string;
|
|
122
|
-
}) => {
|
|
123
|
-
const beadId = args.bead_id.trim();
|
|
124
|
-
if (!beadId) return "Error: bead_id is required.";
|
|
125
|
-
if (!isSafePathSegment(beadId)) {
|
|
126
|
-
return "Error: bead_id must be a single path segment (no slashes or '..').";
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const title = args.title?.trim();
|
|
130
|
-
const taskLine = title ? `${beadId} - ${title}` : beadId;
|
|
131
|
-
|
|
132
|
-
const requiredTools = splitList(args.required_tools);
|
|
133
|
-
const mustDo = splitList(args.must_do);
|
|
134
|
-
const mustNotDo = splitList(args.must_not_do);
|
|
135
|
-
const acceptanceChecks = splitList(args.acceptance_checks);
|
|
136
|
-
const context = args.context?.trim();
|
|
137
|
-
|
|
138
|
-
const now = new Date();
|
|
139
|
-
const stamp = now.toISOString();
|
|
140
|
-
|
|
141
|
-
const packet = [
|
|
142
|
-
"# Delegation Packet",
|
|
143
|
-
"",
|
|
144
|
-
`- TASK: ${taskLine}`,
|
|
145
|
-
`- EXPECTED OUTCOME: ${args.expected_outcome.trim()}`,
|
|
146
|
-
"- REQUIRED TOOLS:",
|
|
147
|
-
formatBullets(requiredTools),
|
|
148
|
-
"- MUST DO:",
|
|
149
|
-
formatBullets(mustDo),
|
|
150
|
-
"- MUST NOT DO:",
|
|
151
|
-
formatBullets(mustNotDo),
|
|
152
|
-
"- ACCEPTANCE CHECKS:",
|
|
153
|
-
formatBullets(acceptanceChecks),
|
|
154
|
-
"- CONTEXT:",
|
|
155
|
-
context ? context : "(none)",
|
|
156
|
-
].join("\n");
|
|
157
|
-
|
|
158
|
-
if (!args.write) {
|
|
159
|
-
return `${packet}\n`;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const artifactDir = path.join(process.cwd(), ".beads", "artifacts", beadId);
|
|
163
|
-
const outName = sanitizeFilename(args.output_file || "delegation.md");
|
|
164
|
-
const outPath = path.join(artifactDir, outName);
|
|
165
|
-
|
|
166
|
-
const specPath = path.join(artifactDir, "spec.md");
|
|
167
|
-
const hasSpec = await fileExists(specPath);
|
|
168
|
-
|
|
169
|
-
await fs.mkdir(artifactDir, { recursive: true });
|
|
170
|
-
|
|
171
|
-
const header = `\n\n---\nGenerated: ${stamp}\n---\n\n`;
|
|
172
|
-
await fs.appendFile(outPath, `${header}${packet}\n`, "utf-8");
|
|
173
|
-
|
|
174
|
-
const specNote = hasSpec
|
|
175
|
-
? ""
|
|
176
|
-
: `\nWarning: spec.md not found at ${specPath}`;
|
|
177
|
-
|
|
178
|
-
return `β Delegation packet appended to ${outPath}${specNote}\n\n${packet}\n`;
|
|
179
|
-
},
|
|
180
|
-
});
|
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { tool } from "@opencode-ai/plugin";
|
|
4
|
-
|
|
5
|
-
const PROGRESS_FILE = ".beads/swarm-progress.jsonl";
|
|
6
|
-
|
|
7
|
-
interface ProgressEntry {
|
|
8
|
-
timestamp: string;
|
|
9
|
-
team_name: string;
|
|
10
|
-
worker_id: string;
|
|
11
|
-
phase: string;
|
|
12
|
-
progress: number;
|
|
13
|
-
status: "idle" | "working" | "completed" | "error" | "blocked";
|
|
14
|
-
message?: string;
|
|
15
|
-
file?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export default tool({
|
|
19
|
-
description: `Swarm progress tracking for parallel task visualization.
|
|
20
|
-
|
|
21
|
-
Operations:
|
|
22
|
-
- progress_update: Update worker progress
|
|
23
|
-
- progress_render: Display ASCII progress UI
|
|
24
|
-
- render_block: Display beautiful markdown block UI for TUI
|
|
25
|
-
- status: Get full team status
|
|
26
|
-
- clear: Clear team data
|
|
27
|
-
|
|
28
|
-
Note: For worker communication, use the Task tool which handles results directly.
|
|
29
|
-
|
|
30
|
-
Usage:
|
|
31
|
-
swarm-monitor({ operation: "progress_update", team_name: "<team-name>", worker_id: "worker-1", phase: "<phase>", progress: <0-100>, status: "working", file: "<current file>" })
|
|
32
|
-
swarm-monitor({ operation: "render_block", team_name: "<team-name>" })
|
|
33
|
-
swarm-monitor({ operation: "status", team_name: "<team-name>" })`,
|
|
34
|
-
|
|
35
|
-
args: {
|
|
36
|
-
operation: tool.schema
|
|
37
|
-
.enum([
|
|
38
|
-
"progress_update",
|
|
39
|
-
"progress_render",
|
|
40
|
-
"status",
|
|
41
|
-
"clear",
|
|
42
|
-
"render_block",
|
|
43
|
-
])
|
|
44
|
-
.describe("Monitoring operation to perform"),
|
|
45
|
-
team_name: tool.schema.string().describe("Team/swarm identifier"),
|
|
46
|
-
worker_id: tool.schema
|
|
47
|
-
.string()
|
|
48
|
-
.optional()
|
|
49
|
-
.describe("Worker identifier (for progress operations)"),
|
|
50
|
-
phase: tool.schema
|
|
51
|
-
.string()
|
|
52
|
-
.optional()
|
|
53
|
-
.describe("Current phase name (for progress_update)"),
|
|
54
|
-
progress: tool.schema
|
|
55
|
-
.number()
|
|
56
|
-
.min(0)
|
|
57
|
-
.max(100)
|
|
58
|
-
.optional()
|
|
59
|
-
.describe("Progress percentage 0-100 (for progress_update)"),
|
|
60
|
-
status: tool.schema
|
|
61
|
-
.enum(["idle", "working", "completed", "error", "blocked"])
|
|
62
|
-
.optional()
|
|
63
|
-
.describe("Worker status (for progress_update)"),
|
|
64
|
-
message: tool.schema
|
|
65
|
-
.string()
|
|
66
|
-
.optional()
|
|
67
|
-
.describe("Status message (for progress_update)"),
|
|
68
|
-
file: tool.schema
|
|
69
|
-
.string()
|
|
70
|
-
.optional()
|
|
71
|
-
.describe("Current file being worked on"),
|
|
72
|
-
},
|
|
73
|
-
|
|
74
|
-
execute: async (args, ctx) => {
|
|
75
|
-
const worktree = ctx.worktree || process.cwd();
|
|
76
|
-
|
|
77
|
-
switch (args.operation) {
|
|
78
|
-
case "progress_update":
|
|
79
|
-
return await updateProgress(
|
|
80
|
-
{
|
|
81
|
-
timestamp: new Date().toISOString(),
|
|
82
|
-
team_name: args.team_name,
|
|
83
|
-
worker_id: args.worker_id || "unknown",
|
|
84
|
-
phase: args.phase || "unknown",
|
|
85
|
-
progress: args.progress || 0,
|
|
86
|
-
status: args.status || "idle",
|
|
87
|
-
message: args.message,
|
|
88
|
-
file: args.file,
|
|
89
|
-
},
|
|
90
|
-
worktree,
|
|
91
|
-
);
|
|
92
|
-
case "progress_render":
|
|
93
|
-
return await renderProgress(args.team_name, worktree);
|
|
94
|
-
case "status":
|
|
95
|
-
return await getFullStatus(args.team_name, worktree);
|
|
96
|
-
case "clear":
|
|
97
|
-
return await clearTeam(args.team_name, worktree);
|
|
98
|
-
case "render_block":
|
|
99
|
-
return await renderBlock(args.team_name, worktree);
|
|
100
|
-
default:
|
|
101
|
-
return `Error: Unknown operation: ${args.operation}`;
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Progress operations
|
|
107
|
-
async function updateProgress(
|
|
108
|
-
entry: ProgressEntry,
|
|
109
|
-
worktree: string,
|
|
110
|
-
): Promise<string> {
|
|
111
|
-
const progressPath = path.join(worktree, PROGRESS_FILE);
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
await fs.mkdir(path.dirname(progressPath), { recursive: true });
|
|
115
|
-
await fs.appendFile(progressPath, JSON.stringify(entry) + "\n", "utf-8");
|
|
116
|
-
return JSON.stringify({ success: true, record: entry }, null, 2);
|
|
117
|
-
} catch (error: any) {
|
|
118
|
-
return `Error: Failed to update progress: ${error}`;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function getProgress(
|
|
123
|
-
teamName: string,
|
|
124
|
-
worktree: string,
|
|
125
|
-
): Promise<{ workers: ProgressEntry[]; total_entries: number }> {
|
|
126
|
-
const progressPath = path.join(worktree, PROGRESS_FILE);
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
const content = await fs.readFile(progressPath, "utf-8");
|
|
130
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
131
|
-
const entries = lines.map((line) => JSON.parse(line) as ProgressEntry);
|
|
132
|
-
const teamEntries = entries.filter((e) => e.team_name === teamName);
|
|
133
|
-
|
|
134
|
-
// Get latest entry per worker
|
|
135
|
-
const workerProgress = new Map<string, ProgressEntry>();
|
|
136
|
-
for (const entry of teamEntries) {
|
|
137
|
-
workerProgress.set(entry.worker_id, entry);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
workers: Array.from(workerProgress.values()),
|
|
142
|
-
total_entries: teamEntries.length,
|
|
143
|
-
};
|
|
144
|
-
} catch {
|
|
145
|
-
return { workers: [], total_entries: 0 };
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function renderProgress(
|
|
150
|
-
teamName: string,
|
|
151
|
-
worktree: string,
|
|
152
|
-
): Promise<string> {
|
|
153
|
-
const { workers } = await getProgress(teamName, worktree);
|
|
154
|
-
|
|
155
|
-
if (workers.length === 0) {
|
|
156
|
-
return `No progress data for team: ${teamName}`;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Group by phase
|
|
160
|
-
const phases = new Map<string, ProgressEntry[]>();
|
|
161
|
-
for (const worker of workers) {
|
|
162
|
-
if (!phases.has(worker.phase)) {
|
|
163
|
-
phases.set(worker.phase, []);
|
|
164
|
-
}
|
|
165
|
-
phases.get(worker.phase)!.push(worker);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Build terminal UI
|
|
169
|
-
const lines: string[] = [];
|
|
170
|
-
lines.push("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
|
|
171
|
-
lines.push(`β Swarm: ${teamName.padEnd(53)}β`);
|
|
172
|
-
lines.push("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€");
|
|
173
|
-
|
|
174
|
-
for (const [phaseName, phaseWorkers] of phases) {
|
|
175
|
-
const avgProgress = Math.round(
|
|
176
|
-
phaseWorkers.reduce((sum, w) => sum + w.progress, 0) /
|
|
177
|
-
phaseWorkers.length,
|
|
178
|
-
);
|
|
179
|
-
const bar = renderProgressBar(avgProgress, 20);
|
|
180
|
-
const statusIcon = getPhaseStatus(phaseWorkers);
|
|
181
|
-
|
|
182
|
-
lines.push(
|
|
183
|
-
`β β`,
|
|
184
|
-
);
|
|
185
|
-
lines.push(`β ${statusIcon} Phase: ${phaseName.padEnd(52)}β`);
|
|
186
|
-
lines.push(
|
|
187
|
-
`β Progress: [${bar}] ${avgProgress.toString().padStart(3)}% β`,
|
|
188
|
-
);
|
|
189
|
-
lines.push(
|
|
190
|
-
`β β`,
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
for (const worker of phaseWorkers) {
|
|
194
|
-
const workerBar = renderProgressBar(worker.progress, 10);
|
|
195
|
-
const icon = getStatusIcon(worker.status);
|
|
196
|
-
const fileInfo = worker.file ? ` (${path.basename(worker.file)})` : "";
|
|
197
|
-
lines.push(
|
|
198
|
-
`β ${icon} ${worker.worker_id.padEnd(10)} [${workerBar}] ${worker.progress.toString().padStart(3)}%${fileInfo.padEnd(20)}β`,
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
lines.push("β β");
|
|
204
|
-
lines.push("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
|
|
205
|
-
|
|
206
|
-
return lines.join("\n");
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function renderProgressBar(progress: number, width: number): string {
|
|
210
|
-
const filled = Math.round((progress / 100) * width);
|
|
211
|
-
const empty = width - filled;
|
|
212
|
-
return "β".repeat(filled) + "β".repeat(empty);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function getStatusIcon(status: string): string {
|
|
216
|
-
switch (status) {
|
|
217
|
-
case "completed":
|
|
218
|
-
return "β";
|
|
219
|
-
case "error":
|
|
220
|
-
return "β";
|
|
221
|
-
case "blocked":
|
|
222
|
-
return "β";
|
|
223
|
-
case "working":
|
|
224
|
-
return "βΆ";
|
|
225
|
-
default:
|
|
226
|
-
return "β";
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function getPhaseStatus(workers: ProgressEntry[]): string {
|
|
231
|
-
if (workers.every((w) => w.status === "completed")) return "β";
|
|
232
|
-
if (workers.some((w) => w.status === "error")) return "β";
|
|
233
|
-
if (workers.some((w) => w.status === "working")) return "βΆ";
|
|
234
|
-
return "β";
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Render beautiful markdown block for TUI
|
|
238
|
-
async function renderBlock(
|
|
239
|
-
teamName: string,
|
|
240
|
-
worktree: string,
|
|
241
|
-
): Promise<string> {
|
|
242
|
-
const { workers } = await getProgress(teamName, worktree);
|
|
243
|
-
|
|
244
|
-
if (workers.length === 0) {
|
|
245
|
-
return `## π Swarm: ${teamName}\n\n_No active workers._`;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Group workers by phase
|
|
249
|
-
const phases = new Map<string, ProgressEntry[]>();
|
|
250
|
-
for (const worker of workers) {
|
|
251
|
-
if (!phases.has(worker.phase)) {
|
|
252
|
-
phases.set(worker.phase, []);
|
|
253
|
-
}
|
|
254
|
-
phases.get(worker.phase)!.push(worker);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Build markdown block
|
|
258
|
-
const lines: string[] = [];
|
|
259
|
-
lines.push(`## π Swarm: \`${teamName}\``);
|
|
260
|
-
lines.push("");
|
|
261
|
-
|
|
262
|
-
// Summary stats
|
|
263
|
-
const completed = workers.filter((w) => w.status === "completed").length;
|
|
264
|
-
const working = workers.filter((w) => w.status === "working").length;
|
|
265
|
-
const errors = workers.filter((w) => w.status === "error").length;
|
|
266
|
-
const total = workers.length;
|
|
267
|
-
|
|
268
|
-
lines.push("### π Summary");
|
|
269
|
-
lines.push("");
|
|
270
|
-
lines.push(`| Metric | Value |`);
|
|
271
|
-
lines.push(`|--------|-------|`);
|
|
272
|
-
lines.push(`| Total Workers | ${total} |`);
|
|
273
|
-
lines.push(`| β
Completed | ${completed} |`);
|
|
274
|
-
lines.push(`| βΆοΈ Working | ${working} |`);
|
|
275
|
-
if (errors > 0) lines.push(`| β Errors | ${errors} |`);
|
|
276
|
-
lines.push("");
|
|
277
|
-
|
|
278
|
-
// Phase details
|
|
279
|
-
if (phases.size > 0) {
|
|
280
|
-
lines.push("### π Phases");
|
|
281
|
-
lines.push("");
|
|
282
|
-
|
|
283
|
-
for (const [phaseName, phaseWorkers] of phases) {
|
|
284
|
-
const avgProgress = Math.round(
|
|
285
|
-
phaseWorkers.reduce((sum, w) => sum + w.progress, 0) /
|
|
286
|
-
phaseWorkers.length,
|
|
287
|
-
);
|
|
288
|
-
const phaseCompleted = phaseWorkers.every(
|
|
289
|
-
(w) => w.status === "completed",
|
|
290
|
-
);
|
|
291
|
-
const phaseError = phaseWorkers.some((w) => w.status === "error");
|
|
292
|
-
const phaseIcon = phaseCompleted ? "β
" : phaseError ? "β" : "π";
|
|
293
|
-
|
|
294
|
-
lines.push(`#### ${phaseIcon} ${phaseName}`);
|
|
295
|
-
lines.push("");
|
|
296
|
-
lines.push(`**Progress:** ${avgProgress}%`);
|
|
297
|
-
lines.push("");
|
|
298
|
-
lines.push(`| Worker | Status | Progress | File |`);
|
|
299
|
-
lines.push(`|--------|--------|----------|------|`);
|
|
300
|
-
|
|
301
|
-
for (const worker of phaseWorkers) {
|
|
302
|
-
const statusIcon =
|
|
303
|
-
worker.status === "completed"
|
|
304
|
-
? "β
"
|
|
305
|
-
: worker.status === "error"
|
|
306
|
-
? "β"
|
|
307
|
-
: worker.status === "working"
|
|
308
|
-
? "βΆοΈ"
|
|
309
|
-
: "βΈοΈ";
|
|
310
|
-
const file = worker.file ? path.basename(worker.file) : "-";
|
|
311
|
-
lines.push(
|
|
312
|
-
`| ${worker.worker_id} | ${statusIcon} ${worker.status} | ${worker.progress}% | ${file} |`,
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
lines.push("");
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return lines.join("\n");
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Combined status
|
|
323
|
-
async function getFullStatus(
|
|
324
|
-
teamName: string,
|
|
325
|
-
worktree: string,
|
|
326
|
-
): Promise<string> {
|
|
327
|
-
const progressData = await getProgress(teamName, worktree);
|
|
328
|
-
const { workers } = progressData;
|
|
329
|
-
|
|
330
|
-
// Calculate overall stats
|
|
331
|
-
const completedWorkers = workers.filter(
|
|
332
|
-
(w) => w.status === "completed",
|
|
333
|
-
).length;
|
|
334
|
-
const errorWorkers = workers.filter((w) => w.status === "error").length;
|
|
335
|
-
const workingWorkers = workers.filter((w) => w.status === "working").length;
|
|
336
|
-
|
|
337
|
-
return JSON.stringify(
|
|
338
|
-
{
|
|
339
|
-
team_name: teamName,
|
|
340
|
-
summary: {
|
|
341
|
-
total_workers: workers.length,
|
|
342
|
-
completed: completedWorkers,
|
|
343
|
-
working: workingWorkers,
|
|
344
|
-
errors: errorWorkers,
|
|
345
|
-
},
|
|
346
|
-
workers,
|
|
347
|
-
},
|
|
348
|
-
null,
|
|
349
|
-
2,
|
|
350
|
-
);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Clear operations
|
|
354
|
-
async function clearTeam(teamName: string, worktree: string): Promise<string> {
|
|
355
|
-
const progressPath = path.join(worktree, PROGRESS_FILE);
|
|
356
|
-
|
|
357
|
-
let clearedProgress = 0;
|
|
358
|
-
|
|
359
|
-
// Clear progress entries
|
|
360
|
-
try {
|
|
361
|
-
const content = await fs.readFile(progressPath, "utf-8");
|
|
362
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
363
|
-
const entries = lines.map((line) => JSON.parse(line) as ProgressEntry);
|
|
364
|
-
const otherEntries = entries.filter((e) => e.team_name !== teamName);
|
|
365
|
-
clearedProgress = entries.length - otherEntries.length;
|
|
366
|
-
|
|
367
|
-
await fs.writeFile(
|
|
368
|
-
progressPath,
|
|
369
|
-
otherEntries.map((e) => JSON.stringify(e)).join("\n") +
|
|
370
|
-
(otherEntries.length > 0 ? "\n" : ""),
|
|
371
|
-
"utf-8",
|
|
372
|
-
);
|
|
373
|
-
} catch {
|
|
374
|
-
// File doesn't exist
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
return JSON.stringify(
|
|
378
|
-
{
|
|
379
|
-
success: true,
|
|
380
|
-
team_name: teamName,
|
|
381
|
-
cleared: {
|
|
382
|
-
progress_entries: clearedProgress,
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
null,
|
|
386
|
-
2,
|
|
387
|
-
);
|
|
388
|
-
}
|