micode 0.3.4 → 0.4.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.
package/README.md CHANGED
@@ -135,29 +135,55 @@ Each task gets its own implement→review loop:
135
135
 
136
136
  ### 4. Session Continuity
137
137
 
138
- Maintain context across long sessions and context clears with the ledger system:
138
+ Maintain context across long sessions and context clears with structured compaction:
139
139
 
140
140
  #### Ledger System
141
141
 
142
- The **continuity ledger** captures essential session state:
142
+ The **continuity ledger** serves as both session state and compaction summary. Based on [Factory.ai's structured compaction research](https://factory.ai/blog/context-compression), which found that structured summarization with deterministic file tracking retains more useful context.
143
143
 
144
144
  ```
145
145
  /ledger
146
146
  ```
147
147
 
148
148
  Creates/updates `thoughts/ledgers/CONTINUITY_{session-name}.md` with:
149
- - Goal and constraints
150
- - Key decisions with rationale
151
- - Current state (Done/Now/Next)
152
- - Working set (branch, key files)
153
149
 
154
- **Auto-injection:** When starting a session, the most recent ledger is automatically injected into the system prompt.
150
+ ```markdown
151
+ # Session: {name}
152
+ Updated: {timestamp}
153
+
154
+ ## Goal
155
+ ## Constraints
156
+ ## Progress
157
+ ### Done
158
+ - [x] {Completed items}
159
+ ### In Progress
160
+ - [ ] {Current work}
161
+ ### Blocked
162
+ - {Issues, if any}
163
+ ## Key Decisions
164
+ - **{Decision}**: {Rationale}
165
+ ## Next Steps
166
+ 1. {Ordered list}
167
+ ## File Operations
168
+ ### Read
169
+ - `{paths read since last compaction}`
170
+ ### Modified
171
+ - `{paths written/edited since last compaction}`
172
+ ## Critical Context
173
+ - {Data, examples, references needed to continue}
174
+ ```
175
+
176
+ **Key features:**
177
+
178
+ - **Iterative merging** - Updates preserve existing information, adding new progress rather than regenerating from scratch
179
+ - **Deterministic file tracking** - Read/write/edit operations tracked automatically via tool call interception, not LLM extraction
180
+ - **Auto-injection** - Most recent ledger injected into system prompt on session start
155
181
 
156
182
  **Auto-clear:** At 80% context usage, the system automatically:
157
- 1. Updates the ledger
158
- 2. Creates a handoff document
183
+ 1. Captures file operations tracked since last clear
184
+ 2. Updates ledger with current state (iterative merge with previous)
159
185
  3. Clears the session
160
- 4. Injects the ledger into the fresh context
186
+ 4. Injects the updated ledger into fresh context
161
187
 
162
188
  #### Artifact Search
163
189
 
@@ -170,26 +196,17 @@ Search past work to find relevant precedent:
170
196
 
171
197
  Searches across:
172
198
  - Ledgers (`thoughts/ledgers/`)
173
- - Handoffs (`thoughts/shared/handoffs/`)
174
199
  - Plans (`thoughts/shared/plans/`)
175
200
 
176
201
  **Auto-indexing:** Artifacts are automatically indexed when created.
177
202
 
178
- #### Handoff
179
-
180
- Save/resume session state for continuity:
181
-
182
- - `handoff-creator`: Save current session (reads ledger for context)
183
- - `handoff-resumer`: Resume from handoff
184
- - Output: `thoughts/shared/handoffs/`
185
-
186
203
  ## Commands
187
204
 
188
205
  | Command | Description |
189
206
  |---------|-------------|
190
207
  | `/init` | Initialize project with ARCHITECTURE.md and CODE_STYLE.md |
191
208
  | `/ledger` | Create or update continuity ledger for session state |
192
- | `/search` | Search past handoffs, plans, and ledgers |
209
+ | `/search` | Search past plans and ledgers |
193
210
 
194
211
  ## Agents
195
212
 
@@ -207,8 +224,6 @@ Save/resume session state for continuity:
207
224
  | reviewer | subagent | claude-opus-4-5 | Review correctness and style |
208
225
  | ledger-creator | subagent | claude-sonnet | Create/update continuity ledgers |
209
226
  | artifact-searcher | subagent | claude-sonnet | Search past work for precedent |
210
- | handoff-creator | subagent | claude-opus-4-5 | Save session state |
211
- | handoff-resumer | subagent | claude-opus-4-5 | Resume from handoff |
212
227
 
213
228
  ## Tools
214
229
 
@@ -217,7 +232,7 @@ Save/resume session state for continuity:
217
232
  | `ast_grep_search` | AST-aware code pattern search |
218
233
  | `ast_grep_replace` | AST-aware code pattern replacement |
219
234
  | `look_at` | Extract file structure for large files |
220
- | `artifact_search` | Search past handoffs, plans, and ledgers |
235
+ | `artifact_search` | Search past plans and ledgers |
221
236
  | `background_task` | Run long-running tasks in background |
222
237
  | `background_output` | Check background task status/output |
223
238
  | `background_cancel` | Cancel background tasks |
@@ -229,7 +244,8 @@ Save/resume session state for continuity:
229
244
  |------|-------------|
230
245
  | Think Mode | Keywords like "think hard" enable 32k token thinking budget |
231
246
  | Ledger Loader | Injects continuity ledger into system prompt |
232
- | Auto-Clear Ledger | At 80% context, saves ledger + handoff and clears session |
247
+ | Auto-Clear Ledger | At 80% context, saves ledger with file ops and clears session |
248
+ | File Ops Tracker | Tracks read/write/edit tool calls for deterministic file operation logging |
233
249
  | Artifact Auto-Index | Indexes artifacts when written to thoughts/ directories |
234
250
  | Auto-Compact | Summarizes session when hitting token limits |
235
251
  | Context Injector | Injects ARCHITECTURE.md, CODE_STYLE.md, .cursorrules |
@@ -276,8 +292,7 @@ micode/
276
292
  ├── ledgers/ # Continuity ledgers
277
293
  └── shared/
278
294
  ├── designs/ # Brainstorm outputs
279
- ├── plans/ # Implementation plans
280
- └── handoffs/ # Session handoffs
295
+ └── plans/ # Implementation plans
281
296
  ```
282
297
 
283
298
  ## Development
@@ -341,3 +356,4 @@ Built on techniques from:
341
356
 
342
357
  - **[oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode)** - OpenCode plugin architecture, agent orchestration patterns, and trusted publishing setup
343
358
  - **[HumanLayer ACE-FCA](https://github.com/humanlayer/12-factor-agents)** - Advanced Context Engineering for Coding Agents, structured workflows, and the research → plan → implement methodology
359
+ - **[Factory.ai Context Compression](https://factory.ai/blog/context-compression)** - Structured compaction research showing that anchored iterative summarization with deterministic file tracking outperforms generic compression
@@ -7,11 +7,9 @@ import { plannerAgent } from "./planner";
7
7
  import { implementerAgent } from "./implementer";
8
8
  import { reviewerAgent } from "./reviewer";
9
9
  import { executorAgent } from "./executor";
10
- import { handoffCreatorAgent } from "./handoff-creator";
11
- import { handoffResumerAgent } from "./handoff-resumer";
12
10
  import { primaryAgent, PRIMARY_AGENT_NAME } from "./commander";
13
11
  import { projectInitializerAgent } from "./project-initializer";
14
12
  import { ledgerCreatorAgent } from "./ledger-creator";
15
13
  import { artifactSearcherAgent } from "./artifact-searcher";
16
14
  export declare const agents: Record<string, AgentConfig>;
17
- export { primaryAgent, PRIMARY_AGENT_NAME, brainstormerAgent, codebaseLocatorAgent, codebaseAnalyzerAgent, patternFinderAgent, plannerAgent, implementerAgent, reviewerAgent, executorAgent, handoffCreatorAgent, handoffResumerAgent, projectInitializerAgent, ledgerCreatorAgent, artifactSearcherAgent, };
15
+ export { primaryAgent, PRIMARY_AGENT_NAME, brainstormerAgent, codebaseLocatorAgent, codebaseAnalyzerAgent, patternFinderAgent, plannerAgent, implementerAgent, reviewerAgent, executorAgent, projectInitializerAgent, ledgerCreatorAgent, artifactSearcherAgent, };
@@ -1,4 +1,14 @@
1
1
  import type { PluginInput } from "@opencode-ai/plugin";
2
+ export declare function parseLedger(content: string, filePath: string, sessionName: string): {
3
+ id: string;
4
+ sessionName: string;
5
+ filePath: string;
6
+ goal: string;
7
+ stateNow: string;
8
+ keyDecisions: string;
9
+ filesRead: string;
10
+ filesModified: string;
11
+ };
2
12
  export declare function createArtifactAutoIndexHook(_ctx: PluginInput): {
3
13
  "tool.execute.after": (input: {
4
14
  tool: string;
@@ -0,0 +1,26 @@
1
+ import type { PluginInput } from "@opencode-ai/plugin";
2
+ interface FileOps {
3
+ read: Set<string>;
4
+ modified: Set<string>;
5
+ }
6
+ export declare function trackFileOp(sessionID: string, operation: "read" | "write" | "edit", filePath: string): void;
7
+ export declare function getFileOps(sessionID: string): FileOps;
8
+ export declare function clearFileOps(sessionID: string): void;
9
+ export declare function getAndClearFileOps(sessionID: string): FileOps;
10
+ export declare function formatFileOpsForPrompt(ops: FileOps): string;
11
+ export declare function createFileOpsTrackerHook(_ctx: PluginInput): {
12
+ "tool.execute.after": (input: {
13
+ tool: string;
14
+ sessionID: string;
15
+ args?: Record<string, unknown>;
16
+ }, _output: {
17
+ output?: string;
18
+ }) => Promise<void>;
19
+ event: ({ event }: {
20
+ event: {
21
+ type: string;
22
+ properties?: unknown;
23
+ };
24
+ }) => Promise<void>;
25
+ };
26
+ export {};
package/dist/index.js CHANGED
@@ -848,159 +848,6 @@ Then after all complete, in ONE message spawn:
848
848
  </never-do>`
849
849
  };
