Files
2026-05-30 14:16:04 -05:00

110 lines
3.4 KiB
TypeScript

import { Type } from "@sinclair/typebox"
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"
import type { BmClient } from "../bm-client.ts"
import { log } from "../logger.ts"
export function registerSchemaDiffTool(
api: OpenClawPluginApi,
client: BmClient,
): void {
api.registerTool(
{
name: "schema_diff",
label: "Schema Diff",
description:
"Detect drift between a Picoschema definition and actual note usage. " +
"Identifies new fields in notes not declared in the schema, " +
"declared fields no longer used, and cardinality changes.",
parameters: Type.Object({
noteType: Type.String({
description:
'The note type to check for drift (e.g., "person", "meeting")',
}),
project: Type.Optional(
Type.String({
description: "Target project name (defaults to current project)",
}),
),
}),
async execute(
_toolCallId: string,
params: { noteType: string; project?: string },
) {
log.debug(`schema_diff: noteType="${params.noteType}"`)
try {
const result = await client.schemaDiff(
params.noteType,
params.project,
)
if (!result.schema_found) {
return {
content: [
{
type: "text" as const,
text: `No schema found for type "${params.noteType}". Use schema_infer to generate one.`,
},
],
details: result,
}
}
const lines: string[] = [`**Type:** ${result.entity_type}`]
if (
result.new_fields.length === 0 &&
result.dropped_fields.length === 0 &&
result.cardinality_changes.length === 0
) {
lines.push("No drift detected — schema and notes are in sync.")
} else {
if (result.new_fields.length > 0) {
lines.push("", "### New Fields (in notes, not in schema)")
for (const f of result.new_fields) {
lines.push(
`- **${f.name}** — ${(f.percentage * 100).toFixed(0)}% of notes`,
)
}
}
if (result.dropped_fields.length > 0) {
lines.push("", "### Dropped Fields (in schema, not in notes)")
for (const f of result.dropped_fields) {
lines.push(`- **${f.name}** — declared in ${f.declared_in}`)
}
}
if (result.cardinality_changes.length > 0) {
lines.push("", "### Cardinality Changes")
for (const c of result.cardinality_changes) {
lines.push(`- ${c}`)
}
}
}
return {
content: [{ type: "text" as const, text: lines.join("\n") }],
details: result,
}
} catch (err) {
log.error("schema_diff failed", err)
return {
content: [
{
type: "text" as const,
text: "Schema diff failed. Check logs for details.",
},
],
details: {
noteType: params.noteType,
error: "schema_diff_failed",
},
}
}
},
},
{ name: "schema_diff" },
)
}