opencode-autognosis 2.2.0 → 2.3.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.
@@ -13,8 +13,16 @@ export declare class CodeGraphDB {
13
13
  }): void;
14
14
  getJob(id: string): any;
15
15
  listJobs(type?: string, limit?: number): any;
16
- postToBlackboard(author: string, message: string, topic?: string): void;
16
+ postToBlackboard(author: string, message: string, topic?: string, symbolId?: string): void;
17
17
  readBlackboard(topic?: string, limit?: number): any;
18
+ getGraffiti(symbolId: string): any;
19
+ acquireLock(resourceId: string, agentName: string, ttlSeconds?: number): void;
20
+ releaseLock(resourceId: string, agentName: string): void;
21
+ isLocked(resourceId: string): {
22
+ owner_agent: string;
23
+ expires_at: string;
24
+ } | undefined;
25
+ listLocks(): any;
18
26
  storeIntent(patchId: string, reasoning: string, planId: string): void;
19
27
  getIntent(patchId: string): any;
20
28
  addArchRule(source: string, target: string): void;
package/dist/database.js CHANGED
@@ -146,9 +146,18 @@ export class CodeGraphDB {
146
146
  author TEXT,
147
147
  message TEXT,
148
148
  topic TEXT,
149
+ symbol_id TEXT, -- Optional link to a code symbol
150
+ embedding BLOB, -- For semantic search on the blackboard
149
151
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
150
152
  );
151
153
 
154
+ CREATE TABLE IF NOT EXISTS locks (
155
+ resource_id TEXT PRIMARY KEY, -- file path or symbol name
156
+ owner_agent TEXT,
157
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
158
+ expires_at DATETIME
159
+ );
160
+
152
161
  CREATE TABLE IF NOT EXISTS intents (
153
162
  patch_id TEXT PRIMARY KEY,
154
163
  reasoning TEXT,
@@ -222,11 +231,18 @@ export class CodeGraphDB {
222
231
  }
223
232
  return this.db.prepare("SELECT * FROM background_jobs ORDER BY created_at DESC LIMIT ?").all(limit);
224
233
  }
225
- postToBlackboard(author, message, topic = 'general') {
234
+ postToBlackboard(author, message, topic = 'general', symbolId) {
235
+ const insert = this.db.prepare(`
236
+ INSERT INTO blackboard (author, message, topic, symbol_id)
237
+ VALUES (?, ?, ?, ?)
238
+ RETURNING id
239
+ `);
240
+ const res = insert.get(author, message, topic, symbolId || null);
241
+ // Queue for embedding (blackboard search)
226
242
  this.db.prepare(`
227
- INSERT INTO blackboard (author, message, topic)
228
- VALUES (?, ?, ?)
229
- `).run(author, message, topic);
243
+ INSERT INTO embedding_queue (chunk_id, text_to_embed)
244
+ VALUES (?, ?)
245
+ `).run(`blackboard-${res.id}`, `${topic.toUpperCase()}: ${message}`);
230
246
  }
231
247
  readBlackboard(topic, limit = 10) {
232
248
  if (topic) {
@@ -238,6 +254,43 @@ export class CodeGraphDB {
238
254
  SELECT * FROM blackboard ORDER BY timestamp DESC LIMIT ?
239
255
  `).all(limit);
240
256
  }
