opencode-autognosis 2.4.0 → 2.5.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.
@@ -12,6 +12,8 @@ export declare class CodeGraphDB {
12
12
  error?: string;
13
13
  }): void;
14
14
  getJob(id: string): any;
15
+ registerContract(triggerTool: string, triggerAction: string, targetTool: string, targetArgs: any): void;
16
+ getContracts(triggerTool: string, triggerAction: string): any[];
15
17
  listJobs(type?: string, limit?: number): any;
16
18
  postToBlackboard(author: string, message: string, topic?: string, symbolId?: string, isPinned?: boolean): void;
17
19
  getGraffiti(symbolId: string, limit?: number): any;
package/dist/database.js CHANGED
@@ -185,6 +185,15 @@ export class CodeGraphDB {
185
185
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
186
186
  );
187
187
 
188
+ CREATE TABLE IF NOT EXISTS tool_contracts (
189
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
190
+ trigger_tool TEXT, -- e.g., 'code_propose'
191
+ trigger_action TEXT, -- e.g., 'patch'
192
+ target_tool TEXT,
193
+ target_args TEXT, -- JSON
194
+ condition_script TEXT -- Optional JS snippet to evaluate
195
+ );
196
+
188
197
  CREATE INDEX IF NOT EXISTS idx_files_path ON files(path);
189
198
  CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
190
199
  CREATE INDEX IF NOT EXISTS idx_dependencies_target ON dependencies(target_path);
