selftune 0.2.31 → 0.2.32

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 (95) hide show
  1. package/README.md +83 -56
  2. package/apps/local-dashboard/dist/assets/index-B-ut4w0B.js +15 -0
  3. package/apps/local-dashboard/dist/assets/index-BFGfCVrL.css +1 -0
  4. package/apps/local-dashboard/dist/assets/vendor-ui-DfowE3Hu.js +1 -0
  5. package/apps/local-dashboard/dist/index.html +3 -3
  6. package/cli/selftune/command-surface.ts +613 -2
  7. package/cli/selftune/create/baseline.ts +429 -0
  8. package/cli/selftune/create/check.ts +35 -0
  9. package/cli/selftune/create/init.ts +115 -0
  10. package/cli/selftune/create/package-candidate-state.ts +771 -0
  11. package/cli/selftune/create/package-evaluator.ts +710 -0
  12. package/cli/selftune/create/package-fingerprint.ts +142 -0
  13. package/cli/selftune/create/package-search.ts +377 -0
  14. package/cli/selftune/create/publish.ts +431 -0
  15. package/cli/selftune/create/readiness.ts +495 -0
  16. package/cli/selftune/create/replay.ts +330 -0
  17. package/cli/selftune/create/report.ts +74 -0
  18. package/cli/selftune/create/scaffold.ts +121 -0
  19. package/cli/selftune/create/skills-ref-adapter.ts +177 -0
  20. package/cli/selftune/create/status.ts +33 -0
  21. package/cli/selftune/create/templates.ts +249 -0
  22. package/cli/selftune/cron/setup.ts +1 -1
  23. package/cli/selftune/dashboard-action-events.ts +4 -1
  24. package/cli/selftune/dashboard-action-result.ts +789 -24
  25. package/cli/selftune/dashboard-action-stream.ts +80 -0
  26. package/cli/selftune/dashboard-contract.ts +146 -3
  27. package/cli/selftune/dashboard-server.ts +5 -4
  28. package/cli/selftune/eval/hooks-to-evals.ts +58 -35
  29. package/cli/selftune/eval/synthetic-evals.ts +145 -17
  30. package/cli/selftune/evolution/bounded-mutations.ts +1045 -0
  31. package/cli/selftune/evolution/evolve-body.ts +9 -36
  32. package/cli/selftune/evolution/evolve.ts +8 -72
  33. package/cli/selftune/evolution/stopping-criteria.ts +5 -13
  34. package/cli/selftune/evolution/unblock-suggestions.ts +0 -16
  35. package/cli/selftune/evolution/validate-host-replay.ts +115 -15
  36. package/cli/selftune/improve.ts +206 -0
  37. package/cli/selftune/index.ts +123 -6
  38. package/cli/selftune/init.ts +1 -1
  39. package/cli/selftune/localdb/queries/dashboard.ts +30 -0
  40. package/cli/selftune/localdb/schema.ts +52 -0
  41. package/cli/selftune/monitoring/watch.ts +257 -23
  42. package/cli/selftune/orchestrate/execute.ts +300 -1
  43. package/cli/selftune/orchestrate/finalize.ts +14 -0
  44. package/cli/selftune/orchestrate/plan.ts +22 -5
  45. package/cli/selftune/orchestrate/prepare.ts +59 -4
  46. package/cli/selftune/orchestrate/report.ts +1 -1
  47. package/cli/selftune/orchestrate.ts +34 -1
  48. package/cli/selftune/publish.ts +35 -0
  49. package/cli/selftune/routes/actions.ts +81 -15
  50. package/cli/selftune/routes/overview.ts +1 -1
  51. package/cli/selftune/routes/skill-report.ts +147 -2
  52. package/cli/selftune/run.ts +18 -0
  53. package/cli/selftune/schedule.ts +3 -3
  54. package/cli/selftune/search-run.ts +703 -0
  55. package/cli/selftune/status.ts +35 -11
  56. package/cli/selftune/testing-readiness.ts +431 -40
  57. package/cli/selftune/types.ts +316 -0
  58. package/cli/selftune/utils/eval-readiness.ts +1 -0
  59. package/cli/selftune/utils/json-output.ts +11 -0
  60. package/cli/selftune/utils/lifecycle-surface.ts +48 -0
  61. package/cli/selftune/utils/query-filter.ts +82 -1
  62. package/cli/selftune/utils/tui.ts +85 -2
  63. package/cli/selftune/verify.ts +205 -0
  64. package/cli/selftune/workflows/proposals.ts +1 -1
  65. package/cli/selftune/workflows/skill-scaffold.ts +141 -63
  66. package/cli/selftune/workflows/workflows.ts +4 -4
  67. package/package.json +1 -1
  68. package/skill/SKILL.md +148 -85
  69. package/skill/references/cli-quick-reference.md +16 -1
  70. package/skill/references/creator-playbook.md +31 -10
  71. package/skill/workflows/Baseline.md +8 -9
  72. package/skill/workflows/Contributions.md +4 -4
  73. package/skill/workflows/Create.md +173 -0
  74. package/skill/workflows/CreateTestDeploy.md +34 -30
  75. package/skill/workflows/Cron.md +2 -2
  76. package/skill/workflows/Dashboard.md +3 -3
  77. package/skill/workflows/Evals.md +13 -7
  78. package/skill/workflows/Evolve.md +75 -32
  79. package/skill/workflows/EvolveBody.md +22 -15
  80. package/skill/workflows/Hook.md +1 -1
  81. package/skill/workflows/Improve.md +168 -0
  82. package/skill/workflows/Initialize.md +3 -3
  83. package/skill/workflows/Orchestrate.md +49 -12
  84. package/skill/workflows/Publish.md +100 -0
  85. package/skill/workflows/Run.md +72 -0
  86. package/skill/workflows/Schedule.md +2 -2
  87. package/skill/workflows/SearchRun.md +89 -0
  88. package/skill/workflows/SignalsDashboard.md +2 -2
  89. package/skill/workflows/UnitTest.md +13 -4
  90. package/skill/workflows/Verify.md +136 -0
  91. package/skill/workflows/Watch.md +114 -47
  92. package/skill/workflows/Workflows.md +13 -8
  93. package/apps/local-dashboard/dist/assets/index-B7v_o1WC.js +0 -15
  94. package/apps/local-dashboard/dist/assets/index-CrO77SVi.css +0 -1
  95. package/apps/local-dashboard/dist/assets/vendor-ui-B0H8s1mP.js +0 -1
