@jmylchreest/aide-plugin 0.0.42 → 0.0.44

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmylchreest/aide-plugin",
3
- "version": "0.0.42",
3
+ "version": "0.0.44",
4
4
  "description": "aide plugin for OpenCode — multi-agent orchestration, memory, skills, and persistence",
5
5
  "type": "module",
6
6
  "main": "./src/opencode/index.ts",
@@ -0,0 +1,157 @@
1
+ ---
2
+ name: context-usage
3
+ description: Analyze current session context and token usage from OpenCode SQLite database
4
+ platforms:
5
+ - opencode
6
+ triggers:
7
+ - context usage
8
+ - token usage
9
+ - session stats
10
+ - how much context
11
+ - context budget
12
+ - how big is this session
13
+ - session size
14
+ ---
15
+
16
+ # Context Usage Analysis
17
+
18
+ **Recommended model tier:** balanced (sonnet) - straightforward SQL queries
19
+
20
+ Analyze the current session's context window consumption, tool usage breakdown,
21
+ and token costs by querying the OpenCode SQLite database directly.
22
+
23
+ ## Prerequisites
24
+
25
+ - This skill **only works on OpenCode**. Verify by checking the environment:
26
+ - `$OPENCODE=1` — set by the OpenCode runtime
27
+ - `$AIDE_PLATFORM=opencode` — set by aide when running under OpenCode
28
+ - `$AIDE_SESSION_ID` — the current session ID (injected by aide)
29
+ - The OpenCode database is at `~/.local/share/opencode/opencode.db`.
30
+ - `sqlite3` must be available on the system.
31
+
32
+ If `$OPENCODE` is not `1` or `$AIDE_PLATFORM` is not `opencode`, abort immediately
33
+ and inform the user that this skill is only supported on OpenCode.
34
+ Do **not** attempt to query other databases (e.g. Claude Code's storage) — the
35
+ schema is OpenCode-specific.
36
+
37
+ If `$AIDE_SESSION_ID` is not set, abort with a message explaining that the
38
+ session ID could not be determined.
39
+
40
+ ## Workflow
41
+
42
+ Run the following queries **sequentially** in a single Bash call (chain with `&&`).
43
+ Present results to the user in a formatted summary after all queries complete.
44
+
45
+ ### Step 1: Validate environment
46
+
47
+ ```bash
48
+ test "$OPENCODE" = "1" && echo "Platform: OpenCode" || echo "ERROR: Not running on OpenCode (OPENCODE=$OPENCODE)"
49
+ test "$AIDE_PLATFORM" = "opencode" && echo "AIDE Platform: opencode" || echo "WARNING: AIDE_PLATFORM=$AIDE_PLATFORM"
50
+ test -n "$AIDE_SESSION_ID" && echo "Session: $AIDE_SESSION_ID" || echo "ERROR: AIDE_SESSION_ID not set"
51
+ ```
52
+
53
+ If `OPENCODE` is not `1`, stop immediately — this skill cannot work outside OpenCode.
54
+ If `AIDE_SESSION_ID` is not set, stop and inform the user.
55
+
56
+ ### Step 2: Session overview
57
+
58
+ ```bash
59
+ sqlite3 ~/.local/share/opencode/opencode.db "
60
+ SELECT
61
+ s.title,
62
+ s.slug,
63
+ ROUND((julianday('now') - julianday(datetime(s.time_created/1000, 'unixepoch'))) * 24, 1) as hours_old,
64
+ (SELECT COUNT(*) FROM message m WHERE m.session_id = s.id) as messages,
65
+ CASE WHEN s.time_compacting IS NOT NULL THEN 'yes' ELSE 'no' END as compacted
66
+ FROM session s
67
+ WHERE s.id = '$AIDE_SESSION_ID';
68
+ "
69
+ ```
70
+
71
+ ### Step 3: Token totals
72
+
73
+ Sum tokens from `step-finish` parts (each represents one LLM turn):
74
+
75
+ ```bash
76
+ sqlite3 ~/.local/share/opencode/opencode.db "
77
+ SELECT
78
+ SUM(json_extract(data, '$.tokens.input')) as input_tokens,
79
+ SUM(json_extract(data, '$.tokens.output')) as output_tokens,
80
+ SUM(json_extract(data, '$.tokens.cache.read')) as cache_read_tokens,
81
+ SUM(json_extract(data, '$.tokens.cache.write')) as cache_write_tokens,
82
+ SUM(json_extract(data, '$.tokens.total')) as total_tokens,
83
+ COUNT(*) as llm_turns
84
+ FROM part
85
+ WHERE session_id = '$AIDE_SESSION_ID'
86
+ AND json_extract(data, '$.type') = 'step-finish';
87
+ "
88
+ ```
89
+
90
+ ### Step 4: Tool output breakdown
91
+
92
+ Show tool usage ranked by total output size:
93
+
94
+ ```bash
95
+ sqlite3 ~/.local/share/opencode/opencode.db "
96
+ SELECT
97
+ json_extract(data, '$.tool') as tool,
98
+ COUNT(*) as calls,
99
+ SUM(length(json_extract(data, '$.state.output'))) as total_output_bytes,
100
+ ROUND(AVG(length(json_extract(data, '$.state.output')))) as avg_bytes,
101
+ MAX(length(json_extract(data, '$.state.output'))) as max_bytes
102
+ FROM part
103
+ WHERE session_id = '$AIDE_SESSION_ID'
104
+ AND json_extract(data, '$.type') = 'tool'
105
+ GROUP BY tool
106
+ ORDER BY total_output_bytes DESC;
107
+ "
108
+ ```
109
+
110
+ ### Step 5: Total session size
111
+
112
+ ```bash
113
+ sqlite3 ~/.local/share/opencode/opencode.db "
114
+ SELECT
115
+ SUM(length(json_extract(data, '$.state.output'))) as tool_output_bytes,
116
+ SUM(length(json_extract(data, '$.state.input'))) as tool_input_bytes,
117
+ SUM(length(data)) as total_part_bytes
118
+ FROM part
119
+ WHERE session_id = '$AIDE_SESSION_ID'
120
+ AND json_extract(data, '$.type') = 'tool';
121
+ "
122
+ ```
123
+
124
+ ## Output Format
125
+
126
+ Present the results as a structured summary:
127
+
128
+ ```
129
+ ## Session Context Usage
130
+
131
+ **Session:** <title> (<slug>)
132
+ **Age:** <hours> hours | **Messages:** <count> | **Compacted:** yes/no
133
+
134
+ ### Token Usage
135
+ | Metric | Count |
136
+ |--------|-------|
137
+ | Input tokens | <n> |
138
+ | Output tokens | <n> |
139
+ | Cache read | <n> |
140
+ | Cache write | <n> |
141
+ | **Total tokens** | **<n>** |
142
+ | LLM turns | <n> |
143
+
144
+ ### Tool Output Breakdown (by total bytes)
145
+ | Tool | Calls | Total Output | Avg/call | Max |
146
+ |------|-------|-------------|----------|-----|
147
+ | ... | ... | ... | ... | ... |
148
+
149
+ ### Session Size
150
+ - Tool outputs: <n> KB
151
+ - Tool inputs: <n> KB
152
+ - Total part storage: <n> KB (includes JSON metadata overhead)
153
+ ```
154
+
155
+ Format byte values as KB (divide by 1024, round to 1 decimal).
156
+ Highlight the top 3 tools by total output as the biggest context consumers.
157
+ If any single tool call exceeds 20KB, flag it as a potential optimization target.
@@ -0,0 +1,174 @@
1
+ ---
2
+ name: survey
3
+ description: Explore codebase structure, entry points, tech stack, hotspots, and call graphs
4
+ triggers:
5
+ - survey
6
+ - codebase structure
7
+ - what is this codebase
8
+ - tech stack
9
+ - entry points
10
+ - entrypoints
11
+ - what modules
12
+ - what packages
13
+ - code churn
14
+ - hotspots
15
+ - call graph
16
+ - who calls
17
+ - what calls
18
+ - orient me
19
+ - onboard
20
+ - codebase overview
21
+ ---
22
+
23
+ # Codebase Survey
24
+
25
+ **Recommended model tier:** balanced (sonnet) - this skill performs structured queries
26
+
27
+ Understand the structure, technology, entry points, and change hotspots of a codebase.
28
+ Survey describes WHAT the codebase IS — not code problems (use `findings` for that).
29
+
30
+ ## Available Tools
31
+
32
+ ### 1. Survey Stats (`mcp__plugin_aide_aide__survey_stats`)
33
+
34
+ **Start here.** Get an overview of what has been surveyed: total entries, breakdown by analyzer and kind.
35
+
36
+ ```
37
+ Is the codebase surveyed?
38
+ → Uses survey_stats
39
+ → Returns: counts by analyzer (topology, entrypoints, churn) and kind
40
+ ```
41
+
42
+ ### 2. Survey Run (`mcp__plugin_aide_aide__survey_run`)
43
+
44
+ Run analyzers to populate survey data. Three analyzers available:
45
+
46
+ - **topology** — Modules, packages, workspaces, build systems, tech stack detection
47
+ - **entrypoints** — main() functions, HTTP handlers, gRPC services, CLI roots (cobra/urfave). Uses code index when available; falls back to file scanning
48
+ - **churn** — Git history hotspots (files/dirs that change most often)
49
+
50
+ ```
51
+ Survey this codebase
52
+ → Uses survey_run (no analyzer param = run all)
53
+ → Returns: entry counts per analyzer
54
+ ```
55
+
56
+ ### 3. Survey List (`mcp__plugin_aide_aide__survey_list`)
57
+
58
+ Browse entries filtered by analyzer, kind, or file path. No search query needed.
59
+
60
+ **Kinds:** module, entrypoint, dependency, tech_stack, churn, submodule, workspace, arch_pattern
61
+
62
+ ```
63
+ What modules are in this codebase?
64
+ → Uses survey_list with kind=module
65
+ → Returns: all module entries
66
+
67
+ What technologies does this use?
68
+ → Uses survey_list with kind=tech_stack
69
+ → Returns: detected frameworks, languages, build systems
70
+
71
+ What files change most?
72
+ → Uses survey_list with kind=churn
73
+ → Returns: high-churn files ranked by commit count
74
+ ```
75
+
76
+ ### 4. Survey Search (`mcp__plugin_aide_aide__survey_search`)
77
+
78
+ Full-text search across entry names, titles, and details. Use when looking for specific modules or technologies.
79
+
80
+ ```
81
+ Find anything related to "auth"
82
+ → Uses survey_search with query="auth"
83
+ → Returns: modules, entrypoints, churn entries matching "auth"
84
+ ```
85
+
86
+ ### 5. Call Graph (`mcp__plugin_aide_aide__survey_graph`)
87
+
88
+ Build a call graph for a symbol showing callers, callees, or both. BFS traversal over the code index.
89
+
90
+ ```
91
+ Who calls BuildCallGraph?
92
+ → Uses survey_graph with symbol="BuildCallGraph" direction="callers"
93
+ → Returns: graph of calling symbols with file:line locations
94
+
95
+ What does handleSurveyRun call?
96
+ → Uses survey_graph with symbol="handleSurveyRun" direction="callees"
97
+ → Returns: graph of called symbols
98
+
99
+ Show call neighborhood of RunTopology
100
+ → Uses survey_graph with symbol="RunTopology" direction="both"
101
+ → Returns: both callers and callees
102
+ ```
103
+
104
+ **Parameters:**
105
+
106
+ - `symbol` (required): Function/method name
107
+ - `direction`: "both" (default), "callers", "callees"
108
+ - `max_depth`: BFS hops (default 2)
109
+ - `max_nodes`: Max nodes (default 50)
110
+
111
+ **Requires:** Code index must be populated (`aide code index`).
112
+
113
+ ## Workflow
114
+
115
+ ### Orienting in an unfamiliar codebase
116
+
117
+ 1. **Check survey status:**
118
+ - Use `survey_stats` to see if data exists
119
+ - If empty, run `survey_run` to populate
120
+
121
+ 2. **Understand the structure:**
122
+ - `survey_list kind=module` — What are the major modules?
123
+ - `survey_list kind=tech_stack` — What technologies are used?
124
+ - `survey_list kind=workspace` — Is this a monorepo?
125
+
126
+ 3. **Find entry points:**
127
+ - `survey_list kind=entrypoint` — Where does execution start?
128
+ - Identifies main() functions, HTTP handlers, CLI roots
129
+
130
+ 4. **Identify hotspots:**
131
+ - `survey_list kind=churn` — What files change most? (complexity/bug magnets)
132
+
133
+ 5. **Trace call relationships:**
134
+ - `survey_graph symbol="handleRequest"` — Map the call neighborhood
135
+ - Use `direction=callers` to find who invokes a function
136
+ - Use `direction=callees` to understand what a function depends on
137
+
138
+ ### Answering specific questions
139
+
140
+ | Question | Tool | Parameters |
141
+ | ----------------------------- | --------------- | --------------------------- |
142
+ | "What is this codebase?" | `survey_list` | kind=module |
143
+ | "What tech stack?" | `survey_list` | kind=tech_stack |
144
+ | "Where are the entry points?" | `survey_list` | kind=entrypoint |
145
+ | "What changes most?" | `survey_list` | kind=churn |
146
+ | "Is there an auth module?" | `survey_search` | query="auth" |
147
+ | "Who calls this function?" | `survey_graph` | symbol=X, direction=callers |
148
+ | "What does this call?" | `survey_graph` | symbol=X, direction=callees |
149
+
150
+ ## Survey vs Findings vs Code Search
151
+
152
+ | Tool | Purpose | Example |
153
+ | --------------- | -------------------- | ---------------------------------------- |
154
+ | **Survey** | WHAT the codebase IS | Modules, tech stack, entry points, churn |
155
+ | **Findings** | Code PROBLEMS | Complexity, security issues, duplication |
156
+ | **Code Search** | Symbol DEFINITIONS | Find function signatures, call sites |
157
+
158
+ Survey gives you the big picture. Code search gives you specific symbols. Findings gives you problems to fix.
159
+
160
+ ## Prerequisites
161
+
162
+ - **Survey data:** Run `aide survey run` or use `survey_run` tool
163
+ - **Code index (for entrypoints + graph):** Run `aide code index`
164
+ - **Git history (for churn):** Must be a git repository (uses go-git, no git binary needed)
165
+
166
+ **Binary location:** The aide binary is at `.aide/bin/aide`. If it's on your `$PATH`, you can use `aide` directly.
167
+
168
+ ## Notes
169
+
170
+ - Survey results are cached in BoltDB — re-run analyzers to refresh after significant changes
171
+ - Topology analyzer inspects the filesystem (build files, directory structure)
172
+ - Entrypoints analyzer uses the code index when available; falls back to file scanning
173
+ - Churn analyzer uses go-git to read git history directly (no git binary required)
174
+ - Call graph is computed on demand from the code index (not stored)
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Dedup strategy: replace repeated identical tool outputs with a short pointer.
3
+ *
4
+ * Safe-to-dedup tools: Read (with mtime check), Glob, Grep, and aide MCP tools
5
+ * like code_search, code_symbols, code_outline, code_references,
6
+ * findings_list, findings_search, memory_list, memory_search.
7
+ *
8
+ * NEVER dedup: Bash, Write, Edit, or any tool with side effects.
9
+ */
10
+
11
+ import type { PruneResult, PruneStrategy, ToolRecord } from "./types.js";
12
+ import { statSync } from "fs";
13
+ import { resolve, isAbsolute } from "path";
14
+
15
+ /** Tools that are safe to deduplicate. */
16
+ const SAFE_DEDUP_TOOLS = new Set([
17
+ // Host built-in read-only tools
18
+ "read",
19
+ "glob",
20
+ "grep",
21
+ // aide MCP tools (read-only)
22
+ "mcp__aide__code_search",
23
+ "mcp__aide__code_symbols",
24
+ "mcp__aide__code_outline",
25
+ "mcp__aide__code_references",
26
+ "mcp__aide__code_stats",
27
+ "mcp__aide__findings_list",
28
+ "mcp__aide__findings_search",
29
+ "mcp__aide__findings_stats",
30
+ "mcp__aide__memory_list",
31
+ "mcp__aide__memory_search",
32
+ "mcp__aide__decision_list",
33
+ "mcp__aide__decision_get",
34
+ "mcp__aide__decision_history",
35
+ "mcp__aide__state_get",
36
+ "mcp__aide__state_list",
37
+ "mcp__aide__task_list",
38
+ "mcp__aide__task_get",
39
+ "mcp__aide__message_list",
40
+ // Claude Code naming convention (no mcp__ prefix)
41
+ "code_search",
42
+ "code_symbols",
43
+ "code_outline",
44
+ "code_references",
45
+ "code_stats",
46
+ "findings_list",
47
+ "findings_search",
48
+ "findings_stats",
49
+ "memory_list",
50
+ "memory_search",
51
+ "decision_list",
52
+ "decision_get",
53
+ "decision_history",
54
+ "state_get",
55
+ "state_list",
56
+ "task_list",
57
+ "task_get",
58
+ "message_list",
59
+ ]);
60
+
61
+ /** Extract the dedup key from tool args (the args that define "same call"). */
62
+ function dedupKey(toolName: string, args: Record<string, unknown>): string {
63
+ const normalized = toolName.toLowerCase();
64
+ // For Read, the key is filePath + offset + limit
65
+ if (normalized === "read") {
66
+ return JSON.stringify({
67
+ tool: "read",
68
+ filePath: args.filePath ?? args.file_path ?? args.path,
69
+ offset: args.offset ?? 0,
70
+ limit: args.limit ?? 2000,
71
+ });
72
+ }
73
+ // For Glob, the key is pattern + path
74
+ if (normalized === "glob") {
75
+ return JSON.stringify({
76
+ tool: "glob",
77
+ pattern: args.pattern,
78
+ path: args.path,
79
+ });
80
+ }
81
+ // For Grep, the key is pattern + path + include
82
+ if (normalized === "grep") {
83
+ return JSON.stringify({
84
+ tool: "grep",
85
+ pattern: args.pattern,
86
+ path: args.path,
87
+ include: args.include,
88
+ });
89
+ }
90
+ // For MCP tools, use all args as the key
91
+ return JSON.stringify({ tool: normalized, ...args });
92
+ }
93
+
94
+ /** Check file mtime for Read dedup safety. */
95
+ function getFileMtime(
96
+ args: Record<string, unknown>,
97
+ cwd?: string,
98
+ ): number | undefined {
99
+ const filePath =
100
+ (args.filePath as string) ??
101
+ (args.file_path as string) ??
102
+ (args.path as string);
103
+ if (!filePath) return undefined;
104
+
105
+ try {
106
+ const resolved = isAbsolute(filePath)
107
+ ? filePath
108
+ : resolve(cwd || process.cwd(), filePath);
109
+ return statSync(resolved).mtimeMs;
110
+ } catch {
111
+ return undefined;
112
+ }
113
+ }
114
+
115
+ export class DedupStrategy implements PruneStrategy {
116
+ name = "dedup" as const;
117
+ private cwd?: string;
118
+
119
+ constructor(cwd?: string) {
120
+ this.cwd = cwd;
121
+ }
122
+
123
+ apply(
124
+ toolName: string,
125
+ args: Record<string, unknown>,
126
+ output: string,
127
+ history: ToolRecord[],
128
+ ): PruneResult {
129
+ const normalized = toolName.toLowerCase();
130
+
131
+ // Only apply to safe tools
132
+ if (!SAFE_DEDUP_TOOLS.has(normalized)) {
133
+ return { output, modified: false, bytesSaved: 0 };
134
+ }
135
+
136
+ const key = dedupKey(toolName, args);
137
+
138
+ // Find the most recent matching call in history
139
+ for (let i = history.length - 1; i >= 0; i--) {
140
+ const prev = history[i];
141
+ const prevKey = dedupKey(prev.toolName, prev.args);
142
+
143
+ if (prevKey !== key) continue;
144
+
145
+ // For Read: check mtime hasn't changed (file might have been edited)
146
+ if (normalized === "read") {
147
+ const currentMtime = getFileMtime(args, this.cwd);
148
+ if (
149
+ currentMtime !== undefined &&
150
+ prev.fileMtime !== undefined &&
151
+ currentMtime !== prev.fileMtime
152
+ ) {
153
+ // File changed — don't dedup
154
+ continue;
155
+ }
156
+ }
157
+
158
+ // Check if output is identical
159
+ const prevOutput = prev.prunedOutput ?? prev.originalOutput;
160
+ if (output === prevOutput) {
161
+ const replacement = `[aide:dedup] Identical to previous ${toolName} call (callId: ${prev.callId}). Output unchanged.`;
162
+ return {
163
+ output: replacement,
164
+ modified: true,
165
+ strategy: "dedup",
166
+ bytesSaved: output.length - replacement.length,
167
+ };
168
+ }
169
+ }
170
+
171
+ return { output, modified: false, bytesSaved: 0 };
172
+ }
173
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Context Pruning — reduces context/token usage by deduplicating,
3
+ * superseding, and purging tool outputs.
4
+ *
5
+ * Platform adapters integrate via the ContextPruningTracker:
6
+ * - OpenCode: tool.execute.after hook modifies output.output
7
+ * - Claude Code: PostToolUse hook returns updatedMCPToolOutput
8
+ */
9
+
10
+ export { ContextPruningTracker } from "./tracker.js";
11
+ export { DedupStrategy } from "./dedup.js";
12
+ export { SupersedeStrategy } from "./supersede.js";
13
+ export { PurgeErrorsStrategy } from "./purge.js";
14
+ export type {
15
+ ToolRecord,
16
+ PruneResult,
17
+ PruneStrategy,
18
+ PruningStats,
19
+ } from "./types.js";
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Purge-errors strategy: replace large error outputs (stack traces, build
3
+ * failures) with a compact summary.
4
+ *
5
+ * When a Bash command fails and produces a large error output, most of the
6
+ * context is stack frames that aren't useful for the model. This strategy
7
+ * trims the output to the first meaningful error lines.
8
+ */
9
+
10
+ import type { PruneResult, PruneStrategy, ToolRecord } from "./types.js";
11
+
12
+ /** Minimum output size to trigger purging (2KB). */
13
+ const MIN_SIZE_FOR_PURGE = 2048;
14
+
15
+ /** Max lines to keep from an error output. */
16
+ const MAX_ERROR_LINES = 30;
17
+
18
+ /** Patterns that indicate an error output. */
19
+ const ERROR_PATTERNS = [
20
+ /^error/im,
21
+ /^ERR!/im,
22
+ /exit code [1-9]/i,
23
+ /FAILED/i,
24
+ /panic:/i,
25
+ /Traceback/i,
26
+ /^Exception/im,
27
+ /compilation failed/i,
28
+ /build failed/i,
29
+ /TypeError:/,
30
+ /SyntaxError:/,
31
+ /ReferenceError:/,
32
+ ];
33
+
34
+ export class PurgeErrorsStrategy implements PruneStrategy {
35
+ name = "purge" as const;
36
+
37
+ apply(
38
+ toolName: string,
39
+ _args: Record<string, unknown>,
40
+ output: string,
41
+ _history: ToolRecord[],
42
+ ): PruneResult {
43
+ const normalized = toolName.toLowerCase();
44
+
45
+ // Only apply to Bash output
46
+ if (normalized !== "bash") {
47
+ return { output, modified: false, bytesSaved: 0 };
48
+ }
49
+
50
+ // Only purge if output is large enough to matter
51
+ if (output.length < MIN_SIZE_FOR_PURGE) {
52
+ return { output, modified: false, bytesSaved: 0 };
53
+ }
54
+
55
+ // Check if output looks like an error
56
+ const isError = ERROR_PATTERNS.some((p) => p.test(output));
57
+ if (!isError) {
58
+ return { output, modified: false, bytesSaved: 0 };
59
+ }
60
+
61
+ // Trim to first MAX_ERROR_LINES lines + a note
62
+ const lines = output.split("\n");
63
+ if (lines.length <= MAX_ERROR_LINES) {
64
+ return { output, modified: false, bytesSaved: 0 };
65
+ }
66
+
67
+ const kept = lines.slice(0, MAX_ERROR_LINES).join("\n");
68
+ const trimmedCount = lines.length - MAX_ERROR_LINES;
69
+ const replacement =
70
+ kept +
71
+ `\n\n[aide:purge] ... ${trimmedCount} additional error lines trimmed. Re-run the command to see full output.`;
72
+
73
+ return {
74
+ output: replacement,
75
+ modified: true,
76
+ strategy: "purge",
77
+ bytesSaved: output.length - replacement.length,
78
+ };
79
+ }
80
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Supersede strategy: when a Write or Edit completes, mark prior Read outputs
3
+ * of the same file as stale so the model doesn't rely on outdated content.
4
+ *
5
+ * This doesn't replace the current tool output — it annotates previous Read
6
+ * records so that if they're re-read (dedup check), the stale content is flagged.
7
+ *
8
+ * For now, this strategy only adds a note to the current Write/Edit output
9
+ * reminding the model that prior reads of this file are now stale.
10
+ */
11
+
12
+ import type { PruneResult, PruneStrategy, ToolRecord } from "./types.js";
13
+
14
+ /** Tools that supersede prior reads. */
15
+ const WRITE_TOOLS = new Set(["write", "edit"]);
16
+
17
+ /** Extract the file path from tool args. */
18
+ function getFilePath(args: Record<string, unknown>): string | undefined {
19
+ return (
20
+ (args.filePath as string) ??
21
+ (args.file_path as string) ??
22
+ (args.path as string) ??
23
+ undefined
24
+ );
25
+ }
26
+
27
+ export class SupersedeStrategy implements PruneStrategy {
28
+ name = "supersede" as const;
29
+
30
+ apply(
31
+ toolName: string,
32
+ args: Record<string, unknown>,
33
+ output: string,
34
+ history: ToolRecord[],
35
+ ): PruneResult {
36
+ const normalized = toolName.toLowerCase();
37
+
38
+ if (!WRITE_TOOLS.has(normalized)) {
39
+ return { output, modified: false, bytesSaved: 0 };
40
+ }
41
+
42
+ const filePath = getFilePath(args);
43
+ if (!filePath) {
44
+ return { output, modified: false, bytesSaved: 0 };
45
+ }
46
+
47
+ // Check if there are prior Read calls for this same file
48
+ const priorReads = history.filter((rec) => {
49
+ if (rec.toolName.toLowerCase() !== "read") return false;
50
+ const recPath = getFilePath(rec.args);
51
+ return recPath === filePath;
52
+ });
53
+
54
+ if (priorReads.length === 0) {
55
+ return { output, modified: false, bytesSaved: 0 };
56
+ }
57
+
58
+ // Annotate: prior reads of this file are now stale
59
+ const note = `\n[aide:supersede] Note: ${priorReads.length} prior Read(s) of "${filePath}" are now stale after this ${toolName}. Re-read if you need current content.`;
60
+ return {
61
+ output: output + note,
62
+ modified: true,
63
+ strategy: "supersede",
64
+ bytesSaved: 0, // We're adding, not saving bytes
65
+ };
66
+ }
67
+ }