selftune 0.2.23 → 0.2.25
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 +93 -15
- package/apps/local-dashboard/dist/assets/index-DgY2KGP-.css +1 -0
- package/apps/local-dashboard/dist/assets/index-Dhgv5BQO.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/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/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 +73 -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/replay-engine.ts +79 -57
- package/cli/selftune/evolution/evolve-body.ts +100 -39
- package/cli/selftune/evolution/evolve.ts +244 -52
- package/cli/selftune/evolution/rollback.ts +0 -1
- package/cli/selftune/evolution/validate-body.ts +68 -42
- 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 +43 -41
- 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/index.ts +35 -10
- 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 +3 -2
- package/cli/selftune/init.ts +27 -3
- package/cli/selftune/localdb/direct-write.ts +35 -1
- 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 -2288
- package/cli/selftune/localdb/schema.ts +21 -0
- package/cli/selftune/monitoring/watch.ts +96 -29
- package/cli/selftune/normalization.ts +3 -0
- package/cli/selftune/observability.ts +4 -2
- 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 +150 -1173
- package/cli/selftune/repair/skill-usage.ts +5 -2
- 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 +39 -2
- package/cli/selftune/testing-readiness.ts +597 -0
- package/cli/selftune/types.ts +44 -4
- 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/llm-call.ts +126 -6
- package/cli/selftune/utils/skill-discovery.ts +2 -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 +1 -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 +1 -1
- package/node_modules/@selftune/telemetry-contract/src/schemas.ts +41 -1
- package/node_modules/@selftune/telemetry-contract/src/types.ts +103 -2
- 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 +1 -1
- package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
- package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
- package/packages/telemetry-contract/src/schemas.ts +41 -1
- package/packages/telemetry-contract/src/types.ts +103 -2
- package/packages/ui/src/components/EvidenceViewer.tsx +80 -25
- package/packages/ui/src/components/OverviewPanels.tsx +67 -26
- package/packages/ui/src/primitives/tabs.tsx +7 -6
- package/packages/ui/src/types.ts +10 -0
- package/skill/SKILL.md +130 -332
- 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}/Initialize.md +8 -4
- package/skill/{Workflows → workflows}/Orchestrate.md +13 -3
- package/skill/{Workflows → workflows}/Schedule.md +3 -3
- package/skill/workflows/SignalsDashboard.md +87 -0
- 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-CwOtTrUS.css +0 -1
- package/apps/local-dashboard/dist/assets/index-f1HQpbeH.js +0 -59
- package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +0 -11
- package/apps/local-dashboard/dist/assets/vendor-ui-jVSaIZey.js +0 -12
- /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}/Ingest.md +0 -0
- /package/skill/{Workflows → workflows}/PlatformHooks.md +0 -0
- /package/skill/{Workflows → workflows}/Quickstart.md +0 -0
- /package/skill/{Workflows → workflows}/Recover.md +0 -0
- /package/skill/{Workflows → workflows}/Registry.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}/Sync.md +0 -0
- /package/skill/{Workflows → workflows}/Telemetry.md +0 -0
- /package/skill/{Workflows → workflows}/Uninstall.md +0 -0
|
@@ -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 {
|
|
@@ -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)];
|
|
@@ -341,6 +341,8 @@ export function extractSkillNamesFromPathReferences(
|
|
|
341
341
|
const patterns = [
|
|
342
342
|
/(?:^|[\s"'`])(?:[^"'`\s]*?\.agents\/skills\/)([^/\s"'`]+)(?=\/)/gi,
|
|
343
343
|
/(?:^|[\s"'`])(?:[^"'`\s]*?\.codex\/skills\/(?:\.system\/)?)([^/\s"'`]+)(?=\/)/gi,
|
|
344
|
+
/(?:^|[\s"'`])(?:[^"'`\s]*?\.opencode\/skills\/)([^/\s"'`]+)(?=\/)/gi,
|
|
345
|
+
/(?:^|[\s"'`])(?:[^"'`\s]*?\.claude\/skills\/)([^/\s"'`]+)(?=\/)/gi,
|
|
344
346
|
/(?:^|[\s"'`])(\/etc\/codex\/skills\/)([^/\s"'`]+)(?=\/)/gi,
|
|
345
347
|
];
|
|
346
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
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skill-scaffold.ts
|
|
3
|
+
*
|
|
4
|
+
* Builds draft workflow skills from repeated telemetry-discovered workflows.
|
|
5
|
+
* The draft is preview-first by default so agents can review the scaffold before
|
|
6
|
+
* writing it into a local skill registry.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
import type { DiscoveredWorkflow } from "../types.js";
|
|
12
|
+
import { findGitRepositoryRoot } from "../utils/skill-discovery.js";
|
|
13
|
+
|
|
14
|
+
export interface WorkflowSkillDraft {
|
|
15
|
+
title: string;
|
|
16
|
+
skill_name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
output_dir: string;
|
|
19
|
+
skill_dir: string;
|
|
20
|
+
skill_path: string;
|
|
21
|
+
content: string;
|
|
22
|
+
source_workflow: {
|
|
23
|
+
workflow_id: string;
|
|
24
|
+
skills: string[];
|
|
25
|
+
occurrence_count: number;
|
|
26
|
+
synergy_score: number;
|
|
27
|
+
representative_query: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface WorkflowSkillDraftOptions {
|
|
32
|
+
outputDir?: string;
|
|
33
|
+
skillName?: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
cwd?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const STOPWORDS = new Set([
|
|
39
|
+
"a",
|
|
40
|
+
"an",
|
|
41
|
+
"and",
|
|
42
|
+
"for",
|
|
43
|
+
"from",
|
|
44
|
+
"in",
|
|
45
|
+
"into",
|
|
46
|
+
"of",
|
|
47
|
+
"on",
|
|
48
|
+
"or",
|
|
49
|
+
"the",
|
|
50
|
+
"to",
|
|
51
|
+
"with",
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
function splitWords(value: string): string[] {
|
|
55
|
+
return value
|
|
56
|
+
.replace(/[^A-Za-z0-9]+/g, " ")
|
|
57
|
+
.trim()
|
|
58
|
+
.split(/\s+/)
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function titleCase(value: string): string {
|
|
63
|
+
return splitWords(value)
|
|
64
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
65
|
+
.join(" ");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function slugifyWorkflowSkillName(value: string): string {
|
|
69
|
+
return splitWords(value)
|
|
70
|
+
.map((word) => word.toLowerCase())
|
|
71
|
+
.join("-")
|
|
72
|
+
.replace(/-+/g, "-")
|
|
73
|
+
.replace(/^-|-$/g, "");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function deriveBaseLabel(workflow: DiscoveredWorkflow): string {
|
|
77
|
+
const filteredQueryWords = splitWords(workflow.representative_query).filter(
|
|
78
|
+
(word) => !STOPWORDS.has(word.toLowerCase()),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (filteredQueryWords.length >= 2) {
|
|
82
|
+
return filteredQueryWords.slice(0, 5).join(" ");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return `${workflow.skills.join(" ")} workflow`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function formatList(items: string[]): string {
|
|
89
|
+
if (items.length === 0) return "";
|
|
90
|
+
if (items.length === 1) return items[0];
|
|
91
|
+
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
92
|
+
return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function wrapFoldedScalar(value: string, width = 78): string[] {
|
|
96
|
+
const words = value.split(/\s+/).filter(Boolean);
|
|
97
|
+
const lines: string[] = [];
|
|
98
|
+
let current = "";
|
|
99
|
+
|
|
100
|
+
for (const word of words) {
|
|
101
|
+
const candidate = current.length === 0 ? word : `${current} ${word}`;
|
|
102
|
+
if (candidate.length > width && current.length > 0) {
|
|
103
|
+
lines.push(` ${current}`);
|
|
104
|
+
current = word;
|
|
105
|
+
} else {
|
|
106
|
+
current = candidate;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (current.length > 0) lines.push(` ${current}`);
|
|
111
|
+
return lines.length > 0 ? lines : [" "];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function getDefaultWorkflowSkillOutputDir(cwd: string = process.cwd()): string {
|
|
115
|
+
const repoRoot = findGitRepositoryRoot(cwd);
|
|
116
|
+
return join(repoRoot ?? cwd, ".agents", "skills");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function buildWorkflowSkillDescription(
|
|
120
|
+
workflow: DiscoveredWorkflow,
|
|
121
|
+
override?: string,
|
|
122
|
+
): string {
|
|
123
|
+
if (override && override.trim().length > 0) return override.trim();
|
|
124
|
+
|
|
125
|
+
const chain = formatList(workflow.skills);
|
|
126
|
+
const query = workflow.representative_query.trim();
|
|
127
|
+
if (query.length > 0) {
|
|
128
|
+
return `Use when the user wants to ${query}. Coordinates ${chain} in sequence.`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return `Use when the task consistently needs ${chain} in sequence.`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function buildWorkflowSkillContent(
|
|
135
|
+
workflow: DiscoveredWorkflow,
|
|
136
|
+
title: string,
|
|
137
|
+
skillName: string,
|
|
138
|
+
description: string,
|
|
139
|
+
): string {
|
|
140
|
+
const workflowName = title.endsWith("Workflow") ? title : `${title} Workflow`;
|
|
141
|
+
const chain = workflow.skills.join(" → ");
|
|
142
|
+
const query = workflow.representative_query.trim();
|
|
143
|
+
const foldedDescription = wrapFoldedScalar(description).join("\n");
|
|
144
|
+
|
|
145
|
+
const whenToUseLines =
|
|
146
|
+
query.length > 0
|
|
147
|
+
? [
|
|
148
|
+
`- The user asks to "${query}"`,
|
|
149
|
+
`- The request repeatedly needs this skill chain: ${chain}`,
|
|
150
|
+
]
|
|
151
|
+
: [`- The request repeatedly needs this skill chain: ${chain}`];
|
|
152
|
+
|
|
153
|
+
const executionPlanLines = workflow.skills.map(
|
|
154
|
+
(skill, index) =>
|
|
155
|
+
`${index + 1}. Invoke \`${skill}\` in its established role for this workflow.`,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return `---
|
|
159
|
+
name: ${skillName}
|
|
160
|
+
description: >
|
|
161
|
+
${foldedDescription}
|
|
162
|
+
metadata:
|
|
163
|
+
author: selftune-autogen
|
|
164
|
+
version: 0.1.0
|
|
165
|
+
category: developer-tools
|
|
166
|
+
generated_by: selftune workflows scaffold
|
|
167
|
+
source_workflow_id: ${workflow.workflow_id}
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
# ${title}
|
|
171
|
+
|
|
172
|
+
This draft skill was scaffolded by selftune from repeated workflow telemetry.
|
|
173
|
+
Review the routing language and execution notes before broad distribution.
|
|
174
|
+
|
|
175
|
+
## When to Use
|
|
176
|
+
|
|
177
|
+
${whenToUseLines.join("\n")}
|
|
178
|
+
|
|
179
|
+
## Execution Plan
|
|
180
|
+
|
|
181
|
+
${executionPlanLines.join("\n")}
|
|
182
|
+
|
|
183
|
+
## Workflows
|
|
184
|
+
|
|
185
|
+
### ${workflowName}
|
|
186
|
+
- **Skills:** ${chain}
|
|
187
|
+
${query.length > 0 ? `- **Trigger:** ${query}\n` : ""}- **Source:** Discovered from ${workflow.occurrence_count} sessions (synergy: ${workflow.synergy_score.toFixed(2)})
|
|
188
|
+
|
|
189
|
+
## Notes
|
|
190
|
+
|
|
191
|
+
- This is a proposal scaffold, not a silently published marketplace skill.
|
|
192
|
+
- Add tighter scope boundaries and richer examples before publishing.
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function buildWorkflowSkillDraft(
|
|
197
|
+
workflow: DiscoveredWorkflow,
|
|
198
|
+
options: WorkflowSkillDraftOptions = {},
|
|
199
|
+
): WorkflowSkillDraft {
|
|
200
|
+
const baseLabel = options.skillName?.trim() || deriveBaseLabel(workflow);
|
|
201
|
+
const skillName = slugifyWorkflowSkillName(baseLabel);
|
|
202
|
+
const title = titleCase(baseLabel) || titleCase(`${workflow.skills.join(" ")} workflow`);
|
|
203
|
+
const description = buildWorkflowSkillDescription(workflow, options.description);
|
|
204
|
+
const outputDir = options.outputDir?.trim() || getDefaultWorkflowSkillOutputDir(options.cwd);
|
|
205
|
+
const skillDir = join(outputDir, skillName);
|
|
206
|
+
const skillPath = join(skillDir, "SKILL.md");
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
title,
|
|
210
|
+
skill_name: skillName,
|
|
211
|
+
description,
|
|
212
|
+
output_dir: outputDir,
|
|
213
|
+
skill_dir: skillDir,
|
|
214
|
+
skill_path: skillPath,
|
|
215
|
+
content: buildWorkflowSkillContent(workflow, title, skillName, description),
|
|
216
|
+
source_workflow: {
|
|
217
|
+
workflow_id: workflow.workflow_id,
|
|
218
|
+
skills: workflow.skills,
|
|
219
|
+
occurrence_count: workflow.occurrence_count,
|
|
220
|
+
synergy_score: workflow.synergy_score,
|
|
221
|
+
representative_query: workflow.representative_query,
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function formatWorkflowSkillDraft(draft: WorkflowSkillDraft): string {
|
|
227
|
+
const lines = [
|
|
228
|
+
`Draft workflow skill: ${draft.title}`,
|
|
229
|
+
`Skill name: ${draft.skill_name}`,
|
|
230
|
+
`Output path: ${draft.skill_path}`,
|
|
231
|
+
`Source workflow: ${draft.source_workflow.workflow_id}`,
|
|
232
|
+
`Occurrences: ${draft.source_workflow.occurrence_count} | Synergy: ${draft.source_workflow.synergy_score.toFixed(2)}`,
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
if (draft.source_workflow.representative_query.trim().length > 0) {
|
|
236
|
+
lines.push(`Representative query: "${draft.source_workflow.representative_query.trim()}"`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
lines.push("", draft.content.trimEnd());
|
|
240
|
+
return lines.join("\n");
|
|
241
|
+
}
|