@comfanion/usethis_todo 0.1.7-dev.0 → 0.1.7-dev.3

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 (2) hide show
  1. package/index.ts +86 -1
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -2,6 +2,12 @@ import type { Plugin } from "@opencode-ai/plugin"
2
2
 
3
3
  import { write, read, read_five, read_by_id, update } from "./tools"
4
4
 
5
+ interface TodoPruneState {
6
+ lastToolCallId: string | null
7
+ }
8
+
9
+ const pruneStates = new Map<string, TodoPruneState>()
10
+
5
11
  const UsethisTodoPlugin: Plugin = async ({ directory, client }) => {
6
12
  // Ensure storage directory exists on init
7
13
  try {
@@ -12,6 +18,77 @@ const UsethisTodoPlugin: Plugin = async ({ directory, client }) => {
12
18
  }
13
19
 
14
20
  return {
21
+ "experimental.chat.messages.transform": async (_input: unknown, output: { messages: any[] }) => {
22
+ const messages = output.messages || []
23
+ const sessionID = messages[0]?.info?.sessionID
24
+
25
+ if (!sessionID) return
26
+
27
+ const state = pruneStates.get(sessionID)
28
+ if (!state?.lastToolCallId) return
29
+
30
+ const prunedToolNames = new Set([
31
+ "usethis_todo_write",
32
+ "usethis_todo_update",
33
+ "usethis_todo_read",
34
+ "usethis_todo_read_five",
35
+ "usethis_todo_read_by_id",
36
+ "todowrite",
37
+ "todoread",
38
+ ])
39
+
40
+ // 1. Collect all TODO-related parts
41
+ const toolParts: { part: any; isLast: boolean }[] = []
42
+ const snapshotParts = new Set<any>()
43
+ let lastSnapshotPart: any = null
44
+
45
+ for (const msg of messages) {
46
+ for (const part of (msg.parts || [])) {
47
+ // Tool parts (contain both state.input and state.output)
48
+ if (part.type === "tool" && prunedToolNames.has(part.tool) && part.state?.status === "completed") {
49
+ toolParts.push({ part, isLast: part.callID === state.lastToolCallId })
50
+ }
51
+ // User "## TODO" snapshot text parts
52
+ if (part.type === "text" && typeof part.text === "string" && part.text.startsWith("## TODO")) {
53
+ snapshotParts.add(part)
54
+ lastSnapshotPart = part
55
+ }
56
+ }
57
+ }
58
+
59
+ if (toolParts.length === 0 && snapshotParts.size === 0) return
60
+
61
+ // 2. Check if all tasks are done by parsing the latest snapshot
62
+ const lastSnapshotText = lastSnapshotPart?.text || ""
63
+ const doneMatch = lastSnapshotText.match(/\[(\d+)\/(\d+) done/)
64
+ const allDone = doneMatch && doneMatch[1] === doneMatch[2] && parseInt(doneMatch[1]) > 0
65
+
66
+ // 3. Prune old tool parts — clear BOTH input and output
67
+ for (const { part, isLast } of toolParts) {
68
+ if (allDone || !isLast) {
69
+ part.state.output = "[TODO pruned]"
70
+ if (part.state.input) part.state.input = {}
71
+ }
72
+ }
73
+
74
+ // 4. Remove old "## TODO" snapshot parts entirely from messages
75
+ // Keep only the last snapshot (or none if allDone)
76
+ const snapshotsToRemove = new Set(snapshotParts)
77
+ if (!allDone && lastSnapshotPart) {
78
+ snapshotsToRemove.delete(lastSnapshotPart)
79
+ }
80
+
81
+ if (snapshotsToRemove.size > 0) {
82
+ for (const msg of messages) {
83
+ if (!Array.isArray(msg.parts)) continue
84
+ msg.parts = msg.parts.filter((p: any) => !snapshotsToRemove.has(p))
85
+ }
86
+ }
87
+
88
+ // 5. Remove messages that became empty after part removal
89
+ output.messages = messages.filter((msg: any) => (msg.parts || []).length > 0)
90
+ },
91
+
15
92
  tool: {
16
93
  usethis_todo_write: write,
17
94
  usethis_todo_read: read,
@@ -22,7 +99,15 @@ const UsethisTodoPlugin: Plugin = async ({ directory, client }) => {
22
99
 
23
100
  // UI niceties + publish snapshot into the chat
24
101
  "tool.execute.after": async (input, output) => {
25
- if (!input.tool.startsWith("usethis_todo_")) return
102
+ if (!input.tool.startsWith("usethis_todo_") && input.tool !== "todowrite" && input.tool !== "todoread") return
103
+
104
+ // Update prune state with latest call ID
105
+ const sessionID = input.sessionID
106
+ if (sessionID) {
107
+ const state = pruneStates.get(sessionID) || { lastToolCallId: null }
108
+ state.lastToolCallId = input.callID
109
+ pruneStates.set(sessionID, state)
110
+ }
26
111
 
27
112
  const out = output.output || ""
28
113
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comfanion/usethis_todo",
3
- "version": "0.1.7-dev.0",
3
+ "version": "0.1.7-dev.3",
4
4
  "description": "OpenCode plugin: enhanced TODO tools (dual storage + dependency graph)",
5
5
  "type": "module",
6
6
  "main": "./index.ts",