opencode-autognosis 2.2.0 → 2.3.1

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,18 @@ 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, isPinned?: boolean): void;
17
+ getGraffiti(symbolId: string, limit?: number): any;
18
+ archiveGraffiti(symbolId: string): void;
19
+ pinGraffiti(id: number, pinned?: boolean): void;
17
20
  readBlackboard(topic?: string, limit?: number): any;
21
+ acquireLock(resourceId: string, agentName: string, ttlSeconds?: number): void;
22
+ releaseLock(resourceId: string, agentName: string): void;
23
+ isLocked(resourceId: string): {
24
+ owner_agent: string;
25
+ expires_at: string;
26
+ } | undefined;
27
+ listLocks(): any;
18
28
  storeIntent(patchId: string, reasoning: string, planId: string): void;
19
29
  getIntent(patchId: string): any;
20
30
  addArchRule(source: string, target: string): void;
package/dist/database.js CHANGED
@@ -146,9 +146,21 @@ export class CodeGraphDB {
146
146
  author TEXT,
147
147
  message TEXT,
148
148
  topic TEXT,
149
+ symbol_id TEXT,
150
+ git_hash TEXT, -- Version of code when note was made
151
+ is_archived BOOLEAN DEFAULT 0,
152
+ is_pinned BOOLEAN DEFAULT 0,
153
+ embedding BLOB,
149
154
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
150
155
  );
151
156
 
157
+ CREATE TABLE IF NOT EXISTS locks (
158
+ resource_id TEXT PRIMARY KEY, -- file path or symbol name
159
+ owner_agent TEXT,
160
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
161
+ expires_at DATETIME
162
+ );
163
+
152
164
  CREATE TABLE IF NOT EXISTS intents (
153
165
  patch_id TEXT PRIMARY KEY,
154
166
  reasoning TEXT,
@@ -222,11 +234,53 @@ export class CodeGraphDB {
222
234
  }
223
235
  return this.db.prepare("SELECT * FROM background_jobs ORDER BY created_at DESC LIMIT ?").all(limit);
224
236
  }
225
- postToBlackboard(author, message, topic = 'general') {
237
+ postToBlackboard(author, message, topic = 'general', symbolId, isPinned = false) {
238
+ // Get current git hash for contextual verification
239
+ let currentHash = "unknown";
240
+ try {
241
+ const { execSync } = require("node:child_process");
242
+ currentHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
243
+ }
244
+ catch { }
245
+ const insert = this.db.prepare(`
246
+ INSERT INTO blackboard (author, message, topic, symbol_id, git_hash, is_pinned)
247
+ VALUES (?, ?, ?, ?, ?, ?)
248
+ RETURNING id
249
+ `);
250
+ const res = insert.get(author, message, topic, symbolId || null, currentHash, isPinned ? 1 : 0);
226
251
  this.db.prepare(`
227
- INSERT INTO blackboard (author, message, topic)
228
- VALUES (?, ?, ?)
229
- `).run(author, message, topic);
252
+ INSERT INTO embedding_queue (chunk_id, text_to_embed)
253
+ VALUES (?, ?)
254
+ `).run(`blackboard-${res.id}`, `${topic.toUpperCase()}: ${message}`);
255
+ }
256
+ getGraffiti(symbolId, limit = 3) {
257
+ // Automatically archive notes older than 7 days that aren't pinned
258
+ this.db.prepare(`
259
+ UPDATE blackboard
260
+ SET is_archived = 1
261
+ WHERE is_pinned = 0
262
+ AND timestamp < datetime('now', '-7 days')
263
+ `).run();
264
+ return this.db.prepare(`
265
+ SELECT author, message, timestamp, git_hash, is_pinned
266
+ FROM blackboard
267
+ WHERE symbol_id = ?
268
+ AND is_archived = 0
269
+ ORDER BY is_pinned DESC, timestamp DESC
270
+ LIMIT ?
271
+ `).all(symbolId, limit);
272
+ }
273
+ archiveGraffiti(symbolId) {
274
+ this.db.prepare(`
275
+ UPDATE blackboard
276
+ SET is_archived = 1
277
+ WHERE symbol_id = ? AND is_pinned = 0
278
+ `).run(symbolId);
279
+ }
280
+ pinGraffiti(id, pinned = true) {
281
+ this.db.prepare(`
282
+ UPDATE blackboard SET is_pinned = ? WHERE id = ?
283
+ `).run(pinned ? 1 : 0, id);
230
284
  }
231
285
  readBlackboard(topic, limit = 10) {
232
286
  if (topic) {
@@ -238,6 +292,35 @@ export class CodeGraphDB {
238
292
  SELECT * FROM blackboard ORDER BY timestamp DESC LIMIT ?
239
293
  `).all(limit);
240
294
  }
