skilld 0.9.5 → 0.10.0

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.
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./chunk.mjs";
2
2
  import { _ as writeSections, b as sanitizeMarkdown, h as readCachedSection, y as repairMarkdown } from "./storage.mjs";
3
- import { d as getPackageRules, o as mapInsert, t as yamlEscape, u as getFilePatterns } from "./yaml.mjs";
3
+ import { o as getFilePatterns, s as getPackageRules, t as yamlEscape } from "./yaml.mjs";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, join, relative } from "pathe";
6
6
  import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
@@ -8,6 +8,7 @@ import { exec, spawn, spawnSync } from "node:child_process";
8
8
  import { globby } from "globby";
9
9
  import { findDynamicImports, findStaticImports } from "mlly";
10
10
  import { createHash } from "node:crypto";
11
+ import { setTimeout } from "node:timers/promises";
11
12
  import { promisify } from "node:util";
12
13
  import { readFile } from "node:fs/promises";
13
14
  import { parseSync } from "oxc-parser";
@@ -610,7 +611,15 @@ function apiChangesSection({ packageName, version, hasReleases, hasChangelog, ha
610
611
  useFor: "Skip unless searching a specific removed API"
611
612
  });
612
613
  const releaseGuidance = hasReleases ? `\n\n**Scan release history:** Read \`./.skilld/releases/_INDEX.md\` for a timeline. Focus on [MAJOR] and [MINOR] releases — these contain breaking changes and renamed/deprecated APIs that LLMs trained on older data will get wrong.` : "";