257
+ getGraffiti(symbolId) {
258
+ return this.db.prepare(`
259
+ SELECT author, message, timestamp
260
+ FROM blackboard
261
+ WHERE symbol_id = ?
262
+ ORDER BY timestamp DESC
263
+ `).all(symbolId);
264
+ }
265
+ acquireLock(resourceId, agentName, ttlSeconds = 300) {
266
+ // Check if already locked by someone else
267
+ const current = this.isLocked(resourceId);
268
+ if (current && current.owner_agent !== agentName) {
269
+ throw new Error(`Resource ${resourceId} is already locked by ${current.owner_agent}`);
270
+ }
271
+ const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
272
+ this.db.prepare(`
273
+ INSERT INTO locks (resource_id, owner_agent, expires_at)
274
+ VALUES (?, ?, ?)
275
+ ON CONFLICT(resource_id) DO UPDATE SET
276
+ owner_agent = excluded.owner_agent,
277
+ expires_at = excluded.expires_at
278
+ `).run(resourceId, agentName, expiresAt);
279
+ }
280
+ releaseLock(resourceId, agentName) {
281
+ this.db.prepare(`
282
+ DELETE FROM locks
283
+ WHERE resource_id = ? AND owner_agent = ?
284
+ `).run(resourceId, agentName);
285
+ }
286
+ isLocked(resourceId) {
287
+ // Automatically prune expired locks
288
+ this.db.prepare("DELETE FROM locks WHERE expires_at < CURRENT_TIMESTAMP").run();
289
+ return this.db.prepare("SELECT * FROM locks WHERE resource_id = ?").get(resourceId);
290
+ }
291
+ listLocks() {
292
+ return this.db.prepare("SELECT * FROM locks").all();
293
+ }
241
294
  storeIntent(patchId, reasoning, planId) {
242
295
  this.db.prepare(`
243
296
  INSERT INTO intents (patch_id, reasoning, plan_id)
@@ -40,13 +40,13 @@ 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.2)
43
+ ## Current Consolidated Tools (Autognosis v2.3)
44
44
  - code_search: Universal search (semantic, symbol, filename, content).
45
45
  - code_analyze: Deep structural analysis and impact reports.
46
- - code_context: Working memory (ActiveSet) management and LRU eviction.
47
- - code_read: Precise symbol jumping and file slicing.
46
+ - code_context: Working memory (ActiveSet) management, LRU eviction, and Symbol Graffiti.
47
+ - code_read: Precise symbol jumping and file slicing with Mutex Lock checks.
48
48
  - code_propose: Planning, patch generation, PR promotion, and Intent indexing.
49
- - code_status: System health, background jobs, compliance, and Multi-Agent Blackboard.
49
+ - code_status: System health, background jobs, Multi-Agent Blackboard, and Resource Locks.
50
50
  - code_setup: Environment initialization, AI setup, and Architectural Boundaries.
51
51
 
52
52
  ## Other Detected Plugins
@@ -63,14 +63,15 @@ ${plugins.filter(p => p !== "opencode-autognosis").map(p => `- ${p}`).join('\n')
63
63
  return "Updated bridge.md with consolidated tools and detected plugins.";
64
64
  }
65
65
  export function unifiedTools() {
66
+ const agentName = process.env.AGENT_NAME || `agent-${process.pid}`;
66
67
  return {
67
68
  code_search: tool({
68
69
  description: "Search the codebase using various engines (filename, content, symbol, or semantic/vector).",
69
70
  args: {
70
71
  query: tool.schema.string().describe("Search query"),
71
- mode: tool.schema.enum(["filename", "content", "symbol", "semantic"]).optional().default("filename").describe("Search strategy"),
72
- path: tool.schema.string().optional().default(".").describe("Root path for search"),
73
- limit: tool.schema.number().optional().default(10).describe("Max results"),
72
+ mode: tool.schema.enum(["filename", "content", "symbol", "semantic"]).optional().default("filename"),
73
+ path: tool.schema.string().optional().default("."),
74
+ limit: tool.schema.number().optional().default(10),
74
75
  plan_id: tool.schema.string().optional()
75
76
  },
76
77
  async execute(args) {
@@ -106,7 +107,7 @@ export function unifiedTools() {
106
107
  action: tool.schema.enum(["create", "load", "add", "remove", "status", "list", "close", "evict"]),
107
108
  target: tool.schema.string().optional().describe("ActiveSet ID or Chunk IDs"),
108
109
  name: tool.schema.string().optional(),
109
- limit: tool.schema.number().optional().default(5).describe("Eviction limit"),
110
+ limit: tool.schema.number().optional().default(5),
110
111
  plan_id: tool.schema.string().optional()
111
112
  },
112
113
  async execute(args) {
@@ -126,7 +127,7 @@ export function unifiedTools() {
126
127
  }
127
128
  }),
128
129
  code_read: tool({
129
- description: "Precise reading of symbols or file slices. Follows the current plan.",
130
+ description: "Precise reading of symbols or file slices. Follows current plan. Checks for locks and returns graffiti.",
130
131
  args: {
131
132
  symbol: tool.schema.string().optional().describe("Symbol to jump to"),
132
133
  file: tool.schema.string().optional().describe("File path to read"),
@@ -135,24 +136,37 @@ export function unifiedTools() {
135
136
  plan_id: tool.schema.string().optional()
136
137
  },
137
138
  async execute(args) {
138
- if (args.symbol) {
139
- getDb().logAccess(args.symbol, args.plan_id);
140
- return internal.jump_to_symbol.execute({ symbol: args.symbol, plan_id: args.plan_id });
141
- }
142
- if (args.file && args.start_line && args.end_line) {
143
- getDb().logAccess(args.file, args.plan_id);
144
- return internal.read_slice.execute({ file: args.file, start_line: args.start_line, end_line: args.end_line, plan_id: args.plan_id });
139
+ const resourceId = args.symbol || args.file;
140
+ if (resourceId) {
141
+ getDb().logAccess(resourceId, args.plan_id);
142
+ const lock = getDb().isLocked(resourceId);
143
+ const graffiti = getDb().getGraffiti(resourceId);
144
+ let result;
145
+ if (args.symbol) {
146
+ result = await internal.jump_to_symbol.execute({ symbol: args.symbol, plan_id: args.plan_id });
147
+ }
148
+ else {
149
+ result = await internal.read_slice.execute({ file: args.file, start_line: args.start_line, end_line: args.end_line, plan_id: args.plan_id });
150
+ }
151
+ const parsed = JSON.parse(result);
152
+ return JSON.stringify({
153
+ ...parsed,
154
+ coordination: {
155
+ lock_status: lock ? `LOCKED by ${lock.owner_agent}` : "FREE",
156
+ graffiti: graffiti.length > 0 ? graffiti : undefined
157
+ }
158
+ }, null, 2);
145
159
  }
146
- throw new Error("Either 'symbol' or 'file' with line range must be provided.");
160
+ throw new Error("Either 'symbol' or 'file' must be provided.");
147
161
  }
148
162
  }),
149
163
  code_propose: tool({
150
- description: "Plan, propose, and promote changes. Includes patch generation, Intent indexing, and PR promotion.",
164
+ description: "Plan, propose, and promote changes. Automatically handles coordination pulse and lock checks.",
151
165
  args: {
152
166
  action: tool.schema.enum(["plan", "patch", "validate", "finalize", "promote"]),
153
167
  symbol: tool.schema.string().optional(),
154
168
  intent: tool.schema.string().optional(),
155
- reasoning: tool.schema.string().optional().describe("Detailed reasoning for the change (Decision Indexing)"),
169
+ reasoning: tool.schema.string().optional(),
156
170
  message: tool.schema.string().optional(),
157
171
  patch_path: tool.schema.string().optional(),
158
172
  branch: tool.schema.string().optional(),
@@ -161,33 +175,38 @@ export function unifiedTools() {
161
175
  },
162
176
  async execute(args) {
163
177
  switch (args.action) {
164
- case "plan": return internal.brief_fix_loop.execute({ symbol: args.symbol, intent: args.intent });
178
+ case "plan": {
179
+ getDb().postToBlackboard(agentName, `Planning ${args.intent} for ${args.symbol}`, "pulse");
180
+ return internal.brief_fix_loop.execute({ symbol: args.symbol, intent: args.intent });
181
+ }
165
182
  case "patch": {
183
+ // 1. Check for locks on all changed files
166
184
  const { stdout: diff } = await internal.runCmd("git diff");
185
+ const { stdout: files } = await internal.runCmd("git diff --name-only");
186
+ const changedFiles = files.split('\n').filter(Boolean);
187
+ for (const file of changedFiles) {
188
+ const lock = getDb().isLocked(file);
189
+ if (lock && lock.owner_agent !== agentName) {
190
+ return JSON.stringify({ status: "COLLISION_PREVENTED", message: `File ${file} is locked by ${lock.owner_agent}. Use 'code_status' to investigate.` });
191
+ }
192
+ }
193
+ // 2. Run Policy Engine
167
194
  const violations = policyEngine.checkDiff(diff);
168
195
  if (violations.some(v => v.severity === "error")) {
169
196
  return JSON.stringify({ status: "POLICY_VIOLATION", violations, message: "Patch rejected by policy engine." }, null, 2);
170
197
  }
198
+ // 3. Prepare patch and record intent
171
199
  const res = await internal.prepare_patch.execute({ message: args.message, plan_id: args.plan_id });
172
200
  const json = JSON.parse(res);
173
- if (json.status === "SUCCESS" && args.reasoning) {
174
- getDb().storeIntent(json.patch_id, args.reasoning, args.plan_id || "adhoc");
201
+ if (json.status === "SUCCESS") {
202
+ if (args.reasoning)
203
+ getDb().storeIntent(json.patch_id, args.reasoning, args.plan_id || "adhoc");
204
+ getDb().postToBlackboard(agentName, `Proposed patch ${json.patch_id}: ${args.message}`, "pulse");
175
205
  }
176
206
  return res;
177
207
  }
178
208
  case "validate": {
179
- // Architectural Boundary Check
180
- const { stdout: diff } = await internal.runCmd("git diff --name-only");
181
- const changedFiles = diff.split('\n').filter(Boolean);
182
- for (const file of changedFiles) {
183
- const deps = await internal.extractDependencies.execute({ content: "", ast: null, filePath: file });
184
- const imports = JSON.parse(deps);
185
- for (const imp of imports) {
186
- const violation = getDb().checkArchViolation(file, imp);
187
- if (violation)
188
- return JSON.stringify({ status: "ARCH_VIOLATION", file, forbidden_import: imp, rule: violation }, null, 2);
189
- }
190
- }
209
+ getDb().postToBlackboard(agentName, `Validating patch ${args.patch_path}`, "pulse");
191
210
  return internal.validate_patch.execute({ patch_path: args.patch_path, plan_id: args.plan_id });
192
211
  }
193
212
  case "promote": {
@@ -198,22 +217,27 @@ export function unifiedTools() {
198
217
  execSync(`git apply ${args.patch_path}`);
199
218
  execSync(`git add . && git commit -m "${args.message || 'Automated promotion'}"`);
200
219
  execSync(`gh pr create --title "${args.message}" --body "Automated promotion from Autognosis v2."`);
220
+ getDb().postToBlackboard(agentName, `Promoted patch to PR on branch ${branch}`, "pulse");
201
221
  return JSON.stringify({ status: "SUCCESS", promoted_to: branch, pr: "OPENED" }, null, 2);
202
222
  }
203
223
  catch (e) {
204
224
  return JSON.stringify({ status: "ERROR", message: e.message }, null, 2);
205
225
  }
206
226
  }
207
- case "finalize": return internal.finalize_plan.execute({ plan_id: args.plan_id, outcome: args.outcome });
227
+ case "finalize": {
228
+ getDb().postToBlackboard(agentName, `Finalized plan ${args.plan_id} with outcome: ${args.outcome}`, "pulse");
229
+ return internal.finalize_plan.execute({ plan_id: args.plan_id, outcome: args.outcome });
230
+ }
208
231
  }
209
232
  }
210
233
  }),
211
234
  code_status: tool({
212
- description: "Monitor system health, background jobs, compliance, and Multi-Agent Blackboard.",
235
+ description: "Monitor system health, background jobs, Multi-Agent Blackboard, and Resource Locks.",
213
236
  args: {
214
- mode: tool.schema.enum(["stats", "hot_files", "jobs", "plan", "doctor", "blackboard"]).optional().default("stats"),
215
- action: tool.schema.enum(["post", "read"]).optional(),
237
+ mode: tool.schema.enum(["stats", "hot_files", "jobs", "plan", "doctor", "blackboard", "locks"]).optional().default("stats"),
238
+ action: tool.schema.enum(["post", "read", "lock", "unlock"]).optional(),
216
239
  topic: tool.schema.string().optional().default("general"),
240
+ target: tool.schema.string().optional().describe("Resource ID (file/symbol) for locks or Symbol ID for graffiti"),
217
241
  message: tool.schema.string().optional(),
218
242
  job_id: tool.schema.string().optional(),
219
243
  plan_id: tool.schema.string().optional(),
@@ -221,9 +245,20 @@ export function unifiedTools() {
221
245
  },
222
246
  async execute(args) {
223
247
  switch (args.mode) {
248
+ case "locks": {
249
+ if (args.action === "lock") {
250
+ getDb().acquireLock(args.target, agentName);
251
+ return JSON.stringify({ status: "SUCCESS", message: `Locked ${args.target}` });
252
+ }
253
+ else if (args.action === "unlock") {
254
+ getDb().releaseLock(args.target, agentName);
255
+ return JSON.stringify({ status: "SUCCESS", message: `Unlocked ${args.target}` });
256
+ }
257
+ return JSON.stringify({ status: "SUCCESS", active_locks: getDb().listLocks() });
258
+ }
224
259
  case "blackboard": {
225
260
  if (args.action === "post") {
226
- getDb().postToBlackboard("Agent", args.message, args.topic);
261
+ getDb().postToBlackboard(agentName, args.message, args.topic, args.target);
227
262
  return JSON.stringify({ status: "SUCCESS", message: "Posted to blackboard." });
228
263
  }
229
264
  return JSON.stringify({ status: "SUCCESS", entries: getDb().readBlackboard(args.topic) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-autognosis",
3
- "version": "2.2.0",
3
+ "version": "2.3.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",