295
+ acquireLock(resourceId, agentName, ttlSeconds = 300) {
296
+ // Check if already locked by someone else
297
+ const current = this.isLocked(resourceId);
298
+ if (current && current.owner_agent !== agentName) {
299
+ throw new Error(`Resource ${resourceId} is already locked by ${current.owner_agent}`);
300
+ }
301
+ const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
302
+ this.db.prepare(`
303
+ INSERT INTO locks (resource_id, owner_agent, expires_at)
304
+ VALUES (?, ?, ?)
305
+ ON CONFLICT(resource_id) DO UPDATE SET
306
+ owner_agent = excluded.owner_agent,
307
+ expires_at = excluded.expires_at
308
+ `).run(resourceId, agentName, expiresAt);
309
+ }
310
+ releaseLock(resourceId, agentName) {
311
+ this.db.prepare(`
312
+ DELETE FROM locks
313
+ WHERE resource_id = ? AND owner_agent = ?
314
+ `).run(resourceId, agentName);
315
+ }
316
+ isLocked(resourceId) {
317
+ // Automatically prune expired locks
318
+ this.db.prepare("DELETE FROM locks WHERE expires_at < CURRENT_TIMESTAMP").run();
319
+ return this.db.prepare("SELECT * FROM locks WHERE resource_id = ?").get(resourceId);
320
+ }
321
+ listLocks() {
322
+ return this.db.prepare("SELECT * FROM locks").all();
323
+ }
241
324
  storeIntent(patchId, reasoning, planId) {
242
325
  this.db.prepare(`
243
326
  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 historical 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,52 @@ 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
+ // Smart History Housekeeping
144
+ const graffiti = getDb().getGraffiti(resourceId, 3); // Limit to top 3 recent/pinned notes
145
+ // Contextual Verification: Get current hash
146
+ let currentHash = "";
147
+ try {
148
+ const { execSync } = await import("node:child_process");
149
+ currentHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
150
+ }
151
+ catch { }
152
+ const verifiedGraffiti = graffiti.map((g) => ({
153
+ author: g.author,
154
+ message: g.message,
155
+ timestamp: g.timestamp,
156
+ status: g.git_hash !== currentHash ? "LEGACY (Potentially Outdated)" : "CURRENT",
157
+ is_pinned: !!g.is_pinned
158
+ }));
159
+ let result;
160
+ if (args.symbol) {
161
+ result = await internal.jump_to_symbol.execute({ symbol: args.symbol, plan_id: args.plan_id });
162
+ }
163
+ else {
164
+ result = await internal.read_slice.execute({ file: args.file, start_line: args.start_line, end_line: args.end_line, plan_id: args.plan_id });
165
+ }
166
+ const parsed = JSON.parse(result);
167
+ return JSON.stringify({
168
+ ...parsed,
169
+ coordination: {
170
+ lock_status: lock ? `LOCKED by ${lock.owner_agent}` : "FREE",
171
+ historical_notes: verifiedGraffiti.length > 0 ? verifiedGraffiti : undefined
172
+ }
173
+ }, null, 2);
145
174
  }
146
- throw new Error("Either 'symbol' or 'file' with line range must be provided.");
175
+ throw new Error("Either 'symbol' or 'file' must be provided.");
147
176
  }
148
177
  }),
149
178
  code_propose: tool({
150
- description: "Plan, propose, and promote changes. Includes patch generation, Intent indexing, and PR promotion.",
179
+ description: "Plan, propose, and promote changes. Automatically handles coordination pulse and lock checks.",
151
180
  args: {
152
181
  action: tool.schema.enum(["plan", "patch", "validate", "finalize", "promote"]),
153
182
  symbol: tool.schema.string().optional(),
154
183
  intent: tool.schema.string().optional(),
155
- reasoning: tool.schema.string().optional().describe("Detailed reasoning for the change (Decision Indexing)"),
184
+ reasoning: tool.schema.string().optional(),
156
185
  message: tool.schema.string().optional(),
157
186
  patch_path: tool.schema.string().optional(),
158
187
  branch: tool.schema.string().optional(),
@@ -161,8 +190,19 @@ export function unifiedTools() {
161
190
  },
162
191
  async execute(args) {
163
192
  switch (args.action) {
164
- case "plan": return internal.brief_fix_loop.execute({ symbol: args.symbol, intent: args.intent });
193
+ case "plan": {
194
+ getDb().postToBlackboard(agentName, `Planning ${args.intent} for ${args.symbol}`, "pulse");
195
+ return internal.brief_fix_loop.execute({ symbol: args.symbol, intent: args.intent });
196
+ }
165
197
  case "patch": {
198
+ const { stdout: files } = await internal.runCmd("git diff --name-only");
199
+ const changedFiles = files.split('\n').filter(Boolean);
200
+ for (const file of changedFiles) {
201
+ const lock = getDb().isLocked(file);
202
+ if (lock && lock.owner_agent !== agentName) {
203
+ return JSON.stringify({ status: "COLLISION_PREVENTED", message: `File ${file} is locked by ${lock.owner_agent}.` });
204
+ }
205
+ }
166
206
  const { stdout: diff } = await internal.runCmd("git diff");
167
207
  const violations = policyEngine.checkDiff(diff);
168
208
  if (violations.some(v => v.severity === "error")) {
@@ -170,24 +210,15 @@ export function unifiedTools() {
170
210
  }
171
211
  const res = await internal.prepare_patch.execute({ message: args.message, plan_id: args.plan_id });
172
212
  const json = JSON.parse(res);
173
- if (json.status === "SUCCESS" && args.reasoning) {
174
- getDb().storeIntent(json.patch_id, args.reasoning, args.plan_id || "adhoc");
213
+ if (json.status === "SUCCESS") {
214
+ if (args.reasoning)
215
+ getDb().storeIntent(json.patch_id, args.reasoning, args.plan_id || "adhoc");
216
+ getDb().postToBlackboard(agentName, `Proposed patch ${json.patch_id}: ${args.message}`, "pulse");
175
217
  }
176
218
  return res;
177
219
  }
178
220
  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
- }
221
+ getDb().postToBlackboard(agentName, `Validating patch ${args.patch_path}`, "pulse");
191
222
  return internal.validate_patch.execute({ patch_path: args.patch_path, plan_id: args.plan_id });
192
223
  }
193
224
  case "promote": {
@@ -198,22 +229,28 @@ export function unifiedTools() {
198
229
  execSync(`git apply ${args.patch_path}`);
199
230
  execSync(`git add . && git commit -m "${args.message || 'Automated promotion'}"`);
200
231
  execSync(`gh pr create --title "${args.message}" --body "Automated promotion from Autognosis v2."`);
232
+ getDb().postToBlackboard(agentName, `Promoted patch to PR on branch ${branch}`, "pulse");
201
233
  return JSON.stringify({ status: "SUCCESS", promoted_to: branch, pr: "OPENED" }, null, 2);
202
234
  }
203
235
  catch (e) {
204
236
  return JSON.stringify({ status: "ERROR", message: e.message }, null, 2);
205
237
  }
206
238
  }
207
- case "finalize": return internal.finalize_plan.execute({ plan_id: args.plan_id, outcome: args.outcome });
239
+ case "finalize": {
240
+ getDb().postToBlackboard(agentName, `Finalized plan ${args.plan_id} with outcome: ${args.outcome}`, "pulse");
241
+ return internal.finalize_plan.execute({ plan_id: args.plan_id, outcome: args.outcome });
242
+ }
208
243
  }
209
244
  }
210
245
  }),
211
246
  code_status: tool({
212
- description: "Monitor system health, background jobs, compliance, and Multi-Agent Blackboard.",
247
+ description: "Monitor system health, Multi-Agent Blackboard, and Resource Locks.",
213
248
  args: {
214
- mode: tool.schema.enum(["stats", "hot_files", "jobs", "plan", "doctor", "blackboard"]).optional().default("stats"),
215
- action: tool.schema.enum(["post", "read"]).optional(),
249
+ mode: tool.schema.enum(["stats", "hot_files", "jobs", "plan", "doctor", "blackboard", "locks"]).optional().default("stats"),
250
+ action: tool.schema.enum(["post", "read", "lock", "unlock", "archive", "pin"]).optional(),
216
251
  topic: tool.schema.string().optional().default("general"),
252
+ target: tool.schema.string().optional().describe("Resource ID (file/symbol) or Note ID"),
253
+ pinned: tool.schema.boolean().optional().default(false),
217
254
  message: tool.schema.string().optional(),
218
255
  job_id: tool.schema.string().optional(),
219
256
  plan_id: tool.schema.string().optional(),
@@ -221,11 +258,30 @@ export function unifiedTools() {
221
258
  },
222
259
  async execute(args) {
223
260
  switch (args.mode) {
261
+ case "locks": {
262
+ if (args.action === "lock") {
263
+ getDb().acquireLock(args.target, agentName);
264
+ return JSON.stringify({ status: "SUCCESS", message: `Locked ${args.target}` });
265
+ }
266
+ else if (args.action === "unlock") {
267
+ getDb().releaseLock(args.target, agentName);
268
+ return JSON.stringify({ status: "SUCCESS", message: `Unlocked ${args.target}` });
269
+ }
270
+ return JSON.stringify({ status: "SUCCESS", active_locks: getDb().listLocks() });
271
+ }
224
272
  case "blackboard": {
225
273
  if (args.action === "post") {
226
- getDb().postToBlackboard("Agent", args.message, args.topic);
274
+ getDb().postToBlackboard(agentName, args.message, args.topic, args.target, args.pinned);
227
275
  return JSON.stringify({ status: "SUCCESS", message: "Posted to blackboard." });
228
276
  }
277
+ else if (args.action === "archive") {
278
+ getDb().archiveGraffiti(args.target);
279
+ return JSON.stringify({ status: "SUCCESS", message: `Archived notes for ${args.target}` });
280
+ }
281
+ else if (args.action === "pin") {
282
+ getDb().pinGraffiti(parseInt(args.target, 10), true);
283
+ return JSON.stringify({ status: "SUCCESS", message: `Pinned note ${args.target}` });
284
+ }
229
285
  return JSON.stringify({ status: "SUCCESS", entries: getDb().readBlackboard(args.topic) });
230
286
  }
231
287
  case "hot_files": return internal.journal_query_hot_files.execute({ path_prefix: args.path });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-autognosis",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
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",