opencode-codegraph 0.1.3 → 0.1.4

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/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## 0.1.4 - 2026-03-20
4
+
5
+ - add conversational pre-edit guidance when a chat message suggests code modification
6
+ - append durable review-trace summary after `git commit` when trace artifacts are available
7
+ - add unified `/status` workflow for freshness plus latest review-trace inspection in the surrounding OpenCode setup
8
+
9
+ ## 0.1.3 - 2026-03-20
10
+
11
+ - surface durable review-trace summary back into OpenCode after commit
12
+ - add machine-readable `review_trace_status.py --json`
13
+
14
+ ## 0.1.2 - 2026-03-20
15
+
16
+ - fix `pattern-results` API path handling in the plugin client
17
+ - align local OpenCode workspace installation with the published plugin flow
package/README.md CHANGED
@@ -23,7 +23,7 @@ Automatically enriches AI conversations with Code Property Graph data -- securit
23
23
 
24
24
  ### Auto-Enrichment
25
25
 
26
- When you mention a file in chat, the plugin adds CPG context automatically:
26
+ When you mention a file in chat, the plugin adds CPG context automatically:
27
27
 
28
28
  ```
29
29
  You: "Refactor src/api/routers/webhook.py"
@@ -32,18 +32,20 @@ Plugin injects:
32
32
  ### CPG context: src/api/routers/webhook.py
33
33
  **12 methods** in file:
34
34
  - `receive_github_webhook` CC=5 fan_in=0 fan_out=3 [entry]
35
- - `_handle_push` CC=2 fan_in=4 fan_out=2
36
- **2 security findings:**
37
- - CWE-89 L42: SQL injection in query parameter
38
- ```
35
+ - `_handle_push` CC=2 fan_in=4 fan_out=2
36
+ **2 security findings:**
37
+ - CWE-89 L42: SQL injection in query parameter
38
+ ```
39
+
40
+ If the message also suggests an edit intent (`refactor`, `fix`, `modify`, `update`, etc.), the plugin appends a pre-edit warning block with complexity, fan-out, dead-code, and security hints for the referenced file.
39
41
 
40
42
  ### System Prompt
41
43
 
42
44
  Every conversation includes a project summary with file count, top complexity hotspots, and open security findings.
43
45
 
44
- ### Post-Commit Updates
45
-
46
- After `git commit`, the plugin triggers incremental CPG re-parsing via GoCPG and syncs the ChromaDB vector store.
46
+ ### Post-Commit Updates
47
+
48
+ After `git commit`, the plugin triggers incremental CPG re-parsing via GoCPG and syncs the ChromaDB vector store. If durable review-trace artifacts exist for the new `HEAD`, the plugin also appends a short review-trace summary with findings and recommendations.
47
49
 
48
50
  ### Custom Tools
49
51
 
@@ -62,10 +64,12 @@ Place in `.opencode/commands/`:
62
64
 
63
65
  | Command | Description |
64
66
  |---------|-------------|
65
- | `/review` | CPG-powered code review |
66
- | `/audit` | Full codebase audit (12 dimensions) |
67
- | `/explain` | Function analysis with call graph |
68
- | `/onboard` | Codebase understanding |
67
+ | `/review` | CPG-powered code review |
68
+ | `/audit` | Full codebase audit (12 dimensions) |
69
+ | `/explain` | Function analysis with call graph |
70
+ | `/onboard` | Codebase understanding |
71
+ | `/update` | Freshness check and incremental CPG update |
72
+ | `/status` | Unified freshness + latest review-trace status |
69
73
 
70
74
  ## Custom Agent
71
75
 
@@ -80,12 +84,13 @@ Place in `.opencode/commands/`:
80
84
 
81
85
  ## Hooks
82
86
 
83
- | Hook | Purpose |
84
- |------|---------|
85
- | `experimental.chat.system.transform` | Inject project summary into system prompt |
86
- | `chat.message` | Add CPG context for mentioned files |
87
- | `tool.execute.after` | Trigger CPG update after git commit |
88
- | `permission.ask` | Auto-allow `codegraph_*` tools |
87
+ | Hook | Purpose |
88
+ |------|---------|
89
+ | `experimental.chat.system.transform` | Inject project summary into system prompt |
90
+ | `chat.message` | Add CPG context for mentioned files |
91
+ | `chat.message` (edit intent) | Add pre-edit warnings for files likely to be modified |
92
+ | `tool.execute.after` | Trigger CPG update after git commit |
93
+ | `permission.ask` | Auto-allow `codegraph_*` tools |
89
94
 
90
95
  ## License
91
96
 
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "opencode-codegraph",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "OpenCode plugin for CodeGraph CPG-powered code analysis",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "exports": {
8
8
  ".": "./src/index.ts"
9
9
  },
10
- "files": [
11
- "src"
12
- ],
10
+ "files": [
11
+ "src",
12
+ "CHANGELOG.md"
13
+ ],
13
14
  "scripts": {
14
15
  "typecheck": "tsc --noEmit",
15
16
  "build": "tsc"
package/src/api.ts CHANGED
@@ -72,7 +72,7 @@ export class CodeGraphAPI {
72
72
  * Get CPG context for a specific file.
73
73
  * Returns markdown with methods, callers, complexity, and security findings.
74
74
  */
75
- async getFileCPGContext(projectId: string, filePath: string): Promise<string | null> {
75
+ async getFileCPGContext(projectId: string, filePath: string): Promise<string | null> {
76
76
  const params = new URLSearchParams()
77
77
  if (projectId) params.set("project_id", projectId)
78
78
  params.set("filename", filePath)
@@ -117,8 +117,80 @@ export class CodeGraphAPI {
117
117
  }
118
118
  }
119
119
 
120
- return lines.join("\n")
121
- }
120
+ return lines.join("\n")
121
+ }
122
+
123
+ /**
124
+ * Get pre-edit guidance for a specific file when the user is likely to modify it.
125
+ */
126
+ async getPreEditGuidance(projectId: string, filePath: string): Promise<string | null> {
127
+ const params = new URLSearchParams()
128
+ if (projectId) params.set("project_id", projectId)
129
+ params.set("filename", filePath)
130
+ const findingsParams = new URLSearchParams(params)
131
+ findingsParams.set("category", "security")
132
+
133
+ const [methodsRes, findingsRes] = await Promise.allSettled([
134
+ this.fetch(`/api/v1/cpg/methods?${params}`),
135
+ this.fetch(`/api/v1/cpg/pattern-results?${findingsParams.toString()}`),
136
+ ])
137
+
138
+ const methods = methodsRes.status === "fulfilled" ? methodsRes.value?.results || [] : []
139
+ const findings = findingsRes.status === "fulfilled" ? findingsRes.value?.results || [] : []
140
+
141
+ const highComplexity = methods.filter((m: any) => (m.cyclomatic_complexity ?? 0) >= 15)
142
+ const highFanOut = methods.filter((m: any) => (m.fan_out ?? 0) >= 30)
143
+ const deadMethods = methods.filter(
144
+ (m: any) => (m.fan_in ?? 0) === 0 && !m.is_entry_point && !m.is_external && !m.is_test,
145
+ )
146
+
147
+ if (!highComplexity.length && !highFanOut.length && !deadMethods.length && !findings.length) {
148
+ return null
149
+ }
150
+
151
+ const lines: string[] = [`### CodeGraph Pre-Edit Warnings: \`${filePath}\``, ""]
152
+
153
+ if (highComplexity.length) {
154
+ lines.push("**High complexity methods:**")
155
+ for (const method of highComplexity.slice(0, 5)) {
156
+ lines.push(
157
+ `- \`${method.name}\` CC=${method.cyclomatic_complexity} fan_in=${method.fan_in} fan_out=${method.fan_out}`,
158
+ )
159
+ }
160
+ lines.push("")
161
+ }
162
+
163
+ if (highFanOut.length) {
164
+ lines.push("**High fan-out methods:**")
165
+ for (const method of highFanOut.slice(0, 5)) {
166
+ lines.push(`- \`${method.name}\` fan_out=${method.fan_out}`)
167
+ }
168
+ lines.push("")
169
+ }
170
+
171
+ if (deadMethods.length) {
172
+ lines.push("**Potential dead methods in file:**")
173
+ for (const method of deadMethods.slice(0, 5)) {
174
+ lines.push(`- \`${method.name}\` has fan_in=0`)
175
+ }
176
+ lines.push("")
177
+ }
178
+
179
+ if (findings.length) {
180
+ lines.push(`**Security findings present:** ${findings.length}`)
181
+ for (const finding of findings.slice(0, 5)) {
182
+ lines.push(`- **${finding.rule_id}** L${finding.line}: ${finding.message}`)
183
+ }
184
+ lines.push("")
185
+ }
186
+
187
+ lines.push("**Before editing:**")
188
+ lines.push("- Re-check callers before refactoring methods with high fan-in or high complexity.")
189
+ lines.push("- Update or add tests for the risky paths you touch in this file.")
190
+ lines.push("- If this file defines handlers/services, verify interface registration after the change.")
191
+
192
+ return lines.join("\n")
193
+ }
122
194
 
