@kodrunhq/opencode-autopilot 1.5.0 → 1.7.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.
@@ -82,9 +82,9 @@ export async function loadAdaptiveSkillContext(
82
82
 
83
83
  const matchingSkills = filterSkillsByStack(allSkills, projectTags);
84
84
  return buildMultiSkillContext(matchingSkills, tokenBudget);
85
- } catch (error: unknown) {
86
- // Best-effort for I/O errors; re-throw programmer errors
87
- if (error instanceof TypeError || error instanceof RangeError) throw error;
85
+ } catch {
86
+ // Best-effort: all errors return empty string. Caller (injectSkillContext)
87
+ // logs the error no need to re-throw since the call site is also best-effort.
88
88
  return "";
89
89
  }
90
90
  }
@@ -314,7 +314,7 @@ async function handleCommit(configPath?: string): Promise<string> {
314
314
  }
315
315
  const newConfig = {
316
316
  ...currentConfig,
317
- version: 4 as const,
317
+ version: 5 as const,
318
318
  configured: true,
319
319
  groups: groupsRecord,
320
320
  overrides: currentConfig.overrides ?? {},
@@ -0,0 +1,164 @@
1
+ /**
2
+ * oc_memory_status tool — inspect memory system state.
3
+ *
4
+ * Shows observation counts, storage size, recent observations,
5
+ * preferences, and per-type breakdowns. Follows the *Core + tool()
6
+ * pattern from create-agent.ts.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ import { Database } from "bun:sqlite";
12
+ import { statSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { tool } from "@opencode-ai/plugin";
15
+ import { DB_FILE, MEMORY_DIR, OBSERVATION_TYPES } from "../memory/constants";
16
+ import { getMemoryDb } from "../memory/database";
17
+ import { getAllPreferences } from "../memory/repository";
18
+ import { getGlobalConfigDir } from "../utils/paths";
19
+
20
+ interface MemoryStatusResult {
21
+ readonly stats: {
22
+ readonly totalObservations: number;
23
+ readonly totalProjects: number;
24
+ readonly totalPreferences: number;
25
+ readonly storageSizeKb: number;
26
+ readonly observationsByType: Record<string, number>;
27
+ } | null;
28
+ readonly recentObservations: readonly {
29
+ readonly type: string;
30
+ readonly summary: string;
31
+ readonly createdAt: string;
32
+ readonly confidence: number;
33
+ }[];
34
+ readonly preferences: readonly {
35
+ readonly key: string;
36
+ readonly value: string;
37
+ readonly confidence: number;
38
+ }[];
39
+ readonly error?: string;
40
+ }
41
+
42
+ /**
43
+ * Core function for memory status inspection.
44
+ * Accepts a Database instance for testability (or uses the singleton).
45
+ */
46
+ export function memoryStatusCore(
47
+ _args: { readonly detail?: "summary" | "full" },
48
+ dbOrPath?: Database | string,
49
+ ): MemoryStatusResult {
50
+ let ownedDb: Database | null = null;
51
+ try {
52
+ if (typeof dbOrPath === "string") {
53
+ ownedDb = new Database(dbOrPath);
54
+ }
55
+ const db = dbOrPath instanceof Database ? dbOrPath : (ownedDb ?? getMemoryDb());
56
+
57
+ // Count observations
58
+ const obsCountRow = db.query("SELECT COUNT(*) as cnt FROM observations").get() as {
59
+ cnt: number;
60
+ };
61
+ const totalObservations = obsCountRow.cnt;
62
+
63
+ // Count by type
64
+ const typeRows = db
65
+ .query("SELECT type, COUNT(*) as cnt FROM observations GROUP BY type")
66
+ .all() as Array<{ type: string; cnt: number }>;
67
+
68
+ const observationsByType: Record<string, number> = {};
69
+ for (const t of OBSERVATION_TYPES) {
70
+ observationsByType[t] = 0;
71
+ }
72
+ for (const row of typeRows) {
73
+ observationsByType[row.type] = row.cnt;
74
+ }
75
+
76
+ // Count projects
77
+ const projCountRow = db.query("SELECT COUNT(*) as cnt FROM projects").get() as {
78
+ cnt: number;
79
+ };
80
+ const totalProjects = projCountRow.cnt;
81
+
82
+ // Count preferences
83
+ const prefCountRow = db.query("SELECT COUNT(*) as cnt FROM preferences").get() as {
84
+ cnt: number;
85
+ };
86
+ const totalPreferences = prefCountRow.cnt;
87
+
88
+ // Storage size — derive from actual DB path, not always the global default
89
+ let storageSizeKb = 0;
90
+ try {
91
+ const statPath =
92
+ typeof dbOrPath === "string" && dbOrPath !== ":memory:"
93
+ ? dbOrPath
94
+ : join(getGlobalConfigDir(), MEMORY_DIR, DB_FILE);
95
+ const stat = statSync(statPath);
96
+ storageSizeKb = Math.round(stat.size / 1024);
97
+ } catch {
98
+ // DB might be in-memory or path doesn't exist
99
+ }
100
+
101
+ // Recent observations (last 10)
102
+ const recentRows = db
103
+ .query(
104
+ "SELECT type, summary, created_at, confidence FROM observations ORDER BY created_at DESC LIMIT 10",
105
+ )
106
+ .all() as Array<{
107
+ type: string;
108
+ summary: string;
109
+ created_at: string;
110
+ confidence: number;
111
+ }>;
112
+
113
+ const recentObservations = recentRows.map((row) => ({
114
+ type: row.type,
115
+ summary: row.summary,
116
+ createdAt: row.created_at,
117
+ confidence: row.confidence,
118
+ }));
119
+
120
+ // All preferences
121
+ const allPrefs = getAllPreferences(db);
122
+ const preferences = allPrefs.map((p) => ({
123
+ key: p.key,
124
+ value: p.value,
125
+ confidence: p.confidence,
126
+ }));
127
+
128
+ return {
129
+ stats: {
130
+ totalObservations,
131
+ totalProjects,
132
+ totalPreferences,
133
+ storageSizeKb,
134
+ observationsByType,
135
+ },
136
+ recentObservations,
137
+ preferences,
138
+ };
139
+ } catch (err) {
140
+ const detail = err instanceof Error ? err.message : String(err);
141
+ return {
142
+ stats: null,
143
+ recentObservations: [],
144
+ preferences: [],
145
+ error: `Memory system error: ${detail}`,
146
+ };
147
+ } finally {
148
+ ownedDb?.close();
149
+ }
150
+ }
151
+
152
+ export const ocMemoryStatus = tool({
153
+ description:
154
+ "Show memory system status: observation counts, recent memories, preferences, and storage size.",
155
+ args: {
156
+ detail: tool.schema
157
+ .enum(["summary", "full"])
158
+ .default("summary")
159
+ .describe("Level of detail to show"),
160
+ },
161
+ async execute(args) {
162
+ return JSON.stringify(memoryStatusCore(args), null, 2);
163
+ },
164
+ });
@@ -5,7 +5,7 @@ import type { DispatchResult } from "../orchestrator/handlers/types";
5
5
  import { buildLessonContext } from "../orchestrator/lesson-injection";
6
6
  import { loadLessonMemory } from "../orchestrator/lesson-memory";
7
7
  import { completePhase, getNextPhase } from "../orchestrator/phase";
8
- import { buildSkillContext, loadSkillContent } from "../orchestrator/skill-injection";
8
+ import { loadAdaptiveSkillContext } from "../orchestrator/skill-injection";
9
9
  import { createInitialState, loadState, patchState, saveState } from "../orchestrator/state";
10
10
  import type { Phase } from "../orchestrator/types";
11
11
  import { isEnoentError } from "../utils/fs-helpers";
@@ -96,17 +96,16 @@ async function injectLessonContext(
96
96
  }
97
97
 
98
98
  /**
99
- * Attempt to inject coding-standards skill context into a dispatch prompt.
99
+ * Attempt to inject stack-filtered adaptive skill context into a dispatch prompt.
100
100
  * Best-effort: failures are silently swallowed to avoid breaking dispatch.
101
101
  */
102
- async function injectSkillContext(prompt: string): Promise<string> {
102
+ async function injectSkillContext(prompt: string, projectRoot?: string): Promise<string> {
103
103
  try {
104
104
  const baseDir = getGlobalConfigDir();
105
- const content = await loadSkillContent(baseDir);
106
- const ctx = buildSkillContext(content);
105
+ const ctx = await loadAdaptiveSkillContext(baseDir, projectRoot ?? process.cwd());
107
106
  if (ctx) return prompt + ctx;
108
- } catch {
109
- // Best-effort: swallow all errors (same as lesson injection)
107
+ } catch (err) {
108
+ console.warn("[opencode-autopilot] skill injection failed:", err);
110
109
  }
111
110
  return prompt;
112
111
  }
@@ -146,7 +145,7 @@ async function processHandlerResult(
146
145
  handlerResult.phase,
147
146
  artifactDir,
148
147
  );
149
- const withSkills = await injectSkillContext(enrichedPrompt);
148
+ const withSkills = await injectSkillContext(enrichedPrompt, join(artifactDir, ".."));
150
149
  if (withSkills !== handlerResult.prompt) {
151
150
  return JSON.stringify({ ...handlerResult, prompt: withSkills });
152
151
  }
@@ -163,7 +162,7 @@ async function processHandlerResult(
163
162
  handlerResult.phase as string,
164
163
  artifactDir,
165
164
  );
166
- const skillSuffix = await injectSkillContext("");
165
+ const skillSuffix = await injectSkillContext("", join(artifactDir, ".."));
167
166
  const combinedSuffix = lessonSuffix + (skillSuffix || "");
168
167
  if (combinedSuffix) {
169
168
  const enrichedAgents = handlerResult.agents.map((entry) => ({