613
- const versionGuidance = major && minor ? `\n\n**New APIs in recent releases are the highest-priority gaps** — the LLM was trained on older data and will use outdated or non-existent APIs instead. Search for recent version tags and "Features" in releases/changelog to find new composables, components, hooks, or utilities added in recent major/minor versions.` : "";
614
+ const versionGuidance = major && minor ? `\n\n**Item scoring** — include only items scoring 3:
615
+
616
+ | Change type | v${major}.x | v${Number(major) - 1}.x | Older |
617
+ |-------------|:---:|:---:|:---:|
618
+ | Silent breakage (compiles, wrong result) | 5 | 4 | 2 |
619
+ | Removed/breaking API | 5 | 3 | 0 |
620
+ | New API unknown to LLMs | 4 | 1 | 0 |
621
+ | Deprecated (still works) | 3 | 1 | 0 |
622
+ | Renamed/moved | 3 | 1 | 0 |` : "";
614
623
  return {
615
624
  referenceWeights,
616
625
  task: `**Find new, deprecated, and renamed APIs from version history.** Focus exclusively on APIs that changed between versions — LLMs trained on older data will use the wrong names, wrong signatures, or non-existent functions.
@@ -621,26 +630,24 @@ Find from releases/changelog:
621
630
  - **Signature changes** where old code compiles but behaves wrong (changed parameter order, return types, default values)
622
631
  - **Breaking changes** in recent versions (v2 → v3 migrations, major version bumps)
623
632
  ${searchHints.length ? `\nSearch: ${searchHints.join(", ")}` : ""}${releaseGuidance}${versionGuidance}`,
624
- format: `## API Changes
633
+ format: `<format-example note="Illustrative structure only — replace placeholder names with real ${packageName} APIs">
634
+ ## API Changes
625
635
 
626
636
  This section documents version-specific API changes — prioritize recent major/minor releases.
627
637
 
628
- \`\`\`
629
- ## API Changes
630
-
631
- ⚠️ \`createClient(url, key)\` — v2 changed to \`createClient({ url, key })\`, old positional args silently ignored [source](./.skilld/releases/v2.0.0.md)
638
+ - BREAKING: \`createClient(url, key)\` — v2 changed to \`createClient({ url, key })\`, old positional args silently ignored [source](./.skilld/releases/v2.0.0.md)
632
639
 
633
- \`useTemplateRef()\` — new in v3.5, replaces \`$refs\` pattern [source](./.skilld/releases/v3.5.0.md)
640
+ - NEW: \`useTemplateRef()\` — new in v3.5, replaces \`$refs\` pattern [source](./.skilld/releases/v3.5.0.md)
634
641
 
635
- ⚠️ \`db.query()\` — returns \`{ rows }\` not raw array since v4 [source](./.skilld/docs/migration.md)
636
- \`\`\`
642
+ - BREAKING: \`db.query()\` — returns \`{ rows }\` not raw array since v4 [source](./.skilld/docs/migration.md)
643
+ </format-example>
637
644
 
638
- Each item: ⚠️ (breaking/deprecated) or ✨ (new) + API name + what changed + source link.`,
645
+ Each item: BREAKING/DEPRECATED/NEW label + API name + what changed + source link. All source links MUST use \`./.skilld/\` prefix (e.g., \`[source](./.skilld/releases/v2.0.0.md)\`). Do NOT use emoji — use plain text markers only.`,
639
646
  rules: [
640
647
  `- **API Changes:** ${maxItems(6, 12, enabledSectionCount)} items from version history, MAX ${maxLines(50, 80, enabledSectionCount)} lines`,
641
648
  "- Prioritize recent major/minor releases over old patch versions",
642
649
  "- Focus on APIs that CHANGED, not general conventions or gotchas",
643
- "- New APIs get ✨, deprecated/breaking get ⚠️",
650
+ "- New APIs get NEW: prefix, deprecated/breaking get BREAKING: or DEPRECATED: prefix",
644
651
  hasReleases ? "- Start with `./.skilld/releases/_INDEX.md` to identify recent major/minor releases, then read specific release files" : "",
645
652
  hasChangelog ? "- Scan CHANGELOG.md for version headings, focus on Features/Breaking Changes sections" : ""
646
653
  ].filter(Boolean)
@@ -659,14 +666,14 @@ function bestPracticesSection({ packageName, hasIssues, hasDiscussions, hasRelea
659
666
  if (hasDiscussions) referenceWeights.push({
660
667
  name: "Discussions",
661
668
  path: "./.skilld/discussions/_INDEX.md",
662
- score: 8,
663
- useFor: "Q&A with accepted answers reveal \"the right way\""
669
+ score: 5,
670
+ useFor: "Only maintainer-confirmed patterns community workarounds are lower confidence"
664
671
  });
665
672
  if (hasIssues) referenceWeights.push({
666
673
  name: "Issues",
667
674
  path: "./.skilld/issues/_INDEX.md",
668
- score: 7,
669
- useFor: "Questions reveal what users find confusing"
675
+ score: 4,
676
+ useFor: "Only workarounds confirmed by maintainers or with broad adoption"
670
677
  });
671
678
  if (hasReleases) referenceWeights.push({
672
679
  name: "Releases",
@@ -682,33 +689,38 @@ function bestPracticesSection({ packageName, hasIssues, hasDiscussions, hasRelea
682
689
  });
683
690
  return {
684
691
  referenceWeights,
685
- task: `**Extract non-obvious best practices from the references.** Focus on recommended patterns Claude wouldn't already know: idiomatic usage, preferred configurations, performance tips, patterns that differ from what a developer would assume. Surface new patterns from recent minor releases that may post-date training data. Every item must link to a verified source file.
692
+ task: `**Extract non-obvious best practices from the references.** Focus on recommended patterns the LLM wouldn't already know: idiomatic usage, preferred configurations, performance tips, patterns that differ from what a developer would assume. Surface new patterns from recent minor releases that may post-date training data.
686
693
 
687
- Skip: obvious API usage, installation steps, general TypeScript/programming patterns, anything a developer would naturally write without reading the docs.
694
+ Skip: obvious API usage, installation steps, general TypeScript/programming patterns not specific to this package, anything a developer would naturally write without reading the docs. Every item must be specific to ${packageName} — reject general programming advice that applies to any project.
688
695
  ${searchHints.length ? `\nSearch: ${searchHints.join(", ")}` : ""}`,
689
- format: `\`\`\`
696
+ format: `<format-example note="Illustrative structure only — replace placeholder names with real ${packageName} APIs">
697
+ \`\`\`
690
698
  ## Best Practices
691
699
 
692
- Pass \`AbortSignal\` to long-lived operationsenables caller-controlled cancellation [source](./.skilld/docs/api.md)
700
+ - Use ${packageName}'s built-in \`createX()\` helper over manual wiring handles cleanup and edge cases automatically [source](./.skilld/docs/api.md)
693
701
 
694
702
  \`\`\`ts
695
- async function fetchUser(id: string, signal?: AbortSignal) {
696
- return fetch(\`/api/users/\${id}\`, { signal })
697
- }
698
- \`\`\`
703
+ // Preferred
704
+ const instance = createX({ ... })
699
705
 
700
- Use \`satisfies\` for config objects preserves literal types while validating shape [source](./.skilld/docs/config.md)
706
+ // Avoidmisses cleanup, error boundaries
707
+ const instance = new X()
708
+ instance.init({ ... })
709
+ \`\`\`
701
710
 
702
- Prefer \`structuredClone()\` over spread for deep copies handles nested objects, Maps, Sets [source](./.skilld/docs/utilities.md)
711
+ - Pass config through \`defineConfig()\` enables type inference and plugin merging [source](./.skilld/docs/config.md)
703
712
 
704
- Set \`isolatedDeclarations: true\` enables parallel .d.ts emit without full type-checking [source](./.skilld/docs/typescript.md)
713
+ - Prefer \`useComposable()\` over direct imports in reactive contexts ensures proper lifecycle binding [source](./.skilld/docs/composables.md)
705
714
  \`\`\`
715
+ </format-example>
706
716
 
707
- Each item: + pattern name + why it's preferred + source link. Code block only when the pattern isn't obvious from the title. Use the most relevant language tag (ts, vue, css, json, etc).`,
717
+ Each item: markdown list item (-) + ${packageName}-specific pattern + why it's preferred + source link. Code block only when the pattern isn't obvious from the title. Use the most relevant language tag (ts, vue, css, json, etc). Every example must be specific to ${packageName} — never generic TypeScript/JS advice. All source links MUST use \`./.skilld/\` prefix (e.g., \`[source](./.skilld/docs/guide.md)\`). Do NOT use emoji — use plain text markers only.`,
708
718
  rules: [
709
719
  `- **${maxItems(4, 10, enabledSectionCount)} best practice items**`,
710
720
  `- **MAX ${maxLines(80, 150, enabledSectionCount)} lines** for best practices section`,
711
- "- **Only link files confirmed to exist** via Glob or Read no guessed paths"
721
+ "- **Verify before including:** Confirm file paths exist via Glob/Read before linking. Confirm functions/composables are real exports in `./.skilld/pkg/` `.d.ts` files before documenting",
722
+ "- **Diversity:** Cover at least 3 distinct areas of the library. No single feature should have more than 40% of items",
723
+ "- **Experimental APIs:** Mark unstable/experimental features with `(experimental)` in the description. Prioritize stable patterns"
712
724
  ]
713
725
  };
714
726
  }
@@ -813,8 +825,11 @@ function formatDocTree(files) {
813
825
  }
814
826
  return [...dirs.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([dir, count]) => `- \`${dir}/\` (${count} .md files)`).join("\n");
815
827
  }
816
- function generateImportantBlock({ packageName, hasIssues, hasDiscussions, hasReleases, hasChangelog, docsType, hasShippedDocs, skillDir, features }) {
817
- const rows = [["Docs", hasShippedDocs ? `\`${skillDir}/.skilld/pkg/docs/\` or \`${skillDir}/.skilld/pkg/README.md\`` : docsType === "llms.txt" ? `\`${skillDir}/.skilld/docs/llms.txt\`` : docsType === "readme" ? `\`${skillDir}/.skilld/pkg/README.md\`` : `\`${skillDir}/.skilld/docs/\``], ["Package", `\`${skillDir}/.skilld/pkg/\``]];
828
+ function generateImportantBlock({ packageName, hasIssues, hasDiscussions, hasReleases, hasChangelog, docsType, hasShippedDocs, skillDir, features, pkgFiles }) {
829
+ const docsPath = hasShippedDocs ? `\`${skillDir}/.skilld/pkg/docs/\` or \`${skillDir}/.skilld/pkg/README.md\`` : docsType === "llms.txt" ? `\`${skillDir}/.skilld/docs/llms.txt\`` : docsType === "readme" ? `\`${skillDir}/.skilld/pkg/README.md\`` : `\`${skillDir}/.skilld/docs/\``;
830
+ const typesFile = pkgFiles?.find((f) => f.endsWith(".d.ts"));
831
+ const rows = [["Docs", docsPath], ["Package", `\`${skillDir}/.skilld/pkg/\``]];
832
+ if (typesFile) rows.push(["Types", `\`${skillDir}/.skilld/pkg/${typesFile}\` — **read this file directly** to verify exports`]);
818
833
  if (hasIssues) rows.push(["Issues", `\`${skillDir}/.skilld/issues/\``]);
819
834
  if (hasDiscussions) rows.push(["Discussions", `\`${skillDir}/.skilld/discussions/\``]);
820
835
  if (hasChangelog) rows.push(["Changelog", `\`${skillDir}/.skilld/${hasChangelog}\``]);
@@ -857,18 +872,10 @@ ${generateImportantBlock({
857
872
  docsType,
858
873
  hasShippedDocs,
859
874
  skillDir,
860
- features: opts.features
875
+ features: opts.features,
876
+ pkgFiles: opts.pkgFiles
861
877
  })}
862
- ${docsSection ? `${docsSection}\n` : ""}
863
-
864
- ## Skill Quality Principles
865
-
866
- The context window is a shared resource. Skills share it with system prompt, conversation history, other skills, and the user request.
867
-
868
- - **Only add what Claude doesn't know.** Claude already knows general programming, popular APIs, common patterns. Challenge every line: "Does this justify its token cost?"
869
- - **Prefer concise examples over verbose explanations.** A 2-line code example beats a paragraph.
870
- - **Skip:** API signatures, installation steps, tutorials, marketing, general programming knowledge, anything in the package README that's obvious
871
- - **Include:** Non-obvious gotchas, surprising defaults, version-specific breaking changes, pitfalls from issues, patterns that differ from what Claude would assume`;
878
+ ${docsSection ? `${docsSection}\n` : ""}`;
872
879
  }
873
880
  function getSectionDef(section, ctx, customPrompt) {
874
881
  switch (section) {
@@ -901,14 +908,14 @@ function buildSectionPrompt(opts) {
901
908
  const packageRules = getPackageRules(packageName);
902
909
  const rules = [
903
910
  ...sectionDef.rules ?? [],
904
- "- Link to exact source file where you found info",
905
- "- TypeScript only",
906
911
  ...packageRules.map((r) => `- ${r}`),
907
- "- Imperative voice (\"Use X\" not \"You should use X\")",
908
912
  `- **NEVER fetch external URLs.** All information is in the local \`./.skilld/\` directory. Use Read, Glob${opts.features?.search !== false ? ", and `skilld search`" : ""} only.`,
909
913
  "- **Do NOT use Task tool or spawn subagents.** Work directly.",
910
914
  "- **Do NOT re-read files** you have already read in this session.",
911
- "- **Read `_INDEX.md` first** in issues/releases/discussions — only drill into files that look relevant. Skip stub/placeholder files."
915
+ "- **Read `_INDEX.md` first** in issues/releases/discussions — only drill into files that look relevant. Skip stub/placeholder files.",
916
+ "- **Skip files starting with `PROMPT_`** — these are generation prompts, not reference material.",
917
+ "- **Stop exploring once you have enough high-quality items** to fill the budget. Do not read additional files just to be thorough.",
918
+ "- **To verify API exports:** Read the `.d.ts` file directly (see Types row in references). Do NOT use search with relative paths or `include` filters on package directories — they may silently return no results."
912
919
  ];
913
920
  return `${preamble}${sectionDef.referenceWeights?.length ? `\n\n## Reference Priority\n\n| Reference | Path | Score | Use For |\n|-----------|------|:-----:|--------|\n${sectionDef.referenceWeights.map((w) => `| ${w.name} | [\`${w.path.split("/").pop()}\`](${w.path}) | ${w.score}/10 | ${w.useFor} |`).join("\n")}` : ""}
914
921
 
@@ -1003,23 +1010,28 @@ function unlinkSkillFromAgents(skillName, cwd) {
1003
1010
  function generateSkillMd(opts) {
1004
1011
  const header = generatePackageHeader(opts);
1005
1012
  const search = !opts.eject && opts.features?.search !== false ? generateSearchBlock(opts.name, opts.hasIssues, opts.hasReleases) : "";
1006
- const content = opts.body ? search ? `${header}\n\n${search}\n\n${opts.body}` : `${header}\n\n${opts.body}` : search ? `${header}\n\n${search}` : header;
1013
+ const body = opts.body && opts.eject ? opts.body.replace(/\.\/\.skilld\//g, "./references/") : opts.body;
1014
+ const content = body ? search ? `${header}\n\n${search}\n\n${body}` : `${header}\n\n${body}` : search ? `${header}\n\n${search}` : header;
1007
1015
  const footer = generateFooter(opts.relatedSkills);
1008
1016
  return sanitizeMarkdown(repairMarkdown(`${generateFrontmatter(opts)}${content}\n${footer}`));
1009
1017
  }
1010
- function formatRelativeDate(isoDate) {
1018
+ function formatShortDate(isoDate) {
1011
1019
  const date = new Date(isoDate);
1012
- const diffMs = (/* @__PURE__ */ new Date()).getTime() - date.getTime();
1013
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
1014
- if (diffDays === 0) return "today";
1015
- if (diffDays === 1) return "yesterday";
1016
- if (diffDays < 7) return `${diffDays} day${diffDays === 1 ? "" : "s"} ago`;
1017
- const weeks = Math.floor(diffDays / 7);
1018
- if (diffDays < 30) return `${weeks} week${weeks === 1 ? "" : "s"} ago`;
1019
- const months = Math.floor(diffDays / 30);
1020
- if (diffDays < 365) return `${months} month${months === 1 ? "" : "s"} ago`;
1021
- const years = Math.floor(diffDays / 365);
1022
- return `${years} year${years === 1 ? "" : "s"} ago`;
1020
+ if (Number.isNaN(date.getTime())) return "";
1021
+ return `${[
1022
+ "Jan",
1023
+ "Feb",
1024
+ "Mar",
1025
+ "Apr",
1026
+ "May",
1027
+ "Jun",
1028
+ "Jul",
1029
+ "Aug",
1030
+ "Sep",
1031
+ "Oct",
1032
+ "Nov",
1033
+ "Dec"
1034
+ ][date.getUTCMonth()]} ${date.getUTCFullYear()}`;
1023
1035
  }
1024
1036
  function generatePackageHeader({ name, description, version, releasedAt, dependencies, distTags, repoUrl, hasIssues, hasDiscussions, hasReleases, pkgFiles, packages, eject }) {
1025
1037
  let title = `# ${name}`;
@@ -1030,8 +1042,8 @@ function generatePackageHeader({ name, description, version, releasedAt, depende
1030
1042
  const lines = [title];
1031
1043
  if (description) lines.push("", `> ${description}`);
1032
1044
  if (version) {
1033
- const relativeDate = releasedAt ? formatRelativeDate(releasedAt) : "";
1034
- const versionStr = relativeDate ? `${version} (${relativeDate})` : version;
1045
+ const dateStr = releasedAt ? formatShortDate(releasedAt) : "";
1046
+ const versionStr = dateStr ? `${version} (${dateStr})` : version;
1035
1047
  lines.push("", `**Version:** ${versionStr}`);
1036
1048
  }
1037
1049
  if (dependencies && Object.keys(dependencies).length > 0) {
@@ -1040,7 +1052,7 @@ function generatePackageHeader({ name, description, version, releasedAt, depende
1040
1052
  }
1041
1053
  if (distTags && Object.keys(distTags).length > 0) {
1042
1054
  const tags = Object.entries(distTags).map(([tag, info]) => {
1043
- const relDate = info.releasedAt ? ` (${formatRelativeDate(info.releasedAt)})` : "";
1055
+ const relDate = info.releasedAt ? ` (${formatShortDate(info.releasedAt)})` : "";
1044
1056
  return `${tag}: ${info.version}${relDate}`;
1045
1057
  }).join(", ");
1046
1058
  lines.push(`**Tags:** ${tags}`);
@@ -1359,11 +1371,17 @@ function parseLine(line) {
1359
1371
  if (obj.type === "message" && obj.role === "assistant" && obj.content) return obj.delta ? { textDelta: obj.content } : { fullText: obj.content };
1360
1372
  if (obj.type === "tool_use" || obj.type === "tool_call") {
1361
1373
  const name = obj.tool_name || obj.name || obj.tool || "tool";
1362
- if (name === "write_file" && obj.args?.content) return {
1374
+ const params = obj.parameters || obj.args || obj.input || {};
1375
+ const hint = params.file_path || params.path || params.dir_path || params.pattern || params.query || params.command || "";
1376
+ if (name === "write_file" && params.content) return {
1363
1377
  toolName: name,
1364
- writeContent: obj.args.content
1378
+ toolHint: hint || void 0,
1379
+ writeContent: params.content
1380
+ };
1381
+ return {
1382
+ toolName: name,
1383
+ toolHint: hint || void 0
1365
1384
  };
1366
- return { toolName: name };
1367
1385
  }
1368
1386
  if (obj.type === "result") {
1369
1387
  const s = obj.stats;
@@ -1392,58 +1410,39 @@ const TOOL_VERBS = {
1392
1410
  search_file_content: "Searching"
1393
1411
  };
1394
1412
  function createToolProgress(log) {
1395
- const pending = /* @__PURE__ */ new Map();
1396
- let timer = null;
1397
- let lastEmitted = "";
1398
- function flush() {
1399
- const parts = [];
1400
- for (const [section, { verb, path, count }] of pending) {
1401
- const suffix = count > 1 ? ` \x1B[90m(+${count - 1})\x1B[0m` : "";
1402
- parts.push(`\x1B[90m[${section}]\x1B[0m ${verb} ${path}${suffix}`);
1403
- }
1404
- const msg = parts.join(" ");
1405
- if (msg && msg !== lastEmitted) {
1413
+ let lastMsg = "";
1414
+ let repeatCount = 0;
1415
+ function emit(msg) {
1416
+ if (msg === lastMsg) {
1417
+ repeatCount++;
1418
+ log.message(`${msg} \x1B[90m(+${repeatCount})\x1B[0m`);
1419
+ } else {
1420
+ lastMsg = msg;
1421
+ repeatCount = 0;
1406
1422
  log.message(msg);
1407
- lastEmitted = msg;
1408
1423
  }
1409
- pending.clear();
1410
- timer = null;
1411
1424
  }
1412
1425
  return ({ type, chunk, section }) => {
1413
1426
  if (type === "text") {
1414
- log.message(`${section ? `\x1B[90m[${section}]\x1B[0m ` : ""}Writing...`);
1427
+ emit(`${section ? `\x1B[90m[${section}]\x1B[0m ` : ""}Writing...`);
1415
1428
  return;
1416
1429
  }
1417
1430
  if (type !== "reasoning" || !chunk.startsWith("[")) return;
1418
- const key = section ?? "";
1419
- const match = chunk.match(/^\[(\w+)(?:,\s\w+)*(?::\s(.+))?\]$/);
1431
+ const match = chunk.match(/^\[([^:[\]]+)(?::\s(.+))?\]$/);
1420
1432
  if (!match) return;
1421
- const rawName = match[1];
1422
- const hint = match[2] ?? "";
1423
- let verb = TOOL_VERBS[rawName] ?? rawName;
1424
- let path = hint || "...";
1425
- if (rawName === "Bash" && hint) {
1426
- const searchMatch = hint.match(/skilld search\s+"([^"]+)"/);
1427
- if (searchMatch) {
1428
- verb = "skilld search:";
1429
- path = searchMatch[1];
1430
- } else path = hint.length > 60 ? `${hint.slice(0, 57)}...` : hint;
1431
- } else path = shortenPath(path);
1432
- if (rawName === "Write") {
1433
- if (timer) flush();
1433
+ const names = match[1].split(",").map((n) => n.trim());
1434
+ const hints = match[2]?.split(",").map((h) => h.trim()) ?? [];
1435
+ for (let i = 0; i < names.length; i++) {
1436
+ const rawName = names[i];
1437
+ const hint = hints[i] ?? hints[0] ?? "";
1438
+ const verb = TOOL_VERBS[rawName] ?? rawName;
1434
1439
  const prefix = section ? `\x1B[90m[${section}]\x1B[0m ` : "";
1435
- log.message(`${prefix}Writing ${path}`);
1436
- return;
1440
+ if (rawName === "Bash" && hint) {
1441
+ const searchMatch = hint.match(/skilld search\s+"([^"]+)"/);
1442
+ if (searchMatch) emit(`${prefix}Searching \x1B[36m"${searchMatch[1]}"\x1B[0m`);
1443
+ else emit(`${prefix}Running ${hint.length > 50 ? `${hint.slice(0, 47)}...` : hint}`);
1444
+ } else emit(`${prefix}${verb} \x1B[90m${shortenPath(hint || "...")}\x1B[0m`);
1437
1445
  }
1438
- const entry = mapInsert(pending, key, () => ({
1439
- verb,
1440
- path,
1441
- count: 0
1442
- }));
1443
- entry.verb = verb;
1444
- entry.path = path;
1445
- entry.count++;
1446
- if (!timer) timer = setTimeout(flush, 400);
1447
1446
  };
1448
1447
  }
1449
1448
  const CLI_DEFS = [
@@ -1622,13 +1621,16 @@ function optimizeSection(opts) {
1622
1621
  } catch {}
1623
1622
  }
1624
1623
  const raw = (existsSync(outputPath) ? readFileSync(outputPath, "utf-8") : lastWriteContent || accumulatedText).trim();
1624
+ const logsDir = join(skilldDir, "logs");
1625
+ const logName = section.toUpperCase().replace(/-/g, "_");
1626
+ if (debug || stderr && (!raw || code !== 0)) {
1627
+ mkdirSync(logsDir, { recursive: true });
1628
+ if (stderr) writeFileSync(join(logsDir, `${logName}.stderr.log`), stderr);
1629
+ }
1625
1630
  if (debug) {
1626
- const logsDir = join(skilldDir, "logs");
1627
1631
  mkdirSync(logsDir, { recursive: true });
1628
- const logName = section.toUpperCase().replace(/-/g, "_");
1629
1632
  if (rawLines.length) writeFileSync(join(logsDir, `${logName}.jsonl`), rawLines.join("\n"));
1630
1633
  if (raw) writeFileSync(join(logsDir, `${logName}.md`), raw);
1631
- if (stderr) writeFileSync(join(logsDir, `${logName}.stderr.log`), stderr);
1632
1634
  }
1633
1635
  if (!raw && code !== 0) {
1634
1636
  resolve({
@@ -1662,7 +1664,7 @@ function optimizeSection(opts) {
1662
1664
  });
1663
1665
  }
1664
1666
  async function optimizeDocs(opts) {
1665
- const { packageName, skillDir, model = "sonnet", version, hasGithub, hasReleases, hasChangelog, docFiles, docsType, hasShippedDocs, onProgress, timeout = 18e4, debug, noCache, sections, customPrompt, features } = opts;
1667
+ const { packageName, skillDir, model = "sonnet", version, hasGithub, hasReleases, hasChangelog, docFiles, docsType, hasShippedDocs, onProgress, timeout = 18e4, debug, noCache, sections, customPrompt, features, pkgFiles } = opts;
1666
1668
  const sectionPrompts = buildAllSectionPrompts({
1667
1669
  packageName,
1668
1670
  skillDir,
@@ -1676,6 +1678,7 @@ async function optimizeDocs(opts) {
1676
1678
  hasShippedDocs,
1677
1679
  customPrompt,
1678
1680
  features,
1681
+ pkgFiles,
1679
1682
  sections: sections ?? [
1680
1683
  "api-changes",
1681
1684
  "best-practices",
@@ -1739,10 +1742,22 @@ async function optimizeDocs(opts) {
1739
1742
  }
1740
1743
  const skilldDir = join(skillDir, ".skilld");
1741
1744
  mkdirSync(skilldDir, { recursive: true });
1745
+ for (const entry of readdirSync(skilldDir)) {
1746
+ const entryPath = join(skilldDir, entry);
1747
+ try {
1748
+ if (lstatSync(entryPath).isSymbolicLink() && !existsSync(entryPath)) onProgress?.({
1749
+ chunk: `[warn: broken symlink .skilld/${entry}]`,
1750
+ type: "reasoning",
1751
+ text: "",
1752
+ reasoning: ""
1753
+ });
1754
+ } catch {}
1755
+ }
1742
1756
  const preExistingFiles = new Set(readdirSync(skilldDir));
1743
- const spawnResults = uncachedSections.length > 0 ? await Promise.allSettled(uncachedSections.map(({ section, prompt }) => {
1757
+ const STAGGER_MS = 3e3;
1758
+ const spawnResults = uncachedSections.length > 0 ? await Promise.allSettled(uncachedSections.map(({ section, prompt }, i) => {
1744
1759
  const outputFile = SECTION_OUTPUT_FILES[section];
1745
- return optimizeSection({
1760
+ const run = () => optimizeSection({
1746
1761
  section,
1747
1762
  prompt,
1748
1763
  outputFile,
@@ -1754,32 +1769,71 @@ async function optimizeDocs(opts) {
1754
1769
  debug,
1755
1770
  preExistingFiles
1756
1771
  });
1772
+ if (i === 0) return run();
1773
+ return setTimeout(i * STAGGER_MS).then(run);
1757
1774
  })) : [];
1758
1775
  const allResults = [...cachedResults];
1759
1776
  let totalUsage;
1760
1777
  let totalCost = 0;
1778
+ const retryQueue = [];
1761
1779
  for (let i = 0; i < spawnResults.length; i++) {
1762
1780
  const r = spawnResults[i];
1763
1781
  const { section, prompt } = uncachedSections[i];
1764
- if (r.status === "fulfilled") {
1765
- const result = r.value;
1766
- allResults.push(result);
1767
- if (result.wasOptimized && !noCache) setCache(prompt, model, section, result.content);
1768
- if (result.usage) {
1782
+ if (r.status === "fulfilled" && r.value.wasOptimized) {
1783
+ allResults.push(r.value);
1784
+ if (r.value.usage) {
1769
1785
  totalUsage = totalUsage ?? {
1770
1786
  input: 0,
1771
1787
  output: 0
1772
1788
  };
1773
- totalUsage.input += result.usage.input;
1774
- totalUsage.output += result.usage.output;
1789
+ totalUsage.input += r.value.usage.input;
1790
+ totalUsage.output += r.value.usage.output;
1775
1791
  }
1776
- if (result.cost != null) totalCost += result.cost;
1777
- } else allResults.push({
1792
+ if (r.value.cost != null) totalCost += r.value.cost;
1793
+ if (!noCache) setCache(prompt, model, section, r.value.content);
1794
+ } else retryQueue.push({
1795
+ index: i,
1796
+ section,
1797
+ prompt
1798
+ });
1799
+ }
1800
+ for (const { section, prompt } of retryQueue) {
1801
+ onProgress?.({
1802
+ chunk: `[${section}: retrying...]`,
1803
+ type: "reasoning",
1804
+ text: "",
1805
+ reasoning: "",
1806
+ section
1807
+ });
1808
+ await setTimeout(STAGGER_MS);
1809
+ const result = await optimizeSection({
1810
+ section,
1811
+ prompt,
1812
+ outputFile: SECTION_OUTPUT_FILES[section],
1813
+ skillDir,
1814
+ model,
1815
+ packageName,
1816
+ onProgress,
1817
+ timeout,
1818
+ debug,
1819
+ preExistingFiles
1820
+ }).catch((err) => ({
1778
1821
  section,
1779
1822
  content: "",
1780
1823
  wasOptimized: false,
1781
- error: String(r.reason)
1782
- });
1824
+ error: err.message
1825
+ }));
1826
+ allResults.push(result);
1827
+ if (result.wasOptimized && !noCache) setCache(prompt, model, section, result.content);
1828
+ if (result.usage) {
1829
+ totalUsage = totalUsage ?? {
1830
+ input: 0,
1831
+ output: 0
1832
+ };
1833
+ totalUsage.input += result.usage.input;
1834
+ totalUsage.output += result.usage.output;
1835
+ }
1836
+ if (result.cost != null) totalCost += result.cost;
1783
1837
  }
1784
1838
  if (version) {
1785
1839
  const sectionFiles = allResults.filter((r) => r.wasOptimized && r.content).map((r) => ({
@@ -1841,7 +1895,12 @@ function validateSectionOutput(content, section) {
1841
1895
  return warnings;
1842
1896
  }
1843
1897
  function cleanSectionOutput(content) {
1844
- let cleaned = content.replace(/^```markdown\n?/m, "").replace(/\n?```$/m, "").trim();
1898
+ let cleaned = content.trim();
1899
+ const wrapMatch = cleaned.match(/^```(?:markdown|md)?[^\S\n]*\n([\s\S]+)\n```[^\S\n]*$/);
1900
+ if (wrapMatch) {
1901
+ const inner = wrapMatch[1].trim();
1902
+ if (/^```(?:markdown|md)/.test(cleaned) || /^##\s/m.test(inner) || /^- (?:BREAKING|DEPRECATED|NEW): /m.test(inner)) cleaned = inner;
1903
+ }
1845
1904
  const fmMatch = cleaned.match(/^-{3,}\n/);
1846
1905
  if (fmMatch) {
1847
1906
  const afterOpen = fmMatch[0].length;
@@ -1849,13 +1908,23 @@ function cleanSectionOutput(content) {
1849
1908
  if (closeMatch) cleaned = cleaned.slice(afterOpen + closeMatch.index + closeMatch[0].length).trim();
1850
1909
  else cleaned = cleaned.slice(afterOpen).trim();
1851
1910
  }
1852
- const firstMarker = cleaned.match(/^(##\s|⚠️|✅)/m);
1911
+ const firstMarker = cleaned.match(/^(##\s|- (?:BREAKING|DEPRECATED|NEW): )/m);
1853
1912
  if (firstMarker?.index && firstMarker.index > 0) {
1854
1913
  const preamble = cleaned.slice(0, firstMarker.index);
1855
1914
  if (/\b(?:function|const |let |var |export |return |import |async |class )\b/.test(preamble)) cleaned = cleaned.slice(firstMarker.index).trim();
1856
1915
  }
1916
+ const headingMatch = cleaned.match(/^(## .+)\n/);
1917
+ if (headingMatch) {
1918
+ const heading = headingMatch[1];
1919
+ const afterFirst = headingMatch[0].length;
1920
+ const secondIdx = cleaned.indexOf(heading, afterFirst);
1921
+ if (secondIdx !== -1) {
1922
+ if (secondIdx - afterFirst < 200) cleaned = cleaned.slice(secondIdx).trim();
1923
+ }
1924
+ }
1925
+ cleaned = cleaned.replace(/\[source\]\(\.\/((docs|issues|discussions|releases|pkg|guide)\/)/g, "[source](./.skilld/$1");
1857
1926
  cleaned = sanitizeMarkdown(cleaned);
1858
- if (!/^##\s/m.test(cleaned) && !/⚠️|✅|✨/.test(cleaned)) return "";
1927
+ if (!/^##\s/m.test(cleaned) && !/^- (?:BREAKING|DEPRECATED|NEW): /m.test(cleaned) && !/\[source\]/.test(cleaned)) return "";
1859
1928
  return cleaned;
1860
1929
  }
1861
1930
  const NUXT_CONFIG_FILES = [