123
195
  /**
124
196
  * Trigger incremental CPG update after a git commit.
package/src/index.ts CHANGED
@@ -16,9 +16,11 @@ import { tool } from "@opencode-ai/plugin/tool"
16
16
  import { CodeGraphAPI } from "./api"
17
17
  import {
18
18
  extractFileRefs,
19
+ fileNeedsRegistrationCheck,
19
20
  formatReviewTraceSummary,
20
21
  getHeadCommit,
21
22
  isGitCommit,
23
+ messageSuggestsEditing,
22
24
  readReviewTraceSnapshot,
23
25
  } from "./util"
24
26
 
@@ -50,10 +52,11 @@ const codegraphPlugin: Plugin = async (input) => {
50
52
  // -----------------------------------------------------------------
51
53
  // 2. Chat message: auto-enrich with CPG context for mentioned files
52
54
  // -----------------------------------------------------------------
53
- "chat.message": async (_inp, output) => {
54
- const files = extractFileRefs(output.parts)
55
- if (files.length === 0) return
56
-
55
+ "chat.message": async (_inp, output) => {
56
+ const files = extractFileRefs(output.parts)
57
+ if (files.length === 0) return
58
+ const editIntent = messageSuggestsEditing(output.parts)
59
+
57
60
  try {
58
61
  for (const file of files.slice(0, 5)) {
59
62
  const context = await api.getFileCPGContext(projectId, file)
@@ -63,6 +66,23 @@ const codegraphPlugin: Plugin = async (input) => {
63
66
  text: context,
64
67
  } as any)
65
68
  }
69
+
70
+ if (editIntent) {
71
+ const guidance = await api.getPreEditGuidance(projectId, file)
72
+ if (guidance) {
73
+ output.parts.push({
74
+ type: "text",
75
+ text: guidance,
76
+ } as any)
77
+ } else if (fileNeedsRegistrationCheck(file)) {
78
+ output.parts.push({
79
+ type: "text",
80
+ text:
81
+ `### CodeGraph Pre-Edit Reminder: \`${file}\`\n\n` +
82
+ "- This looks like a handler/service file. After editing, verify that CLI/API/MCP/ACP registration still matches the intended entry points.",
83
+ } as any)
84
+ }
85
+ }
66
86
  }
67
87
  } catch {
68
88
  // CodeGraph API not available — skip silently
package/src/util.ts CHANGED
@@ -20,7 +20,7 @@ export type ReviewTraceSnapshot = {
20
20
  * Extract file references from message parts.
21
21
  * Looks for file paths in text parts (e.g., `src/api/main.py`, @filename patterns).
22
22
  */