850
850
 
851
- // src/agents/handoff-creator.ts
852
- var handoffCreatorAgent = {
853
- description: "Creates handoff documents for session continuity",
854
- mode: "subagent",
855
- model: "anthropic/claude-opus-4-5",
856
- temperature: 0.2,
857
- tools: {
858
- edit: false,
859
- task: false
860
- },
861
- prompt: `<purpose>
862
- Create handoff document to transfer context to future session.
863
- </purpose>
864
-
865
- <when-to-use>
866
- <trigger>Hitting context limits</trigger>
867
- <trigger>Ending work session</trigger>
868
- <trigger>Switching to different task</trigger>
869
- </when-to-use>
870
-
871
- <rules>
872
- <rule>FIRST check for existing ledger at thoughts/ledgers/CONTINUITY_*.md</rule>
873
- <rule>If ledger exists, use its session name for handoff directory</rule>
874
- <rule>Capture ALL in-progress work</rule>
875
- <rule>Include exact file:line references for changes</rule>
876
- <rule>Document learnings and gotchas</rule>
877
- <rule>Prioritize next steps clearly</rule>
878
- <rule>Include git state (branch, commit)</rule>
879
- <rule>Reference all artifacts created</rule>
880
- </rules>
881
-
882
- <process>
883
- <step>Check for ledger at thoughts/ledgers/CONTINUITY_*.md</step>
884
- <step>If ledger exists, extract session name and state</step>
885
- <step>Review what was worked on</step>
886
- <step>Check git status for uncommitted changes</step>
887
- <step>Gather learnings and decisions made</step>
888
- <step>Identify next steps in priority order</step>
889
- <step>Write handoff document</step>
890
- <step>Commit handoff document</step>
891
- </process>
892
-
893
- <output-path>
894
- If ledger exists: thoughts/shared/handoffs/{session-name}/YYYY-MM-DD_HH-MM-SS.md
895
- Otherwise: thoughts/shared/handoffs/YYYY-MM-DD_HH-MM-SS_description.md
896
- </output-path>
897
-
898
- <document-format>
899
- <frontmatter>
900
- date: [ISO datetime]
901
- branch: [branch name]
902
- commit: [hash]
903
- session: [session name from ledger, if available]
904
- </frontmatter>
905
- <sections>
906
- <section name="Tasks">Table with Task | Status (completed/in-progress/blocked)</section>
907
- <section name="Current State">Working on, Blocked by, Plan location</section>
908
- <section name="Changes Made">file:line - what changed</section>
909
- <section name="Learnings">Discoveries, gotchas, decisions made and why</section>
910
- <section name="Next Steps">Prioritized list 1-3</section>
911
- <section name="Notes">Anything else for next session</section>
912
- </sections>
913
- </document-format>
914
-
915
- <output-summary>
916
- <template>
917
- Handoff: [path]
918
- Tasks: [X done, Y in-progress]
919
- Next: [top priority]
920
- </template>
921
- </output-summary>`
922
- };
923
-
924
- // src/agents/handoff-resumer.ts
925
- var handoffResumerAgent = {
926
- description: "Resumes work from a handoff document",
927
- mode: "subagent",
928
- model: "anthropic/claude-opus-4-5",
929
- temperature: 0.2,
930
- tools: {
931
- write: false,
932
- edit: false,
933
- task: false
934
- },
935
- prompt: `<purpose>
936
- Resume work from a handoff document. Verify state before proceeding.
937
- </purpose>
938
-
939
- <rules>
940
- <rule>Read handoff document COMPLETELY</rule>
941
- <rule>Load ALL referenced artifacts</rule>
942
- <rule>Verify git state matches</rule>
943
- <rule>Check for changes since handoff</rule>
944
- <rule>Report discrepancies before proceeding</rule>
945
- <rule>Don't assume - verify</rule>
946
- </rules>
947
-
948
- <process>
949
- <step>Find handoff (use provided path or list available)</step>
950
- <step>Read handoff completely</step>
951
- <step>Load referenced plans, research, files</step>
952
- <step>Verify current state matches</step>
953
- <step>Report analysis</step>
954
- <step>Wait for confirmation</step>
955
- </process>
956
-
957
- <state-verification>
958
- <check>Current branch</check>
959
- <check>Commit history (ahead/behind)</check>
960
- <check>Files mentioned still exist</check>
961
- <check>Changes mentioned are present</check>
962
- <check>No conflicting changes made</check>
963
- </state-verification>
964
-
965
- <output-format>
966
- <template>
967
- ## Resuming: [handoff path]
968
-
969
- **Created**: [date]
970
- **Branch**: [expected] \u2192 [actual]
971
- **Commit**: [expected] \u2192 [actual]
972
-
973
- ### State
974
- - Branch: [matches/differs]
975
- - Commit: [matches/X ahead/X behind]
976
- - Files: [verified/issues]
977
-
978
- ### Tasks
979
- | Task | Status | Verified |
980
- |------|--------|----------|
981
- | [Task] | [status] | [yes/no] |
982
-
983
- ### Learnings
984
- - [From handoff]
985
-
986
- ### Next Action
987
- [Top priority from handoff]
988
-
989
- ### Loaded
990
- - [x] [artifact]
991
- - [x] [artifact]
992
- </template>
993
- </output-format>
994
-
995
- <on-mismatch>
996
- <action>Report discrepancy and wait for guidance</action>
997
- <discrepancy>Branch different</discrepancy>
998
- <discrepancy>Unexpected commits</discrepancy>
999
- <discrepancy>Files changed/missing</discrepancy>
1000
- <discrepancy>Conflicting work detected</discrepancy>
1001
- </on-mismatch>`
1002
- };
1003
-
1004
851
  // src/agents/commander.ts
