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.
Files changed (270) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +95 -15
  3. package/apps/local-dashboard/dist/assets/index-DgY2KGP-.css +1 -0
  4. package/apps/local-dashboard/dist/assets/index-Dmx7LPVX.js +15 -0
  5. package/apps/local-dashboard/dist/assets/vendor-react-C5oyHiV1.js +11 -0
  6. package/apps/local-dashboard/dist/assets/{vendor-table-BIiI3YhS.js → vendor-table-Bc_bbKd8.js} +1 -1
  7. package/apps/local-dashboard/dist/assets/vendor-ui-B3BPIYy7.js +1 -0
  8. package/apps/local-dashboard/dist/index.html +5 -5
  9. package/cli/selftune/adapters/codex/install.ts +310 -78
  10. package/cli/selftune/adapters/opencode/install.ts +3 -4
  11. package/cli/selftune/adapters/pi/hook.ts +273 -0
  12. package/cli/selftune/adapters/pi/install.ts +207 -0
  13. package/cli/selftune/alpha-upload/build-payloads.ts +3 -3
  14. package/cli/selftune/alpha-upload/stage-canonical.ts +17 -11
  15. package/cli/selftune/auto-update.ts +200 -8
  16. package/cli/selftune/canonical-export.ts +55 -25
  17. package/cli/selftune/command-surface.ts +397 -0
  18. package/cli/selftune/constants.ts +10 -1
  19. package/cli/selftune/contribute/contribute.ts +64 -13
  20. package/cli/selftune/contribution-config.ts +57 -3
  21. package/cli/selftune/contribution-preferences.ts +117 -0
  22. package/cli/selftune/contribution-signals.ts +8 -4
  23. package/cli/selftune/contribution-staging.ts +13 -2
  24. package/cli/selftune/contributions.ts +55 -121
  25. package/cli/selftune/creator-contributions.ts +29 -10
  26. package/cli/selftune/cron/setup.ts +7 -3
  27. package/cli/selftune/dashboard-contract.ts +87 -0
  28. package/cli/selftune/dashboard-server.ts +168 -17
  29. package/cli/selftune/dashboard.ts +350 -17
  30. package/cli/selftune/eval/baseline.ts +21 -5
  31. package/cli/selftune/eval/execution-eval.ts +170 -0
  32. package/cli/selftune/eval/family-overlap.ts +2 -2
  33. package/cli/selftune/eval/hooks-to-evals.ts +228 -82
  34. package/cli/selftune/eval/import-skillsbench.ts +2 -2
  35. package/cli/selftune/eval/invocation-classifier.ts +56 -0
  36. package/cli/selftune/eval/synthetic-evals.ts +5 -3
  37. package/cli/selftune/eval/unit-test-cli.ts +7 -4
  38. package/cli/selftune/evolution/apply-proposal.ts +295 -0
  39. package/cli/selftune/evolution/engines/judge-engine.ts +96 -0
  40. package/cli/selftune/evolution/engines/replay-engine.ts +180 -0
  41. package/cli/selftune/evolution/evidence.ts +2 -6
  42. package/cli/selftune/evolution/evolve-body.ts +152 -38
  43. package/cli/selftune/evolution/evolve.ts +244 -52
  44. package/cli/selftune/evolution/rollback.ts +0 -1
  45. package/cli/selftune/evolution/validate-body.ts +111 -49
  46. package/cli/selftune/evolution/validate-host-replay.ts +510 -60
  47. package/cli/selftune/evolution/validate-proposal.ts +11 -150
  48. package/cli/selftune/evolution/validate-routing.ts +51 -108
  49. package/cli/selftune/evolution/validation-contract.ts +91 -0
  50. package/cli/selftune/grading/auto-grade.ts +11 -7
  51. package/cli/selftune/grading/grade-session.ts +10 -16
  52. package/cli/selftune/hooks/skill-eval.ts +2 -1
  53. package/cli/selftune/hooks-shared/types.ts +1 -0
  54. package/cli/selftune/index.ts +58 -15
  55. package/cli/selftune/ingestors/claude-replay.ts +15 -10
  56. package/cli/selftune/ingestors/codex-wrapper.ts +3 -3
  57. package/cli/selftune/ingestors/opencode-ingest.ts +2 -2
  58. package/cli/selftune/ingestors/pi-ingest.ts +727 -0
  59. package/cli/selftune/init.ts +38 -4
  60. package/cli/selftune/localdb/direct-write.ts +120 -1
  61. package/cli/selftune/localdb/materialize.ts +6 -7
  62. package/cli/selftune/localdb/queries/cron.ts +34 -0
  63. package/cli/selftune/localdb/queries/dashboard.ts +834 -0
  64. package/cli/selftune/localdb/queries/evolution.ts +158 -0
  65. package/cli/selftune/localdb/queries/execution.ts +133 -0
  66. package/cli/selftune/localdb/queries/json.ts +18 -0
  67. package/cli/selftune/localdb/queries/monitoring.ts +263 -0
  68. package/cli/selftune/localdb/queries/raw.ts +95 -0
  69. package/cli/selftune/localdb/queries/staging.ts +270 -0
  70. package/cli/selftune/localdb/queries/trust.ts +392 -0
  71. package/cli/selftune/localdb/queries.ts +60 -2162
  72. package/cli/selftune/localdb/schema.ts +59 -0
  73. package/cli/selftune/monitoring/watch.ts +96 -29
  74. package/cli/selftune/normalization.ts +3 -0
  75. package/cli/selftune/observability.ts +12 -3
  76. package/cli/selftune/orchestrate/cli.ts +161 -0
  77. package/cli/selftune/orchestrate/execute.ts +295 -0
  78. package/cli/selftune/orchestrate/finalize.ts +157 -0
  79. package/cli/selftune/orchestrate/locks.ts +40 -0
  80. package/cli/selftune/orchestrate/plan.ts +131 -0
  81. package/cli/selftune/orchestrate/post-run.ts +59 -0
  82. package/cli/selftune/orchestrate/prepare.ts +334 -0
  83. package/cli/selftune/orchestrate/report.ts +182 -0
  84. package/cli/selftune/orchestrate/runtime.ts +120 -0
  85. package/cli/selftune/orchestrate/signals.ts +48 -0
  86. package/cli/selftune/orchestrate.ts +162 -1142
  87. package/cli/selftune/registry/client.ts +74 -0
  88. package/cli/selftune/registry/history.ts +54 -0
  89. package/cli/selftune/registry/index.ts +90 -0
  90. package/cli/selftune/registry/install.ts +141 -0
  91. package/cli/selftune/registry/list.ts +44 -0
  92. package/cli/selftune/registry/push.ts +171 -0
  93. package/cli/selftune/registry/rollback.ts +49 -0
  94. package/cli/selftune/registry/status.ts +62 -0
  95. package/cli/selftune/registry/sync.ts +125 -0
  96. package/cli/selftune/repair/skill-usage.ts +9 -3
  97. package/cli/selftune/routes/overview.ts +5 -2
  98. package/cli/selftune/routes/skill-report.ts +15 -2
  99. package/cli/selftune/schedule.ts +5 -5
  100. package/cli/selftune/status.ts +70 -2
  101. package/cli/selftune/sync.ts +127 -23
  102. package/cli/selftune/testing-readiness.ts +597 -0
  103. package/cli/selftune/types.ts +46 -5
  104. package/cli/selftune/uninstall.ts +2 -1
  105. package/cli/selftune/utils/canonical-log.ts +1 -9
  106. package/cli/selftune/utils/cli-error.ts +9 -0
  107. package/cli/selftune/utils/jsonl.ts +1 -30
  108. package/cli/selftune/utils/llm-call.ts +126 -6
  109. package/cli/selftune/utils/skill-discovery.ts +24 -0
  110. package/cli/selftune/workflows/proposals.ts +184 -0
  111. package/cli/selftune/workflows/skill-scaffold.ts +241 -0
  112. package/cli/selftune/workflows/workflows.ts +100 -26
  113. package/node_modules/@selftune/telemetry-contract/fixtures/complete-push.ts +1 -1
  114. package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +2 -2
  115. package/node_modules/@selftune/telemetry-contract/fixtures/golden.test.ts +0 -1
  116. package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
  117. package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +2 -2
  118. package/node_modules/@selftune/telemetry-contract/package.json +1 -1
  119. package/node_modules/@selftune/telemetry-contract/src/index.ts +1 -0
  120. package/node_modules/@selftune/telemetry-contract/src/schemas.ts +63 -5
  121. package/node_modules/@selftune/telemetry-contract/src/types.ts +97 -7
  122. package/node_modules/@selftune/telemetry-contract/tests/compatibility.test.ts +0 -1
  123. package/package.json +25 -9
  124. package/packages/dashboard-core/AGENTS.md +18 -0
  125. package/packages/dashboard-core/README.md +30 -0
  126. package/packages/dashboard-core/index.ts +3 -0
  127. package/packages/dashboard-core/package.json +39 -0
  128. package/packages/dashboard-core/src/chrome/DashboardChrome.tsx +74 -0
  129. package/packages/dashboard-core/src/chrome/DashboardHeader.tsx +200 -0
  130. package/packages/dashboard-core/src/chrome/DashboardSidebar.tsx +219 -0
  131. package/packages/dashboard-core/src/chrome/RuntimeBadge.tsx +46 -0
  132. package/packages/dashboard-core/src/chrome/index.ts +14 -0
  133. package/packages/dashboard-core/src/chrome/types.ts +81 -0
  134. package/packages/dashboard-core/src/chrome/utils.ts +23 -0
  135. package/packages/dashboard-core/src/gates/FeatureGate.tsx +11 -0
  136. package/packages/dashboard-core/src/gates/LockedRoute.tsx +29 -0
  137. package/packages/dashboard-core/src/gates/UpgradeCard.tsx +89 -0
  138. package/packages/dashboard-core/src/gates/index.ts +3 -0
  139. package/packages/dashboard-core/src/host/DashboardHostProvider.tsx +62 -0
  140. package/packages/dashboard-core/src/host/adapter.ts +47 -0
  141. package/packages/dashboard-core/src/host/capabilities.ts +55 -0
  142. package/packages/dashboard-core/src/host/index.ts +3 -0
  143. package/packages/dashboard-core/src/models/analytics.ts +39 -0
  144. package/packages/dashboard-core/src/models/index.ts +4 -0
  145. package/packages/dashboard-core/src/models/overview.ts +98 -0
  146. package/packages/dashboard-core/src/models/runtime.ts +7 -0
  147. package/packages/dashboard-core/src/models/skills.ts +34 -0
  148. package/packages/dashboard-core/src/routes/index.ts +2 -0
  149. package/packages/dashboard-core/src/routes/manifest.test.ts +70 -0
  150. package/packages/dashboard-core/src/routes/manifest.ts +451 -0
  151. package/packages/dashboard-core/src/routes/types.ts +39 -0
  152. package/packages/dashboard-core/src/screens/analytics/AnalyticsScreen.tsx +278 -0
  153. package/packages/dashboard-core/src/screens/analytics/index.ts +1 -0
  154. package/packages/dashboard-core/src/screens/index.ts +37 -0
  155. package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.test.ts +101 -0
  156. package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.tsx +393 -0
  157. package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.test.tsx +113 -0
  158. package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.tsx +72 -0
  159. package/packages/dashboard-core/src/screens/overview/OverviewCoreSurface.tsx +71 -0
  160. package/packages/dashboard-core/src/screens/overview/OverviewOnboardingBanner.tsx +90 -0
  161. package/packages/dashboard-core/src/screens/overview/OverviewRunSummary.tsx +40 -0
  162. package/packages/dashboard-core/src/screens/overview/index.ts +16 -0
  163. package/packages/dashboard-core/src/screens/overview/types.ts +13 -0
  164. package/packages/dashboard-core/src/screens/skill-report/SkillReportDailyBreakdownSection.tsx +99 -0
  165. package/packages/dashboard-core/src/screens/skill-report/SkillReportDataQualityTabContent.tsx +35 -0
  166. package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceRail.tsx +71 -0
  167. package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceSection.tsx +63 -0
  168. package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceTabContent.tsx +25 -0
  169. package/packages/dashboard-core/src/screens/skill-report/SkillReportInvocationsSection.tsx +24 -0
  170. package/packages/dashboard-core/src/screens/skill-report/SkillReportMissedQueriesSection.tsx +79 -0
  171. package/packages/dashboard-core/src/screens/skill-report/SkillReportScaffold.tsx +150 -0
  172. package/packages/dashboard-core/src/screens/skill-report/SkillReportSections.test.tsx +224 -0
  173. package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.test.tsx +76 -0
  174. package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.tsx +88 -0
  175. package/packages/dashboard-core/src/screens/skill-report/SkillReportTrendSection.tsx +33 -0
  176. package/packages/dashboard-core/src/screens/skill-report/SkillReportTrustBadge.tsx +67 -0
  177. package/packages/dashboard-core/src/screens/skill-report/index.ts +45 -0
  178. package/packages/dashboard-core/src/screens/skills/SkillsLibraryScreen.tsx +162 -0
  179. package/packages/dashboard-core/src/screens/skills/index.ts +6 -0
  180. package/packages/telemetry-contract/fixtures/complete-push.ts +1 -1
  181. package/packages/telemetry-contract/fixtures/evidence-only-push.ts +2 -2
  182. package/packages/telemetry-contract/fixtures/golden.test.ts +0 -1
  183. package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
  184. package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +2 -2
  185. package/packages/telemetry-contract/package.json +1 -1
  186. package/packages/telemetry-contract/src/index.ts +1 -0
  187. package/packages/telemetry-contract/src/schemas.ts +63 -5
  188. package/packages/telemetry-contract/src/types.ts +97 -7
  189. package/packages/telemetry-contract/tests/compatibility.test.ts +0 -1
  190. package/packages/ui/AGENTS.md +16 -0
  191. package/packages/ui/README.md +1 -1
  192. package/packages/ui/package.json +1 -1
  193. package/packages/ui/src/components/ActivityTimeline.tsx +152 -168
  194. package/packages/ui/src/components/AnalyticsCharts.tsx +344 -0
  195. package/packages/ui/src/components/EvidenceViewer.tsx +229 -464
  196. package/packages/ui/src/components/EvolutionTimeline.tsx +34 -87
  197. package/packages/ui/src/components/InfoTip.tsx +1 -2
  198. package/packages/ui/src/components/InvocationsPanel.tsx +413 -0
  199. package/packages/ui/src/components/JobHistoryTimeline.tsx +156 -0
  200. package/packages/ui/src/components/OrchestrateRunsPanel.tsx +18 -36
  201. package/packages/ui/src/components/OverviewPanels.tsx +693 -0
  202. package/packages/ui/src/components/PipelineStatusBar.tsx +65 -0
  203. package/packages/ui/src/components/SkillReportGuide.tsx +215 -0
  204. package/packages/ui/src/components/SkillReportPanels.tsx +919 -0
  205. package/packages/ui/src/components/SkillsLibrary.tsx +437 -0
  206. package/packages/ui/src/components/index.ts +56 -1
  207. package/packages/ui/src/components/section-cards.tsx +18 -35
  208. package/packages/ui/src/components/skill-health-grid.tsx +47 -37
  209. package/packages/ui/src/lib/constants.tsx +0 -1
  210. package/packages/ui/src/primitives/card.tsx +1 -1
  211. package/packages/ui/src/primitives/checkbox.tsx +1 -1
  212. package/packages/ui/src/primitives/dropdown-menu.tsx +2 -2
  213. package/packages/ui/src/primitives/select.tsx +2 -2
  214. package/packages/ui/src/primitives/tabs.tsx +7 -6
  215. package/packages/ui/src/types.ts +182 -4
  216. package/skill/SKILL.md +130 -318
  217. package/skill/agents/diagnosis-analyst.md +3 -3
  218. package/skill/agents/evolution-reviewer.md +3 -3
  219. package/skill/agents/integration-guide.md +3 -3
  220. package/skill/agents/pattern-analyst.md +2 -2
  221. package/skill/references/cli-quick-reference.md +89 -0
  222. package/skill/references/creator-playbook.md +131 -0
  223. package/skill/references/examples.md +48 -0
  224. package/skill/references/troubleshooting.md +47 -0
  225. package/skill/references/version-history.md +1 -1
  226. package/skill/selftune.contribute.json +11 -0
  227. package/skill/{Workflows → workflows}/Baseline.md +20 -1
  228. package/skill/{Workflows → workflows}/Contribute.md +23 -10
  229. package/skill/{Workflows → workflows}/Contributions.md +13 -5
  230. package/skill/workflows/CreateTestDeploy.md +170 -0
  231. package/skill/{Workflows → workflows}/CreatorContributions.md +18 -6
  232. package/skill/{Workflows → workflows}/Cron.md +1 -1
  233. package/skill/{Workflows → workflows}/Dashboard.md +20 -0
  234. package/skill/{Workflows → workflows}/Doctor.md +1 -1
  235. package/skill/{Workflows → workflows}/Evals.md +67 -2
  236. package/skill/{Workflows → workflows}/Evolve.md +119 -30
  237. package/skill/{Workflows → workflows}/EvolveBody.md +41 -1
  238. package/skill/{Workflows → workflows}/Grade.md +1 -1
  239. package/skill/{Workflows → workflows}/Ingest.md +60 -2
  240. package/skill/{Workflows → workflows}/Initialize.md +16 -9
  241. package/skill/{Workflows → workflows}/Orchestrate.md +13 -3
  242. package/skill/{Workflows → workflows}/PlatformHooks.md +19 -3
  243. package/skill/workflows/Registry.md +99 -0
  244. package/skill/{Workflows → workflows}/Schedule.md +3 -3
  245. package/skill/workflows/SignalsDashboard.md +87 -0
  246. package/skill/{Workflows → workflows}/Sync.md +3 -1
  247. package/skill/{Workflows → workflows}/UnitTest.md +19 -0
  248. package/skill/{Workflows → workflows}/Watch.md +42 -2
  249. package/skill/{Workflows → workflows}/Workflows.md +39 -2
  250. package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +0 -60
  251. package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +0 -1
  252. package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +0 -11
  253. package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +0 -12
  254. package/cli/selftune/utils/html.ts +0 -27
  255. package/packages/ui/src/components/RecentActivityFeed.tsx +0 -117
  256. /package/skill/{Workflows → workflows}/AlphaUpload.md +0 -0
  257. /package/skill/{Workflows → workflows}/AutoActivation.md +0 -0
  258. /package/skill/{Workflows → workflows}/Badge.md +0 -0
  259. /package/skill/{Workflows → workflows}/Composability.md +0 -0
  260. /package/skill/{Workflows → workflows}/EvolutionMemory.md +0 -0
  261. /package/skill/{Workflows → workflows}/ExportCanonical.md +0 -0
  262. /package/skill/{Workflows → workflows}/Hook.md +0 -0
  263. /package/skill/{Workflows → workflows}/ImportSkillsBench.md +0 -0
  264. /package/skill/{Workflows → workflows}/Quickstart.md +0 -0
  265. /package/skill/{Workflows → workflows}/Recover.md +0 -0
  266. /package/skill/{Workflows → workflows}/RepairSkillUsage.md +0 -0
  267. /package/skill/{Workflows → workflows}/Replay.md +0 -0
  268. /package/skill/{Workflows → workflows}/Rollback.md +0 -0
  269. /package/skill/{Workflows → workflows}/Telemetry.md +0 -0
  270. /package/skill/{Workflows → workflows}/Uninstall.md +0 -0
