@kodrunhq/opencode-autopilot 1.15.1 → 1.16.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.
- package/README.md +14 -0
- package/bin/cli.ts +5 -0
- package/bin/inspect.ts +337 -0
- package/package.json +1 -1
- package/src/agents/autopilot.ts +7 -15
- package/src/agents/index.ts +54 -21
- package/src/health/checks.ts +108 -4
- package/src/health/runner.ts +3 -0
- package/src/index.ts +105 -12
- package/src/inspect/formatters.ts +225 -0
- package/src/inspect/repository.ts +882 -0
- package/src/kernel/database.ts +45 -0
- package/src/kernel/migrations.ts +62 -0
- package/src/kernel/repository.ts +571 -0
- package/src/kernel/schema.ts +122 -0
- package/src/kernel/types.ts +66 -0
- package/src/memory/capture.ts +221 -25
- package/src/memory/database.ts +74 -12
- package/src/memory/index.ts +17 -1
- package/src/memory/project-key.ts +6 -0
- package/src/memory/repository.ts +833 -42
- package/src/memory/retrieval.ts +83 -169
- package/src/memory/schemas.ts +39 -7
- package/src/memory/types.ts +4 -0
- package/src/observability/event-handlers.ts +28 -17
- package/src/observability/event-store.ts +29 -1
- package/src/observability/forensic-log.ts +159 -0
- package/src/observability/forensic-schemas.ts +69 -0
- package/src/observability/forensic-types.ts +10 -0
- package/src/observability/index.ts +21 -27
- package/src/observability/log-reader.ts +142 -111
- package/src/observability/log-writer.ts +41 -83
- package/src/observability/retention.ts +2 -2
- package/src/observability/session-logger.ts +36 -57
- package/src/observability/summary-generator.ts +31 -19
- package/src/observability/types.ts +12 -24
- package/src/orchestrator/contracts/invariants.ts +14 -0
- package/src/orchestrator/contracts/legacy-result-adapter.ts +8 -20
- package/src/orchestrator/fallback/event-handler.ts +47 -3
- package/src/orchestrator/handlers/architect.ts +2 -1
- package/src/orchestrator/handlers/build.ts +55 -97
- package/src/orchestrator/handlers/retrospective.ts +2 -1
- package/src/orchestrator/handlers/types.ts +0 -1
- package/src/orchestrator/lesson-memory.ts +29 -9
- package/src/orchestrator/orchestration-logger.ts +37 -23
- package/src/orchestrator/phase.ts +8 -4
- package/src/orchestrator/state.ts +79 -17
- package/src/projects/database.ts +47 -0
- package/src/projects/repository.ts +264 -0
- package/src/projects/resolve.ts +301 -0
- package/src/projects/schemas.ts +30 -0
- package/src/projects/types.ts +12 -0
- package/src/review/memory.ts +29 -9
- package/src/tools/doctor.ts +40 -5
- package/src/tools/forensics.ts +7 -12
- package/src/tools/logs.ts +6 -5
- package/src/tools/memory-preferences.ts +157 -0
- package/src/tools/memory-status.ts +17 -96
- package/src/tools/orchestrate.ts +97 -81
- package/src/tools/pipeline-report.ts +3 -2
- package/src/tools/quick.ts +2 -2
- package/src/tools/review.ts +39 -6
- package/src/tools/session-stats.ts +3 -2
- package/src/utils/paths.ts +20 -1
package/src/health/runner.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
commandHealthCheck,
|
|
6
6
|
configHealthCheck,
|
|
7
7
|
memoryHealthCheck,
|
|
8
|
+
nativeAgentSuppressionHealthCheck,
|
|
8
9
|
skillHealthCheck,
|
|
9
10
|
} from "./checks";
|
|
10
11
|
import type { HealthReport, HealthResult } from "./types";
|
|
@@ -45,6 +46,7 @@ export async function runHealthChecks(options?: {
|
|
|
45
46
|
const settled = await Promise.allSettled([
|
|
46
47
|
configHealthCheck(options?.configPath),
|
|
47
48
|
agentHealthCheck(options?.openCodeConfig ?? null),
|
|
49
|
+
nativeAgentSuppressionHealthCheck(options?.openCodeConfig ?? null),
|
|
48
50
|
assetHealthCheck(options?.assetsDir, options?.targetDir),
|
|
49
51
|
skillHealthCheck(options?.projectRoot ?? process.cwd()),
|
|
50
52
|
memoryHealthCheck(options?.targetDir),
|
|
@@ -54,6 +56,7 @@ export async function runHealthChecks(options?: {
|
|
|
54
56
|
const fallbackNames = [
|
|
55
57
|
"config-validity",
|
|
56
58
|
"agent-injection",
|
|
59
|
+
"native-agent-suppression",
|
|
57
60
|
"asset-directories",
|
|
58
61
|
"skill-loading",
|
|
59
62
|
"memory-db",
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,12 @@ import { isFirstLoad, loadConfig } from "./config";
|
|
|
4
4
|
import { runHealthChecks } from "./health/runner";
|
|
5
5
|
import { createAntiSlopHandler } from "./hooks/anti-slop";
|
|
6
6
|
import { installAssets } from "./installer";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
createMemoryCaptureHandler,
|
|
9
|
+
createMemoryChatMessageHandler,
|
|
10
|
+
createMemoryInjector,
|
|
11
|
+
getMemoryDb,
|
|
12
|
+
} from "./memory";
|
|
8
13
|
import { ContextMonitor } from "./observability/context-monitor";
|
|
9
14
|
import {
|
|
10
15
|
createObservabilityEventHandler,
|
|
@@ -12,9 +17,9 @@ import {
|
|
|
12
17
|
createToolExecuteBeforeHandler,
|
|
13
18
|
} from "./observability/event-handlers";
|
|
14
19
|
import { SessionEventStore } from "./observability/event-store";
|
|
20
|
+
import { createForensicEvent } from "./observability/forensic-log";
|
|
15
21
|
import { writeSessionLog } from "./observability/log-writer";
|
|
16
22
|
import { pruneOldLogs } from "./observability/retention";
|
|
17
|
-
import type { SessionEvent } from "./observability/types";
|
|
18
23
|
import type { SdkOperations } from "./orchestrator/fallback";
|
|
19
24
|
import {
|
|
20
25
|
createChatMessageHandler,
|
|
@@ -34,10 +39,11 @@ import {
|
|
|
34
39
|
import { ocCreateAgent } from "./tools/create-agent";
|
|
35
40
|
import { ocCreateCommand } from "./tools/create-command";
|
|
36
41
|
import { ocCreateSkill } from "./tools/create-skill";
|
|
37
|
-
import { ocDoctor } from "./tools/doctor";
|
|
42
|
+
import { ocDoctor, setOpenCodeConfig as setDoctorOpenCodeConfig } from "./tools/doctor";
|
|
38
43
|
import { ocForensics } from "./tools/forensics";
|
|
39
44
|
import { ocHashlineEdit } from "./tools/hashline-edit";
|
|
40
45
|
import { ocLogs } from "./tools/logs";
|
|
46
|
+
import { ocMemoryPreferences } from "./tools/memory-preferences";
|
|
41
47
|
import { ocMemoryStatus } from "./tools/memory-status";
|
|
42
48
|
import { ocMockFallback } from "./tools/mock-fallback";
|
|
43
49
|
import { ocOrchestrate } from "./tools/orchestrate";
|
|
@@ -148,6 +154,29 @@ const plugin: Plugin = async (input) => {
|
|
|
148
154
|
manager,
|
|
149
155
|
sdk: sdkOps,
|
|
150
156
|
config: fallbackConfig,
|
|
157
|
+
onFallbackEvent: (event) => {
|
|
158
|
+
if (event.type === "fallback") {
|
|
159
|
+
eventStore.appendEvent(event.sessionId, {
|
|
160
|
+
type: "fallback",
|
|
161
|
+
timestamp: new Date().toISOString(),
|
|
162
|
+
sessionId: event.sessionId,
|
|
163
|
+
failedModel: event.failedModel ?? "unknown",
|
|
164
|
+
nextModel: event.nextModel ?? "unknown",
|
|
165
|
+
reason: event.reason ?? "fallback",
|
|
166
|
+
success: event.success === true,
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
eventStore.appendEvent(event.sessionId, {
|
|
172
|
+
type: "model_switch",
|
|
173
|
+
timestamp: new Date().toISOString(),
|
|
174
|
+
sessionId: event.sessionId,
|
|
175
|
+
fromModel: event.fromModel ?? "unknown",
|
|
176
|
+
toModel: event.toModel ?? "unknown",
|
|
177
|
+
trigger: event.trigger ?? "fallback",
|
|
178
|
+
});
|
|
179
|
+
},
|
|
151
180
|
});
|
|
152
181
|
const chatMessageHandler = createChatMessageHandler(manager);
|
|
153
182
|
const toolExecuteAfterHandler = createToolExecuteAfterHandler(manager);
|
|
@@ -165,6 +194,9 @@ const plugin: Plugin = async (input) => {
|
|
|
165
194
|
const memoryCaptureHandler = memoryConfig.enabled
|
|
166
195
|
? createMemoryCaptureHandler({ getDb: () => getMemoryDb(), projectRoot: process.cwd() })
|
|
167
196
|
: null;
|
|
197
|
+
const memoryChatMessageHandler = memoryConfig.enabled
|
|
198
|
+
? createMemoryChatMessageHandler({ getDb: () => getMemoryDb(), projectRoot: process.cwd() })
|
|
199
|
+
: null;
|
|
168
200
|
|
|
169
201
|
const memoryInjector = memoryConfig.enabled
|
|
170
202
|
? createMemoryInjector({
|
|
@@ -183,18 +215,73 @@ const plugin: Plugin = async (input) => {
|
|
|
183
215
|
showToast: sdkOps.showToast,
|
|
184
216
|
writeSessionLog: async (sessionData) => {
|
|
185
217
|
if (!sessionData) return;
|
|
186
|
-
// Filter to schema-valid event types that match SessionEvent discriminated union
|
|
187
|
-
const schemaEvents: SessionEvent[] = sessionData.events.filter(
|
|
188
|
-
(e): e is SessionEvent =>
|
|
189
|
-
e.type === "fallback" ||
|
|
190
|
-
e.type === "error" ||
|
|
191
|
-
e.type === "decision" ||
|
|
192
|
-
e.type === "model_switch",
|
|
193
|
-
);
|
|
194
218
|
await writeSessionLog({
|
|
219
|
+
projectRoot: process.cwd(),
|
|
195
220
|
sessionId: sessionData.sessionId,
|
|
196
221
|
startedAt: sessionData.startedAt,
|
|
197
|
-
events:
|
|
222
|
+
events: sessionData.events.map((event) =>
|
|
223
|
+
createForensicEvent({
|
|
224
|
+
projectRoot: process.cwd(),
|
|
225
|
+
domain: "session",
|
|
226
|
+
timestamp: event.timestamp,
|
|
227
|
+
sessionId: event.sessionId,
|
|
228
|
+
type: event.type,
|
|
229
|
+
message: event.type === "error" ? event.message : null,
|
|
230
|
+
code:
|
|
231
|
+
event.type === "error"
|
|
232
|
+
? event.errorType
|
|
233
|
+
: event.type === "fallback"
|
|
234
|
+
? "FALLBACK"
|
|
235
|
+
: null,
|
|
236
|
+
payload:
|
|
237
|
+
event.type === "error"
|
|
238
|
+
? {
|
|
239
|
+
model: event.model,
|
|
240
|
+
errorType: event.errorType,
|
|
241
|
+
...(event.statusCode !== undefined ? { statusCode: event.statusCode } : {}),
|
|
242
|
+
}
|
|
243
|
+
: event.type === "fallback"
|
|
244
|
+
? {
|
|
245
|
+
failedModel: event.failedModel,
|
|
246
|
+
nextModel: event.nextModel,
|
|
247
|
+
reason: event.reason,
|
|
248
|
+
success: event.success,
|
|
249
|
+
}
|
|
250
|
+
: event.type === "decision"
|
|
251
|
+
? {
|
|
252
|
+
decision: event.decision,
|
|
253
|
+
rationale: event.rationale,
|
|
254
|
+
}
|
|
255
|
+
: event.type === "model_switch"
|
|
256
|
+
? {
|
|
257
|
+
fromModel: event.fromModel,
|
|
258
|
+
toModel: event.toModel,
|
|
259
|
+
trigger: event.trigger,
|
|
260
|
+
}
|
|
261
|
+
: event.type === "context_warning"
|
|
262
|
+
? {
|
|
263
|
+
utilization: event.utilization,
|
|
264
|
+
contextLimit: event.contextLimit,
|
|
265
|
+
inputTokens: event.inputTokens,
|
|
266
|
+
}
|
|
267
|
+
: event.type === "tool_complete"
|
|
268
|
+
? {
|
|
269
|
+
tool: event.tool,
|
|
270
|
+
durationMs: event.durationMs,
|
|
271
|
+
success: event.success,
|
|
272
|
+
}
|
|
273
|
+
: event.type === "phase_transition"
|
|
274
|
+
? {
|
|
275
|
+
fromPhase: event.fromPhase,
|
|
276
|
+
toPhase: event.toPhase,
|
|
277
|
+
}
|
|
278
|
+
: event.type === "compacted"
|
|
279
|
+
? {
|
|
280
|
+
trigger: event.trigger,
|
|
281
|
+
}
|
|
282
|
+
: {},
|
|
283
|
+
}),
|
|
284
|
+
),
|
|
198
285
|
});
|
|
199
286
|
},
|
|
200
287
|
});
|
|
@@ -224,6 +311,7 @@ const plugin: Plugin = async (input) => {
|
|
|
224
311
|
oc_stocktake: ocStocktake,
|
|
225
312
|
oc_update_docs: ocUpdateDocs,
|
|
226
313
|
oc_memory_status: ocMemoryStatus,
|
|
314
|
+
oc_memory_preferences: ocMemoryPreferences,
|
|
227
315
|
},
|
|
228
316
|
event: async ({ event }) => {
|
|
229
317
|
// 1. Observability: collect (pure observer, no side effects on session)
|
|
@@ -255,6 +343,7 @@ const plugin: Plugin = async (input) => {
|
|
|
255
343
|
config: async (cfg: Config) => {
|
|
256
344
|
openCodeConfig = cfg;
|
|
257
345
|
setOpenCodeConfig(cfg);
|
|
346
|
+
setDoctorOpenCodeConfig(cfg);
|
|
258
347
|
await configHook(cfg);
|
|
259
348
|
},
|
|
260
349
|
"chat.message": async (
|
|
@@ -268,6 +357,10 @@ const plugin: Plugin = async (input) => {
|
|
|
268
357
|
parts: unknown[];
|
|
269
358
|
},
|
|
270
359
|
) => {
|
|
360
|
+
if (memoryChatMessageHandler) {
|
|
361
|
+
await memoryChatMessageHandler(hookInput, output);
|
|
362
|
+
}
|
|
363
|
+
|
|
271
364
|
if (fallbackConfig.enabled) {
|
|
272
365
|
await chatMessageHandler(hookInput, output);
|
|
273
366
|
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
InspectEventSummary,
|
|
3
|
+
InspectLessonSummary,
|
|
4
|
+
InspectMemoryOverview,
|
|
5
|
+
InspectPreferenceSummary,
|
|
6
|
+
InspectProjectDetails,
|
|
7
|
+
InspectProjectSummary,
|
|
8
|
+
InspectRunSummary,
|
|
9
|
+
} from "./repository";
|
|
10
|
+
|
|
11
|
+
function sanitizeCell(value: string | number | boolean | null): string {
|
|
12
|
+
return String(value ?? "")
|
|
13
|
+
.replace(/\|/g, "\\|")
|
|
14
|
+
.replace(/\n/g, " ");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function formatTimestamp(value: string | null): string {
|
|
18
|
+
return value ?? "-";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function formatProjects(projects: readonly InspectProjectSummary[]): string {
|
|
22
|
+
if (projects.length === 0) {
|
|
23
|
+
return "No projects found.";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const lines = [
|
|
27
|
+
"Projects",
|
|
28
|
+
"",
|
|
29
|
+
"| Project | Current Path | Updated | Runs | Events | Lessons |",
|
|
30
|
+
"|---------|--------------|---------|------|--------|---------|",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
for (const project of projects) {
|
|
34
|
+
lines.push(
|
|
35
|
+
`| ${sanitizeCell(project.name)} | ${sanitizeCell(project.path)} | ${sanitizeCell(project.lastUpdated)} | ${project.runCount} | ${project.eventCount} | ${project.lessonCount} |`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return lines.join("\n");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formatProjectDetails(details: InspectProjectDetails): string {
|
|
43
|
+
const { project, paths, gitFingerprints } = details;
|
|
44
|
+
const lines = [
|
|
45
|
+
`Project: ${project.name}`,
|
|
46
|
+
"",
|
|
47
|
+
`ID: ${project.id}`,
|
|
48
|
+
`Current Path: ${project.path}`,
|
|
49
|
+
`First Seen: ${project.firstSeenAt}`,
|
|
50
|
+
`Last Updated: ${project.lastUpdated}`,
|
|
51
|
+
`Runs: ${project.runCount}`,
|
|
52
|
+
`Events: ${project.eventCount}`,
|
|
53
|
+
`Lessons: ${project.lessonCount}`,
|
|
54
|
+
`Observations: ${project.observationCount}`,
|
|
55
|
+
"",
|
|
56
|
+
"Paths:",
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
if (paths.length === 0) {
|
|
60
|
+
lines.push("- none");
|
|
61
|
+
} else {
|
|
62
|
+
for (const path of paths) {
|
|
63
|
+
lines.push(`- ${path.path}${path.isCurrent ? " [current]" : ""}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
lines.push("", "Git Fingerprints:");
|
|
68
|
+
if (gitFingerprints.length === 0) {
|
|
69
|
+
lines.push("- none");
|
|
70
|
+
} else {
|
|
71
|
+
for (const fingerprint of gitFingerprints) {
|
|
72
|
+
lines.push(
|
|
73
|
+
`- ${fingerprint.normalizedRemoteUrl}${fingerprint.defaultBranch ? ` (${fingerprint.defaultBranch})` : ""}`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return lines.join("\n");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function formatRuns(runs: readonly InspectRunSummary[]): string {
|
|
82
|
+
if (runs.length === 0) {
|
|
83
|
+
return "No runs found.";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const lines = [
|
|
87
|
+
"Runs",
|
|
88
|
+
"",
|
|
89
|
+
"| Project | Run ID | Status | Phase | Revision | Updated |",
|
|
90
|
+
"|---------|--------|--------|-------|----------|---------|",
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
for (const run of runs) {
|
|
94
|
+
lines.push(
|
|
95
|
+
`| ${sanitizeCell(run.projectName)} | ${sanitizeCell(run.runId)} | ${sanitizeCell(run.status)} | ${sanitizeCell(run.currentPhase ?? "-")} | ${run.stateRevision} | ${sanitizeCell(run.lastUpdatedAt)} |`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return lines.join("\n");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function formatEvents(events: readonly InspectEventSummary[]): string {
|
|
103
|
+
if (events.length === 0) {
|
|
104
|
+
return "No events found.";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const lines = [
|
|
108
|
+
"Events",
|
|
109
|
+
"",
|
|
110
|
+
"| Timestamp | Project | Domain | Type | Phase | Agent | Code | Message |",
|
|
111
|
+
"|-----------|---------|--------|------|-------|-------|------|---------|",
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
for (const event of events) {
|
|
115
|
+
lines.push(
|
|
116
|
+
`| ${sanitizeCell(event.timestamp)} | ${sanitizeCell(event.projectName)} | ${sanitizeCell(event.domain)} | ${sanitizeCell(event.type)} | ${sanitizeCell(event.phase ?? "-")} | ${sanitizeCell(event.agent ?? "-")} | ${sanitizeCell(event.code ?? "-")} | ${sanitizeCell(event.message ?? "")} |`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return lines.join("\n");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function formatLessons(lessons: readonly InspectLessonSummary[]): string {
|
|
124
|
+
if (lessons.length === 0) {
|
|
125
|
+
return "No lessons found.";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const lines = [
|
|
129
|
+
"Lessons",
|
|
130
|
+
"",
|
|
131
|
+
"| Extracted | Project | Domain | Source Phase | Content |",
|
|
132
|
+
"|-----------|---------|--------|--------------|---------|",
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
for (const lesson of lessons) {
|
|
136
|
+
lines.push(
|
|
137
|
+
`| ${sanitizeCell(lesson.extractedAt)} | ${sanitizeCell(lesson.projectName)} | ${sanitizeCell(lesson.domain)} | ${sanitizeCell(lesson.sourcePhase)} | ${sanitizeCell(lesson.content)} |`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return lines.join("\n");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function formatPreferences(preferences: readonly InspectPreferenceSummary[]): string {
|
|
145
|
+
if (preferences.length === 0) {
|
|
146
|
+
return "No preferences found.";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const lines = [
|
|
150
|
+
"Preferences",
|
|
151
|
+
"",
|
|
152
|
+
"| Key | Scope | Value | Confidence | Evidence | Updated |",
|
|
153
|
+
"|-----|-------|-------|------------|----------|---------|",
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
for (const preference of preferences) {
|
|
157
|
+
lines.push(
|
|
158
|
+
`| ${sanitizeCell(preference.key)} | ${sanitizeCell(preference.scope)}${preference.projectId ? `:${sanitizeCell(preference.projectId)}` : ""} | ${sanitizeCell(preference.value)} | ${sanitizeCell(preference.confidence)} | ${sanitizeCell(preference.evidenceCount)} | ${sanitizeCell(preference.lastUpdated)} |`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return lines.join("\n");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function formatMemoryOverview(overview: InspectMemoryOverview): string {
|
|
166
|
+
const lines = [
|
|
167
|
+
"Memory Overview",
|
|
168
|
+
"",
|
|
169
|
+
`Total observations: ${overview.stats.totalObservations}`,
|
|
170
|
+
`Total projects: ${overview.stats.totalProjects}`,
|
|
171
|
+
`Total preferences: ${overview.stats.totalPreferences}`,
|
|
172
|
+
`Storage size: ${overview.stats.storageSizeKb} KB`,
|
|
173
|
+
"",
|
|
174
|
+
"Observations by type:",
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
for (const [type, count] of Object.entries(overview.stats.observationsByType)) {
|
|
178
|
+
lines.push(`- ${type}: ${count}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
lines.push("", "Recent observations:");
|
|
182
|
+
if (overview.recentObservations.length === 0) {
|
|
183
|
+
lines.push("- none");
|
|
184
|
+
} else {
|
|
185
|
+
for (const observation of overview.recentObservations) {
|
|
186
|
+
lines.push(
|
|
187
|
+
`- [${observation.type}] ${observation.summary} (${formatTimestamp(observation.createdAt)})`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
lines.push("", "Preferences:");
|
|
193
|
+
if (overview.preferences.length === 0) {
|
|
194
|
+
lines.push("- none");
|
|
195
|
+
} else {
|
|
196
|
+
for (const preference of overview.preferences) {
|
|
197
|
+
lines.push(
|
|
198
|
+
`- ${preference.key}: ${preference.value} (${preference.scope}, confidence ${preference.confidence}, evidence ${preference.evidenceCount})`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return lines.join("\n");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function formatPaths(details: InspectProjectDetails): string {
|
|
207
|
+
if (details.paths.length === 0) {
|
|
208
|
+
return `No paths found for ${details.project.name}.`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const lines = [
|
|
212
|
+
`Paths for ${details.project.name}`,
|
|
213
|
+
"",
|
|
214
|
+
"| Path | Current | First Seen | Last Updated |",
|
|
215
|
+
"|------|---------|------------|--------------|",
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
for (const path of details.paths) {
|
|
219
|
+
lines.push(
|
|
220
|
+
`| ${sanitizeCell(path.path)} | ${path.isCurrent ? "yes" : "no"} | ${sanitizeCell(path.firstSeenAt)} | ${sanitizeCell(path.lastUpdated)} |`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return lines.join("\n");
|
|
225
|
+
}
|