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
package/cli/selftune/types.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
305
|
-
if (!agent
|
|
383
|
+
const agent = detectLlmAgent();
|
|
384
|
+
if (!agent) {
|
|
306
385
|
throw new Error(
|
|
307
|
-
|
|
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
|
+
}
|