1005
852
  var PROMPT = `<identity>
1006
853
  You are Commander - pragmatic software engineer and orchestrator.
@@ -1078,9 +925,9 @@ Just do it - including obvious follow-up actions.
1078
925
  <rule>Reference plan file in commit body</rule>
1079
926
  </phase>
1080
927
 
1081
- <phase name="handoff">
1082
- <agent name="handoff-creator">Save session state</agent>
1083
- <agent name="handoff-resumer">Resume from handoff</agent>
928
+ <phase name="ledger" trigger="context getting full or session ending">
929
+ <action>System auto-updates ledger at 80% context usage</action>
930
+ <output>thoughts/ledgers/CONTINUITY_{session-name}.md</output>
1084
931
  </phase>
1085
932
  </workflow>
1086
933
 
@@ -1091,8 +938,7 @@ Just do it - including obvious follow-up actions.
1091
938
  <agent name="pattern-finder" mode="subagent" purpose="Find existing patterns"/>
1092
939
  <agent name="planner" mode="subagent" purpose="Create detailed implementation plans"/>
1093
940
  <agent name="executor" mode="subagent" purpose="Execute plan (runs implementer then reviewer automatically)"/>
1094
- <agent name="handoff-creator" mode="subagent" purpose="Create handoff docs"/>
1095
- <agent name="handoff-resumer" mode="subagent" purpose="Resume from handoffs"/>
941
+ <agent name="ledger-creator" mode="subagent" purpose="Create/update continuity ledgers"/>
1096
942
  <parallelization>
1097
943
  <safe>locator, analyzer, pattern-finder</safe>
1098
944
  <sequential>planner then executor</sequential>
@@ -1339,19 +1185,50 @@ Create or update a continuity ledger to preserve session state across context cl
1339
1185
  The ledger captures the essential context needed to resume work seamlessly.
1340
1186
  </purpose>
1341
1187
 
1188
+ <modes>
1189
+ <mode name="initial">Create new ledger when none exists</mode>
1190
+ <mode name="iterative">Update existing ledger with new information</mode>
1191
+ </modes>
1192
+
1342
1193
  <rules>
1343
1194
  <rule>Keep the ledger CONCISE - only essential information</rule>
1344
1195
  <rule>Focus on WHAT and WHY, not HOW</rule>
1345
- <rule>State should have exactly ONE item in "Now"</rule>
1346
1196
  <rule>Mark uncertain information as UNCONFIRMED</rule>
1347
1197
  <rule>Include git branch and key file paths</rule>
1348
1198
  </rules>
1349
1199
 
1200
+ <iterative-update-rules>
1201
+ <rule>PRESERVE all existing information from previous ledger</rule>
1202
+ <rule>ADD new progress, decisions, context from new messages</rule>
1203
+ <rule>UPDATE Progress: move In Progress items to Done when completed</rule>
1204
+ <rule>UPDATE Next Steps based on current state</rule>
1205
+ <rule>MERGE file operations: combine previous + new (passed deterministically)</rule>
1206
+ <rule>Never lose information - only add or update</rule>
1207
+ </iterative-update-rules>
1208
+
1209
+ <input-format-for-update>
1210
+ When updating an existing ledger, you will receive:
1211
+
1212
+ <previous-ledger>
1213
+ {content of existing ledger}
1214
+ </previous-ledger>
1215
+
1216
+ <file-operations>
1217
+ Read: path1, path2, path3
1218
+ Modified: path4, path5
1219
+ </file-operations>
1220
+
1221
+ <instruction>
1222
+ Update the ledger with the current session state. Merge the file operations above with any existing ones in the previous ledger.
1223
+ </instruction>
1224
+ </input-format-for-update>
1225
+
1350
1226
  <process>
1351
- <step>Check for existing ledger at thoughts/ledgers/CONTINUITY_*.md</step>
1352
- <step>If exists, read and update it</step>
1353
- <step>If not, create new ledger with session name from current task</step>
1227
+ <step>Check if previous-ledger is provided in input</step>
1228
+ <step>If provided: parse existing content and merge with new state</step>
1229
+ <step>If not: create new ledger with session name from current task</step>
1354
1230
  <step>Gather current state: goal, decisions, progress, blockers</step>
1231
+ <step>Merge file operations (previous + new from input)</step>
1355
1232
  <step>Write ledger in the exact format below</step>
1356
1233
  </process>
1357
1234
 
@@ -1362,21 +1239,37 @@ The ledger captures the essential context needed to resume work seamlessly.
1362
1239
  Updated: {ISO timestamp}
1363
1240
 
1364
1241
  ## Goal
1365
- {One sentence describing success criteria}
1242
+ {What we're trying to accomplish - one sentence describing success criteria}
1366
1243
 
1367
1244
  ## Constraints
1368
1245
  {Technical requirements, patterns to follow, things to avoid}
1369
1246
 
1247
+ ## Progress
1248
+ ### Done
1249
+ - [x] {Completed items}
1250
+
1251
+ ### In Progress
1252
+ - [ ] {Current work - what's actively being worked on}
1253
+
1254
+ ### Blocked
1255
+ - {Issues preventing progress, if any}
1256
+
1370
1257
  ## Key Decisions
1371
- - {Decision}: {Rationale}
1258
+ - **{Decision}**: {Rationale}
1259
+
1260
+ ## Next Steps
1261
+ 1. {Ordered list of what to do next}
1262
+
1263
+ ## File Operations
1264
+ ### Read
1265
+ - \`{paths that were read}\`
1372
1266
 
1373
- ## State
1374
- - Done: {Completed items as comma-separated list}
1375
- - Now: {Current focus - exactly ONE thing}
1376
- - Next: {Queued items in priority order}
1267
+ ### Modified
1268
+ - \`{paths that were written or edited}\`
1377
1269
 
1378
- ## Open Questions
1379
- - UNCONFIRMED: {Things needing verification}
1270
+ ## Critical Context
1271
+ - {Data, examples, references needed to continue work}
1272
+ - {Important findings or discoveries}
1380
1273
 
1381
1274
  ## Working Set
1382
1275
  - Branch: \`{branch-name}\`
@@ -1385,7 +1278,7 @@ Updated: {ISO timestamp}
1385
1278
 
1386
1279
  <output-summary>
1387
1280
  Ledger updated: thoughts/ledgers/CONTINUITY_{session-name}.md
1388
- State: {Now item}
1281
+ State: {Current In Progress item}
1389
1282
  </output-summary>`
