micode 0.3.3 → 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/INSTALL_CLAUDE.md +10 -10
- package/README.md +44 -28
- package/dist/agents/index.d.ts +1 -3
- package/dist/hooks/artifact-auto-index.d.ts +10 -0
- package/dist/hooks/file-ops-tracker.d.ts +26 -0
- package/dist/index.js +186 -320
- package/dist/tools/artifact-index/index.d.ts +3 -12
- package/dist/tools/artifact-search.d.ts +1 -2
- package/package.json +1 -1
- package/dist/agents/handoff-creator.d.ts +0 -2
- package/dist/agents/handoff-resumer.d.ts +0 -2
package/INSTALL_CLAUDE.md
CHANGED
|
@@ -4,7 +4,7 @@ This document guides AI assistants through the micode plugin installation proces
|
|
|
4
4
|
|
|
5
5
|
## Step 0: Ask User About Setup
|
|
6
6
|
|
|
7
|
-
micode uses Claude Opus 4.5 for its primary agents (
|
|
7
|
+
micode uses Claude Opus 4.5 for its primary agents (commander, brainstormer, project-initializer). Ask the user:
|
|
8
8
|
|
|
9
9
|
### Question 1: Claude Subscription
|
|
10
10
|
|
|
@@ -22,8 +22,8 @@ micode uses Claude Opus 4.5 for its primary agents (Commander, Brainstormer, pro
|
|
|
22
22
|
```json
|
|
23
23
|
{
|
|
24
24
|
"agents": {
|
|
25
|
-
"
|
|
26
|
-
"
|
|
25
|
+
"commander": { "model": "opencode/big-pickle" },
|
|
26
|
+
"brainstormer": { "model": "opencode/big-pickle" },
|
|
27
27
|
"project-initializer": { "model": "opencode/big-pickle" }
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -98,7 +98,7 @@ opencode auth login
|
|
|
98
98
|
## Step 5: Verify Everything Works
|
|
99
99
|
|
|
100
100
|
1. Run `opencode` in terminal
|
|
101
|
-
2. Check that
|
|
101
|
+
2. Check that commander agent is available
|
|
102
102
|
3. Try `/init` command to test project-initializer
|
|
103
103
|
|
|
104
104
|
## Step 6: Congratulate User
|
|
@@ -108,8 +108,8 @@ Say to user:
|
|
|
108
108
|
> Congratulations! 🎉 You have successfully set up micode!
|
|
109
109
|
>
|
|
110
110
|
> **What you can do now:**
|
|
111
|
-
> - Use **
|
|
112
|
-
> - Use **
|
|
111
|
+
> - Use **commander** as your main orchestrator
|
|
112
|
+
> - Use **brainstormer** for design exploration
|
|
113
113
|
> - Run `/init` to generate ARCHITECTURE.md and CODE_STYLE.md
|
|
114
114
|
>
|
|
115
115
|
> **Workflow:** Brainstorm → Research → Plan → Implement → Review
|
|
@@ -137,8 +137,8 @@ Never run this automatically without consent.
|
|
|
137
137
|
|
|
138
138
|
| Agent | Model | Purpose |
|
|
139
139
|
|-------|-------|---------|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
140
|
+
| commander | claude-opus-4-5 | Main orchestrator |
|
|
141
|
+
| brainstormer | claude-opus-4-5 | Design exploration |
|
|
142
142
|
| project-initializer | claude-opus-4-5 | Generate docs |
|
|
143
143
|
| codebase-locator | - | Find files |
|
|
144
144
|
| codebase-analyzer | - | Analyze code |
|
|
@@ -168,8 +168,8 @@ To use different models, create `~/.config/opencode/micode.json`:
|
|
|
168
168
|
```json
|
|
169
169
|
{
|
|
170
170
|
"agents": {
|
|
171
|
-
"
|
|
172
|
-
"
|
|
171
|
+
"commander": { "model": "your-preferred-model" },
|
|
172
|
+
"brainstormer": { "model": "your-preferred-model" }
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
```
|
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
|
|
138
|
+
Maintain context across long sessions and context clears with structured compaction:
|
|
139
139
|
|
|
140
140
|
#### Ledger System
|
|
141
141
|
|
|
142
|
-
The **continuity ledger**
|
|
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
|
-
|
|
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.
|
|
158
|
-
2.
|
|
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
|
|
186
|
+
4. Injects the updated ledger into fresh context
|
|
161
187
|
|
|
162
188
|
#### Artifact Search
|
|
163
189
|
|
|
@@ -170,33 +196,24 @@ 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
|
|
209
|
+
| `/search` | Search past plans and ledgers |
|
|
193
210
|
|
|
194
211
|
## Agents
|
|
195
212
|
|
|
196
213
|
| Agent | Mode | Model | Purpose |
|
|
197
214
|
|-------|------|-------|---------|
|
|
198
|
-
|
|
|
199
|
-
|
|
|
215
|
+
| commander | primary | claude-opus-4-5 | Orchestrator, delegates to specialists |
|
|
216
|
+
| brainstormer | primary | claude-opus-4-5 | Design exploration through questioning |
|
|
200
217
|
| project-initializer | subagent | claude-opus-4-5 | Generate ARCHITECTURE.md and CODE_STYLE.md |
|
|
201
218
|
| codebase-locator | subagent | claude-sonnet | Find file locations |
|
|
202
219
|
| codebase-analyzer | subagent | claude-sonnet | Deep code analysis |
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
package/dist/agents/index.d.ts
CHANGED
|
@@ -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,
|
|
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="
|
|
1082
|
-
<
|
|
1083
|
-
<
|
|
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="
|
|
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>
|
|
@@ -1117,7 +963,7 @@ var primaryAgent = {
|
|
|
1117
963
|
prompt: PROMPT,
|
|
1118
964
|
tools: { ask: true }
|
|
1119
965
|
};
|
|
1120
|
-
var PRIMARY_AGENT_NAME = process.env.OPENCODE_AGENT_NAME || "
|
|
966
|
+
var PRIMARY_AGENT_NAME = process.env.OPENCODE_AGENT_NAME || "commander";
|
|
1121
967
|
|
|
1122
968
|
// src/agents/project-initializer.ts
|
|
1123
969
|
var PROMPT2 = `
|
|
@@ -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
|
|
1352
|
-
<step>If
|
|
1353
|
-
<step>If not
|
|
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
|
-
{
|
|
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}
|
|
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
|
-
|
|
1374
|
-
-
|
|
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
|
-
##
|
|
1379
|
-
-
|
|
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: {
|
|
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
|
|
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", "
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 {
|
|
@@ -16083,7 +15947,7 @@ var OpenCodeConfigPlugin = async (ctx) => {
|
|
|
16083
15947
|
};
|
|
16084
15948
|
const mergedAgents = mergeAgentConfigs(agents, userConfig);
|
|
16085
15949
|
config2.agent = {
|
|
16086
|
-
|
|
15950
|
+
[PRIMARY_AGENT_NAME]: mergedAgents[PRIMARY_AGENT_NAME],
|
|
16087
15951
|
...Object.fromEntries(Object.entries(mergedAgents).filter(([k]) => k !== PRIMARY_AGENT_NAME)),
|
|
16088
15952
|
...config2.agent,
|
|
16089
15953
|
build: { ...config2.agent?.build, mode: "subagent" },
|
|
@@ -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: "
|
|
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" | "
|
|
15
|
+
type?: "all" | "plan" | "ledger" | undefined;
|
|
17
16
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
18
17
|
};
|
package/package.json
CHANGED