@jiggai/recipes 0.3.6 → 0.3.7

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/index.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  } from "./src/handlers/install";
27
27
  import {
28
28
  handleAssign,
29
+ handleCleanupClosedAssignments,
29
30
  handleDispatch,
30
31
  handleHandoff,
31
32
  handleMoveTicket,
@@ -482,6 +483,19 @@ const recipesPlugin = {
482
483
  console.log(JSON.stringify({ ok: true, moved: { from: res.from, to: res.to } }, null, 2));
483
484
  });
484
485
 
486
+ cmd
487
+ .command("cleanup-closed-assignments")
488
+ .description("Archive assignment stubs for tickets already in work/done (prevents done work resurfacing)")
489
+ .requiredOption("--team-id <teamId>", "Team id")
490
+ .option("--ticket <ticketNums...>", "Optional ticket numbers to target (e.g. 0050 0064)")
491
+ .action(async (options: { teamId?: string; ticket?: string[] }) => {
492
+ if (!options.teamId) throw new Error("--team-id is required");
493
+ const res = await handleCleanupClosedAssignments(api, {
494
+ teamId: options.teamId,
495
+ ticketNums: options.ticket,
496
+ });
497
+ console.log(JSON.stringify(res, null, 2));
498
+ });
485
499
 
486
500
  cmd
487
501
  .command("assign")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jiggai/recipes",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "ClawRecipes plugin for OpenClaw (markdown recipes -> scaffold agents/teams)",
5
5
  "main": "index.ts",
6
6
  "type": "commonjs",
@@ -59,6 +59,21 @@ agents:
59
59
  deny: ["exec"]
60
60
 
61
61
  templates:
62
+ tools: |
63
+ # TOOLS.md
64
+
65
+ # Agent-local notes (paths, conventions, env quirks).
66
+
67
+ status: |
68
+ # STATUS.md
69
+
70
+ - (empty)
71
+
72
+ notes: |
73
+ # NOTES.md
74
+
75
+ - (empty)
76
+
62
77
  lead.soul: |
63
78
  # SOUL.md
64
79
 
@@ -59,6 +59,21 @@ agents:
59
59
  deny: ["exec"]
60
60
 
61
61
  templates:
62
+ tools: |
63
+ # TOOLS.md
64
+
65
+ # Agent-local notes (paths, conventions, env quirks).
66
+
67
+ status: |
68
+ # STATUS.md
69
+
70
+ - (empty)
71
+
72
+ notes: |
73
+ # NOTES.md
74
+
75
+ - (empty)
76
+
62
77
  lead.soul: |
63
78
  # SOUL.md
64
79
 
@@ -59,6 +59,21 @@ agents:
59
59
  deny: ["exec"]
60
60
 
61
61
  templates:
62
+ tools: |
63
+ # TOOLS.md
64
+
65
+ # Agent-local notes (paths, conventions, env quirks).
66
+
67
+ status: |
68
+ # STATUS.md
69
+
70
+ - (empty)
71
+
72
+ notes: |
73
+ # NOTES.md
74
+
75
+ - (empty)
76
+
62
77
  lead.soul: |
63
78
  # SOUL.md
64
79
 
@@ -59,6 +59,21 @@ agents:
59
59
  deny: ["exec"]
60
60
 
61
61
  templates:
62
+ tools: |
63
+ # TOOLS.md
64
+
65
+ # Agent-local notes (paths, conventions, env quirks).
66
+
67
+ status: |
68
+ # STATUS.md
69
+
70
+ - (empty)
71
+
72
+ notes: |
73
+ # NOTES.md
74
+
75
+ - (empty)
76
+
62
77
  lead.soul: |
63
78
  # SOUL.md
64
79
 
@@ -59,6 +59,21 @@ agents:
59
59
  deny: ["exec"]
60
60
 
61
61
  templates:
