@jmylchreest/aide-plugin 0.0.40 → 0.0.43

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.40",
3
+ "version": "0.0.43",
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,167 @@
1
+ ---
2
+ name: assess-findings
3
+ description: Triage static analysis findings, assess merit, and accept noise or irrelevant items
4
+ triggers:
5
+ - assess findings
6
+ - analyse findings
7
+ - analyze findings
8
+ - triage findings
9
+ - review findings
10
+ - accept findings
11
+ - dismiss findings
12
+ - clean up findings
13
+ ---
14
+
15
+ # Assess Findings
16
+
17
+ **Recommended model tier:** balanced (sonnet) - this skill requires reading code and making judgement calls
18
+
19
+ Triage static analysis findings by reading the actual code, assessing whether each finding
20
+ is genuine or noise, and accepting (dismissing) irrelevant ones using `findings_accept`.
21
+ Accepted findings are hidden from future output by default.
22
+
23
+ ## Prerequisites
24
+
25
+ - Findings must already exist. If `findings_stats` returns zero counts, tell the user to run:
26
+ ```bash
27
+ ./.aide/bin/aide findings run --path .
28
+ ```
29
+ - The `findings_accept` tool must be available (provided by the aide MCP server).
30
+
31
+ ## Available Tools
32
+
33
+ ### Read-only (shared with `patterns` skill)
34
+
35
+ | Tool | Purpose |
36
+ | ----------------- | ------------------------------------------------------- |
37
+ | `findings_stats` | Counts by analyzer and severity — start here |
38
+ | `findings_list` | Browse findings with filters (analyzer, severity, file) |
39
+ | `findings_search` | Full-text search across finding titles and details |
40
+
41
+ ### Write (unique to this skill)
42
+
43
+ | Tool | Purpose |
44
+ | ----------------- | --------------------------------------------------- |
45
+ | `findings_accept` | Mark findings as accepted/dismissed by ID or filter |
46
+
47
+ ### Code inspection
48
+
49
+ | Tool | Purpose |
50
+ | -------------- | --------------------------------------------------- |
51
+ | `code_outline` | Get collapsed file structure to understand context |
52
+ | `Read` | Read specific line ranges to evaluate finding merit |
53
+
54
+ ## Workflow
55
+
56
+ ### 1. Get the Landscape
57
+
58
+ Call `findings_stats` to understand the scope:
59
+
60
+ ```
61
+ findings_stats
62
+ -> Returns: counts per analyzer (complexity, coupling, secrets, clones) and severity
63
+ ```
64
+
65
+ If the user asked to focus on a specific analyzer or severity, note that and filter accordingly.
66
+ Otherwise, work through all findings systematically.
67
+
68
+ ### 2. Prioritise Review Order
69
+
70
+ Work through findings in this order:
71
+
72
+ 1. **Secrets** (critical first) — these need immediate attention; false positives are common in test fixtures
73
+ 2. **Complexity** (critical, then warning) — assess whether high complexity is inherent or decomposable
74
+ 3. **Clones** (all) — determine if duplication is extractable or structural boilerplate
75
+ 4. **Coupling** (all) — assess whether high fan-in/fan-out is expected for the file's role
76
+
77
+ ### 3. Assess Each Finding
78
+
79
+ For each finding or group of related findings:
80
+
81
+ 1. **Read the finding details** — note the file, line range, and metric values
82
+ 2. **Read the actual code** — use `code_outline` first, then `Read` with offset/limit on the flagged section
83
+ 3. **Make a judgement call** using these criteria:
84
+
85
+ #### Accept (dismiss) when:
86
+
87
+ - **Complexity**: The function is inherently complex (CLI dispatch, protocol handling, state machines) and cannot be meaningfully decomposed without harming readability
88
+ - **Clones**: The duplication is structural boilerplate (e.g., CLI subcommand wiring, store method patterns) where extraction would require framework-level abstraction
89
+ - **Coupling**: High fan-in/fan-out is expected for the file's architectural role (e.g., a main entry point, a facade, a registry)
90
+ - **Secrets**: The flagged string is a test fixture, example config, documentation placeholder, or env var name (not an actual secret)
91
+
92
+ #### Keep (do NOT accept) when:
93
+
94
+ - The finding points to a genuine problem that should be fixed
95
+ - Complexity can be reduced by extracting helper functions
96
+ - Duplication can be resolved by creating a shared utility
97
+ - A coupling cycle exists that indicates poor module boundaries
98
+ - A string looks like it could be a real secret or credential
99
+
100
+ ### 4. Accept Findings
101
+
102
+ Use `findings_accept` to dismiss noise. You can accept:
103
+
104
+ - **By IDs** — for individual findings after assessment:
105
+ ```
106
+ findings_accept ids=["finding-id-1", "finding-id-2"]
107
+ ```
108
+ - **By filter** — for bulk dismissal of an entire category:
109
+ ```
110
+ findings_accept analyzer="clones" file="cmd/"
111
+ ```
112
+
113
+ Always explain **why** each finding is being accepted before calling the tool.
114
+
115
+ ### 5. Report Summary
116
+
117
+ After completing the triage, produce a summary:
118
+
119
+ ```markdown
120
+ ## Findings Triage Summary
121
+
122
+ ### Before
123
+
124
+ - Total: X findings (Y critical, Z warnings, W info)
125
+
126
+ ### Accepted (Dismissed)
127
+
128
+ - N findings accepted as noise/irrelevant
129
+ - Complexity: X (inherent complexity in [files])
130
+ - Clones: Y (structural boilerplate in [area])
131
+ - Coupling: Z (expected for [role])
132
+ - Secrets: W (test fixtures / placeholders)
133
+
134
+ ### Remaining (Genuine)
135
+
136
+ - M findings require attention
137
+ - [List each with file:line and brief description]
138
+
139
+ ### Recommendations
140
+
141
+ 1. [Prioritised action items for genuine findings]
142
+ ```
143
+
144
+ ## Decision Criteria Reference
145
+
146
+ | Analyzer | Accept If | Keep If |
147
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
148
+ | complexity | Cyclomatic complexity is inherent to the problem domain; function handles unavoidable branching (CLI dispatch, protocol negotiation) | Function can be decomposed into smaller, testable units |
149
+ | clones | Duplication is cross-cutting boilerplate (CLI wiring, store CRUD patterns) | A shared utility or abstraction would reduce maintenance burden |
150
+ | coupling | File is an intentional integration point (main, facade, registry) | Circular dependencies or unexpected transitive coupling exists |
151
+ | secrets | Test fixture, documentation example, env var name, or placeholder | Looks like a real credential, API key, or connection string |
152
+
153
+ ## Failure Handling
154
+
155
+ 1. **No findings** — Tell user to run `./.aide/bin/aide findings run --path .` first
156
+ 2. **`findings_accept` not available** — The aide MCP server may not expose this tool; tell the user to update aide
157
+ 3. **Uncertain about a finding** — When in doubt, **keep it**. It's better to flag a false positive for human review than to dismiss a real issue
158
+ 4. **Large number of findings** — Work in batches by analyzer. Accept obvious noise first, then do detailed code review for borderline cases
159
+
160
+ ## Verification
161
+
162
+ - [ ] Called `findings_stats` for baseline counts
163
+ - [ ] Reviewed each finding category (secrets, complexity, clones, coupling)
164
+ - [ ] Read actual code for every finding before accepting
165
+ - [ ] Provided rationale for each acceptance
166
+ - [ ] Produced summary with before/after counts
167
+ - [ ] Remaining findings are genuinely actionable
@@ -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.
@@ -10,7 +10,6 @@ triggers:
10
10
  - clones