1390
1283
  };
1391
1284
 
@@ -1445,8 +1338,6 @@ var agents = {
1445
1338
  implementer: implementerAgent,
1446
1339
  reviewer: reviewerAgent,
1447
1340
  executor: executorAgent,
1448
- "handoff-creator": handoffCreatorAgent,
1449
- "handoff-resumer": handoffResumerAgent,
1450
1341
  "project-initializer": projectInitializerAgent,
1451
1342
  "ledger-creator": ledgerCreatorAgent,
1452
1343
  "artifact-searcher": artifactSearcherAgent
@@ -14144,18 +14035,6 @@ class ArtifactIndex {
14144
14035
  }
14145
14036
  getInlineSchema() {
14146
14037
  return `
14147
- CREATE TABLE IF NOT EXISTS handoffs (
14148
- id TEXT PRIMARY KEY,
14149
- session_name TEXT,
14150
- file_path TEXT UNIQUE NOT NULL,
14151
- task_summary TEXT,
14152
- what_worked TEXT,
14153
- what_failed TEXT,
14154
- learnings TEXT,
14155
- outcome TEXT,
14156
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
14157
- indexed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
14158
- );
14159
14038
  CREATE TABLE IF NOT EXISTS plans (
14160
14039
  id TEXT PRIMARY KEY,
14161
14040
  title TEXT,
@@ -14172,55 +14051,15 @@ class ArtifactIndex {
14172
14051
  goal TEXT,
14173
14052
  state_now TEXT,
14174
14053
  key_decisions TEXT,
14054
+ files_read TEXT,
14055
+ files_modified TEXT,
14175
14056
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
14176
14057
  indexed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
14177
14058
  );
14178
- CREATE VIRTUAL TABLE IF NOT EXISTS handoffs_fts USING fts5(id, session_name, task_summary, what_worked, what_failed, learnings);
14179
14059
  CREATE VIRTUAL TABLE IF NOT EXISTS plans_fts USING fts5(id, title, overview, approach);
14180
14060
  CREATE VIRTUAL TABLE IF NOT EXISTS ledgers_fts USING fts5(id, session_name, goal, state_now, key_decisions);
14181
14061
  `;
14182
14062
  }
14183
- async indexHandoff(record2) {
14184
- if (!this.db)
14185
- throw new Error("Database not initialized");
14186
- const existing = this.db.query(`SELECT id FROM handoffs WHERE file_path = ?`).get(record2.filePath);
14187
- if (existing) {
14188
- this.db.run(`DELETE FROM handoffs_fts WHERE id = ?`, [existing.id]);
14189
- }
14190
- this.db.run(`
14191
- INSERT INTO handoffs (id, session_name, file_path, task_summary, what_worked, what_failed, learnings, outcome, indexed_at)
14192
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
14193
- ON CONFLICT(file_path) DO UPDATE SET
14194
- id = excluded.id,
14195
- session_name = excluded.session_name,
14196
- task_summary = excluded.task_summary,
14197
- what_worked = excluded.what_worked,
14198
- what_failed = excluded.what_failed,
14199
- learnings = excluded.learnings,
14200
- outcome = excluded.outcome,
14201
- indexed_at = CURRENT_TIMESTAMP
14202
- `, [
14203
- record2.id,
14204
- record2.sessionName ?? null,
14205
- record2.filePath,
14206
- record2.taskSummary ?? null,
14207
- record2.whatWorked ?? null,
14208
- record2.whatFailed ?? null,
14209
- record2.learnings ?? null,
14210
- record2.outcome ?? null
14211
- ]);
14212
- this.db.run(`
14213
- INSERT INTO handoffs_fts (id, session_name, task_summary, what_worked, what_failed, learnings)
14214
- VALUES (?, ?, ?, ?, ?, ?)
14215
- `, [
14216
- record2.id,
14217
- record2.sessionName ?? null,
14218
- record2.taskSummary ?? null,
14219
- record2.whatWorked ?? null,
14220
- record2.whatFailed ?? null,
14221
- record2.learnings ?? null
14222
- ]);
14223
- }
14224
14063
  async indexPlan(record2) {
14225
14064
  if (!this.db)
14226
14065
  throw new Error("Database not initialized");
@@ -14251,14 +14090,16 @@ class ArtifactIndex {
14251
14090
  this.db.run(`DELETE FROM ledgers_fts WHERE id = ?`, [existing.id]);
14252
14091
  }
14253
14092
  this.db.run(`
14254
- INSERT INTO ledgers (id, session_name, file_path, goal, state_now, key_decisions, indexed_at)
14255
- VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
14093
+ INSERT INTO ledgers (id, session_name, file_path, goal, state_now, key_decisions, files_read, files_modified, indexed_at)
14094
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
14256
14095
  ON CONFLICT(file_path) DO UPDATE SET
14257
14096
  id = excluded.id,
14258
14097
  session_name = excluded.session_name,
14259
14098
  goal = excluded.goal,
14260
14099
  state_now = excluded.state_now,
14261
14100
  key_decisions = excluded.key_decisions,
14101
+ files_read = excluded.files_read,
14102
+ files_modified = excluded.files_modified,
14262
14103
  indexed_at = CURRENT_TIMESTAMP
14263
14104
  `, [
14264
14105
  record2.id,
@@ -14266,7 +14107,9 @@ class ArtifactIndex {
14266
14107
  record2.filePath,
14267
14108
  record2.goal ?? null,
14268
14109
  record2.stateNow ?? null,
14269
- record2.keyDecisions ?? null
14110
+ record2.keyDecisions ?? null,
14111
+ record2.filesRead ?? null,
14112
+ record2.filesModified ?? null
14270
14113
  ]);
14271
14114
  this.db.run(`
14272
14115
  INSERT INTO ledgers_fts (id, session_name, goal, state_now, key_decisions)
@@ -14284,23 +14127,6 @@ class ArtifactIndex {
14284
14127
  throw new Error("Database not initialized");
14285
14128
  const results = [];
14286
14129
  const escapedQuery = this.escapeFtsQuery(query);
14287
- const handoffs = this.db.query(`
14288
- SELECT h.id, h.file_path, h.task_summary, rank
14289
- FROM handoffs_fts
14290
- JOIN handoffs h ON handoffs_fts.id = h.id
14291
- WHERE handoffs_fts MATCH ?
14292
- ORDER BY rank
14293
- LIMIT ?
14294
- `).all(escapedQuery, limit);
14295
- for (const row of handoffs) {
14296
- results.push({
14297
- type: "handoff",
14298
- id: row.id,
14299
- filePath: row.file_path,
14300
- summary: row.task_summary,
14301
- score: -row.rank
14302
- });
14303
- }
14304
14130
  const plans = this.db.query(`
14305
14131
  SELECT p.id, p.file_path, p.title, rank
14306
14132
  FROM plans_fts
@@ -14360,7 +14186,7 @@ async function getArtifactIndex() {
14360
14186
 
14361
14187
  // src/tools/artifact-search.ts
14362
14188
  var artifact_search = tool({
14363
- description: `Search past handoffs, plans, and ledgers for relevant precedent.
14189
+ description: `Search past plans and ledgers for relevant precedent.
14364
14190
  Use this to find:
14365
14191
  - Similar problems you've solved before
14366
14192
  - Patterns and approaches that worked
@@ -14369,7 +14195,7 @@ Returns ranked results with file paths for further reading.`,
14369
14195
  args: {
14370
14196
  query: tool.schema.string().describe("Search query - describe what you're looking for"),
14371
14197
  limit: tool.schema.number().optional().describe("Max results to return (default: 10)"),
14372
- type: tool.schema.enum(["all", "handoff", "plan", "ledger"]).optional().describe("Filter by artifact type (default: all)")
14198
+ type: tool.schema.enum(["all", "plan", "ledger"]).optional().describe("Filter by artifact type (default: all)")
14373
14199
  },
14374
14200
  execute: async (args) => {
14375
14201
  try {
@@ -15261,6 +15087,69 @@ ${output.system}`;
15261
15087
  };
15262
15088
  }
15263
15089
 
15090
+ // src/hooks/file-ops-tracker.ts
15091
+ var sessionFileOps = new Map;
15092
+ function getOrCreateOps(sessionID) {
15093
+ let ops = sessionFileOps.get(sessionID);
15094
+ if (!ops) {
15095
+ ops = { read: new Set, modified: new Set };
15096
+ sessionFileOps.set(sessionID, ops);
15097
+ }
15098
+ return ops;
15099
+ }
15100
+ function trackFileOp(sessionID, operation, filePath) {
15101
+ const ops = getOrCreateOps(sessionID);
15102
+ if (operation === "read") {
15103
+ ops.read.add(filePath);
15104
+ } else {
15105
+ ops.modified.add(filePath);
15106
+ }
15107
+ }
15108
+ function getFileOps(sessionID) {
15109
+ const ops = sessionFileOps.get(sessionID);
15110
+ if (!ops) {
15111
+ return { read: new Set, modified: new Set };
15112
+ }
15113
+ return ops;
15114
+ }
15115
+ function clearFileOps(sessionID) {
15116
+ sessionFileOps.delete(sessionID);
15117
+ }
15118
+ function formatFileOpsForPrompt(ops) {
15119
+ const readPaths = Array.from(ops.read).sort();
15120
+ const modifiedPaths = Array.from(ops.modified).sort();
15121
+ let result = `<file-operations>
15122
+ `;
15123
+ result += `Read: ${readPaths.length > 0 ? readPaths.join(", ") : "(none)"}
15124
+ `;
15125
+ result += `Modified: ${modifiedPaths.length > 0 ? modifiedPaths.join(", ") : "(none)"}
15126
+ `;
15127
+ result += "</file-operations>";
15128
+ return result;
15129
+ }
15130
+ function createFileOpsTrackerHook(_ctx) {
15131
+ return {
15132
+ "tool.execute.after": async (input, _output) => {
15133
+ const toolName = input.tool.toLowerCase();
15134
+ if (!["read", "write", "edit"].includes(toolName)) {
15135
+ return;
15136
+ }
15137
+ const filePath = input.args?.filePath;
15138
+ if (!filePath)
15139
+ return;
15140
+ trackFileOp(input.sessionID, toolName, filePath);
15141
+ },
15142
+ event: async ({ event }) => {
15143
+ if (event.type === "session.deleted") {
15144
+ const props = event.properties;
15145
+ if (props?.info?.id) {
15146
+ clearFileOps(props.info.id);
15147
+ }
15148
+ }
15149
+ }
15150
+ };
15151
+ }
15152
+
15264
15153
  // src/hooks/auto-clear-ledger.ts
15265
15154
  var MODEL_CONTEXT_LIMITS2 = {
15266
15155
  "claude-opus": 200000,
@@ -15337,23 +15226,40 @@ function createAutoClearLedgerHook(ctx) {
15337
15226
  duration: 3000
15338
15227
  }
15339
15228
  }).catch(() => {});
15229
+ const fileOps = getFileOps(sessionID);
15230
+ const existingLedger = await findCurrentLedger(ctx.directory);
15340
15231
  const ledgerSessionResp = await ctx.client.session.create({
15341
15232
  body: {},
15342
15233
  query: { directory: ctx.directory }
15343
15234
  });
15344
15235
  const ledgerSessionID = ledgerSessionResp.data?.id;
15345
15236
  if (ledgerSessionID) {
15237
+ let promptText = "";
15238
+ if (existingLedger) {
15239
+ promptText += `<previous-ledger>
15240
+ ${existingLedger.content}
15241
+ </previous-ledger>
15242
+
15243
+ `;
15244
+ }
15245
+ promptText += formatFileOpsForPrompt(fileOps);
15246
+ promptText += `
15247
+
15248
+ <instruction>
15249
+ `;
15250
+ promptText += existingLedger ? "Update the ledger with the current session state. Merge the file operations above with any existing ones in the previous ledger." : "Create a new continuity ledger for this session.";
15251
+ promptText += `
15252
+ </instruction>`;
15346
15253
  await ctx.client.session.prompt({
15347
15254
  path: { id: ledgerSessionID },
15348
15255
  body: {
15349
- parts: [
15350
- { type: "text", text: "Update the continuity ledger with current session state before context clear." }
15351
- ],
15256
+ parts: [{ type: "text", text: promptText }],
15352
15257
  agent: "ledger-creator"
15353
15258
  },
15354
15259
  query: { directory: ctx.directory }
15355
15260
  });
15356
15261
  let attempts = 0;
15262
+ let ledgerCompleted = false;
15357
15263
  while (attempts < 30) {
15358
15264
  await new Promise((resolve2) => setTimeout(resolve2, 2000));
15359
15265
  const statusResp = await ctx.client.session.get({
@@ -15361,41 +15267,13 @@ function createAutoClearLedgerHook(ctx) {
15361
15267
  query: { directory: ctx.directory }
15362
15268
  });
15363
15269
  if (statusResp.data?.status === "idle") {
15270
+ ledgerCompleted = true;
15364
15271
  break;
15365
15272
  }
15366
15273
  attempts++;
15367
15274
  }
15368
- }
15369
- const handoffSessionResp = await ctx.client.session.create({
15370
- body: {},
15371
- query: { directory: ctx.directory }
15372
- });
15373
- const handoffSessionID = handoffSessionResp.data?.id;
15374
- if (handoffSessionID) {
15375
- await ctx.client.session.prompt({
15376
- path: { id: handoffSessionID },
15377
- body: {
15378
- parts: [
15379
- {
15380
- type: "text",
15381
- text: "Create a handoff document. Read the current ledger at thoughts/ledgers/ for context."
15382
- }
15383
- ],
15384
- agent: "handoff-creator"
15385
- },
15386
- query: { directory: ctx.directory }
15387
- });
15388
- let attempts = 0;
15389
- while (attempts < 30) {
15390
- await new Promise((resolve2) => setTimeout(resolve2, 2000));
15391
- const statusResp = await ctx.client.session.get({
15392
- path: { id: handoffSessionID },
15393
- query: { directory: ctx.directory }
15394
- });
15395
- if (statusResp.data?.status === "idle") {
15396
- break;
15397
- }
15398
- attempts++;
15275
+ if (ledgerCompleted) {
15276
+ clearFileOps(sessionID);
15399
15277
  }
15400
15278
  }
15401
15279
  const firstMessage = messages[0];
@@ -15423,7 +15301,7 @@ function createAutoClearLedgerHook(ctx) {
15423
15301
  await ctx.client.tui.showToast({
15424
15302
  body: {
15425
15303
  title: "Context Cleared",
15426
- message: "Ledger + handoff saved. Session ready to continue.",
15304
+ message: "Ledger saved. Session ready to continue.",
15427
15305
  variant: "success",
15428
15306
  duration: 5000
15429
15307
  }
@@ -15475,41 +15353,35 @@ function createAutoClearLedgerHook(ctx) {
15475
15353
  // src/hooks/artifact-auto-index.ts
15476
15354
  import { readFileSync as readFileSync3 } from "fs";
15477
15355
  var LEDGER_PATH_PATTERN = /thoughts\/ledgers\/CONTINUITY_(.+)\.md$/;
15478
- var HANDOFF_PATH_PATTERN = /thoughts\/shared\/handoffs\/(.+)\.md$/;
15479
15356
  var PLAN_PATH_PATTERN = /thoughts\/shared\/plans\/(.+)\.md$/;
15480
15357
  function parseLedger(content, filePath, sessionName) {
15481
15358
  const goalMatch = content.match(/## Goal\n([^\n]+)/);
15482
- const stateMatch = content.match(/- Now: ([^\n]+)/);
15359
+ const stateMatch = content.match(/### In Progress\n- \[ \] ([^\n]+)/);
15483
15360
  const decisionsMatch = content.match(/## Key Decisions\n([\s\S]*?)(?=\n## |$)/);
15361
+ const fileOpsSection = content.match(/## File Operations\n([\s\S]*?)(?=\n## |$)/);
15362
+ let filesRead = "";
15363
+ let filesModified = "";
15364
+ if (fileOpsSection) {
15365
+ const readMatch = fileOpsSection[1].match(/### Read\n([\s\S]*?)(?=\n### |$)/);
15366
+ const modifiedMatch = fileOpsSection[1].match(/### Modified\n([\s\S]*?)(?=\n### |$)/);
15367
+ if (readMatch) {
15368
+ const paths = readMatch[1].match(/`([^`]+)`/g);
15369
+ filesRead = paths ? paths.map((p) => p.replace(/`/g, "")).join(",") : "";
15370
+ }
15371
+ if (modifiedMatch) {
15372
+ const paths = modifiedMatch[1].match(/`([^`]+)`/g);
15373
+ filesModified = paths ? paths.map((p) => p.replace(/`/g, "")).join(",") : "";
15374
+ }
15375
+ }
15484
15376
  return {
15485
15377
  id: `ledger-${sessionName}`,
15486
15378
  sessionName,
15487
15379
  filePath,
15488
15380
  goal: goalMatch?.[1] || "",
15489
15381
  stateNow: stateMatch?.[1] || "",
15490
- keyDecisions: decisionsMatch?.[1]?.trim() || ""
15491
- };
15492
- }
15493
- function parseHandoff(content, filePath, fileName) {
15494
- const sessionMatch = content.match(/^session:\s*(.+)$/m);
15495
- const sessionName = sessionMatch?.[1] || fileName;
15496
- const taskMatch = content.match(/\*\*Working on:\*\*\s*([^\n]+)/);
15497
- const taskSummary = taskMatch?.[1] || "";
15498
- const learningsMatch = content.match(/## Learnings\n\n([\s\S]*?)(?=\n## |$)/);
15499
- const learnings = learningsMatch?.[1]?.trim() || "";
15500
- const workedMatch = content.match(/## What Worked\n\n([\s\S]*?)(?=\n## |$)/);
15501
- const whatWorked = workedMatch?.[1]?.trim() || learnings;
15502
- const failedMatch = content.match(/## What Failed\n\n([\s\S]*?)(?=\n## |$)/);
15503
- const whatFailed = failedMatch?.[1]?.trim() || "";
15504
- return {
15505
- id: `handoff-${fileName}`,
15506
- sessionName,
15507
- filePath,
15508
- taskSummary,
15509
- whatWorked,
15510
- whatFailed,
15511
- learnings,
15512
- outcome: "UNKNOWN"
15382
+ keyDecisions: decisionsMatch?.[1]?.trim() || "",
15383
+ filesRead,
15384
+ filesModified
15513
15385
  };
15514
15386
  }
15515
15387
  function parsePlan(content, filePath, fileName) {
@@ -15545,15 +15417,6 @@ function createArtifactAutoIndexHook(_ctx) {
15545
15417
  console.log(`[artifact-auto-index] Indexed ledger: ${filePath}`);
15546
15418
  return;
15547
15419
  }
15548
- const handoffMatch = filePath.match(HANDOFF_PATH_PATTERN);
15549
- if (handoffMatch) {
15550
- const content = readFileSync3(filePath, "utf-8");
15551
- const index = await getArtifactIndex();
15552
- const record2 = parseHandoff(content, filePath, handoffMatch[1]);
15553
- await index.indexHandoff(record2);
15554
- console.log(`[artifact-auto-index] Indexed handoff: ${filePath}`);
15555
- return;
15556
- }
15557
15420
  const planMatch = filePath.match(PLAN_PATH_PATTERN);
15558
15421
  if (planMatch) {
15559
15422
  const content = readFileSync3(filePath, "utf-8");
@@ -16062,6 +15925,7 @@ var OpenCodeConfigPlugin = async (ctx) => {
16062
15925
  const contextWindowMonitorHook = createContextWindowMonitorHook(ctx);
16063
15926
  const commentCheckerHook = createCommentCheckerHook(ctx);
16064
15927
  const artifactAutoIndexHook = createArtifactAutoIndexHook(ctx);
15928
+ const fileOpsTrackerHook = createFileOpsTrackerHook(ctx);
16065
15929
  const backgroundTaskManager = new BackgroundTaskManager(ctx);
16066
15930
  const backgroundTaskTools = createBackgroundTaskTools(backgroundTaskManager);
16067
15931
  return {
@@ -16135,6 +15999,7 @@ var OpenCodeConfigPlugin = async (ctx) => {
16135
15999
  await commentCheckerHook["tool.execute.after"]({ tool: input.tool, args: input.args }, output);
16136
16000
  await contextInjectorHook["tool.execute.after"]({ tool: input.tool, args: input.args }, output);
16137
16001
  await artifactAutoIndexHook["tool.execute.after"]({ tool: input.tool, args: input.args }, output);
16002
+ await fileOpsTrackerHook["tool.execute.after"]({ tool: input.tool, sessionID: input.sessionID, args: input.args }, output);
16138
16003
  },
16139
16004
  event: async ({ event }) => {
16140
16005
  if (event.type === "session.deleted") {
@@ -16149,6 +16014,7 @@ var OpenCodeConfigPlugin = async (ctx) => {
16149
16014
  await tokenAwareTruncationHook.event({ event });
16150
16015
  await contextWindowMonitorHook.event({ event });
16151
16016
  backgroundTaskManager.handleEvent(event);
16017
+ await fileOpsTrackerHook.event({ event });
16152
16018
  }
16153
16019
  };
16154
16020
  };
@@ -1,13 +1,3 @@
1
- export interface HandoffRecord {
2
- id: string;
3
- sessionName?: string;
4
- filePath: string;
5
- taskSummary?: string;
6
- whatWorked?: string;
7
- whatFailed?: string;
8
- learnings?: string;
9
- outcome?: "SUCCEEDED" | "PARTIAL_PLUS" | "PARTIAL_MINUS" | "FAILED" | "UNKNOWN";
10
- }
11
1
  export interface PlanRecord {
12
2
  id: string;
13
3
  title?: string;
@@ -22,9 +12,11 @@ export interface LedgerRecord {
22
12
  goal?: string;
23
13
  stateNow?: string;
24
14
  keyDecisions?: string;
15
+ filesRead?: string;
16
+ filesModified?: string;
25
17
  }
26
18
  export interface SearchResult {
27
- type: "handoff" | "plan" | "ledger";
19
+ type: "plan" | "ledger";
28
20
  id: string;
29
21
  filePath: string;
30
22
  title?: string;
@@ -37,7 +29,6 @@ export declare class ArtifactIndex {
37
29
  constructor(dbDir?: string);
38
30
  initialize(): Promise<void>;
39
31
  private getInlineSchema;
40
- indexHandoff(record: HandoffRecord): Promise<void>;
41
32
  indexPlan(record: PlanRecord): Promise<void>;
42
33
  indexLedger(record: LedgerRecord): Promise<void>;
43
34
  search(query: string, limit?: number): Promise<SearchResult[]>;
@@ -5,7 +5,6 @@ export declare const artifact_search: {
5
5
  limit: import("zod").ZodOptional<import("zod").ZodNumber>;
6
6
  type: import("zod").ZodOptional<import("zod").ZodEnum<{
7
7
  all: "all";
8
- handoff: "handoff";
9
8
  plan: "plan";
10
9
  ledger: "ledger";
11
10
  }>>;
@@ -13,6 +12,6 @@ export declare const artifact_search: {
13
12
  execute(args: {
14
13
  query: string;
15
14
  limit?: number | undefined;
16
- type?: "all" | "handoff" | "plan" | "ledger" | undefined;
15
+ type?: "all" | "plan" | "ledger" | undefined;
17
16
  }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
18
17
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micode",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "OpenCode plugin with Brainstorm-Research-Plan-Implement workflow",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,2 +0,0 @@
1
- import type { AgentConfig } from "@opencode-ai/sdk";
2
- export declare const handoffCreatorAgent: AgentConfig;
@@ -1,2 +0,0 @@
1
- import type { AgentConfig } from "@opencode-ai/sdk";
2
- export declare const handoffResumerAgent: AgentConfig;