23
- export function extractFileRefs(parts: Array<{ type: string; text?: string }>): string[] {
23
+ export function extractFileRefs(parts: Array<{ type: string; text?: string }>): string[] {
24
24
  const files: string[] = []
25
25
  const seen = new Set<string>()
26
26
 
@@ -68,6 +68,37 @@ export function isGitCommit(output: string): boolean {
68
68
  )
69
69
  }
70
70
 
71
+ /**
72
+ * Heuristic: detect whether the user is likely asking to modify code.
73
+ */
74
+ export function messageSuggestsEditing(parts: Array<{ type: string; text?: string }>): boolean {
75
+ const patterns = [
76
+ /\b(edit|change|modify|update|refactor|rewrite|implement|fix|patch|remove)\b/i,
77
+ /\b(add|improve|cleanup|clean up|rename|split|extract)\b/i,
78
+ ]
79
+
80
+ for (const part of parts) {
81
+ if (part.type !== "text" || !part.text) continue
82
+ const text = part.text
83
+ if (patterns.some((pattern) => pattern.test(text))) {
84
+ return true
85
+ }
86
+ }
87
+
88
+ return false
89
+ }
90
+
91
+ export function fileNeedsRegistrationCheck(filePath: string): boolean {
92
+ const normalized = filePath.replace(/\\/g, "/")
93
+ const isHandlerLike = ["scenarios/", "services/", "analysis/", "security/"].some((segment) =>
94
+ normalized.includes(segment),
95
+ )
96
+ const isInterfaceLayer = ["src/cli/", "src/api/routers/", "src/tui/commands/", "src/mcp/", "src/acp/"].some(
97
+ (segment) => normalized.includes(segment),
98
+ )
99
+ return isHandlerLike && !isInterfaceLayer
100
+ }
101
+
71
102
  export async function getHeadCommit($: any): Promise<string | null> {
72
103
  try {
73
104
  const sha = (await $`git rev-parse HEAD`.quiet().text()).trim()