11
11
  - secrets
12
12
  - coupling
13
- - findings
14
13
  - static analysis
15
14
  - code health
16
15
  ---
@@ -47,6 +46,7 @@ Findings must be generated first by running analyzers via the CLI:
47
46
  Full-text search across all findings. Supports Bleve query syntax for advanced searches.
48
47
 
49
48
  **Parameters:**
49
+
50
50
  - `query` (required) — Search term or Bleve query
51
51
  - `analyzer` (optional) — Filter to one analyzer: `complexity`, `coupling`, `secrets`, `clones`
52
52
  - `severity` (optional) — Filter by severity: `info`, `warning`, `critical`
@@ -66,6 +66,7 @@ Search for: "high complexity"
66
66
  List findings with filters. Use when you want to browse rather than search.
67
67
 
68
68
  **Parameters:**
69
+
69
70
  - `analyzer` (optional) — Filter to one analyzer
70
71
  - `severity` (optional) — Filter by severity
71
72
  - `file` (optional) — Filter by file path substring
@@ -133,13 +134,13 @@ How healthy is the codebase?
133
134
 
134
135
  Beyond the automated analyzers, look for these patterns using findings as starting points:
135
136
 
136
- | Finding | Likely Anti-Pattern | Action |
137
- |---------|-------------------|--------|
138
- | Complexity > 20 | God function | Decompose into smaller functions |
139
- | Fan-out > 15 | Kitchen sink module | Split responsibilities |
140
- | Fan-in > 20 | Fragile dependency | Consider interface/abstraction |
141
- | Multiple clones | Copy-paste programming | Extract shared utility |
142
- | Import cycle | Circular dependency | Restructure module boundaries |
137
+ | Finding | Likely Anti-Pattern | Action |
138
+ | --------------- | ---------------------- | -------------------------------- |
139
+ | Complexity > 20 | God function | Decompose into smaller functions |
140
+ | Fan-out > 15 | Kitchen sink module | Split responsibilities |
141
+ | Fan-in > 20 | Fragile dependency | Consider interface/abstraction |
142
+ | Multiple clones | Copy-paste programming | Extract shared utility |
143
+ | Import cycle | Circular dependency | Restructure module boundaries |
143
144
 
