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.
Files changed (56) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/template/.opencode/AGENTS.md +106 -384
  3. package/dist/template/.opencode/README.md +170 -104
  4. package/dist/template/.opencode/agent/build.md +39 -32
  5. package/dist/template/.opencode/agent/explore.md +2 -0
  6. package/dist/template/.opencode/agent/review.md +3 -0
  7. package/dist/template/.opencode/agent/scout.md +22 -11
  8. package/dist/template/.opencode/command/create.md +164 -106
  9. package/dist/template/.opencode/command/design.md +5 -1
  10. package/dist/template/.opencode/command/handoff.md +6 -4
  11. package/dist/template/.opencode/command/init.md +1 -1
  12. package/dist/template/.opencode/command/plan.md +26 -23
  13. package/dist/template/.opencode/command/research.md +13 -6
  14. package/dist/template/.opencode/command/resume.md +8 -6
  15. package/dist/template/.opencode/command/ship.md +1 -1
  16. package/dist/template/.opencode/command/start.md +30 -25
  17. package/dist/template/.opencode/command/status.md +9 -42
  18. package/dist/template/.opencode/command/verify.md +11 -11
  19. package/dist/template/.opencode/memory/README.md +67 -37
  20. package/dist/template/.opencode/memory/_templates/prd.md +102 -18
  21. package/dist/template/.opencode/memory/project/gotchas.md +31 -0
  22. package/dist/template/.opencode/memory.db +0 -0
  23. package/dist/template/.opencode/memory.db-shm +0 -0
  24. package/dist/template/.opencode/memory.db-wal +0 -0
  25. package/dist/template/.opencode/opencode.json +0 -10
  26. package/dist/template/.opencode/package.json +1 -1
  27. package/dist/template/.opencode/skill/beads/SKILL.md +164 -380
  28. package/dist/template/.opencode/skill/beads/references/BOUNDARIES.md +23 -22
  29. package/dist/template/.opencode/skill/beads/references/DEPENDENCIES.md +23 -29
  30. package/dist/template/.opencode/skill/beads/references/RESUMABILITY.md +5 -8
  31. package/dist/template/.opencode/skill/beads/references/WORKFLOWS.md +43 -39
  32. package/dist/template/.opencode/skill/beads-bridge/SKILL.md +80 -53
  33. package/dist/template/.opencode/skill/brainstorming/SKILL.md +19 -5
  34. package/dist/template/.opencode/skill/context-engineering/SKILL.md +30 -63
  35. package/dist/template/.opencode/skill/context-management/SKILL.md +115 -0
  36. package/dist/template/.opencode/skill/deep-research/SKILL.md +4 -4
  37. package/dist/template/.opencode/skill/development-lifecycle/SKILL.md +305 -0
  38. package/dist/template/.opencode/skill/memory-system/SKILL.md +3 -3
  39. package/dist/template/.opencode/skill/prd/SKILL.md +47 -122
  40. package/dist/template/.opencode/skill/prd-task/SKILL.md +48 -4
  41. package/dist/template/.opencode/skill/prd-task/references/prd-schema.json +120 -24
  42. package/dist/template/.opencode/skill/swarm-coordination/SKILL.md +79 -61
  43. package/dist/template/.opencode/skill/tool-priority/SKILL.md +31 -22
  44. package/dist/template/.opencode/tool/context7.ts +183 -0
  45. package/dist/template/.opencode/tool/memory-admin.ts +445 -0
  46. package/dist/template/.opencode/tool/swarm.ts +572 -0
  47. package/package.json +1 -1
  48. package/dist/template/.opencode/memory/_templates/spec.md +0 -66
  49. package/dist/template/.opencode/tool/beads-sync.ts +0 -657
  50. package/dist/template/.opencode/tool/context7-query-docs.ts +0 -89
  51. package/dist/template/.opencode/tool/context7-resolve-library-id.ts +0 -113
  52. package/dist/template/.opencode/tool/memory-maintain.ts +0 -167
  53. package/dist/template/.opencode/tool/memory-migrate.ts +0 -319
  54. package/dist/template/.opencode/tool/swarm-delegate.ts +0 -180
  55. package/dist/template/.opencode/tool/swarm-monitor.ts +0 -388
  56. 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
- }