selftune 0.2.22 → 0.2.24
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 +6 -0
- package/README.md +95 -15
- package/apps/local-dashboard/dist/assets/index-DgY2KGP-.css +1 -0
- package/apps/local-dashboard/dist/assets/index-Dmx7LPVX.js +15 -0
- package/apps/local-dashboard/dist/assets/vendor-react-C5oyHiV1.js +11 -0
- package/apps/local-dashboard/dist/assets/{vendor-table-BIiI3YhS.js → vendor-table-Bc_bbKd8.js} +1 -1
- package/apps/local-dashboard/dist/assets/vendor-ui-B3BPIYy7.js +1 -0
- package/apps/local-dashboard/dist/index.html +5 -5
- package/cli/selftune/adapters/codex/install.ts +310 -78
- package/cli/selftune/adapters/opencode/install.ts +3 -4
- package/cli/selftune/adapters/pi/hook.ts +273 -0
- package/cli/selftune/adapters/pi/install.ts +207 -0
- package/cli/selftune/alpha-upload/build-payloads.ts +3 -3
- package/cli/selftune/alpha-upload/stage-canonical.ts +17 -11
- package/cli/selftune/auto-update.ts +200 -8
- package/cli/selftune/canonical-export.ts +55 -25
- package/cli/selftune/command-surface.ts +397 -0
- package/cli/selftune/constants.ts +10 -1
- package/cli/selftune/contribute/contribute.ts +64 -13
- package/cli/selftune/contribution-config.ts +57 -3
- package/cli/selftune/contribution-preferences.ts +117 -0
- package/cli/selftune/contribution-signals.ts +8 -4
- package/cli/selftune/contribution-staging.ts +13 -2
- package/cli/selftune/contributions.ts +55 -121
- package/cli/selftune/creator-contributions.ts +29 -10
- package/cli/selftune/cron/setup.ts +7 -3
- package/cli/selftune/dashboard-contract.ts +87 -0
- package/cli/selftune/dashboard-server.ts +168 -17
- package/cli/selftune/dashboard.ts +350 -17
- package/cli/selftune/eval/baseline.ts +21 -5
- package/cli/selftune/eval/execution-eval.ts +170 -0
- package/cli/selftune/eval/family-overlap.ts +2 -2
- package/cli/selftune/eval/hooks-to-evals.ts +228 -82
- package/cli/selftune/eval/import-skillsbench.ts +2 -2
- package/cli/selftune/eval/invocation-classifier.ts +56 -0
- package/cli/selftune/eval/synthetic-evals.ts +5 -3
- package/cli/selftune/eval/unit-test-cli.ts +7 -4
- package/cli/selftune/evolution/apply-proposal.ts +295 -0
- package/cli/selftune/evolution/engines/judge-engine.ts +96 -0
- package/cli/selftune/evolution/engines/replay-engine.ts +180 -0
- package/cli/selftune/evolution/evidence.ts +2 -6
- package/cli/selftune/evolution/evolve-body.ts +152 -38
- package/cli/selftune/evolution/evolve.ts +244 -52
- package/cli/selftune/evolution/rollback.ts +0 -1
- package/cli/selftune/evolution/validate-body.ts +111 -49
- package/cli/selftune/evolution/validate-host-replay.ts +510 -60
- package/cli/selftune/evolution/validate-proposal.ts +11 -150
- package/cli/selftune/evolution/validate-routing.ts +51 -108
- package/cli/selftune/evolution/validation-contract.ts +91 -0
- package/cli/selftune/grading/auto-grade.ts +11 -7
- package/cli/selftune/grading/grade-session.ts +10 -16
- package/cli/selftune/hooks/skill-eval.ts +2 -1
- package/cli/selftune/hooks-shared/types.ts +1 -0
- package/cli/selftune/index.ts +58 -15
- package/cli/selftune/ingestors/claude-replay.ts +15 -10
- package/cli/selftune/ingestors/codex-wrapper.ts +3 -3
- package/cli/selftune/ingestors/opencode-ingest.ts +2 -2
- package/cli/selftune/ingestors/pi-ingest.ts +727 -0
- package/cli/selftune/init.ts +38 -4
- package/cli/selftune/localdb/direct-write.ts +120 -1
- package/cli/selftune/localdb/materialize.ts +6 -7
- package/cli/selftune/localdb/queries/cron.ts +34 -0
- package/cli/selftune/localdb/queries/dashboard.ts +834 -0
- package/cli/selftune/localdb/queries/evolution.ts +158 -0
- package/cli/selftune/localdb/queries/execution.ts +133 -0
- package/cli/selftune/localdb/queries/json.ts +18 -0
- package/cli/selftune/localdb/queries/monitoring.ts +263 -0
- package/cli/selftune/localdb/queries/raw.ts +95 -0
- package/cli/selftune/localdb/queries/staging.ts +270 -0
- package/cli/selftune/localdb/queries/trust.ts +392 -0
- package/cli/selftune/localdb/queries.ts +60 -2162
- package/cli/selftune/localdb/schema.ts +59 -0
- package/cli/selftune/monitoring/watch.ts +96 -29
- package/cli/selftune/normalization.ts +3 -0
- package/cli/selftune/observability.ts +12 -3
- package/cli/selftune/orchestrate/cli.ts +161 -0
- package/cli/selftune/orchestrate/execute.ts +295 -0
- package/cli/selftune/orchestrate/finalize.ts +157 -0
- package/cli/selftune/orchestrate/locks.ts +40 -0
- package/cli/selftune/orchestrate/plan.ts +131 -0
- package/cli/selftune/orchestrate/post-run.ts +59 -0
- package/cli/selftune/orchestrate/prepare.ts +334 -0
- package/cli/selftune/orchestrate/report.ts +182 -0
- package/cli/selftune/orchestrate/runtime.ts +120 -0
- package/cli/selftune/orchestrate/signals.ts +48 -0
- package/cli/selftune/orchestrate.ts +162 -1142
- package/cli/selftune/registry/client.ts +74 -0
- package/cli/selftune/registry/history.ts +54 -0
- package/cli/selftune/registry/index.ts +90 -0
- package/cli/selftune/registry/install.ts +141 -0
- package/cli/selftune/registry/list.ts +44 -0
- package/cli/selftune/registry/push.ts +171 -0
- package/cli/selftune/registry/rollback.ts +49 -0
- package/cli/selftune/registry/status.ts +62 -0
- package/cli/selftune/registry/sync.ts +125 -0
- package/cli/selftune/repair/skill-usage.ts +9 -3
- package/cli/selftune/routes/overview.ts +5 -2
- package/cli/selftune/routes/skill-report.ts +15 -2
- package/cli/selftune/schedule.ts +5 -5
- package/cli/selftune/status.ts +70 -2
- package/cli/selftune/sync.ts +127 -23
- package/cli/selftune/testing-readiness.ts +597 -0
- package/cli/selftune/types.ts +46 -5
- package/cli/selftune/uninstall.ts +2 -1
- package/cli/selftune/utils/canonical-log.ts +1 -9
- package/cli/selftune/utils/cli-error.ts +9 -0
- package/cli/selftune/utils/jsonl.ts +1 -30
- package/cli/selftune/utils/llm-call.ts +126 -6
- package/cli/selftune/utils/skill-discovery.ts +24 -0
- package/cli/selftune/workflows/proposals.ts +184 -0
- package/cli/selftune/workflows/skill-scaffold.ts +241 -0
- package/cli/selftune/workflows/workflows.ts +100 -26
- package/node_modules/@selftune/telemetry-contract/fixtures/complete-push.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +2 -2
- package/node_modules/@selftune/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +2 -2
- package/node_modules/@selftune/telemetry-contract/package.json +1 -1
- package/node_modules/@selftune/telemetry-contract/src/index.ts +1 -0
- package/node_modules/@selftune/telemetry-contract/src/schemas.ts +63 -5
- package/node_modules/@selftune/telemetry-contract/src/types.ts +97 -7
- package/node_modules/@selftune/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/package.json +25 -9
- package/packages/dashboard-core/AGENTS.md +18 -0
- package/packages/dashboard-core/README.md +30 -0
- package/packages/dashboard-core/index.ts +3 -0
- package/packages/dashboard-core/package.json +39 -0
- package/packages/dashboard-core/src/chrome/DashboardChrome.tsx +74 -0
- package/packages/dashboard-core/src/chrome/DashboardHeader.tsx +200 -0
- package/packages/dashboard-core/src/chrome/DashboardSidebar.tsx +219 -0
- package/packages/dashboard-core/src/chrome/RuntimeBadge.tsx +46 -0
- package/packages/dashboard-core/src/chrome/index.ts +14 -0
- package/packages/dashboard-core/src/chrome/types.ts +81 -0
- package/packages/dashboard-core/src/chrome/utils.ts +23 -0
- package/packages/dashboard-core/src/gates/FeatureGate.tsx +11 -0
- package/packages/dashboard-core/src/gates/LockedRoute.tsx +29 -0
- package/packages/dashboard-core/src/gates/UpgradeCard.tsx +89 -0
- package/packages/dashboard-core/src/gates/index.ts +3 -0
- package/packages/dashboard-core/src/host/DashboardHostProvider.tsx +62 -0
- package/packages/dashboard-core/src/host/adapter.ts +47 -0
- package/packages/dashboard-core/src/host/capabilities.ts +55 -0
- package/packages/dashboard-core/src/host/index.ts +3 -0
- package/packages/dashboard-core/src/models/analytics.ts +39 -0
- package/packages/dashboard-core/src/models/index.ts +4 -0
- package/packages/dashboard-core/src/models/overview.ts +98 -0
- package/packages/dashboard-core/src/models/runtime.ts +7 -0
- package/packages/dashboard-core/src/models/skills.ts +34 -0
- package/packages/dashboard-core/src/routes/index.ts +2 -0
- package/packages/dashboard-core/src/routes/manifest.test.ts +70 -0
- package/packages/dashboard-core/src/routes/manifest.ts +451 -0
- package/packages/dashboard-core/src/routes/types.ts +39 -0
- package/packages/dashboard-core/src/screens/analytics/AnalyticsScreen.tsx +278 -0
- package/packages/dashboard-core/src/screens/analytics/index.ts +1 -0
- package/packages/dashboard-core/src/screens/index.ts +37 -0
- package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.test.ts +101 -0
- package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.tsx +393 -0
- package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.test.tsx +113 -0
- package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.tsx +72 -0
- package/packages/dashboard-core/src/screens/overview/OverviewCoreSurface.tsx +71 -0
- package/packages/dashboard-core/src/screens/overview/OverviewOnboardingBanner.tsx +90 -0
- package/packages/dashboard-core/src/screens/overview/OverviewRunSummary.tsx +40 -0
- package/packages/dashboard-core/src/screens/overview/index.ts +16 -0
- package/packages/dashboard-core/src/screens/overview/types.ts +13 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportDailyBreakdownSection.tsx +99 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportDataQualityTabContent.tsx +35 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceRail.tsx +71 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceSection.tsx +63 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceTabContent.tsx +25 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportInvocationsSection.tsx +24 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportMissedQueriesSection.tsx +79 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportScaffold.tsx +150 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportSections.test.tsx +224 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.test.tsx +76 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.tsx +88 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTrendSection.tsx +33 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTrustBadge.tsx +67 -0
- package/packages/dashboard-core/src/screens/skill-report/index.ts +45 -0
- package/packages/dashboard-core/src/screens/skills/SkillsLibraryScreen.tsx +162 -0
- package/packages/dashboard-core/src/screens/skills/index.ts +6 -0
- package/packages/telemetry-contract/fixtures/complete-push.ts +1 -1
- package/packages/telemetry-contract/fixtures/evidence-only-push.ts +2 -2
- package/packages/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
- package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +2 -2
- package/packages/telemetry-contract/package.json +1 -1
- package/packages/telemetry-contract/src/index.ts +1 -0
- package/packages/telemetry-contract/src/schemas.ts +63 -5
- package/packages/telemetry-contract/src/types.ts +97 -7
- package/packages/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/packages/ui/AGENTS.md +16 -0
- package/packages/ui/README.md +1 -1
- package/packages/ui/package.json +1 -1
- package/packages/ui/src/components/ActivityTimeline.tsx +152 -168
- package/packages/ui/src/components/AnalyticsCharts.tsx +344 -0
- package/packages/ui/src/components/EvidenceViewer.tsx +229 -464
- package/packages/ui/src/components/EvolutionTimeline.tsx +34 -87
- package/packages/ui/src/components/InfoTip.tsx +1 -2
- package/packages/ui/src/components/InvocationsPanel.tsx +413 -0
- package/packages/ui/src/components/JobHistoryTimeline.tsx +156 -0
- package/packages/ui/src/components/OrchestrateRunsPanel.tsx +18 -36
- package/packages/ui/src/components/OverviewPanels.tsx +693 -0
- package/packages/ui/src/components/PipelineStatusBar.tsx +65 -0
- package/packages/ui/src/components/SkillReportGuide.tsx +215 -0
- package/packages/ui/src/components/SkillReportPanels.tsx +919 -0
- package/packages/ui/src/components/SkillsLibrary.tsx +437 -0
- package/packages/ui/src/components/index.ts +56 -1
- package/packages/ui/src/components/section-cards.tsx +18 -35
- package/packages/ui/src/components/skill-health-grid.tsx +47 -37
- package/packages/ui/src/lib/constants.tsx +0 -1
- package/packages/ui/src/primitives/card.tsx +1 -1
- package/packages/ui/src/primitives/checkbox.tsx +1 -1
- package/packages/ui/src/primitives/dropdown-menu.tsx +2 -2
- package/packages/ui/src/primitives/select.tsx +2 -2
- package/packages/ui/src/primitives/tabs.tsx +7 -6
- package/packages/ui/src/types.ts +182 -4
- package/skill/SKILL.md +130 -318
- package/skill/agents/diagnosis-analyst.md +3 -3
- package/skill/agents/evolution-reviewer.md +3 -3
- package/skill/agents/integration-guide.md +3 -3
- package/skill/agents/pattern-analyst.md +2 -2
- package/skill/references/cli-quick-reference.md +89 -0
- package/skill/references/creator-playbook.md +131 -0
- package/skill/references/examples.md +48 -0
- package/skill/references/troubleshooting.md +47 -0
- package/skill/references/version-history.md +1 -1
- package/skill/selftune.contribute.json +11 -0
- package/skill/{Workflows → workflows}/Baseline.md +20 -1
- package/skill/{Workflows → workflows}/Contribute.md +23 -10
- package/skill/{Workflows → workflows}/Contributions.md +13 -5
- package/skill/workflows/CreateTestDeploy.md +170 -0
- package/skill/{Workflows → workflows}/CreatorContributions.md +18 -6
- package/skill/{Workflows → workflows}/Cron.md +1 -1
- package/skill/{Workflows → workflows}/Dashboard.md +20 -0
- package/skill/{Workflows → workflows}/Doctor.md +1 -1
- package/skill/{Workflows → workflows}/Evals.md +67 -2
- package/skill/{Workflows → workflows}/Evolve.md +119 -30
- package/skill/{Workflows → workflows}/EvolveBody.md +41 -1
- package/skill/{Workflows → workflows}/Grade.md +1 -1
- package/skill/{Workflows → workflows}/Ingest.md +60 -2
- package/skill/{Workflows → workflows}/Initialize.md +16 -9
- package/skill/{Workflows → workflows}/Orchestrate.md +13 -3
- package/skill/{Workflows → workflows}/PlatformHooks.md +19 -3
- package/skill/workflows/Registry.md +99 -0
- package/skill/{Workflows → workflows}/Schedule.md +3 -3
- package/skill/workflows/SignalsDashboard.md +87 -0
- package/skill/{Workflows → workflows}/Sync.md +3 -1
- package/skill/{Workflows → workflows}/UnitTest.md +19 -0
- package/skill/{Workflows → workflows}/Watch.md +42 -2
- package/skill/{Workflows → workflows}/Workflows.md +39 -2
- package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +0 -60
- package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +0 -1
- package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +0 -11
- package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +0 -12
- package/cli/selftune/utils/html.ts +0 -27
- package/packages/ui/src/components/RecentActivityFeed.tsx +0 -117
- /package/skill/{Workflows → workflows}/AlphaUpload.md +0 -0
- /package/skill/{Workflows → workflows}/AutoActivation.md +0 -0
- /package/skill/{Workflows → workflows}/Badge.md +0 -0
- /package/skill/{Workflows → workflows}/Composability.md +0 -0
- /package/skill/{Workflows → workflows}/EvolutionMemory.md +0 -0
- /package/skill/{Workflows → workflows}/ExportCanonical.md +0 -0
- /package/skill/{Workflows → workflows}/Hook.md +0 -0
- /package/skill/{Workflows → workflows}/ImportSkillsBench.md +0 -0
- /package/skill/{Workflows → workflows}/Quickstart.md +0 -0
- /package/skill/{Workflows → workflows}/Recover.md +0 -0
- /package/skill/{Workflows → workflows}/RepairSkillUsage.md +0 -0
- /package/skill/{Workflows → workflows}/Replay.md +0 -0
- /package/skill/{Workflows → workflows}/Rollback.md +0 -0
- /package/skill/{Workflows → workflows}/Telemetry.md +0 -0
- /package/skill/{Workflows → workflows}/Uninstall.md +0 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Pi hook adapter for selftune.
|
|
4
|
+
*
|
|
5
|
+
* Reads Pi hook payloads from stdin and delegates to shared selftune hook logic.
|
|
6
|
+
* Pi extensions emit events for tool calls, tool results, and session lifecycle.
|
|
7
|
+
*
|
|
8
|
+
* Usage: echo '$HOOK_PAYLOAD' | selftune pi hook
|
|
9
|
+
*
|
|
10
|
+
* Event routing:
|
|
11
|
+
* tool_call -> skill-change-guard + evolution-guard (PreToolUse)
|
|
12
|
+
* tool_result -> skill-eval (processToolUse) + commit-track (processCommitTrack)
|
|
13
|
+
* message (user) -> prompt-log (processPrompt) + auto-activate
|
|
14
|
+
* session_shutdown -> session-stop (processSessionStop)
|
|
15
|
+
*
|
|
16
|
+
* Fail-open: any unhandled error -> exit 0, never crash the host agent.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
PostToolUsePayload,
|
|
21
|
+
PreToolUsePayload,
|
|
22
|
+
PromptSubmitPayload,
|
|
23
|
+
StopPayload,
|
|
24
|
+
} from "../../types.js";
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Types
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
/** Pi hook payload — superset of all event fields. */
|
|
31
|
+
export interface PiHookPayload {
|
|
32
|
+
event_type?: string;
|
|
33
|
+
session_id?: string;
|
|
34
|
+
cwd?: string;
|
|
35
|
+
tool_name?: string;
|
|
36
|
+
tool_input?: Record<string, unknown>;
|
|
37
|
+
tool_use_id?: string;
|
|
38
|
+
tool_output?: Record<string, unknown>;
|
|
39
|
+
prompt?: string;
|
|
40
|
+
user_prompt?: string;
|
|
41
|
+
model?: string;
|
|
42
|
+
provider?: string;
|
|
43
|
+
last_assistant_message?: string;
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Response written to stdout. Empty object = no-op. */
|
|
48
|
+
type HookResponse = Record<string, unknown>;
|
|
49
|
+
|
|
50
|
+
const EMPTY_RESPONSE: HookResponse = {};
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Event handlers (dynamic imports for fast startup)
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
async function handlePromptSubmit(payload: PiHookPayload): Promise<HookResponse> {
|
|
57
|
+
// 1. Prompt logging
|
|
58
|
+
try {
|
|
59
|
+
const { processPrompt } = await import("../../hooks/prompt-log.js");
|
|
60
|
+
const promptPayload: PromptSubmitPayload = {
|
|
61
|
+
session_id: payload.session_id,
|
|
62
|
+
cwd: payload.cwd,
|
|
63
|
+
prompt: payload.prompt ?? payload.user_prompt,
|
|
64
|
+
user_prompt: payload.user_prompt ?? payload.prompt,
|
|
65
|
+
hook_event_name: "UserPromptSubmit",
|
|
66
|
+
};
|
|
67
|
+
await processPrompt(promptPayload);
|
|
68
|
+
} catch {
|
|
69
|
+
// fail-open
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. Auto-activate suggestions
|
|
73
|
+
let response: HookResponse = EMPTY_RESPONSE;
|
|
74
|
+
try {
|
|
75
|
+
const { processAutoActivate } = await import("../../hooks/auto-activate.js");
|
|
76
|
+
const sessionId = payload.session_id ?? "unknown";
|
|
77
|
+
const suggestions = await processAutoActivate(sessionId);
|
|
78
|
+
if (suggestions.length > 0) {
|
|
79
|
+
const context = suggestions.map((s) => `[selftune] Suggestion: ${s}`).join("\n");
|
|
80
|
+
response = { additionalContext: context };
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// fail-open
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return response;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function handlePreToolUse(
|
|
90
|
+
payload: PiHookPayload,
|
|
91
|
+
): Promise<{ response: HookResponse; exitCode: number }> {
|
|
92
|
+
const prePayload: PreToolUsePayload = {
|
|
93
|
+
tool_name: payload.tool_name ?? "",
|
|
94
|
+
tool_input: payload.tool_input ?? {},
|
|
95
|
+
tool_use_id: payload.tool_use_id,
|
|
96
|
+
session_id: payload.session_id,
|
|
97
|
+
cwd: payload.cwd,
|
|
98
|
+
hook_event_name: "PreToolUse",
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
let constants:
|
|
102
|
+
| { EVOLUTION_AUDIT_LOG: string; SELFTUNE_CONFIG_DIR: string; SESSION_STATE_DIR: string }
|
|
103
|
+
| undefined;
|
|
104
|
+
try {
|
|
105
|
+
constants = await import("../../constants.js");
|
|
106
|
+
} catch {
|
|
107
|
+
// fail-open
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 1. Evolution guard (can block with exit 2)
|
|
111
|
+
try {
|
|
112
|
+
if (constants) {
|
|
113
|
+
const { processEvolutionGuard } = await import("../../hooks/evolution-guard.js");
|
|
114
|
+
const guardResult = await processEvolutionGuard(prePayload, {
|
|
115
|
+
auditLogPath: constants.EVOLUTION_AUDIT_LOG,
|
|
116
|
+
selftuneDir: constants.SELFTUNE_CONFIG_DIR,
|
|
117
|
+
});
|
|
118
|
+
if (guardResult) {
|
|
119
|
+
process.stderr.write(`${guardResult.message}\n`);
|
|
120
|
+
return { response: EMPTY_RESPONSE, exitCode: guardResult.exitCode };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
// fail-open
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 2. Skill change guard (advisory only, never blocks)
|
|
128
|
+
try {
|
|
129
|
+
if (constants) {
|
|
130
|
+
const { processPreToolUse } = await import("../../hooks/skill-change-guard.js");
|
|
131
|
+
const sessionId = payload.session_id ?? "unknown";
|
|
132
|
+
const safe = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
133
|
+
const statePath = `${constants.SESSION_STATE_DIR}/guard-state-${safe}.json`;
|
|
134
|
+
const suggestion = processPreToolUse(prePayload, statePath);
|
|
135
|
+
if (suggestion) {
|
|
136
|
+
process.stderr.write(`[selftune] Suggestion: ${suggestion}\n`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// fail-open
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return { response: EMPTY_RESPONSE, exitCode: 0 };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function handlePostToolUse(payload: PiHookPayload): Promise<HookResponse> {
|
|
147
|
+
const postPayload: PostToolUsePayload = {
|
|
148
|
+
tool_name: payload.tool_name ?? "",
|
|
149
|
+
tool_input: payload.tool_input ?? {},
|
|
150
|
+
tool_use_id: payload.tool_use_id,
|
|
151
|
+
tool_response: payload.tool_output,
|
|
152
|
+
session_id: payload.session_id,
|
|
153
|
+
cwd: payload.cwd,
|
|
154
|
+
hook_event_name: "PostToolUse",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// 1. Skill eval (Read/Skill tool usage tracking)
|
|
158
|
+
try {
|
|
159
|
+
const { processToolUse } = await import("../../hooks/skill-eval.js");
|
|
160
|
+
await processToolUse(postPayload);
|
|
161
|
+
} catch {
|
|
162
|
+
// fail-open
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 2. Commit tracking (git commit detection in Bash output)
|
|
166
|
+
try {
|
|
167
|
+
const { processCommitTrack } = await import("../../hooks/commit-track.js");
|
|
168
|
+
await processCommitTrack(postPayload);
|
|
169
|
+
} catch {
|
|
170
|
+
// fail-open
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return EMPTY_RESPONSE;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function handleSessionEnd(payload: PiHookPayload): Promise<HookResponse> {
|
|
177
|
+
try {
|
|
178
|
+
const { processSessionStop } = await import("../../hooks/session-stop.js");
|
|
179
|
+
const stopPayload: StopPayload = {
|
|
180
|
+
session_id: payload.session_id,
|
|
181
|
+
cwd: payload.cwd,
|
|
182
|
+
last_assistant_message:
|
|
183
|
+
typeof payload.last_assistant_message === "string"
|
|
184
|
+
? payload.last_assistant_message
|
|
185
|
+
: undefined,
|
|
186
|
+
hook_event_name: "Stop",
|
|
187
|
+
};
|
|
188
|
+
await processSessionStop(stopPayload);
|
|
189
|
+
} catch {
|
|
190
|
+
// fail-open
|
|
191
|
+
}
|
|
192
|
+
return EMPTY_RESPONSE;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Main entry point
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
function writeResponseAndExit(response: HookResponse, code: number): void {
|
|
200
|
+
const data = JSON.stringify(response);
|
|
201
|
+
process.stdout.write(data, () => {
|
|
202
|
+
process.exit(code);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* CLI entry point. Reads stdin, routes to the correct handler, writes response.
|
|
208
|
+
*/
|
|
209
|
+
export async function cliMain(): Promise<void> {
|
|
210
|
+
let exitCode = 0;
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const raw = await Bun.stdin.text();
|
|
214
|
+
|
|
215
|
+
// Fast-path: empty stdin -> no-op
|
|
216
|
+
if (!raw.trim()) {
|
|
217
|
+
writeResponseAndExit(EMPTY_RESPONSE, 0);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let payload: PiHookPayload;
|
|
222
|
+
try {
|
|
223
|
+
payload = JSON.parse(raw) as PiHookPayload;
|
|
224
|
+
} catch {
|
|
225
|
+
writeResponseAndExit(EMPTY_RESPONSE, 0);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const eventType = typeof payload.event_type === "string" ? payload.event_type : "";
|
|
230
|
+
|
|
231
|
+
if (!eventType) {
|
|
232
|
+
writeResponseAndExit(EMPTY_RESPONSE, 0);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let response: HookResponse = EMPTY_RESPONSE;
|
|
237
|
+
|
|
238
|
+
switch (eventType) {
|
|
239
|
+
case "message": {
|
|
240
|
+
response = await handlePromptSubmit(payload);
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case "tool_call": {
|
|
244
|
+
const result = await handlePreToolUse(payload);
|
|
245
|
+
response = result.response;
|
|
246
|
+
exitCode = result.exitCode;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case "tool_result": {
|
|
250
|
+
response = await handlePostToolUse(payload);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case "session_shutdown": {
|
|
254
|
+
response = await handleSessionEnd(payload);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
default: {
|
|
258
|
+
// Unknown event — no-op
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
writeResponseAndExit(response, exitCode);
|
|
264
|
+
} catch {
|
|
265
|
+
// Fail-open: never crash
|
|
266
|
+
writeResponseAndExit(EMPTY_RESPONSE, 0);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// --- stdin main (only when executed directly, not when imported) ---
|
|
271
|
+
if (import.meta.main) {
|
|
272
|
+
await cliMain();
|
|
273
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Install selftune hooks into Pi coding agent environment.
|
|
4
|
+
*
|
|
5
|
+
* Pi supports extensions that hook into its lifecycle. This installer
|
|
6
|
+
* creates a selftune extension that pipes events to `selftune pi hook`.
|
|
7
|
+
*
|
|
8
|
+
* Extension location: ~/.pi/extensions/selftune/
|
|
9
|
+
*
|
|
10
|
+
* Events hooked:
|
|
11
|
+
* - tool_call (pre-tool — skill guards, inline)
|
|
12
|
+
* - tool_result (post-tool — skill eval + commit tracking, inline)
|
|
13
|
+
* - message (prompt submit — prompt logging + auto-activate, inline)
|
|
14
|
+
* - session_shutdown (session end — session telemetry, background)
|
|
15
|
+
*
|
|
16
|
+
* Usage: selftune pi install [--dry-run] [--uninstall]
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
|
|
23
|
+
const PI_DIR = process.env.SELFTUNE_PI_DIR ?? join(homedir(), ".pi");
|
|
24
|
+
const PI_EXTENSIONS_DIR = join(PI_DIR, "extensions", "selftune");
|
|
25
|
+
const MARKER = "# selftune-managed";
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Hook script generators
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
/** Build a hook command that prefers SELFTUNE_CLI_PATH, then npx. */
|
|
32
|
+
const HOOK_CMD =
|
|
33
|
+
'if [ -n "$SELFTUNE_CLI_PATH" ]; then "$SELFTUNE_CLI_PATH" pi hook; else npx selftune pi hook; fi';
|
|
34
|
+
|
|
35
|
+
function hookScript(eventType: string, inline: boolean): string {
|
|
36
|
+
if (inline) {
|
|
37
|
+
// Inline — fast path; finish before Pi moves on.
|
|
38
|
+
// Capture output and exit code separately to avoid double JSON and preserve guard blocks (exit 2).
|
|
39
|
+
return `#!/usr/bin/env bash
|
|
40
|
+
${MARKER}
|
|
41
|
+
input=$(cat)
|
|
42
|
+
result=$(echo "$input" | (${HOOK_CMD}) 2>/dev/null)
|
|
43
|
+
rc=$?
|
|
44
|
+
[ -z "$result" ] && result='{}'
|
|
45
|
+
echo "$result"
|
|
46
|
+
exit $rc
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Background — don't block Pi
|
|
51
|
+
return `#!/usr/bin/env bash
|
|
52
|
+
${MARKER}
|
|
53
|
+
input=$(cat)
|
|
54
|
+
echo "$input" | (${HOOK_CMD}) &>/dev/null &
|
|
55
|
+
echo '{}'
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Hook definitions
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
const HOOKS: Array<{ name: string; description: string; inline: boolean }> = [
|
|
64
|
+
{ name: "tool_call", description: "Pre-tool guards (evolution, skill change)", inline: true },
|
|
65
|
+
{ name: "tool_result", description: "Post-tool eval + commit tracking", inline: true },
|
|
66
|
+
{ name: "message", description: "Prompt logging + auto-activate", inline: true },
|
|
67
|
+
{ name: "session_shutdown", description: "Session telemetry recording", inline: false },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Install
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
function installHooks(dryRun: boolean): void {
|
|
75
|
+
console.log("Setting up selftune hooks for Pi...");
|
|
76
|
+
console.log(`Extensions directory: ${PI_EXTENSIONS_DIR}`);
|
|
77
|
+
console.log("");
|
|
78
|
+
|
|
79
|
+
if (!dryRun) {
|
|
80
|
+
mkdirSync(PI_EXTENSIONS_DIR, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let installed = 0;
|
|
84
|
+
let skipped = 0;
|
|
85
|
+
|
|
86
|
+
for (const hook of HOOKS) {
|
|
87
|
+
const hookPath = join(PI_EXTENSIONS_DIR, hook.name);
|
|
88
|
+
|
|
89
|
+
if (existsSync(hookPath)) {
|
|
90
|
+
const existing = readFileSync(hookPath, "utf-8");
|
|
91
|
+
if (existing.includes(MARKER)) {
|
|
92
|
+
if (dryRun) {
|
|
93
|
+
console.log(` Would update: ${hook.name}`);
|
|
94
|
+
} else {
|
|
95
|
+
writeFileSync(hookPath, hookScript(hook.name, hook.inline), { mode: 0o755 });
|
|
96
|
+
chmodSync(hookPath, 0o755);
|
|
97
|
+
console.log(` Updated: ${hook.name}`);
|
|
98
|
+
}
|
|
99
|
+
installed++;
|
|
100
|
+
} else {
|
|
101
|
+
console.log(` Skipped: ${hook.name} (existing hook not managed by selftune)`);
|
|
102
|
+
skipped++;
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
if (dryRun) {
|
|
106
|
+
console.log(` Would create: ${hook.name}`);
|
|
107
|
+
} else {
|
|
108
|
+
writeFileSync(hookPath, hookScript(hook.name, hook.inline), { mode: 0o755 });
|
|
109
|
+
console.log(` Created: ${hook.name}`);
|
|
110
|
+
}
|
|
111
|
+
installed++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log("");
|
|
116
|
+
if (dryRun) {
|
|
117
|
+
console.log(`Dry run: ${installed} hook(s) would be installed.`);
|
|
118
|
+
} else if (installed > 0) {
|
|
119
|
+
console.log(`Installed ${installed} hook(s).`);
|
|
120
|
+
}
|
|
121
|
+
if (skipped > 0) {
|
|
122
|
+
console.log(`Skipped ${skipped} hook(s) with existing non-selftune content.`);
|
|
123
|
+
}
|
|
124
|
+
if (!dryRun && installed > 0) {
|
|
125
|
+
console.log("");
|
|
126
|
+
if (skipped === 0) {
|
|
127
|
+
console.log("Pi will now track commits and record session telemetry.");
|
|
128
|
+
} else {
|
|
129
|
+
console.log("Partial install: some hooks were skipped. Telemetry may be incomplete.");
|
|
130
|
+
}
|
|
131
|
+
console.log("Run `selftune status` to verify setup.");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Uninstall
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
function uninstallHooks(dryRun: boolean): void {
|
|
140
|
+
console.log("Removing selftune hooks from Pi...");
|
|
141
|
+
console.log("");
|
|
142
|
+
|
|
143
|
+
let removed = 0;
|
|
144
|
+
let skipped = 0;
|
|
145
|
+
|
|
146
|
+
for (const hook of HOOKS) {
|
|
147
|
+
const hookPath = join(PI_EXTENSIONS_DIR, hook.name);
|
|
148
|
+
|
|
149
|
+
if (!existsSync(hookPath)) {
|
|
150
|
+
console.log(` Not found: ${hook.name}`);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const existing = readFileSync(hookPath, "utf-8");
|
|
155
|
+
if (!existing.includes(MARKER)) {
|
|
156
|
+
console.log(` Skipped: ${hook.name} (not managed by selftune)`);
|
|
157
|
+
skipped++;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (dryRun) {
|
|
162
|
+
console.log(` Would remove: ${hook.name}`);
|
|
163
|
+
} else {
|
|
164
|
+
rmSync(hookPath);
|
|
165
|
+
console.log(` Removed: ${hook.name}`);
|
|
166
|
+
}
|
|
167
|
+
removed++;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log("");
|
|
171
|
+
if (dryRun) {
|
|
172
|
+
console.log(`Dry run: ${removed} hook(s) would be removed.`);
|
|
173
|
+
} else if (removed > 0) {
|
|
174
|
+
console.log(`Removed ${removed} hook(s).`);
|
|
175
|
+
}
|
|
176
|
+
if (skipped > 0) {
|
|
177
|
+
console.log(`Skipped ${skipped} hook(s) not managed by selftune.`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// Main entry point
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
export async function cliMain(): Promise<void> {
|
|
186
|
+
const args = process.argv.slice(2);
|
|
187
|
+
const dryRun = args.includes("--dry-run");
|
|
188
|
+
const uninstall = args.includes("--uninstall");
|
|
189
|
+
|
|
190
|
+
if (uninstall) {
|
|
191
|
+
uninstallHooks(dryRun);
|
|
192
|
+
} else {
|
|
193
|
+
installHooks(dryRun);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// --- stdin main (only when executed directly, not when imported) ---
|
|
198
|
+
if (import.meta.main) {
|
|
199
|
+
try {
|
|
200
|
+
await cliMain();
|
|
201
|
+
} catch (err) {
|
|
202
|
+
console.error(
|
|
203
|
+
`[selftune] Pi install failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
204
|
+
);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type { Database } from "bun:sqlite";
|
|
13
13
|
|
|
14
|
-
import type { CanonicalRecord } from "@selftune/telemetry-contract";
|
|
14
|
+
import type { CanonicalRecord, PushPayloadV2 } from "@selftune/telemetry-contract/types";
|
|
15
15
|
|
|
16
16
|
import { buildPushPayloadV2 } from "../canonical-export.js";
|
|
17
17
|
import type { EvolutionEvidenceEntry } from "../types.js";
|
|
@@ -19,7 +19,7 @@ import type { EvolutionEvidenceEntry } from "../types.js";
|
|
|
19
19
|
// -- Types --------------------------------------------------------------------
|
|
20
20
|
|
|
21
21
|
export interface BuildV2Result {
|
|
22
|
-
payload: Record<string,
|
|
22
|
+
payload: PushPayloadV2 & { content_hashes?: Record<string, string> };
|
|
23
23
|
lastSeq: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -152,7 +152,7 @@ export function buildV2PushPayload(
|
|
|
152
152
|
return null;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
const payload = buildPushPayloadV2(
|
|
155
|
+
const payload: BuildV2Result["payload"] = buildPushPayloadV2(
|
|
156
156
|
canonicalRecords,
|
|
157
157
|
evidenceEntries,
|
|
158
158
|
orchestrateRuns,
|
|
@@ -68,6 +68,12 @@ export function generateSignalId(record: Record<string, unknown>): string {
|
|
|
68
68
|
return `sig_${createHash("sha256").update(key).digest("hex").slice(0, 16)}`;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function addOptional(record: Record<string, unknown>, key: string, value: unknown): void {
|
|
72
|
+
if (value !== undefined && value !== null) {
|
|
73
|
+
record[key] = value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
71
77
|
/**
|
|
72
78
|
* Enrich a raw parsed record: if it is an execution_fact missing
|
|
73
79
|
* execution_fact_id, inject a deterministic one.
|
|
@@ -128,7 +134,7 @@ function extractRecordId(record: CanonicalRecord): string {
|
|
|
128
134
|
* Extract session_id from a canonical record (if the record has one).
|
|
129
135
|
*/
|
|
130
136
|
function extractSessionId(record: CanonicalRecord): string | null {
|
|
131
|
-
if ("session_id" in record) return record.session_id;
|
|
137
|
+
if ("session_id" in record && typeof record.session_id === "string") return record.session_id;
|
|
132
138
|
return null;
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -136,7 +142,7 @@ function extractSessionId(record: CanonicalRecord): string | null {
|
|
|
136
142
|
* Extract prompt_id from a canonical record (if the record has one).
|
|
137
143
|
*/
|
|
138
144
|
function extractPromptId(record: CanonicalRecord): string | null {
|
|
139
|
-
if ("prompt_id" in record) return record.prompt_id;
|
|
145
|
+
if ("prompt_id" in record && typeof record.prompt_id === "string") return record.prompt_id;
|
|
140
146
|
return null;
|
|
141
147
|
}
|
|
142
148
|
|
|
@@ -213,19 +219,19 @@ export function stageCanonicalRecords(db: Database, logPath: string = CANONICAL_
|
|
|
213
219
|
for (const entry of evidence) {
|
|
214
220
|
const evidenceRecord: Record<string, unknown> = {
|
|
215
221
|
skill_name: entry.skill_name,
|
|
216
|
-
skill_path: entry.skill_path,
|
|
217
|
-
proposal_id: entry.proposal_id,
|
|
218
222
|
target: entry.target,
|
|
219
223
|
stage: entry.stage,
|
|
220
|
-
rationale: entry.rationale,
|
|
221
|
-
confidence: entry.confidence,
|
|
222
|
-
details: entry.details,
|
|
223
|
-
original_text: entry.original_text,
|
|
224
|
-
proposed_text: entry.proposed_text,
|
|
225
|
-
eval_set_json: entry.eval_set,
|
|
226
|
-
validation_json: entry.validation,
|
|
227
224
|
timestamp: entry.timestamp,
|
|
228
225
|
};
|
|
226
|
+
addOptional(evidenceRecord, "skill_path", entry.skill_path);
|
|
227
|
+
addOptional(evidenceRecord, "proposal_id", entry.proposal_id);
|
|
228
|
+
addOptional(evidenceRecord, "rationale", entry.rationale);
|
|
229
|
+
addOptional(evidenceRecord, "confidence", entry.confidence);
|
|
230
|
+
addOptional(evidenceRecord, "details", entry.details);
|
|
231
|
+
addOptional(evidenceRecord, "original_text", entry.original_text);
|
|
232
|
+
addOptional(evidenceRecord, "proposed_text", entry.proposed_text);
|
|
233
|
+
addOptional(evidenceRecord, "eval_set_json", entry.eval_set);
|
|
234
|
+
addOptional(evidenceRecord, "validation_json", entry.validation);
|
|
229
235
|
// Generate deterministic evidence_id if not already present
|
|
230
236
|
const evidenceId = generateEvidenceId(evidenceRecord);
|
|
231
237
|
evidenceRecord.evidence_id = evidenceId;
|