@@ -0,0 +1,205 @@
1
+ import { join } from "node:path";
2
+ import { parseArgs } from "node:util";
3
+
4
+ import type { CreatePackageEvaluationResult } from "./create/package-evaluator.js";
5
+ import { formatCreatePackageBenchmarkReport } from "./create/package-evaluator.js";
6
+ import { runCreateReport } from "./create/report.js";
7
+ import { computeCreateCheckResult, formatCreateCheckResult } from "./create/readiness.js";
8
+ import { PUBLIC_COMMAND_SURFACES, renderCommandHelp } from "./command-surface.js";
9
+ import type { CreateCheckResult } from "./types.js";
10
+ import { handleCLIError } from "./utils/cli-error.js";
11
+
12
+ export interface VerifyResult {
13
+ skill: string;
14
+ skill_path: string;
15
+ readiness_state: CreateCheckResult["state"];
16
+ verified: boolean;
17
+ next_command: string | null;
18
+ readiness: CreateCheckResult;
19
+ report: CreatePackageEvaluationResult | null;
20
+ }
21
+
22
+ export interface RunVerifyOptions {
23
+ skillPath: string;
24
+ agent?: string;
25
+ evalSetPath?: string;
26
+ autoFix?: boolean;
27
+ }
28
+
29
+ export interface RunVerifyDeps {
30
+ computeCreateCheckResult?: typeof computeCreateCheckResult;
31
+ runCreateReport?: typeof runCreateReport;
32
+ runSelftuneSubCommand?: (command: string[]) => {
33
+ exitCode: number | null;
34
+ stdout: string;
35
+ stderr: string;
36
+ };
37
+ }
38
+
39
+ const MAX_AUTO_FIX_ITERATIONS = 4;
40
+
41
+ function runSelftuneSubCommand(command: string[]): {
42
+ exitCode: number | null;
43
+ stdout: string;
44
+ stderr: string;
45
+ } {
46
+ const indexPath = join(import.meta.dir, "index.ts");
47
+ const result = Bun.spawnSync(["bun", "run", indexPath, ...command], {
48
+ stdout: "pipe",
49
+ stderr: "pipe",
50
+ env: process.env,
51
+ });
52
+ return {
53
+ exitCode: result.exitCode,
54
+ stdout: Buffer.from(result.stdout).toString("utf-8"),
55
+ stderr: Buffer.from(result.stderr).toString("utf-8"),
56
+ };
57
+ }
58
+
59
+ function withPublishRecommendation(
60
+ result: CreatePackageEvaluationResult,
61
+ ): CreatePackageEvaluationResult {
62
+ if (!result.summary.evaluation_passed) return result;
63
+ return {
64
+ ...result,
65
+ summary: {
66
+ ...result.summary,
67
+ next_command: `selftune publish --skill-path ${result.summary.skill_path}`,
68
+ },
69
+ };
70
+ }
71
+
72
+ function buildAutoFixCommand(
73
+ readiness: CreateCheckResult,
74
+ options: Pick<RunVerifyOptions, "evalSetPath">,
75
+ ): string[] | null {
76
+ switch (readiness.state) {
77
+ case "needs_evals":
78
+ return [
79
+ "eval",
80
+ "generate",
81
+ "--skill",
82
+ readiness.skill,
83
+ "--skill-path",
84
+ readiness.skill_path,
85
+ "--auto-synthetic",
86
+ ];
87
+ case "needs_unit_tests":
88
+ return [
89
+ "eval",
90
+ "unit-test",
91
+ "--skill",
92
+ readiness.skill,
93
+ "--generate",
94
+ ...(options.evalSetPath ? ["--eval-set", options.evalSetPath] : []),
95
+ "--skill-path",
96
+ readiness.skill_path,
97
+ ];
98
+ case "needs_routing_replay":
99
+ return ["create", "replay", "--skill-path", readiness.skill_path];
100
+ case "needs_baseline":
101
+ return ["create", "baseline", "--skill-path", readiness.skill_path];
102
+ default:
103
+ return null;
104
+ }
105
+ }
106
+
107
+ export async function runVerify(
108
+ options: RunVerifyOptions,
109
+ deps: RunVerifyDeps = {},
110
+ ): Promise<VerifyResult> {
111
+ const computeReadiness = deps.computeCreateCheckResult ?? computeCreateCheckResult;
112
+ const buildReport = deps.runCreateReport ?? runCreateReport;
113
+ const execSubCommand = deps.runSelftuneSubCommand ?? runSelftuneSubCommand;
114
+ let readiness = await computeReadiness(options.skillPath);
115
+ const autoFix = options.autoFix !== false;
116
+
117
+ if (!readiness.ok && autoFix) {
118
+ for (let i = 0; i < MAX_AUTO_FIX_ITERATIONS; i++) {
119
+ const command = buildAutoFixCommand(readiness, options);
120
+ if (!command) break;
121
+
122
+ process.stderr.write(`[verify] Auto-fixing: selftune ${command.join(" ")}\n`);
123
+
124
+ const result = execSubCommand(command);
125
+ if (result.exitCode !== 0) {
126
+ process.stderr.write(`[verify] Sub-command exited ${result.exitCode}, stopping auto-fix\n`);
127
+ break;
128
+ }
129
+
130
+ // eslint-disable-next-line no-await-in-loop -- each remediation step changes the next readiness state
131
+ readiness = await computeReadiness(options.skillPath);
132
+ if (readiness.ok) break;
133
+ }
134
+ }
135
+
136
+ if (!readiness.ok) {
137
+ return {
138
+ skill: readiness.skill,
139
+ skill_path: readiness.skill_path,
140
+ readiness_state: readiness.state,
141
+ verified: false,
142
+ next_command: readiness.next_command,
143
+ readiness,
144
+ report: null,
145
+ };
146
+ }
147
+
148
+ const report = withPublishRecommendation(
149
+ await buildReport({
150
+ skillPath: readiness.skill_path,
151
+ agent: options.agent,
152
+ evalSetPath: options.evalSetPath,
153
+ }),
154
+ );
155
+
156
+ return {
157
+ skill: readiness.skill,
158
+ skill_path: readiness.skill_path,
159
+ readiness_state: readiness.state,
160
+ verified: report.summary.evaluation_passed,
161
+ next_command: report.summary.next_command,
162
+ readiness,
163
+ report,
164
+ };
165
+ }
166
+
167
+ export async function cliMain(): Promise<void> {
168
+ const { values } = parseArgs({
169
+ options: {
170
+ "skill-path": { type: "string" },
171
+ agent: { type: "string" },
172
+ "eval-set": { type: "string" },
173
+ "no-auto-fix": { type: "boolean", default: false },
174
+ json: { type: "boolean", default: false },
175
+ help: { type: "boolean", short: "h", default: false },
176
+ },
177
+ strict: true,
178
+ });
179
+
180
+ if (values.help) {
181
+ console.log(renderCommandHelp(PUBLIC_COMMAND_SURFACES.verify));
182
+ process.exit(0);
183
+ }
184
+
185
+ const result = await runVerify({
186
+ skillPath: values["skill-path"] ?? "",
187
+ agent: values.agent,
188
+ evalSetPath: values["eval-set"],
189
+ autoFix: !values["no-auto-fix"],
190
+ });
191
+
192
+ if (values.json || !process.stdout.isTTY) {
193
+ console.log(JSON.stringify(result, null, 2));
194
+ } else if (result.report) {
195
+ console.log(formatCreatePackageBenchmarkReport(result.report));
196
+ } else {
197
+ console.log(formatCreateCheckResult(result.readiness));
198
+ }
199
+
200
+ process.exit(result.verified ? 0 : 1);
201
+ }
202
+
203
+ if (import.meta.main) {
204
+ cliMain().catch(handleCLIError);
205
+ }
@@ -129,7 +129,7 @@ export function discoverWorkflowSkillProposals(
129
129
 
130
130
  const summary = buildWorkflowProposalSummary(workflow, draft);
131
131
  const currentValue = `No dedicated workflow skill exists for ${workflow.skills.join(" -> ")}.`;
132
- const proposedValue = `Create ${draft.skill_name} at ${draft.skill_path}`;
132
+ const proposedValue = `Create package ${draft.skill_name} at ${draft.skill_dir}`;
133
133
  const queryClause = workflow.representative_query.trim()
134
134
  ? ` Common trigger: "${workflow.representative_query.trim()}".`
135
135
  : "";
@@ -1,23 +1,20 @@
1
1
  /**
2
2
  * skill-scaffold.ts
3
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.
4
+ * Builds draft workflow skill packages from repeated telemetry-discovered
5
+ * workflows. The draft is preview-first by default so agents can review the
6
+ * scaffold before writing it into a local skill registry.
7
7
  */
8
8
 
9
9
  import { join } from "node:path";
10
10
 
11
+ import type { CreateSkillDraft } from "../create/templates.js";
12
+ import { buildCreateSkillDraft } from "../create/templates.js";
11
13
  import type { DiscoveredWorkflow } from "../types.js";
12
14
  import { findGitRepositoryRoot } from "../utils/skill-discovery.js";
13
15
 
14
- export interface WorkflowSkillDraft {
16
+ export interface WorkflowSkillDraft extends CreateSkillDraft {
15
17
  title: string;
16
- skill_name: string;
17
- description: string;
18
- output_dir: string;
19
- skill_dir: string;
20
- skill_path: string;
21
18
  content: string;
22
19
  source_workflow: {
23
20
  workflow_id: string;
@@ -33,6 +30,7 @@ export interface WorkflowSkillDraftOptions {
33
30
  skillName?: string;
34
31
  description?: string;
35
32
  cwd?: string;
33
+ generatedBy?: string;
36
34
  }
37
35
 
38
36
  const STOPWORDS = new Set([
@@ -85,6 +83,11 @@ function deriveBaseLabel(workflow: DiscoveredWorkflow): string {
85
83
  return `${workflow.skills.join(" ")} workflow`;
86
84
  }
87
85
 
86
+ export function getDefaultWorkflowSkillOutputDir(cwd: string = process.cwd()): string {
87
+ const repoRoot = findGitRepositoryRoot(cwd);
88
+ return join(repoRoot ?? cwd, ".agents", "skills");
89
+ }
90
+
88
91
  function formatList(items: string[]): string {
89
92
  if (items.length === 0) return "";
90
93
  if (items.length === 1) return items[0];
@@ -92,30 +95,6 @@ function formatList(items: string[]): string {
92
95
  return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
93
96
  }
94
97
 
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
98
  export function buildWorkflowSkillDescription(
120
99
  workflow: DiscoveredWorkflow,
121
100
  override?: string,
@@ -131,16 +110,15 @@ export function buildWorkflowSkillDescription(
131
110
  return `Use when the task consistently needs ${chain} in sequence.`;
132
111
  }
133
112
 
134
- export function buildWorkflowSkillContent(
113
+ function buildWorkflowSkillContent(
135
114
  workflow: DiscoveredWorkflow,
136
115
  title: string,
137
116
  skillName: string,
138
117
  description: string,
118
+ generatedBy: string,
139
119
  ): string {
140
- const workflowName = title.endsWith("Workflow") ? title : `${title} Workflow`;
141
120
  const chain = workflow.skills.join(" → ");
142
121
  const query = workflow.representative_query.trim();
143
- const foldedDescription = wrapFoldedScalar(description).join("\n");
144
122
 
145
123
  const whenToUseLines =
146
124
  query.length > 0
@@ -150,49 +128,121 @@ export function buildWorkflowSkillContent(
150
128
  ]
151
129
  : [`- The request repeatedly needs this skill chain: ${chain}`];
152
130
 
153
- const executionPlanLines = workflow.skills.map(
154
- (skill, index) =>
155
- `${index + 1}. Invoke \`${skill}\` in its established role for this workflow.`,
156
- );
157
-
158
131
  return `---
159
132
  name: ${skillName}
160
133
  description: >
161
- ${foldedDescription}
134
+ ${description}
162
135
  metadata:
163
136
  author: selftune-autogen
164
137
  version: 0.1.0
165
- category: developer-tools
166
- generated_by: selftune workflows scaffold
138
+ category: custom
139
+ generated_by: ${generatedBy}
167
140
  source_workflow_id: ${workflow.workflow_id}
168
141
  ---
169
142
 
170
143
  # ${title}
171
144
 
172
- This draft skill was scaffolded by selftune from repeated workflow telemetry.
173
- Review the routing language and execution notes before broad distribution.
145
+ This draft skill package was scaffolded by selftune from repeated workflow
146
+ telemetry. Review the routing language and package contents before broad
147
+ distribution.
174
148
 
175
149
  ## When to Use
176
150
 
177
151
  ${whenToUseLines.join("\n")}
178
152
 
179
- ## Execution Plan
153
+ ## Workflow Routing
180
154
 
181
- ${executionPlanLines.join("\n")}
155
+ | Trigger | Workflow | File |
156
+ | --- | --- | --- |
157
+ | Default execution path | Default | workflows/default.md |
182
158
 
183
- ## Workflows
159
+ ## Package Resources
184
160
 
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)})
161
+ - \`workflows/default.md\` contains the ordered execution steps for the workflow.
162
+ - \`references/overview.md\` preserves the observed provenance and trigger
163
+ evidence from telemetry.
164
+ - \`selftune.create.json\` records the package-level readiness hints for future
165
+ validation.
188
166
 
189
167
  ## Notes
190
168
 
191
- - This is a proposal scaffold, not a silently published marketplace skill.
169
+ - This is a proposal scaffold, not a silently published skill.
170
+ - Source workflow: ${chain}
192
171
  - Add tighter scope boundaries and richer examples before publishing.
193
172
  `;
194
173
  }
195
174
 
175
+ function buildWorkflowDefaultContent(workflow: DiscoveredWorkflow, title: string): string {
176
+ const query = workflow.representative_query.trim();
177
+ const chain = workflow.skills.join(" → ");
178
+ const executionPlanLines = workflow.skills.map(
179
+ (skill, index) =>
180
+ `${index + 1}. Invoke \`${skill}\` in its established role for this workflow.`,
181
+ );
182
+
183
+ return `# ${title} Default Workflow
184
+
185
+ ## When to Use
186
+
187
+ Use this after \`../SKILL.md\` has already matched the request.${query.length > 0 ? ` The representative trigger was "${query}".` : ""}
188
+
189
+ ## Goal
190
+
191
+ Coordinate this repeated multi-skill chain without making the top-level router
192
+ carry all of the execution detail: ${chain}.
193
+
194
+ ## Steps
195
+
196
+ ${executionPlanLines.join("\n")}
197
+
198
+ ## Notes
199
+
200
+ - Provenance lives in \`../references/overview.md\`.
201
+ - Replace these placeholders with concrete execution mechanics before shipping.
202
+ `;
203
+ }
204
+
205
+ function buildWorkflowReferenceContent(workflow: DiscoveredWorkflow, title: string): string {
206
+ const query = workflow.representative_query.trim();
207
+ const chain = workflow.skills.join(" → ");
208
+
209
+ return `# ${title} Overview
210
+
211
+ This package was scaffolded from observed workflow telemetry.
212
+
213
+ ## Provenance
214
+
215
+ - Workflow ID: ${workflow.workflow_id}
216
+ - Skills: ${chain}
217
+ - Observed sessions: ${workflow.occurrence_count}
218
+ - Synergy score: ${workflow.synergy_score.toFixed(2)}
219
+ - Sequence consistency: ${Math.round(workflow.sequence_consistency * 100)}%
220
+ - Completion rate: ${Math.round(workflow.completion_rate * 100)}%
221
+ ${query.length > 0 ? `- Representative trigger: ${query}` : "- Representative trigger: not captured"}
222
+
223
+ ## Authoring Notes
224
+
225
+ - Keep the router lean in \`../SKILL.md\`.
226
+ - Move detailed steps into \`../workflows/default.md\`.
227
+ - Expand this file with examples, vocabulary, and edge cases as the skill matures.
228
+ `;
229
+ }
230
+
231
+ function replaceDraftFile(
232
+ draft: CreateSkillDraft,
233
+ relativePath: string,
234
+ content: string,
235
+ ): CreateSkillDraft["files"] {
236
+ return draft.files.map((file) =>
237
+ file.relative_path === relativePath
238
+ ? {
239
+ ...file,
240
+ content,
241
+ }
242
+ : file,
243
+ );
244
+ }
245
+
196
246
  export function buildWorkflowSkillDraft(
197
247
  workflow: DiscoveredWorkflow,
198
248
  options: WorkflowSkillDraftOptions = {},
@@ -201,18 +251,38 @@ export function buildWorkflowSkillDraft(
201
251
  const skillName = slugifyWorkflowSkillName(baseLabel);
202
252
  const title = titleCase(baseLabel) || titleCase(`${workflow.skills.join(" ")} workflow`);
203
253
  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");
254
+ const generatedBy = options.generatedBy?.trim() || "selftune workflows scaffold";
255
+ const baseDraft = buildCreateSkillDraft({
256
+ name: title,
257
+ description,
258
+ outputDir: options.outputDir,
259
+ cwd: options.cwd,
260
+ });
261
+ const skillContent = buildWorkflowSkillContent(
262
+ workflow,
263
+ title,
264
+ skillName,
265
+ description,
266
+ generatedBy,
267
+ );
268
+ const workflowContent = buildWorkflowDefaultContent(workflow, title);
269
+ const referenceContent = buildWorkflowReferenceContent(workflow, title);
270
+ const files = replaceDraftFile(baseDraft, "SKILL.md", skillContent).map((file) => {
271
+ if (file.relative_path === "workflows/default.md") {
272
+ return { ...file, content: workflowContent };
273
+ }
274
+ if (file.relative_path === "references/overview.md") {
275
+ return { ...file, content: referenceContent };
276
+ }
277
+ return file;
278
+ });
207
279
 
208
- return {
280
+ const draft: WorkflowSkillDraft = {
281
+ ...baseDraft,
209
282
  title,
210
283
  skill_name: skillName,
211
- description,
212
- output_dir: outputDir,
213
- skill_dir: skillDir,
214
- skill_path: skillPath,
215
- content: buildWorkflowSkillContent(workflow, title, skillName, description),
284
+ files,
285
+ content: "",
216
286
  source_workflow: {
217
287
  workflow_id: workflow.workflow_id,
218
288
  skills: workflow.skills,
@@ -221,6 +291,9 @@ export function buildWorkflowSkillDraft(
221
291
  representative_query: workflow.representative_query,
222
292
  },
223
293
  };
294
+
295
+ draft.content = formatWorkflowSkillDraft(draft);
296
+ return draft;
224
297
  }
225
298
 
226
299
  export function formatWorkflowSkillDraft(draft: WorkflowSkillDraft): string {
@@ -236,6 +309,11 @@ export function formatWorkflowSkillDraft(draft: WorkflowSkillDraft): string {
236
309
  lines.push(`Representative query: "${draft.source_workflow.representative_query.trim()}"`);
237
310
  }
238
311
 
239
- lines.push("", draft.content.trimEnd());
312
+ lines.push("");
313
+ for (const file of draft.files) {
314
+ lines.push(`=== ${file.relative_path} ===`, file.content.trimEnd(), "");
315
+ }
316
+
317
+ lines.push("Empty directories:", " - scripts/", " - assets/");
240
318
  return lines.join("\n");
241
319
  }
@@ -8,9 +8,10 @@
8
8
  * - cliMain() (reads logs, discovers workflows, prints output or saves)
9
9
  */
10
10
 
11
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
12
12
  import { parseArgs } from "node:util";
13
13
 
14
+ import { writeCreateSkillDraft } from "../create/init.js";
14
15
  import { getDb } from "../localdb/db.js";
15
16
  import { querySessionTelemetry, querySkillUsageRecords } from "../localdb/queries.js";
16
17
  import type {
@@ -242,13 +243,12 @@ Options:
242
243
  );
243
244
  }
244
245
 
245
- mkdirSync(draft.skill_dir, { recursive: true });
246
- writeFileSync(draft.skill_path, draft.content, "utf-8");
246
+ writeCreateSkillDraft(draft, { force: values.force });
247
247
 
248
248
  if (values.json || !process.stdout.isTTY) {
249
249
  console.log(JSON.stringify({ ...draft, written: true }, null, 2));
250
250
  } else {
251
- console.log(`Scaffolded skill "${draft.skill_name}" to ${draft.skill_path}`);
251
+ console.log(`Scaffolded skill package "${draft.skill_name}" to ${draft.skill_dir}`);
252
252
  }
253
253
  return;
254
254
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selftune",
3
- "version": "0.2.31",
3
+ "version": "0.2.32",
4
4
  "description": "Skill-level observability and self-improvement for AI agents — monitors skill routing, detects missed triggers, and evolves descriptions automatically",
5
5
  "keywords": [
6
6
  "agent",