144
145
  ## Output Format
145
146
 
@@ -147,18 +148,22 @@ Beyond the automated analyzers, look for these patterns using findings as starti
147
148
  ## Code Health Report
148
149
 
149
150
  ### Overview
151
+
150
152
  - Total findings: X (Y critical, Z warnings)
151
153
  - Top concern: [area/file with most issues]
152
154
 
153
155
  ### Hotspots
156
+
154
157
  1. **`file:line`** - [description] (severity)
155
158
  - Impact: [why this matters]
156
159
  - Recommendation: [what to do]
157
160
 
158
161
  ### Patterns Detected
162
+
159
163
  - [List of anti-patterns found with evidence]
160
164
 
161
165
  ### Recommendations
166
+
162
167
  1. [Prioritized action items]
163
168
  ```
164
169
 
package/src/cli/config.ts CHANGED
@@ -51,7 +51,10 @@ export function readConfig(configPath: string): OpenCodeConfig {
51
51
  }
52
52
  try {
53
53
  const raw = readFileSync(configPath, "utf-8");
54
- return JSON.parse(raw) as OpenCodeConfig;
54
+ const parsed: unknown = JSON.parse(raw);
55
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
56
+ return {};
57
+ return parsed as OpenCodeConfig;
55
58
  } catch {
56
59
  return {};
57
60
  }
@@ -8,7 +8,7 @@
8
8
  * platform-specific options instead of relying on CLAUDE_PLUGIN_ROOT.
9
9
  */
10
10
 
11
- import { execSync, execFileSync } from "child_process";
11
+ import { execFileSync } from "child_process";
12
12
  import { existsSync, realpathSync } from "fs";
13
13
  import { join } from "path";
14
14
  import type { FindBinaryOptions } from "./types.js";
@@ -64,7 +64,10 @@ export function findAideBinary(opts: FindBinaryOptions = {}): string | null {
64
64
 
65
65
  // 4. PATH fallback
66
66
  try {
67
- const result = execSync("which aide", { stdio: "pipe", timeout: 2000 })
67
+ const result = execFileSync("which", ["aide"], {
68
+ stdio: "pipe",
69
+ timeout: 2000,
70
+ })
68
71
  .toString()
69
72
  .trim();
70
73
  if (result) return result;
@@ -187,15 +190,15 @@ export function clearAgentState(
187
190
  }
188
191
 
189
192
  /**
190
- * Escape a string for safe shell usage (when shell is unavoidable)
193
+ * Sanitize a string for safe inclusion in log messages and CLI arguments.
194
+ *
195
+ * Strips control characters and limits length. This is NOT shell escaping —
196
+ * use execFileSync (which avoids shells entirely) for subprocess execution.
191
197
  */
192
- export function shellEscape(str: string): string {
193
- return str
194
- .replace(/\\/g, "\\\\")
195
- .replace(/"/g, '\\"')
196
- .replace(/\$/g, "\\$")
197
- .replace(/`/g, "\\`")
198
- .replace(/!/g, "\\!")
199
- .replace(/\n/g, " ")
200
- .slice(0, 1000);
198
+ export function sanitizeForLog(str: string): string {
199
+ // eslint-disable-next-line no-control-regex
200
+ return str.replace(/[\x00-\x1f\x7f]/g, " ").slice(0, 1000);
201
201
  }
202
+
203
+ /** @deprecated Use sanitizeForLog instead */
204
+ export const shellEscape = sanitizeForLog;
@@ -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";