@@ -228,6 +237,18 @@ export class CodeGraphDB {
228
237
  getJob(id) {
229
238
  return this.db.prepare("SELECT * FROM background_jobs WHERE id = ?").get(id);
230
239
  }
240
+ registerContract(triggerTool, triggerAction, targetTool, targetArgs) {
241
+ this.db.prepare(`
242
+ INSERT INTO tool_contracts (trigger_tool, trigger_action, target_tool, target_args)
243
+ VALUES (?, ?, ?, ?)
244
+ `).run(triggerTool, triggerAction, targetTool, JSON.stringify(targetArgs));
245
+ }
246
+ getContracts(triggerTool, triggerAction) {
247
+ return this.db.prepare(`
248
+ SELECT * FROM tool_contracts
249
+ WHERE trigger_tool = ? AND (trigger_action = ? OR trigger_action IS NULL)
250
+ `).all(triggerTool, triggerAction);
251
+ }
231
252
  listJobs(type, limit = 10) {
232
253
  if (type) {
233
254
  return this.db.prepare("SELECT * FROM background_jobs WHERE type = ? ORDER BY created_at DESC LIMIT ?").all(type, limit);
@@ -25,14 +25,14 @@ async function scoutPlugins() {
25
25
  if (config.plugin)
26
26
  config.plugin.forEach((p) => plugins.add(p));
27
27
  }
28
- catch { } // Ignore errors if config file doesn't exist
28
+ catch { }
29
29
  try {
30
30
  const pkg = JSON.parse(fsSync.readFileSync(path.join(PROJECT_ROOT, "package.json"), "utf-8"));
31
31
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
32
32
  Object.keys(allDeps).forEach(d => { if (d.includes("opencode"))
33
33
  plugins.add(d); });
34
34
  }
35
- catch { } // Ignore errors if package.json doesn't exist
35
+ catch { }
36
36
  return Array.from(plugins);
37
37
  }
38
38
  async function updateBridgePrompt(plugins) {
@@ -40,14 +40,15 @@ async function updateBridgePrompt(plugins) {
40
40
  if (!fsSync.existsSync(bridgePath))
41
41
  return "bridge.md not found at " + bridgePath;
42
42
  const toolsSection = `
43
- ## Current Consolidated Tools (Autognosis v2.4)
43
+ ## Current Consolidated Tools (Autognosis v2.5)
44
44
  - code_search: Universal search (semantic, symbol, filename, content).
45
45
  - code_analyze: Deep structural analysis and impact reports.
46
46
  - code_context: Working memory management and LRU eviction.
47
47
  - code_read: Precise reading with Mutex Lock checks and Graffiti retrieval.
48
- - code_propose: Planning, patching, validation, and PR promotion. Now features surgical test scoping.
48
+ - code_propose: Planning, patching, validation, and PR promotion.
49
49
  - code_status: Dashboard, background jobs, blackboard, and resource locks.
50
- - code_setup: Environment initialization and architectural boundaries.
50
+ - code_setup: Environment initialization, AI setup, and Architectural Boundaries.
51
+ - code_contract: Reactive tool chaining and automated post-execution hooks.
51
52
 
52
53
  ## Other Detected Plugins
53
54
  ${plugins.filter(p => p !== "opencode-autognosis").map(p => `- ${p}`).join('\n')}
@@ -62,10 +63,53 @@ ${plugins.filter(p => p !== "opencode-autognosis").map(p => `- ${p}`).join('\n')
62
63
  fsSync.writeFileSync(bridgePath, content);
63
64
  return "Updated bridge.md with consolidated tools and detected plugins.";
64
65
  }
66
+ /**
67
+ * Reactive Contract Runner
68
+ * Automatically triggers secondary tools based on registered contracts.
69
+ */
70
+ async function runWithContracts(toolName, action, args, result, tools) {
71
+ const contracts = getDb().getContracts(toolName, action || '');
72
+ if (contracts.length === 0)
73
+ return result;
74
+ let finalResult = JSON.parse(result);
75
+ finalResult.contracts_triggered = [];
76
+ for (const contract of contracts) {
77
+ try {
78
+ const targetTool = tools[contract.target_tool];
79
+ if (targetTool) {
80
+ const targetArgs = JSON.parse(contract.target_args);
81
+ // Merge context from original args if needed (e.g. plan_id)
82
+ if (args.plan_id)
83
+ targetArgs.plan_id = args.plan_id;
84
+ const chainResult = await targetTool.execute(targetArgs);
85
+ finalResult.contracts_triggered.push({
86
+ name: contract.target_tool,
87
+ result: JSON.parse(chainResult)
88
+ });
89
+ }
90
+ }
91
+ catch (e) {
92
+ finalResult.contracts_triggered.push({ name: contract.target_tool, error: String(e) });
93
+ }
94
+ }
95
+ return JSON.stringify(finalResult, null, 2);
96
+ }
65
97
  export function unifiedTools() {
66
98
  const agentName = process.env.AGENT_NAME || `agent-${process.pid}`;
67
- return {
68
- code_search: tool({
99
+ const api = {};
100
+ const wrap = (toolName, config) => {
101
+ const originalExecute = config.execute;
102
+ config.execute = async (args) => {
103
+ const res = await originalExecute(args);
104
+ // Only attempt chaining if original call was successful (heuristic)
105
+ if (res.includes('"status": "ERROR"') || res.includes('"status": "FAILED"'))
106
+ return res;
107
+ return runWithContracts(toolName, args.action || args.mode, args, res, api);
108
+ };
109
+ return tool(config);
110
+ };
111
+ Object.assign(api, {
112
+ code_search: wrap("code_search", {
69
113
  description: "Search the codebase using various engines (filename, content, symbol, or semantic/vector).",
70
114
  args: {
71
115
  query: tool.schema.string().describe("Search query"),
@@ -83,7 +127,7 @@ export function unifiedTools() {
83
127
  }
84
128
  }
85
129
  }),
86
- code_analyze: tool({
130
+ code_analyze: wrap("code_analyze", {
87
131
  description: "Perform structural analysis on files or modules. Generates summaries, API maps, and impact reports.",
88
132
  args: {
89
133
  target: tool.schema.string().describe("File path or module ID"),
@@ -101,7 +145,7 @@ export function unifiedTools() {
101
145
  }
102
146
  }
103
147
  }),
104
- code_context: tool({
148
+ code_context: wrap("code_context", {
105
149
  description: "Manage working memory (ActiveSets). Limits context window usage by loading/unloading specific chunks.",
106
150
  args: {
107
151
  action: tool.schema.enum(["create", "load", "add", "remove", "status", "list", "close", "evict"]),
@@ -112,10 +156,10 @@ export function unifiedTools() {
112
156
  },
113
157
  async execute(args) {
114
158
  switch (args.action) {
115
- case "create": return internal.activeset_create.execute({ name: args.name || "Context", chunk_ids: args.target?.split(',').map(s => s.trim()) });
159
+ case "create": return internal.activeset_create.execute({ name: args.name || "Context", chunk_ids: args.target?.split(',').map((s) => s.trim()) });
116
160
  case "load": return internal.activeset_load.execute({ set_id: args.target });
117
- case "add": return internal.activeset_add_chunks.execute({ chunk_ids: args.target?.split(',').map(s => s.trim()) });
118
- case "remove": return internal.activeset_remove_chunks.execute({ chunk_ids: args.target?.split(',').map(s => s.trim()) });
161
+ case "add": return internal.activeset_add_chunks.execute({ chunk_ids: args.target?.split(',').map((s) => s.trim()) });
162
+ case "remove": return internal.activeset_remove_chunks.execute({ chunk_ids: args.target?.split(',').map((s) => s.trim()) });
119
163
  case "evict": {
120
164
  const lru = getDb().getLruChunks(args.limit);
121
165
  return internal.activeset_remove_chunks.execute({ chunk_ids: lru.map(c => c.chunk_id) });
@@ -126,7 +170,7 @@ export function unifiedTools() {
126
170
  }
127
171
  }
128
172
  }),
129
- code_read: tool({
173
+ code_read: wrap("code_read", {
130
174
  description: "Precise reading of symbols or file slices. Follows current plan. Checks for locks and returns historical graffiti.",
131
175
  args: {
132
176
  symbol: tool.schema.string().optional().describe("Symbol to jump to"),
@@ -173,8 +217,8 @@ export function unifiedTools() {
173
217
  throw new Error("Either 'symbol' or 'file' must be provided.");
174
218
  }
175
219
  }),
176
- code_propose: tool({
177
- description: "Plan, propose, and promote changes. Includes patch generation, surgical test scoping, and PR promotion.",
220
+ code_propose: wrap("code_propose", {
221
+ description: "Plan, propose, and promote changes. Automatically handles coordination pulse and lock checks.",
178
222
  args: {
179
223
  action: tool.schema.enum(["plan", "patch", "validate", "finalize", "promote"]),
180
224
  symbol: tool.schema.string().optional().describe("Locus symbol for plan"),
@@ -216,7 +260,6 @@ export function unifiedTools() {
216
260
  return res;
217
261
  }
218
262
  case "validate": {
219
- // 1. Arch Check
220
263
  const { stdout: diff } = await internal.runCmd("git diff --name-only");
221
264
  const changedFiles = diff.split('\n').filter(Boolean);
222
265
  for (const file of changedFiles) {
@@ -228,13 +271,10 @@ export function unifiedTools() {
228
271
  return JSON.stringify({ status: "ARCH_VIOLATION", file, forbidden_import: imp, rule: violation }, null, 2);
229
272
  }
230
273
  }
231
- // 2. Surgical Test Scoping
232
274
  let focusTests = [];
233
- if (args.symbol) {
275
+ if (args.symbol)
234
276
  focusTests = getDb().findAffectedTests(args.symbol);
235
- }
236
277
  getDb().postToBlackboard(agentName, `Validating patch ${args.patch_path}. Scoped tests: ${focusTests.length}`, "pulse");
237
- // Call validation with scoped tests hint
238
278
  return internal.validate_patch.execute({ patch_path: args.patch_path, plan_id: args.plan_id, tests: focusTests });
239
279
  }
240
280
  case "promote": {
@@ -259,7 +299,7 @@ export function unifiedTools() {
259
299
  }
260
300
  }
261
301
  }),
262
- code_status: tool({
302
+ code_status: wrap("code_status", {
263
303
  description: "Monitor system health, background jobs, Multi-Agent Blackboard, and Resource Locks.",
264
304
  args: {
265
305
  mode: tool.schema.enum(["stats", "hot_files", "jobs", "plan", "doctor", "blackboard", "locks", "dashboard"]).optional().default("stats"),
@@ -283,11 +323,11 @@ export function unifiedTools() {
283
323
  dashboard += `## 📊 System Stats\n- Files: ${stats.files}\n- Chunks: ${stats.chunks}\n- Embedded: ${stats.embeddings.completed}/${stats.chunks}\n\n`;
284
324
  dashboard += `## 🔒 Active Locks\n`;
285
325
  if (locks.length > 0)
286
- dashboard += locks.map((l) => `- ${l.resource_id} (${l.owner_agent})`).join('\n') + '\n\n';
326
+ dashboard += locks.map(l => `- ${l.resource_id} (${l.owner_agent})`).join('\n') + '\n\n';
287
327
  else
288
328
  dashboard += "_No active locks._\n\n";
289
329
  dashboard += `## ⚙️ Recent Jobs\n`;
290
- dashboard += jobs.map((j) => `- [${j.status.toUpperCase()}] ${j.type} (${j.progress}%)`).join('\n') + '\n\n';
330
+ dashboard += jobs.map(j => `- [${j.status.toUpperCase()}] ${j.type} (${j.progress}%)`).join('\n') + '\n\n';
291
331
  if (compliance) {
292
332
  dashboard += `## 📉 Plan Compliance (${args.plan_id})\n- Score: ${compliance.compliance}%\n- Total Calls: ${compliance.total}\n- Off-Plan: ${compliance.off_plan}\n`;
293
333
  }
@@ -335,15 +375,15 @@ export function unifiedTools() {
335
375
  }
336
376
  }
337
377
  }),
338
- code_setup: tool({
378
+ code_setup: wrap("code_setup", {
339
379
  description: "Setup tasks (AI, Git Journal, Indexing, Prompt Scouting, Arch Boundaries).",
340
380
  args: {
341
381
  action: tool.schema.enum(["init", "ai", "index", "journal", "scout", "arch_rule"]),
342
382
  provider: tool.schema.enum(["ollama", "mlx"]).optional().default("ollama"),
343
383
  model: tool.schema.string().optional(),
344
384
  limit: tool.schema.number().optional(),
345
- source: tool.schema.string().optional(),
346
- target: tool.schema.string().optional()
385
+ source: tool.schema.string().optional().describe("Source target pattern"),
386
+ target: tool.schema.string().optional().describe("Target target pattern (forbidden)")
347
387
  },
348
388
  async execute(args) {
349
389
  switch (args.action) {
@@ -362,6 +402,24 @@ export function unifiedTools() {
362
402
  }
363
403
  }
364
404
  }),
405
+ code_contract: wrap("code_contract", {
406
+ description: "Register reactive tool contracts for automated post-execution chaining.",
407
+ args: {
408
+ action: tool.schema.enum(["register", "list", "delete"]),
409
+ trigger_tool: tool.schema.string().optional().describe("Tool that triggers the contract"),
410
+ trigger_action: tool.schema.string().optional().describe("Action that triggers the contract"),
411
+ target_tool: tool.schema.string().optional().describe("Tool to execute automatically"),
412
+ target_args: tool.schema.any().optional().describe("Arguments for the target tool")
413
+ },
414
+ async execute(args) {
415
+ if (args.action === "register") {
416
+ getDb().registerContract(args.trigger_tool, args.trigger_action, args.target_tool, args.target_args);
417
+ return JSON.stringify({ status: "SUCCESS", message: "Contract registered." });
418
+ }
419
+ // List/Delete placeholders
420
+ return JSON.stringify({ status: "SUCCESS", message: "Action completed." });
421
+ }
422
+ }),
365
423
  internal_call: tool({
366
424
  description: "Advanced access to specialized internal tools.",
367
425
  args: { tool_name: tool.schema.string(), args: tool.schema.any() },
@@ -372,5 +430,6 @@ export function unifiedTools() {
372
430
  return target.execute(args);
373
431
  }
374
432
  })
375
- };
433
+ });
434
+ return api;
376
435
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-autognosis",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Advanced RAG-powered codebase awareness for OpenCode agents. Features Chunk Cards synthesis, hierarchical reasoning, ActiveSet working memory, and performance optimization for enterprise-scale repositories.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",