opencode-codegraph 0.1.5 → 0.1.7
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 +12 -0
- package/README.md +22 -5
- package/package.json +1 -1
- package/src/index.ts +48 -11
- package/src/util.ts +146 -19
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.7 - 2026-03-20
|
|
4
|
+
|
|
5
|
+
- add command-oriented next-step guidance (`/status`, `/update`, `/review`) to review-trace and dogfooding status flows
|
|
6
|
+
- unify `/review`, `/audit`, and `/update` around the shared `dogfood status --json` backend
|
|
7
|
+
- keep session-start and post-commit guidance aligned on the same recommended command semantics
|
|
8
|
+
|
|
9
|
+
## 0.1.6 - 2026-03-20
|
|
10
|
+
|
|
11
|
+
- add explicit pending post-commit summary when durable review trace is not yet available
|
|
12
|
+
- guide developers to `/status` instead of leaving post-commit review state ambiguous
|
|
13
|
+
- document pending-trace behavior in plugin and OpenCode workflow docs
|
|
14
|
+
|
|
3
15
|
## 0.1.5 - 2026-03-20
|
|
4
16
|
|
|
5
17
|
- add unified next-action summary to review-trace status output
|
package/README.md
CHANGED
|
@@ -39,13 +39,30 @@ Plugin injects:
|
|
|
39
39
|
|
|
40
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.
|
|
41
41
|
|
|
42
|
-
### System Prompt
|
|
43
|
-
|
|
44
|
-
Every conversation includes
|
|
42
|
+
### System Prompt
|
|
43
|
+
|
|
44
|
+
Every conversation includes:
|
|
45
|
+
|
|
46
|
+
- a project summary with file count, top complexity hotspots, and open security findings;
|
|
47
|
+
- a lightweight dogfooding status block when available, including freshness, current `HEAD`, review-trace state, and recommended next action.
|
|
48
|
+
- a recommended command (`/status`, `/update`, or `/review`) when the workflow can point to a deterministic next step.
|
|
45
49
|
|
|
46
50
|
### Post-Commit Updates
|
|
47
51
|
|
|
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
|
|
52
|
+
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 structured post-commit block with:
|
|
53
|
+
|
|
54
|
+
- what changed in the review trace
|
|
55
|
+
- why it matters
|
|
56
|
+
- top recommendations
|
|
57
|
+
- one clear next action
|
|
58
|
+
- one suggested command to run next
|
|
59
|
+
|
|
60
|
+
If the durable review trace is not available yet, the plugin still appends a pending summary telling the developer to check `/status` instead of leaving the post-commit state ambiguous.
|
|
61
|
+
|
|
62
|
+
This means the post-commit UX now has two explicit states:
|
|
63
|
+
|
|
64
|
+
- trace ready -> structured summary with findings, recommendations, and next action
|
|
65
|
+
- trace pending/missing -> structured pending summary with deterministic follow-up
|
|
49
66
|
|
|
50
67
|
### Custom Tools
|
|
51
68
|
|
|
@@ -89,7 +106,7 @@ Place in `.opencode/commands/`:
|
|
|
89
106
|
| `experimental.chat.system.transform` | Inject project summary into system prompt |
|
|
90
107
|
| `chat.message` | Add CPG context for mentioned files |
|
|
91
108
|
| `chat.message` (edit intent) | Add pre-edit warnings for files likely to be modified |
|
|
92
|
-
| `tool.execute.after` | Trigger CPG update after git commit and append
|
|
109
|
+
| `tool.execute.after` | Trigger CPG update after git commit and append structured post-commit review summary |
|
|
93
110
|
| `permission.ask` | Auto-allow `codegraph_*` tools |
|
|
94
111
|
|
|
95
112
|
## License
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -17,12 +17,17 @@ import { CodeGraphAPI } from "./api"
|
|
|
17
17
|
import {
|
|
18
18
|
extractFileRefs,
|
|
19
19
|
fileNeedsRegistrationCheck,
|
|
20
|
+
formatDogfoodStatusSummary,
|
|
21
|
+
formatPendingReviewTraceSummary,
|
|
20
22
|
formatReviewTraceSummary,
|
|
21
23
|
getHeadCommit,
|
|
22
24
|
isGitCommit,
|
|
23
25
|
messageSuggestsEditing,
|
|
26
|
+
recommendedCommandFromReviewTrace,
|
|
24
27
|
recommendedNextActionFromReviewTrace,
|
|
25
28
|
readReviewTraceSnapshot,
|
|
29
|
+
whatChangedFromReviewTrace,
|
|
30
|
+
whyItMattersFromReviewTrace,
|
|
26
31
|
} from "./util"
|
|
27
32
|
|
|
28
33
|
const codegraphPlugin: Plugin = async (input) => {
|
|
@@ -39,16 +44,26 @@ const codegraphPlugin: Plugin = async (input) => {
|
|
|
39
44
|
// -----------------------------------------------------------------
|
|
40
45
|
// 1. System prompt: inject CPG project summary
|
|
41
46
|
// -----------------------------------------------------------------
|
|
42
|
-
"experimental.chat.system.transform": async (_inp, output) => {
|
|
43
|
-
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
"experimental.chat.system.transform": async (_inp, output) => {
|
|
48
|
+
try {
|
|
49
|
+
const rawStatus = await $`python -m src.cli.import_commands dogfood status --json`.quiet().text()
|
|
50
|
+
const statusSummary = formatDogfoodStatusSummary(JSON.parse(rawStatus))
|
|
51
|
+
if (statusSummary) {
|
|
52
|
+
output.system.push(statusSummary)
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// Dogfooding status unavailable — keep session startup lightweight
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const summary = await api.getProjectSummary(projectId)
|
|
60
|
+
if (summary) {
|
|
61
|
+
output.system.push(summary)
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// CodeGraph API not available — skip silently
|
|
65
|
+
}
|
|
66
|
+
},
|
|
52
67
|
|
|
53
68
|
// -----------------------------------------------------------------
|
|
54
69
|
// 2. Chat message: auto-enrich with CPG context for mentioned files
|
|
@@ -114,13 +129,35 @@ const codegraphPlugin: Plugin = async (input) => {
|
|
|
114
129
|
codegraph_review_trace_recommendations:
|
|
115
130
|
traceSnapshot.review_recommendations?.slice(0, 3) || [],
|
|
116
131
|
codegraph_review_trace_next_action: recommendedNextActionFromReviewTrace(traceSnapshot),
|
|
132
|
+
codegraph_review_trace_recommended_command:
|
|
133
|
+
traceSnapshot.recommended_command || recommendedCommandFromReviewTrace(traceSnapshot),
|
|
134
|
+
codegraph_review_trace_what_changed: whatChangedFromReviewTrace(traceSnapshot),
|
|
135
|
+
codegraph_review_trace_why_it_matters: whyItMattersFromReviewTrace(traceSnapshot),
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
traceSummary = formatPendingReviewTraceSummary(commit)
|
|
139
|
+
output.metadata = {
|
|
140
|
+
...output.metadata,
|
|
141
|
+
codegraph_review_trace_status: "pending_or_missing",
|
|
142
|
+
codegraph_review_trace_phase: "awaiting_trace",
|
|
143
|
+
codegraph_review_trace_findings: null,
|
|
144
|
+
codegraph_review_trace_recommendations: [],
|
|
145
|
+
codegraph_review_trace_next_action:
|
|
146
|
+
"Wait briefly for review trace completion, then run /status to check the latest result.",
|
|
147
|
+
codegraph_review_trace_recommended_command: "/status",
|
|
148
|
+
codegraph_review_trace_what_changed: [
|
|
149
|
+
`Incremental CPG update was triggered for ${commit.slice(0, 12)}.`,
|
|
150
|
+
"Durable review trace is not available yet or has not been written.",
|
|
151
|
+
],
|
|
152
|
+
codegraph_review_trace_why_it_matters:
|
|
153
|
+
"Post-commit findings and recommendations may still be pending, so the commit is not fully evaluated yet.",
|
|
117
154
|
}
|
|
118
155
|
}
|
|
119
156
|
}
|
|
120
157
|
|
|
121
158
|
// Notify user via output metadata (visible in OpenCode UI)
|
|
122
159
|
output.title = traceSummary
|
|
123
|
-
? "CodeGraph:
|
|
160
|
+
? "CodeGraph: post-commit review summary ready"
|
|
124
161
|
: "CodeGraph: CPG update triggered"
|
|
125
162
|
output.metadata = {
|
|
126
163
|
...output.metadata,
|
package/src/util.ts
CHANGED
|
@@ -14,6 +14,19 @@ export type ReviewTraceSnapshot = {
|
|
|
14
14
|
review_severity_counts?: Record<string, number>
|
|
15
15
|
review_recommendations?: string[]
|
|
16
16
|
error?: string | null
|
|
17
|
+
recommended_command?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type DogfoodStatusSnapshot = {
|
|
21
|
+
cpg_status?: {
|
|
22
|
+
is_fresh?: boolean
|
|
23
|
+
freshness_reason?: string
|
|
24
|
+
commits_behind?: number
|
|
25
|
+
db_exists?: boolean
|
|
26
|
+
}
|
|
27
|
+
head_commit?: string
|
|
28
|
+
review_trace?: ReviewTraceSnapshot | null
|
|
29
|
+
recommended_next_action?: string
|
|
17
30
|
}
|
|
18
31
|
|
|
19
32
|
/**
|
|
@@ -129,18 +142,68 @@ export async function readReviewTraceSnapshot(
|
|
|
129
142
|
return null
|
|
130
143
|
}
|
|
131
144
|
|
|
145
|
+
export function formatPendingReviewTraceSummary(commit: string | null): string {
|
|
146
|
+
const commitLabel = commit ? ` for \`${commit.slice(0, 12)}\`` : ""
|
|
147
|
+
return [
|
|
148
|
+
"## CodeGraph Post-Commit Summary",
|
|
149
|
+
"",
|
|
150
|
+
"### What changed",
|
|
151
|
+
"",
|
|
152
|
+
`- Incremental CPG update was triggered${commitLabel}.`,
|
|
153
|
+
"- Durable review trace is not available yet or has not been written.",
|
|
154
|
+
"",
|
|
155
|
+
"### Why it matters",
|
|
156
|
+
"",
|
|
157
|
+
"- Post-commit findings and recommendations may still be pending, so the commit is not fully evaluated yet.",
|
|
158
|
+
"",
|
|
159
|
+
"### What to do now",
|
|
160
|
+
"",
|
|
161
|
+
"- Wait briefly for review trace completion, then run `/status` to check the latest result.",
|
|
162
|
+
"- Suggested command: `/status`",
|
|
163
|
+
].join("\n")
|
|
164
|
+
}
|
|
165
|
+
|
|
132
166
|
export function formatReviewTraceSummary(snapshot: ReviewTraceSnapshot): string | null {
|
|
133
|
-
const status = snapshot.status || "unknown"
|
|
134
|
-
const phase = snapshot.phase || "unknown"
|
|
135
|
-
const findingsCount = snapshot.review_findings_count
|
|
136
167
|
const recommendations = Array.isArray(snapshot.review_recommendations)
|
|
137
168
|
? snapshot.review_recommendations.filter(Boolean)
|
|
138
169
|
: []
|
|
170
|
+
const whatChanged = whatChangedFromReviewTrace(snapshot)
|
|
171
|
+
const whyItMatters = whyItMattersFromReviewTrace(snapshot)
|
|
172
|
+
const nextAction = recommendedNextActionFromReviewTrace(snapshot)
|
|
173
|
+
const nextCommand = recommendedCommandFromReviewTrace(snapshot)
|
|
139
174
|
|
|
140
|
-
const lines = ["## CodeGraph
|
|
175
|
+
const lines = ["## CodeGraph Post-Commit Summary", ""]
|
|
141
176
|
|
|
142
|
-
if (
|
|
143
|
-
lines.push(`-
|
|
177
|
+
if (whatChanged) {
|
|
178
|
+
lines.push("### What changed", "", ...whatChanged.map((item) => `- ${item}`), "")
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (whyItMatters) {
|
|
182
|
+
lines.push("### Why it matters", "", `- ${whyItMatters}`, "")
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (recommendations.length) {
|
|
186
|
+
lines.push("### Top recommendations", "")
|
|
187
|
+
for (const recommendation of recommendations.slice(0, 3)) {
|
|
188
|
+
lines.push(`- ${recommendation}`)
|
|
189
|
+
}
|
|
190
|
+
lines.push("")
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
lines.push("### What to do now", "", `- ${nextAction}`)
|
|
194
|
+
lines.push(`- Suggested command: \`${nextCommand}\``)
|
|
195
|
+
|
|
196
|
+
return lines.join("\n")
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function whatChangedFromReviewTrace(snapshot: ReviewTraceSnapshot): string[] {
|
|
200
|
+
const items: string[] = []
|
|
201
|
+
const status = snapshot.status || "unknown"
|
|
202
|
+
const phase = snapshot.phase || "unknown"
|
|
203
|
+
items.push(`Review trace status is ${status} (phase: ${phase}).`)
|
|
204
|
+
|
|
205
|
+
if (typeof snapshot.review_findings_count === "number") {
|
|
206
|
+
items.push(`CodeGraph reported ${snapshot.review_findings_count} review finding(s).`)
|
|
144
207
|
}
|
|
145
208
|
|
|
146
209
|
const severityCounts = snapshot.review_severity_counts || {}
|
|
@@ -149,26 +212,37 @@ export function formatReviewTraceSummary(snapshot: ReviewTraceSnapshot): string
|
|
|
149
212
|
.map(([severity, count]) => `${severity}:${count}`)
|
|
150
213
|
.join(", ")
|
|
151
214
|
if (severitySummary) {
|
|
152
|
-
|
|
215
|
+
items.push(`Severity mix: ${severitySummary}.`)
|
|
153
216
|
}
|
|
154
217
|
|
|
155
218
|
if (snapshot.error) {
|
|
156
|
-
|
|
219
|
+
items.push(`Review trace recorded an error: ${snapshot.error}`)
|
|
157
220
|
}
|
|
158
221
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
for (const recommendation of recommendations.slice(0, 3)) {
|
|
162
|
-
lines.push(`- ${recommendation}`)
|
|
163
|
-
}
|
|
164
|
-
}
|
|
222
|
+
return items
|
|
223
|
+
}
|
|
165
224
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
225
|
+
export function whyItMattersFromReviewTrace(snapshot: ReviewTraceSnapshot): string {
|
|
226
|
+
const severityCounts = snapshot.review_severity_counts || {}
|
|
227
|
+
const high = severityCounts.high || 0
|
|
228
|
+
const medium = severityCounts.medium || 0
|
|
170
229
|
|
|
171
|
-
|
|
230
|
+
if (snapshot.error) {
|
|
231
|
+
return "The post-commit feedback loop is incomplete, so current review guidance may be missing or stale."
|
|
232
|
+
}
|
|
233
|
+
if ((snapshot.status || "").toLowerCase() === "running") {
|
|
234
|
+
return "The review trace is still running, so findings and recommendations can still change."
|
|
235
|
+
}
|
|
236
|
+
if (high > 0) {
|
|
237
|
+
return "High-severity findings were recorded, so the commit may need immediate follow-up before merge or push."
|
|
238
|
+
}
|
|
239
|
+
if (medium > 0) {
|
|
240
|
+
return "Medium-severity findings were recorded, so follow-up review is recommended before treating the commit as clean."
|
|
241
|
+
}
|
|
242
|
+
if (typeof snapshot.review_findings_count === "number" && snapshot.review_findings_count > 0) {
|
|
243
|
+
return "The review trace found issues worth checking, even if none were marked high severity."
|
|
244
|
+
}
|
|
245
|
+
return "No review findings were recorded, so the commit looks clean from the current trace perspective."
|
|
172
246
|
}
|
|
173
247
|
|
|
174
248
|
export function recommendedNextActionFromReviewTrace(snapshot: ReviewTraceSnapshot): string {
|
|
@@ -196,6 +270,59 @@ export function recommendedNextActionFromReviewTrace(snapshot: ReviewTraceSnapsh
|
|
|
196
270
|
return "No review findings recorded; continue with /review or push once the rest of your checks are green."
|
|
197
271
|
}
|
|
198
272
|
|
|
273
|
+
export function recommendedCommandFromReviewTrace(snapshot: ReviewTraceSnapshot): string {
|
|
274
|
+
const status = (snapshot.status || "").toLowerCase()
|
|
275
|
+
const findingsCount = snapshot.review_findings_count
|
|
276
|
+
|
|
277
|
+
if (snapshot.error) {
|
|
278
|
+
return "/review"
|
|
279
|
+
}
|
|
280
|
+
if (status === "running") {
|
|
281
|
+
return "/status"
|
|
282
|
+
}
|
|
283
|
+
if (status && status !== "completed") {
|
|
284
|
+
return "/review"
|
|
285
|
+
}
|
|
286
|
+
if (typeof findingsCount === "number" && findingsCount > 0) {
|
|
287
|
+
return "/review"
|
|
288
|
+
}
|
|
289
|
+
return "/review"
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function formatDogfoodStatusSummary(snapshot: DogfoodStatusSnapshot): string | null {
|
|
293
|
+
const cpg = snapshot.cpg_status || {}
|
|
294
|
+
const isFresh = cpg.is_fresh
|
|
295
|
+
const freshnessReason = cpg.freshness_reason || "unknown"
|
|
296
|
+
const commitsBehind = cpg.commits_behind
|
|
297
|
+
const headCommit = snapshot.head_commit ? snapshot.head_commit.slice(0, 12) : null
|
|
298
|
+
const reviewTrace = snapshot.review_trace || null
|
|
299
|
+
const traceStatus = reviewTrace?.status || "missing"
|
|
300
|
+
const nextAction = snapshot.recommended_next_action
|
|
301
|
+
const nextCommand = (snapshot as { recommended_command?: string }).recommended_command
|
|
302
|
+
|
|
303
|
+
if (typeof isFresh === "undefined" && !headCommit && !nextAction) {
|
|
304
|
+
return null
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const lines = ["## CodeGraph Dogfooding Status", ""]
|
|
308
|
+
if (typeof isFresh !== "undefined") {
|
|
309
|
+
const freshness = isFresh ? "fresh" : "stale"
|
|
310
|
+
const behind = typeof commitsBehind === "number" ? `, commits behind: ${commitsBehind}` : ""
|
|
311
|
+
lines.push(`- CPG freshness: ${freshness} (reason: ${freshnessReason}${behind})`)
|
|
312
|
+
}
|
|
313
|
+
if (headCommit) {
|
|
314
|
+
lines.push(`- HEAD: ${headCommit}`)
|
|
315
|
+
}
|
|
316
|
+
lines.push(`- Review trace: ${traceStatus}`)
|
|
317
|
+
if (nextAction) {
|
|
318
|
+
lines.push(`- Next action: ${nextAction}`)
|
|
319
|
+
}
|
|
320
|
+
if (nextCommand) {
|
|
321
|
+
lines.push(`- Suggested command: ${nextCommand}`)
|
|
322
|
+
}
|
|
323
|
+
return lines.join("\n")
|
|
324
|
+
}
|
|
325
|
+
|
|
199
326
|
// File extensions recognized as source code
|
|
200
327
|
const SOURCE_EXTENSIONS = new Set([
|
|
201
328
|
"py",
|