62
+ tools: |
63
+ # TOOLS.md
64
+
65
+ # Agent-local notes (paths, conventions, env quirks).
66
+
67
+ status: |
68
+ # STATUS.md
69
+
70
+ - (empty)
71
+
72
+ notes: |
73
+ # NOTES.md
74
+
75
+ - (empty)
76
+
62
77
  lead.soul: |
63
78
  # SOUL.md
64
79
 
@@ -59,6 +59,21 @@ agents:
59
59
  deny: ["exec"]
60
60
 
61
61
  templates:
62
+ tools: |
63
+ # TOOLS.md
64
+
65
+ # Agent-local notes (paths, conventions, env quirks).
66
+
67
+ status: |
68
+ # STATUS.md
69
+
70
+ - (empty)
71
+
72
+ notes: |
73
+ # NOTES.md
74
+
75
+ - (empty)
76
+
62
77
  lead.soul: |
63
78
  # SOUL.md
64
79
 
@@ -325,3 +325,59 @@ export async function handleDispatch(
325
325
  }
326
326
  return { ok: true as const, wrote: plan.files.map((f) => f.path), nudgeQueued };
327
327
  }
328
+
329
+ /**
330
+ * Cleanup assignment stubs for tickets that are already closed (in work/done).
331
+ *
332
+ * Why: some automation/board views treat assignment stubs as active work signals.
333
+ * If a ticket is manually moved to done (outside `openclaw recipes move-ticket`),
334
+ * its `work/assignments/<num>-assigned-*.md` stubs may linger and resurface the ticket.
335
+ *
336
+ * This command archives any matching assignment stubs into `work/assignments/archive/`.
337
+ */
338
+ export async function handleCleanupClosedAssignments(
339
+ api: OpenClawPluginApi,
340
+ options: { teamId: string; ticketNums?: string[] }
341
+ ): Promise<{ ok: true; teamId: string; archived: Array<{ from: string; to: string }> }> {
342
+ const teamId = String(options.teamId);
343
+ const { teamDir } = await resolveTeamContext(api, teamId);
344
+
345
+ const assignmentsDir = ticketStageDir(teamDir, "assignments");
346
+ const archiveDir = path.join(assignmentsDir, "archive");
347
+ const doneDir = ticketStageDir(teamDir, "done");
348
+
349
+ const archived: Array<{ from: string; to: string }> = [];
350
+ if (!(await fileExists(assignmentsDir))) return { ok: true, teamId, archived };
351
+ await ensureDir(archiveDir);
352
+
353
+ const ticketNumsFilter = Array.isArray(options.ticketNums) && options.ticketNums.length
354
+ ? new Set(options.ticketNums.map((n) => String(n).padStart(4, "0")))
355
+ : null;
356
+
357
+ const doneFiles = (await fileExists(doneDir)) ? await fs.readdir(doneDir) : [];
358
+ const doneNums = new Set(
359
+ doneFiles
360
+ .map((f) => f.match(/^([0-9]{4})-/)?.[1])
361
+ .filter((x): x is string => !!x)
362
+ );
363
+
364
+ const files = (await fs.readdir(assignmentsDir)).filter((f) => f.endsWith(".md"));
365
+ for (const f of files) {
366
+ if (f === "archive") continue;
367
+ if (f.startsWith("archive" + path.sep)) continue;
368
+ const m = f.match(/^([0-9]{4})-assigned-.*\.md$/);
369
+ if (!m) continue;
370
+ const num = m[1];
371
+ if (ticketNumsFilter && !ticketNumsFilter.has(num)) continue;
372
+
373
+ // If the ticket number is present in done/, this assignment is considered closed.
374
+ if (!doneNums.has(num)) continue;
375
+
376
+ const from = path.join(assignmentsDir, f);
377
+ const to = path.join(archiveDir, f);
378
+ await fs.rename(from, to);
379
+ archived.push({ from, to });
380
+ }
381
+
382
+ return { ok: true, teamId, archived };
383
+ }