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.
- package/dist/_chunks/detect-imports.mjs +195 -126
- package/dist/_chunks/detect-imports.mjs.map +1 -1
- package/dist/_chunks/npm.mjs +201 -98
- package/dist/_chunks/npm.mjs.map +1 -1
- package/dist/_chunks/storage.mjs +12 -1
- package/dist/_chunks/storage.mjs.map +1 -1
- package/dist/_chunks/utils.d.mts +14 -1
- package/dist/_chunks/utils.d.mts.map +1 -1
- package/dist/_chunks/version.d.mts.map +1 -1
- package/dist/_chunks/yaml.mjs +1 -20
- package/dist/_chunks/yaml.mjs.map +1 -1
- package/dist/agent/index.d.mts +5 -1
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/cli.mjs +408 -270
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/sources/index.d.mts +2 -2
- package/dist/sources/index.mjs +3 -3
- package/dist/types.d.mts +1 -1
- package/package.json +1 -1
|
@@ -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 {
|
|
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**
|
|
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:
|
|
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
|
-
|
|
640
|
+
- NEW: \`useTemplateRef()\` — new in v3.5, replaces \`$refs\` pattern [source](./.skilld/releases/v3.5.0.md)
|
|
634
641
|
|
|
635
|
-
|
|
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:
|
|
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
|
|
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:
|
|
663
|
-
useFor: "
|
|
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:
|
|
669
|
-
useFor: "
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
\`\`\`
|
|
703
|
+
// Preferred
|
|
704
|
+
const instance = createX({ ... })
|
|
699
705
|
|
|
700
|
-
|
|
706
|
+
// Avoid — misses cleanup, error boundaries
|
|
707
|
+
const instance = new X()
|
|
708
|
+
instance.init({ ... })
|
|
709
|
+
\`\`\`
|
|
701
710
|
|
|
702
|
-
|
|
711
|
+
- Pass config through \`defineConfig()\` — enables type inference and plugin merging [source](./.skilld/docs/config.md)
|
|
703
712
|
|
|
704
|
-
|
|
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:
|
|
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
|
-
"- **
|
|
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
|
|
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
|
|
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
|
|
1018
|
+
function formatShortDate(isoDate) {
|
|
1011
1019
|
const date = new Date(isoDate);
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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
|
|
1034
|
-
const versionStr =
|
|
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 ? ` (${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1396
|
-
let
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
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
|
-
|
|
1427
|
+
emit(`${section ? `\x1B[90m[${section}]\x1B[0m ` : ""}Writing...`);
|
|
1415
1428
|
return;
|
|
1416
1429
|
}
|
|
1417
1430
|
if (type !== "reasoning" || !chunk.startsWith("[")) return;
|
|
1418
|
-
const
|
|
1419
|
-
const match = chunk.match(/^\[(\w+)(?:,\s\w+)*(?::\s(.+))?\]$/);
|
|
1431
|
+
const match = chunk.match(/^\[([^:[\]]+)(?::\s(.+))?\]$/);
|
|
1420
1432
|
if (!match) return;
|
|
1421
|
-
const
|
|
1422
|
-
const
|
|
1423
|
-
let
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
const
|
|
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
|
-
|
|
1436
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1766
|
-
|
|
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 +=
|
|
1774
|
-
totalUsage.output +=
|
|
1789
|
+
totalUsage.input += r.value.usage.input;
|
|
1790
|
+
totalUsage.output += r.value.usage.output;
|
|
1775
1791
|
}
|
|
1776
|
-
if (
|
|
1777
|
-
|
|
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:
|
|
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.
|
|
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
|
|
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) &&
|
|
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 = [
|