@@ -12,6 +12,8 @@ export interface AlphaIdentity {
12
12
  cloud_user_id?: string;
13
13
  /** Cloud-issued org ID. Set during device-code approval. */
14
14
  cloud_org_id?: string;
15
+ /** Optional override for cloud API base URL. */
16
+ cloud_api_url?: string;
15
17
  /** Cached email from cloud account. Not authoritative. */
16
18
  email?: string;
17
19
  /** Cached display name from cloud account. Not authoritative. */
@@ -34,7 +36,7 @@ export type AlphaLinkState =
34
36
  | "ready";
35
37
 
36
38
  export interface SelftuneConfig {
37
- agent_type: "claude_code" | "codex" | "opencode" | "openclaw" | "unknown";
39
+ agent_type: "claude_code" | "codex" | "opencode" | "openclaw" | "pi" | "unknown";
38
40
  cli_path: string;
39
41
  llm_mode: "agent";
40
42
  agent_cli: string | null;
@@ -134,6 +136,7 @@ export type {
134
136
  CanonicalRecordKind,
135
137
  CanonicalSchemaVersion,
136
138
  CanonicalSessionRecord,
139
+ CanonicalSessionRecordBase,
137
140
  CanonicalSkillInvocationRecord,
138
141
  CanonicalSourceSessionKind,
139
142
  } from "@selftune/telemetry-contract/types";
@@ -167,7 +170,7 @@ export interface TranscriptMetrics {
167
170
  total_tool_calls: number;
168
171
  bash_commands: string[];
169
172
  skills_triggered: string[];
170
- skills_invoked: string[];
173
+ skills_invoked?: string[];
171
174
  assistant_turns: number;
172
175
  errors_encountered: number;
173
176
  transcript_chars: number;
@@ -247,6 +250,40 @@ export interface EvalEntry {
247
250
  query: string;
248
251
  should_trigger: boolean;
249
252
  invocation_type?: InvocationType;
253
+ /** Provenance: where this eval entry originated */
254
+ source?: "synthetic" | "log" | "blended";
255
+ /** ISO timestamp when this eval entry was created */
256
+ created_at?: string;
257
+ }
258
+
259
+ /** Experimental execution eval entry — extends trigger evals with assertion-based validation. */
260
+ export interface ExecutionEvalEntry extends EvalEntry {
261
+ /** Assertions to verify against the execution result */
262
+ assertions: ExecutionAssertion[];
263
+ /** Whether this entry requires a staged workspace */
264
+ requires_workspace?: boolean;
265
+ /** Experimental flag — must be explicitly opted into */
266
+ experimental: true;
267
+ }
268
+
269
+ export interface ExecutionAssertion {
270
+ /** What to check: file existence, content match, command output, etc. */
271
+ type: "file_exists" | "file_contains" | "command_output" | "skill_triggered" | "custom";
272
+ /** Target path, command, or skill name depending on type */
273
+ target: string;
274
+ /** Expected value or pattern (regex for content/output checks) */
275
+ expected?: string;
276
+ /** Whether the assertion is negated (must NOT match) */
277
+ negated?: boolean;
278
+ }
279
+
280
+ export interface EvalSourceStats {
281
+ total: number;
282
+ synthetic: number;
283
+ log: number;
284
+ blended: number;
285
+ oldest?: string;
286
+ newest?: string;
250
287
  }
251
288
 
252
289
  // ---------------------------------------------------------------------------
@@ -414,12 +451,14 @@ export interface EvolutionEvidenceValidation {
414
451
  regressions?: EvalEntry[] | string[];
415
452
  new_passes?: EvalEntry[];
416
453
  per_entry_results?: Array<{ entry: EvalEntry; before_pass: boolean; after_pass: boolean }>;
454
+ before_entry_results?: Array<{ entry: EvalEntry; before_pass: boolean; after_pass: boolean }>;
417
455
  gates_passed?: number;
418
456
  gates_total?: number;
419
457
  gate_results?: Array<{ gate: ValidationGate; passed: boolean; reason: string }>;
420
458
  validation_mode?: ValidationMode;
421
459
  validation_agent?: string;
422
460
  validation_fixture_id?: string;
461
+ validation_fallback_reason?: string;
423
462
  validation_evidence_ref?: string;
424
463
  }
425
464
 
@@ -429,7 +468,7 @@ export interface EvolutionEvidenceEntry {
429
468
  skill_name: string;
430
469
  skill_path: string;
431
470
  target: EvolutionTarget;
432
- stage: "created" | "validated" | "deployed" | "rejected" | "rolled_back";
471
+ stage: "proposed" | "created" | "validated" | "deployed" | "rejected" | "rolled_back";
433
472
  rationale?: string;
434
473
  confidence?: number;
435
474
  details?: string;
@@ -677,7 +716,7 @@ export interface ContributionBundle {
677
716
  // ---------------------------------------------------------------------------
678
717
 
679
718
  /** Which part of a skill is being evolved. */
680
- export type EvolutionTarget = "description" | "routing" | "body";
719
+ export type EvolutionTarget = "description" | "routing" | "body" | "new_skill";
681
720
 
682
721
  /** Parsed sections of a SKILL.md file. */
683
722
  export interface SkillSections {
@@ -709,7 +748,7 @@ export type ValidationMode = "structural_guard" | "host_replay" | "llm_judge";
709
748
 
710
749
  export interface RoutingReplayFixture {
711
750
  fixture_id: string;
712
- platform: "claude_code" | "codex";
751
+ platform: "claude_code" | "codex" | "opencode";
713
752
  target_skill_name: string;
714
753
  target_skill_path: string;
715
754
  competing_skill_paths: string[];
@@ -735,9 +774,11 @@ export interface BodyValidationResult {
735
774
  validation_mode?: ValidationMode;
736
775
  validation_agent?: string;
737
776
  validation_fixture_id?: string;
777
+ validation_fallback_reason?: string;
738
778
  before_pass_rate?: number;
739
779
  after_pass_rate?: number;
740
780
  per_entry_results?: RoutingReplayEntryResult[];
781
+ before_entry_results?: RoutingReplayEntryResult[];
741
782
  }
742
783
 
743
784
  /** Configuration for which LLM model a role should use. */
@@ -121,7 +121,8 @@ function isSelfttuneHookEntry(entry: unknown): boolean {
121
121
 
122
122
  // Check direct command
123
123
  if (typeof obj.command === "string") {
124
- return SELFTUNE_HOOK_SCRIPTS.some((script) => obj.command?.includes(script));
124
+ const command = obj.command;
125
+ return SELFTUNE_HOOK_SCRIPTS.some((script) => command.includes(script));
125
126
  }
126
127
 
127
128
  // Check hooks array (the nested structure used in settings.json)
@@ -1,4 +1,4 @@
1
- import { existsSync, writeFileSync } from "node:fs";
1
+ import { existsSync } from "node:fs";
2
2
 
3
3
  import {
4
4
  type CanonicalPlatform,
@@ -37,11 +37,3 @@ export function serializeCanonicalRecords(records: CanonicalRecord[], pretty = f
37
37
  records.map((record) => JSON.stringify(record)).join("\n") + (records.length > 0 ? "\n" : "")
38
38
  );
39
39
  }
40
-
41
- export function writeCanonicalExport(
42
- records: CanonicalRecord[],
43
- outPath: string,
44
- pretty = false,
45
- ): void {
46
- writeFileSync(outPath, serializeCanonicalRecords(records, pretty), "utf-8");
47
- }
@@ -21,11 +21,20 @@ export type CLIErrorCode =
21
21
  | "MISSING_FLAG"
22
22
  | "CONFIG_MISSING"
23
23
  | "FILE_NOT_FOUND"
24
+ | "FILE_EXISTS"
24
25
  | "AGENT_NOT_FOUND"
25
26
  | "UNKNOWN_COMMAND"
26
27
  | "GUARD_BLOCKED"
27
28
  | "OPERATION_FAILED"
29
+ | "API_ERROR"
30
+ | "AUTH_MISSING"
31
+ | "BLEND_NO_LOGS"
32
+ | "INVALID_PROPOSAL"
33
+ | "INVALID_STATUS"
28
34
  | "MISSING_DATA"
35
+ | "NOT_FOUND"
36
+ | "REPLAY_UNAVAILABLE"
37
+ | "UNSUPPORTED_TYPE"
29
38
  | "INTERNAL_ERROR";
30
39
 
31
40
  export class CLIError extends Error {
@@ -1,9 +1,8 @@
1
1
  /**
2
- * JSONL read/write/append utilities.
2
+ * JSONL read utilities and marker file helpers.
3
3
  */
4
4
 
5
5
  import {
6
- appendFileSync,
7
6
  closeSync,
8
7
  existsSync,
9
8
  fstatSync,
@@ -15,10 +14,6 @@ import {
15
14
  } from "node:fs";
16
15
  import { dirname } from "node:path";
17
16
 
18
- import { createLogger } from "./logging.js";
19
- import type { LogType } from "./schema-validator.js";
20
- import { validateRecord } from "./schema-validator.js";
21
-
22
17
  /**
23
18
  * Read a JSONL file and return parsed records.
24
19
  * Skips blank lines and lines that fail to parse.
@@ -86,30 +81,6 @@ export function readJsonlFrom<T = Record<string, unknown>>(
86
81
  }
87
82
  }
88
83
 
89
- /**
90
- * Append a single record to a JSONL file. Creates parent directories if needed.
91
- * When logType is provided, validates the record and logs warnings on failure
92
- * but still writes the record (fail-open: hooks must never block).
93
- *
94
- * @deprecated Phase 3: JSONL writes removed. Retained for materializer/test utilities only.
95
- */
96
- export function appendJsonl(path: string, record: unknown, logType?: LogType): void {
97
- if (logType) {
98
- const result = validateRecord(record, logType);
99
- if (!result.valid) {
100
- const logger = createLogger("jsonl");
101
- for (const error of result.errors) {
102
- logger.warn(`Validation warning for ${logType}: ${error}`);
103
- }
104
- }
105
- }
106
- const dir = dirname(path);
107
- if (!existsSync(dir)) {
108
- mkdirSync(dir, { recursive: true });
109
- }
110
- appendFileSync(path, `${JSON.stringify(record)}\n`, "utf-8");
111
- }
112
-
113
84
  /**
114
85
  * Load a marker file (JSON array of strings) for idempotent ingestion.
115
86
  */
@@ -2,7 +2,7 @@
2
2
  * Shared LLM call utility.
3
3
  *
4
4
  * Provides a unified interface for calling LLMs via agent subprocess
5
- * (claude/codex/opencode). Extracted from grade-session.ts so other
5
+ * (claude/codex/opencode/pi). Extracted from grade-session.ts so other
6
6
  * modules can reuse the same calling logic.
7
7
  */
8
8
 
@@ -14,6 +14,8 @@ import { AGENT_CANDIDATES } from "../constants.js";
14
14
  import { createLogger } from "./logging.js";
15
15
 
16
16
  const logger = createLogger("llm-call");
17
+ export const LLM_BACKED_AGENT_CANDIDATES = ["claude", "codex", "opencode", "pi"] as const;
18
+ export type LlmBackedAgent = (typeof LLM_BACKED_AGENT_CANDIDATES)[number];
17
19
 
18
20
  // ---------------------------------------------------------------------------
19
21
  // Model alias resolution
@@ -48,6 +50,17 @@ function resolveOpenCodeModel(flag: string): string {
48
50
  return OPENCODE_MODEL_MAP[flag] ?? flag;
49
51
  }
50
52
 
53
+ const PI_THINKING_MAP: Record<EffortLevel, string> = {
54
+ low: "low",
55
+ medium: "medium",
56
+ high: "high",
57
+ max: "xhigh",
58
+ };
59
+
60
+ function resolvePiThinking(effort: EffortLevel): string {
61
+ return PI_THINKING_MAP[effort];
62
+ }
63
+
51
64
  // ---------------------------------------------------------------------------
52
65
  // Bundled agent file loading (for codex inline prompt injection)
53
66
  // ---------------------------------------------------------------------------
@@ -79,6 +92,33 @@ export function detectAgent(): string | null {
79
92
  return null;
80
93
  }
81
94
 
95
+ /** Detect first available agent CLI that can execute selftune LLM-backed workflows. */
96
+ export function detectLlmAgent(): LlmBackedAgent | null {
97
+ for (const agent of LLM_BACKED_AGENT_CANDIDATES) {
98
+ if (Bun.which(agent)) return agent;
99
+ }
100
+ return null;
101
+ }
102
+
103
+ export function isLlmBackedAgent(value: string): value is LlmBackedAgent {
104
+ return (LLM_BACKED_AGENT_CANDIDATES as readonly string[]).includes(value);
105
+ }
106
+
107
+ function unsupportedAgentError(agent: string, capability: "llm calls" | "subagent calls"): Error {
108
+ const supported = LLM_BACKED_AGENT_CANDIDATES.join(", ");
109
+ if (agent === "openclaw") {
110
+ return new Error(
111
+ `Detected agent CLI '${agent}', but selftune ${capability} currently support only ${supported}. ` +
112
+ `LLM-backed judge, eval, and optimizer workflows are unavailable on ${agent}; ` +
113
+ `use Claude Code, Codex, OpenCode, or Pi for those workflows, or stay on ingest/sync support for ${agent}.`,
114
+ );
115
+ }
116
+
117
+ return new Error(
118
+ `Unknown agent '${agent}'. selftune ${capability} currently support only ${supported}.`,
119
+ );
120
+ }
121
+
82
122
  // ---------------------------------------------------------------------------
83
123
  // Markdown fence stripping
84
124
  // ---------------------------------------------------------------------------
@@ -160,7 +200,7 @@ function sleep(ms: number): Promise<void> {
160
200
  /** Effort level for Claude CLI (controls thinking depth). Opus 4.6 only for 'max'. */
161
201
  export type EffortLevel = "low" | "medium" | "high" | "max";
162
202
 
163
- /** Call LLM via agent subprocess (claude/codex/opencode). Returns raw text. */
203
+ /** Call LLM via agent subprocess (claude/codex/opencode/pi). Returns raw text. */
164
204
  export async function callViaAgent(
165
205
  systemPrompt: string,
166
206
  userPrompt: string,
@@ -194,8 +234,30 @@ export async function callViaAgent(
194
234
  cmd.push("--model", resolveOpenCodeModel(modelFlag));
195
235
  }
196
236
  cmd.push(promptContent);
237
+ } else if (agent === "pi") {
238
+ cmd = [
239
+ "pi",
240
+ "-p",
241
+ "--mode",
242
+ "text",
243
+ "--no-session",
244
+ "--no-tools",
245
+ "--no-extensions",
246
+ "--no-skills",
247
+ "--no-prompt-templates",
248
+ "--no-themes",
249
+ "--system-prompt",
250
+ systemPrompt,
251
+ ];
252
+ if (modelFlag) {
253
+ cmd.push("--model", modelFlag);
254
+ }
255
+ if (effort) {
256
+ cmd.push("--thinking", resolvePiThinking(effort));
257
+ }
258
+ cmd.push(userPrompt);
197
259
  } else {
198
- throw new Error(`Unknown agent: ${agent}`);
260
+ throw unsupportedAgentError(agent, "llm calls");
199
261
  }
200
262
 
201
263
  // Retry loop with exponential backoff for transient failures
@@ -256,6 +318,23 @@ export async function callViaAgent(
256
318
  }
257
319
  }
258
320
 
321
+ function mapAllowedToolsToPi(tools?: string[]): string[] {
322
+ if (!tools || tools.length === 0) return [];
323
+
324
+ const mapped = new Set<string>();
325
+ for (const tool of tools) {
326
+ if (tool === "Read") mapped.add("read");
327
+ else if (tool === "Write") mapped.add("write");
328
+ else if (tool === "Edit") mapped.add("edit");
329
+ else if (tool === "Bash") mapped.add("bash");
330
+ else if (tool === "Grep") mapped.add("grep");
331
+ else if (tool === "Glob" || tool === "Find") mapped.add("find");
332
+ else if (tool === "LS" || tool === "Ls") mapped.add("ls");
333
+ }
334
+
335
+ return [...mapped];
336
+ }
337
+
259
338
  // ---------------------------------------------------------------------------
260
339
  // Call LLM via named subagent (multi-turn, agentic)
261
340
  // ---------------------------------------------------------------------------
@@ -301,10 +380,10 @@ export async function callViaSubagent(options: SubagentCallOptions): Promise<str
301
380
  allowedTools,
302
381
  } = options;
303
382
 
304
- const agent = detectAgent();
305
- if (!agent || (agent !== "claude" && agent !== "opencode" && agent !== "codex")) {
383
+ const agent = detectLlmAgent();
384
+ if (!agent) {
306
385
  throw new Error(
307
- `Subagent calls require 'claude', 'opencode', or 'codex' CLI in PATH (detected: ${agent ?? "none"})`,
386
+ "Subagent calls require one of these CLIs in PATH: claude, codex, opencode, pi.",
308
387
  );
309
388
  }
310
389
 
@@ -333,6 +412,47 @@ export async function callViaSubagent(options: SubagentCallOptions): Promise<str
333
412
  const agentInstructions = loadAgentInstructions(agentName);
334
413
  const fullPrompt = agentInstructions ? `${agentInstructions}\n\n---\n\n${prompt}` : prompt;
335
414
  cmd = ["codex", "exec", "--skip-git-repo-check", fullPrompt];
415
+ } else if (agent === "pi") {
416
+ if (maxTurns !== 8) {
417
+ logger.warn(`Subagent '${agentName}' on pi: maxTurns is not supported and will be ignored`);
418
+ }
419
+ const agentInstructions = loadAgentInstructions(agentName);
420
+ const systemParts = [agentInstructions, appendSystemPrompt].filter((value): value is string =>
421
+ Boolean(value?.trim()),
422
+ );
423
+
424
+ cmd = [
425
+ "pi",
426
+ "-p",
427
+ "--mode",
428
+ "text",
429
+ "--no-session",
430
+ "--no-extensions",
431
+ "--no-skills",
432
+ "--no-prompt-templates",
433
+ "--no-themes",
434
+ ];
435
+
436
+ if (systemParts.length > 0) {
437
+ cmd.push("--system-prompt", systemParts.join("\n\n"));
438
+ }
439
+ if (modelFlag) {
440
+ cmd.push("--model", modelFlag);
441
+ }
442
+ if (effort) {
443
+ cmd.push("--thinking", resolvePiThinking(effort));
444
+ }
445
+
446
+ const piTools = mapAllowedToolsToPi(allowedTools);
447
+ if (allowedTools && allowedTools.length > 0) {
448
+ if (piTools.length > 0) {
449
+ cmd.push("--tools", piTools.join(","));
450
+ } else {
451
+ cmd.push("--no-tools");
452
+ }
453
+ }
454
+
455
+ cmd.push(prompt);
336
456
  } else {
337
457
  // Claude Code
338
458
  cmd = ["claude", "-p", prompt, "--agent", agentName, "--max-turns", String(maxTurns)];
@@ -263,6 +263,28 @@ export function classifySkillPath(
263
263
  return { skill_scope: "unknown" };
264
264
  }
265
265
 
266
+ const TEST_PATH_SEGMENTS = [
267
+ "/tests/",
268
+ "/__tests__/",
269
+ "/test/",
270
+ "/fixtures/",
271
+ "/sandbox/",
272
+ "/test-data/",
273
+ "/testdata/",
274
+ "/mock/",
275
+ "/mocks/",
276
+ ];
277
+
278
+ /**
279
+ * Check if a skill path is inside a test/fixture directory.
280
+ * Used to prevent test fixture skills from leaking into production data.
281
+ */
282
+ export function isTestFixturePath(skillPath: string): boolean {
283
+ if (!skillPath) return false;
284
+ const normalized = skillPath.toLowerCase();
285
+ return TEST_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
286
+ }
287
+
266
288
  export function extractSkillNamesFromInstructions(
267
289
  text: string,
268
290
  knownSkillNames?: Iterable<string>,
@@ -319,6 +341,8 @@ export function extractSkillNamesFromPathReferences(
319
341
  const patterns = [
320
342
  /(?:^|[\s"'`])(?:[^"'`\s]*?\.agents\/skills\/)([^/\s"'`]+)(?=\/)/gi,
321
343
  /(?:^|[\s"'`])(?:[^"'`\s]*?\.codex\/skills\/(?:\.system\/)?)([^/\s"'`]+)(?=\/)/gi,
344
+ /(?:^|[\s"'`])(?:[^"'`\s]*?\.opencode\/skills\/)([^/\s"'`]+)(?=\/)/gi,
345
+ /(?:^|[\s"'`])(?:[^"'`\s]*?\.claude\/skills\/)([^/\s"'`]+)(?=\/)/gi,
322
346
  /(?:^|[\s"'`])(\/etc\/codex\/skills\/)([^/\s"'`]+)(?=\/)/gi,
323
347
  ];
324
348
 
@@ -0,0 +1,184 @@
1
+ /**
2
+ * proposals.ts
3
+ *
4
+ * Turns strong multi-skill workflow patterns into review-first new-skill
5
+ * proposals that can be surfaced locally and synced to the cloud.
6
+ */
7
+
8
+ import { createHash } from "node:crypto";
9
+
10
+ import { appendAuditEntry } from "../evolution/audit.js";
11
+ import { appendEvidenceEntry } from "../evolution/evidence.js";
12
+ import type {
13
+ DiscoveredWorkflow,
14
+ EvolutionAuditEntry,
15
+ EvolutionEvidenceEntry,
16
+ SessionTelemetryRecord,
17
+ SkillUsageRecord,
18
+ } from "../types.js";
19
+ import { discoverWorkflows } from "./discover.js";
20
+ import { buildWorkflowSkillDraft, type WorkflowSkillDraft } from "./skill-scaffold.js";
21
+
22
+ export interface WorkflowSkillProposal {
23
+ proposal_id: string;
24
+ source_skill_name: string;
25
+ workflow: DiscoveredWorkflow;
26
+ draft: WorkflowSkillDraft;
27
+ summary: string;
28
+ current_value: string;
29
+ proposed_value: string;
30
+ rationale: string;
31
+ confidence: number;
32
+ }
33
+
34
+ export interface WorkflowSkillProposalOptions {
35
+ cwd?: string;
36
+ skillFilter?: string;
37
+ maxProposals?: number;
38
+ minOccurrences?: number;
39
+ minSynergy?: number;
40
+ minConsistency?: number;
41
+ minCompletionRate?: number;
42
+ resolveSkillPath?: (skillName: string) => string | undefined;
43
+ existingAuditEntries?: EvolutionAuditEntry[];
44
+ }
45
+
46
+ export interface WorkflowSkillProposalPersistOptions {
47
+ now?: Date;
48
+ sourceSkillPath?: string;
49
+ appendAudit?: (entry: EvolutionAuditEntry) => void;
50
+ appendEvidence?: (entry: EvolutionEvidenceEntry) => void;
51
+ }
52
+
53
+ export const DEFAULT_WORKFLOW_PROPOSAL_MIN_OCCURRENCES = 3;
54
+ export const DEFAULT_WORKFLOW_PROPOSAL_MAX = 2;
55
+ export const DEFAULT_WORKFLOW_PROPOSAL_MIN_SYNERGY = 0;
56
+ export const DEFAULT_WORKFLOW_PROPOSAL_MIN_CONSISTENCY = 0.75;
57
+ export const DEFAULT_WORKFLOW_PROPOSAL_MIN_COMPLETION = 0.65;
58
+
59
+ function round2(value: number): number {
60
+ return Math.round(value * 100) / 100;
61
+ }
62
+
63
+ function clamp01(value: number): number {
64
+ return Math.max(0, Math.min(1, value));
65
+ }
66
+
67
+ function buildWorkflowProposalConfidence(workflow: DiscoveredWorkflow): number {
68
+ const normalizedSynergy = clamp01((workflow.synergy_score + 1) / 2);
69
+ const occurrenceBoost = clamp01(workflow.occurrence_count / 6);
70
+ return round2(
71
+ normalizedSynergy * 0.4 +
72
+ workflow.sequence_consistency * 0.3 +
73
+ workflow.completion_rate * 0.2 +
74
+ occurrenceBoost * 0.1,
75
+ );
76
+ }
77
+
78
+ function buildWorkflowProposalId(sourceSkillName: string, draft: WorkflowSkillDraft): string {
79
+ const digest = createHash("sha256")
80
+ .update(`${sourceSkillName}:${draft.skill_name}:${draft.source_workflow.workflow_id}`)
81
+ .digest("hex")
82
+ .slice(0, 16);
83
+ return `wf-${draft.skill_name}-${digest}`;
84
+ }
85
+
86
+ function buildWorkflowProposalSummary(
87
+ workflow: DiscoveredWorkflow,
88
+ draft: WorkflowSkillDraft,
89
+ ): string {
90
+ const chain = workflow.skills.join(" -> ");
91
+ return `Create new_skill "${draft.skill_name}" from workflow ${chain} (${workflow.occurrence_count} sessions, synergy ${workflow.synergy_score.toFixed(2)}, consistency ${Math.round(workflow.sequence_consistency * 100)}%, completion ${Math.round(workflow.completion_rate * 100)}%).`;
92
+ }
93
+
94
+ function hasExistingProposal(proposalId: string, auditEntries: EvolutionAuditEntry[]): boolean {
95
+ return auditEntries.some((entry) => entry.proposal_id === proposalId);
96
+ }
97
+
98
+ export function discoverWorkflowSkillProposals(
99
+ telemetry: SessionTelemetryRecord[],
100
+ usage: SkillUsageRecord[],
101
+ options: WorkflowSkillProposalOptions = {},
102
+ ): WorkflowSkillProposal[] {
103
+ const minOccurrences = options.minOccurrences ?? DEFAULT_WORKFLOW_PROPOSAL_MIN_OCCURRENCES;
104
+ const maxProposals = options.maxProposals ?? DEFAULT_WORKFLOW_PROPOSAL_MAX;
105
+ const minSynergy = options.minSynergy ?? DEFAULT_WORKFLOW_PROPOSAL_MIN_SYNERGY;
106
+ const minConsistency = options.minConsistency ?? DEFAULT_WORKFLOW_PROPOSAL_MIN_CONSISTENCY;
107
+ const minCompletionRate = options.minCompletionRate ?? DEFAULT_WORKFLOW_PROPOSAL_MIN_COMPLETION;
108
+ const report = discoverWorkflows(telemetry, usage, {
109
+ minOccurrences,
110
+ skill: options.skillFilter,
111
+ });
112
+ const existingAuditEntries = options.existingAuditEntries ?? [];
113
+ const proposals: WorkflowSkillProposal[] = [];
114
+
115
+ for (const workflow of report.workflows) {
116
+ if (workflow.occurrence_count < minOccurrences) continue;
117
+ if (workflow.synergy_score < minSynergy) continue;
118
+ if (workflow.sequence_consistency < minConsistency) continue;
119
+ if (workflow.completion_rate < minCompletionRate) continue;
120
+ if (workflow.skills.length < 2) continue;
121
+
122
+ const draft = buildWorkflowSkillDraft(workflow, { cwd: options.cwd });
123
+ if (!draft.skill_name) continue;
124
+ if (options.resolveSkillPath?.(draft.skill_name)) continue;
125
+
126
+ const sourceSkillName = workflow.skills[0];
127
+ const proposalId = buildWorkflowProposalId(sourceSkillName, draft);
128
+ if (hasExistingProposal(proposalId, existingAuditEntries)) continue;
129
+
130
+ const summary = buildWorkflowProposalSummary(workflow, draft);
131
+ const currentValue = `No dedicated workflow skill exists for ${workflow.skills.join(" -> ")}.`;
132
+ const proposedValue = `Create ${draft.skill_name} at ${draft.skill_path}`;
133
+ const queryClause = workflow.representative_query.trim()
134
+ ? ` Common trigger: "${workflow.representative_query.trim()}".`
135
+ : "";
136
+
137
+ proposals.push({
138
+ proposal_id: proposalId,
139
+ source_skill_name: sourceSkillName,
140
+ workflow,
141
+ draft,
142
+ summary,
143
+ current_value: currentValue,
144
+ proposed_value: proposedValue,
145
+ rationale: `${summary}${queryClause}`,
146
+ confidence: buildWorkflowProposalConfidence(workflow),
147
+ });
148
+
149
+ if (proposals.length >= maxProposals) break;
150
+ }
151
+
152
+ return proposals;
153
+ }
154
+
155
+ export function persistWorkflowSkillProposal(
156
+ proposal: WorkflowSkillProposal,
157
+ options: WorkflowSkillProposalPersistOptions = {},
158
+ ): void {
159
+ const timestamp = (options.now ?? new Date()).toISOString();
160
+ const appendAudit = options.appendAudit ?? appendAuditEntry;
161
+ const appendEvidence = options.appendEvidence ?? appendEvidenceEntry;
162
+
163
+ appendAudit({
164
+ timestamp,
165
+ proposal_id: proposal.proposal_id,
166
+ skill_name: proposal.source_skill_name,
167
+ action: "created",
168
+ details: proposal.summary,
169
+ });
170
+
171
+ appendEvidence({
172
+ timestamp,
173
+ proposal_id: proposal.proposal_id,
174
+ skill_name: proposal.source_skill_name,
175
+ skill_path: options.sourceSkillPath ?? "",
176
+ target: "new_skill",
177
+ stage: "proposed",
178
+ rationale: proposal.rationale,
179
+ confidence: proposal.confidence,
180
+ details: proposal.proposed_value,
181
+ original_text: proposal.current_value,
182
+ proposed_text: proposal.draft.content,
183
+ });
184
+ }