dsh-continual-evolve 0.1.1 → 0.3.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.
Files changed (64) hide show
  1. package/README.md +172 -17
  2. package/README.zh.md +87 -10
  3. package/lib/apply.js +3 -1
  4. package/lib/approval.d.ts +25 -0
  5. package/lib/approval.js +9 -1
  6. package/lib/auto.d.ts +99 -5
  7. package/lib/auto.js +165 -6
  8. package/lib/benchmark-command.d.ts +9 -0
  9. package/lib/benchmark-command.js +331 -0
  10. package/lib/benchmark.d.ts +84 -0
  11. package/lib/benchmark.js +107 -1
  12. package/lib/command.js +33 -221
  13. package/lib/evaluate.d.ts +43 -7
  14. package/lib/evaluate.js +172 -43
  15. package/lib/evolve-event.d.ts +38 -0
  16. package/lib/evolve-event.js +49 -0
  17. package/lib/failures.d.ts +39 -0
  18. package/lib/failures.js +170 -0
  19. package/lib/fate.d.ts +128 -0
  20. package/lib/fate.js +342 -0
  21. package/lib/goal-command.d.ts +7 -0
  22. package/lib/goal-command.js +37 -0
  23. package/lib/index.d.ts +51 -21
  24. package/lib/index.js +32 -2
  25. package/lib/inject.d.ts +8 -0
  26. package/lib/inject.js +51 -4
  27. package/lib/llm-text.d.ts +30 -0
  28. package/lib/llm-text.js +49 -0
  29. package/lib/mount-command.d.ts +10 -0
  30. package/lib/mount-command.js +48 -0
  31. package/lib/mount.js +5 -0
  32. package/lib/plan.js +5 -0
  33. package/lib/planner.d.ts +8 -1
  34. package/lib/planner.js +40 -39
  35. package/lib/render.d.ts +1 -3
  36. package/lib/render.js +2 -5
  37. package/lib/review.d.ts +5 -2
  38. package/lib/review.js +27 -38
  39. package/lib/rollback.d.ts +1 -3
  40. package/lib/rollback.js +0 -8
  41. package/lib/score.d.ts +37 -4
  42. package/lib/score.js +120 -10
  43. package/lib/service.d.ts +2 -2
  44. package/lib/service.js +5 -2
  45. package/lib/skill-render.d.ts +15 -0
  46. package/lib/skill-render.js +30 -0
  47. package/lib/skill.d.ts +12 -7
  48. package/lib/skill.js +36 -31
  49. package/lib/skillquality.d.ts +80 -0
  50. package/lib/skillquality.js +311 -0
  51. package/lib/store.d.ts +1 -3
  52. package/lib/store.js +0 -7
  53. package/lib/tool.js +28 -4
  54. package/lib/types.d.ts +39 -0
  55. package/lib/types.js +19 -0
  56. package/lib/usage.d.ts +32 -0
  57. package/lib/usage.js +84 -0
  58. package/lib/validate.d.ts +12 -2
  59. package/lib/validate.js +51 -2
  60. package/lib/wrapup-command.d.ts +8 -0
  61. package/lib/wrapup-command.js +211 -0
  62. package/lib/wrapup.d.ts +215 -0
  63. package/lib/wrapup.js +427 -0
  64. package/package.json +8 -8
package/lib/score.js CHANGED
@@ -1,8 +1,22 @@
1
- export const DEFAULT_AGGREGATE = { passThreshold: 60, regressionTolerance: 0 };
2
- /** Aggregate raw cells into code-owned per-case means + overall mean. */
1
+ export const DEFAULT_AGGREGATE = {
2
+ passThreshold: 60,
3
+ regressionTolerance: 0,
4
+ maxFailedCells: 0,
5
+ };
6
+ /**
7
+ * Aggregate raw cells into code-owned per-case means + overall mean.
8
+ * Failure-cell protocol (gap A2): failed cells are EXCLUDED from means and
9
+ * counted separately — a crashed unit can never silently drag the mean down
10
+ * like a zero. A case whose cells all failed reports null (no mean).
11
+ */
3
12
  export function aggregate(cells) {
4
13
  const byCase = new Map();
14
+ let failed = 0;
5
15
  for (const cell of cells) {
16
+ if (cell.status === "failed") {
17
+ failed += 1;
18
+ continue;
19
+ }
6
20
  const list = byCase.get(cell.caseId) ?? [];
7
21
  list.push(clampScore(cell.score));
8
22
  byCase.set(cell.caseId, list);
@@ -12,7 +26,19 @@ export function aggregate(cells) {
12
26
  perCase[caseId] = mean(scores);
13
27
  }
14
28
  const all = [...byCase.values()].flat();
15
- return { ...perCase, overall: all.length > 0 ? mean(all) : null };
29
+ let totalDurationMs = 0;
30
+ for (const cell of cells) {
31
+ if (cell.durationMs !== undefined && cell.durationMs >= 0) {
32
+ totalDurationMs += cell.durationMs;
33
+ }
34
+ }
35
+ return {
36
+ ...perCase,
37
+ overall: all.length > 0 ? mean(all) : null,
38
+ failed,
39
+ total: cells.length,
40
+ totalDurationMs,
41
+ };
16
42
  }
17
43
  export function entryFromCells(label, cells, refinementId) {
18
44
  const aggr = aggregate(cells);
@@ -28,33 +54,111 @@ export function entryFromCells(label, cells, refinementId) {
28
54
  /** Human-readable decision report with per-case before → after deltas. */
29
55
  export function decisionReport(reference, candidate, decision) {
30
56
  const lines = [`overall: ${reference.overall ?? "?"} → ${candidate.overall ?? "?"}`];
31
- for (const [caseId, refScore] of Object.entries(reference.aggregate)) {
32
- if (caseId === "overall" || refScore === null)
57
+ // Per-case deltas over the actual evaluated cases only (aggregate() mixes
58
+ // case means with metadata keys like totalDurationMs — never render those
59
+ // as cases).
60
+ for (const caseId of new Set(reference.cells.map((cell) => cell.caseId))) {
61
+ const refScore = reference.aggregate[caseId];
62
+ if (refScore === null || refScore === undefined)
33
63
  continue;
34
64
  const candScore = candidate.aggregate[caseId];
35
- lines.push(` ${caseId}: ${refScore} ${candScore ?? "?"}`);
65
+ const failedMark = isCaseFailed(reference, caseId) || isCaseFailed(candidate, caseId) ? " (failed)" : "";
66
+ lines.push(` ${caseId}: ${refScore} → ${candScore ?? "?"}${failedMark}`);
67
+ }
68
+ const refFailed = reference.aggregate.failed ?? 0;
69
+ const candFailed = candidate.aggregate.failed ?? 0;
70
+ if (refFailed > 0 || candFailed > 0) {
71
+ lines.push(`failed cells: reference ${refFailed}/${reference.aggregate.total ?? 0} · candidate ${candFailed}/${candidate.aggregate.total ?? 0}`);
72
+ }
73
+ // Gap C3: show duration summary when available.
74
+ const refDuration = reference.aggregate.totalDurationMs ?? 0;
75
+ const candDuration = candidate.aggregate.totalDurationMs ?? 0;
76
+ if (refDuration > 0 || candDuration > 0) {
77
+ lines.push(`duration: ${formatDuration(refDuration)} → ${formatDuration(candDuration)}`);
36
78
  }
37
79
  lines.push(decision.accepted
38
80
  ? "DECISION: ACCEPTED — overall improved, no regression"
39
81
  : `DECISION: REJECTED — ${decision.reasons.join("; ")}`);
40
82
  return lines;
41
83
  }
84
+ /**
85
+ * Gap A3 (version_changed semantics): detect cells whose case material
86
+ * changed between the reference and candidate evaluation runs. A candidate
87
+ * cell whose `caseHash` differs from the reference cell of the SAME case
88
+ * means the statement/rubric was edited between the two runs — its score is
89
+ * not comparable to the baseline and must not count toward the decision.
90
+ *
91
+ * The check is conservative: cells without a hash on either side (pre-A3
92
+ * data) and cells already failed are left untouched. Mismatched cells are
93
+ * returned re-marked as `failed` with a reason in notes, so aggregation
94
+ * excludes them and the acceptance rule can reject the round.
95
+ */
96
+ export function flagMaterialDrift(reference, candidateCells) {
97
+ const referenceHashes = new Map();
98
+ for (const cell of reference.cells) {
99
+ if (cell.status !== "failed" && cell.caseHash !== undefined && !referenceHashes.has(cell.caseId)) {
100
+ referenceHashes.set(cell.caseId, cell.caseHash);
101
+ }
102
+ }
103
+ if (referenceHashes.size === 0) {
104
+ return [...candidateCells];
105
+ }
106
+ return candidateCells.map((cell) => {
107
+ if (cell.status === "failed" || cell.caseHash === undefined) {
108
+ return cell;
109
+ }
110
+ const refHash = referenceHashes.get(cell.caseId);
111
+ if (refHash !== undefined && refHash !== cell.caseHash) {
112
+ return {
113
+ ...cell,
114
+ status: "failed",
115
+ passed: false,
116
+ notes: `materials changed: case ${cell.caseId} hash ${cell.caseHash} ≠ reference ${refHash} (re-run the reference or fix the material)`,
117
+ };
118
+ }
119
+ return cell;
120
+ });
121
+ }
122
+ function isCaseFailed(entry, caseId) {
123
+ return entry.cells.some((cell) => cell.caseId === caseId && cell.status === "failed");
124
+ }
42
125
  /**
43
126
  * Non-regressive acceptance rule (Self-Harness style):
44
127
  * the candidate is accepted iff its overall mean is STRICTLY higher than the
45
- * reference AND no case regresses by more than `regressionTolerance` points.
128
+ * reference, no case regresses by more than `regressionTolerance` points,
129
+ * and neither side has more failed cells than `maxFailedCells` (failure-cell
130
+ * protocol, gap A2 — a partial/invalid round is never accepted).
46
131
  */
47
132
  export function decide(reference, candidate, opts) {
48
133
  const reasons = [];
49
134
  if (reference.overall === null || candidate.overall === null) {
50
135
  return { accepted: false, reasons: ["reference or candidate evaluation is incomplete"] };
51
136
  }
137
+ const refFailed = reference.aggregate.failed ?? 0;
138
+ const candFailed = candidate.aggregate.failed ?? 0;
139
+ if (refFailed > opts.maxFailedCells) {
140
+ reasons.push(`reference has ${refFailed} failed cells (max ${opts.maxFailedCells})`);
141
+ }
142
+ if (candFailed > opts.maxFailedCells) {
143
+ reasons.push(`candidate has ${candFailed} failed cells (max ${opts.maxFailedCells})`);
144
+ }
145
+ if (reasons.length > 0) {
146
+ return { accepted: false, reasons };
147
+ }
52
148
  if (candidate.overall <= reference.overall) {
53
149
  reasons.push(`overall not improved: ${candidate.overall} <= ${reference.overall}`);
54
150
  }
55
- for (const [caseId, refScore] of Object.entries(reference.aggregate)) {
56
- if (caseId === "overall" || refScore === null)
57
- continue;
151
+ // Per-case regression is judged ONLY over the cases that actually exist in
152
+ // the evaluation — aggregate() mixes per-case means with metadata keys
153
+ // (failed/total/totalDurationMs), and iterating raw keys would (and did)
154
+ // treat totalDurationMs as a case score, rejecting a candidate whose run
155
+ // merely took longer. Derived from reference.cells, never from the
156
+ // aggregate key set.
157
+ for (const caseId of new Set(reference.cells.map((cell) => cell.caseId))) {
158
+ const refScore = reference.aggregate[caseId];
159
+ if (refScore === null || refScore === undefined) {
160
+ continue; // case had no comparable mean (all cells failed) — nothing to regress
161
+ }
58
162
  const candScore = candidate.aggregate[caseId];
59
163
  if (candScore === null || candScore === undefined) {
60
164
  reasons.push(`candidate missing case ${caseId}`);
@@ -78,4 +182,10 @@ function clampScore(score) {
78
182
  function round2(value) {
79
183
  return Math.round(value * 100) / 100;
80
184
  }
185
+ /** Gap C3: human-readable duration (ms → "1.2s" or "340ms"). */
186
+ function formatDuration(ms) {
187
+ if (ms < 1000)
188
+ return `${Math.round(ms)}ms`;
189
+ return `${(ms / 1000).toFixed(1)}s`;
190
+ }
81
191
  //# sourceMappingURL=score.js.map
package/lib/service.d.ts CHANGED
@@ -5,7 +5,6 @@
5
5
  */
6
6
  import type { EntrySource, HarnessScope, RefinementProposal, RefinementResult } from "./types.js";
7
7
  import { applyRefinementProposal } from "./apply.js";
8
- import { storePaths } from "./store.js";
9
8
  export interface ApplyContext {
10
9
  scope: HarnessScope;
11
10
  sessionId?: string;
@@ -13,6 +12,8 @@ export interface ApplyContext {
13
12
  baselineState?: Parameters<typeof applyRefinementProposal>[0];
14
13
  /** Trajectory citation stamped into newly created entries (see apply.ts). */
15
14
  source?: EntrySource | undefined;
15
+ /** Marks the resulting refinement as the deterministic rollback of another (audit chain). */
16
+ rollbackOf?: string | undefined;
16
17
  }
17
18
  export interface EvolutionHooks {
18
19
  /** Called after every applied refinement (side-effect boundary: skills sync, etc.). */
@@ -26,5 +27,4 @@ export declare function createEvolutionEngine(baseDir: string, hooks?: Evolution
26
27
  baseDir: string;
27
28
  };
28
29
  export type EvolutionEngine = ReturnType<typeof createEvolutionEngine>;
29
- export { storePaths };
30
30
  //# sourceMappingURL=service.d.ts.map
package/lib/service.js CHANGED
@@ -17,6 +17,7 @@ export function createEvolutionEngine(baseDir, hooks = {}) {
17
17
  scope,
18
18
  ...(context?.source ? { source: context.source } : {}),
19
19
  ...(context?.baselineState ? { baselineState: context.baselineState } : {}),
20
+ ...(context?.rollbackOf ? { rollbackOf: context.rollbackOf } : {}),
20
21
  });
21
22
  saveHarnessState(paths.stateDir, state);
22
23
  appendResult(paths, result);
@@ -31,12 +32,14 @@ export function createEvolutionEngine(baseDir, hooks = {}) {
31
32
  throw new Error(`Refinement ${refinementId} not found in ${scope} history`);
32
33
  }
33
34
  const proposal = rollbackProposal(target);
34
- return apply(scope, sessionId, proposal);
35
+ // The rollback refinement carries rollbackOf so the audit chain links
36
+ // the inverse operation back to its origin (previously the rollback
37
+ // record only echoed "Rollback refinement <id>" in its summary text).
38
+ return apply(scope, sessionId, proposal, { scope, rollbackOf: refinementId });
35
39
  }
36
40
  function history(scope, sessionId) {
37
41
  return loadResults(storePaths(baseDir, scope, sessionId));
38
42
  }
39
43
  return { load, apply, rollback, history, baseDir };
40
44
  }
41
- export { storePaths };
42
45
  //# sourceMappingURL=service.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Skill rendering: pure functions that convert harness entries into
3
+ * SKILL.md documents. Extracted from skill.ts to break the circular
4
+ * dependency between skill.ts ↔ skillquality.ts.
5
+ *
6
+ * Both skill.ts (materializer) and skillquality.ts (validator) need
7
+ * these rendering functions; importing from this shared leaf module
8
+ * keeps the dependency graph acyclic.
9
+ */
10
+ import type { HarnessEntry } from "./types.js";
11
+ /** Convert a harness entry id (underscore slug) to a kebab-case skill name. */
12
+ export declare function skillNameOf(id: string): string;
13
+ /** Render a harness skill entry as a discoverable SKILL.md document. */
14
+ export declare function renderSkillMarkdown(entry: HarnessEntry): string;
15
+ //# sourceMappingURL=skill-render.d.ts.map
@@ -0,0 +1,30 @@
1
+ /** Convert a harness entry id (underscore slug) to a kebab-case skill name. */
2
+ export function skillNameOf(id) {
3
+ return id.toLowerCase().replace(/_/g, "-");
4
+ }
5
+ /** Render a harness skill entry as a discoverable SKILL.md document. */
6
+ export function renderSkillMarkdown(entry) {
7
+ const lines = [
8
+ "---",
9
+ `name: ${skillNameOf(entry.id)}`,
10
+ `description: ${oneLine(entry.title)}`,
11
+ "---",
12
+ "",
13
+ entry.content.trim(),
14
+ ];
15
+ const reference = entry.reference;
16
+ if (reference && typeof reference === "object" && Object.keys(reference).length > 0) {
17
+ lines.push("", "## Invocation");
18
+ for (const [key, value] of Object.entries(reference)) {
19
+ lines.push(`- ${key}: ${JSON.stringify(value)}`);
20
+ }
21
+ }
22
+ if (Object.keys(entry.arguments).length > 0) {
23
+ lines.push("", "## Arguments", "```json", JSON.stringify(entry.arguments, null, 2), "```");
24
+ }
25
+ return `${lines.join("\n").trimEnd()}\n`;
26
+ }
27
+ function oneLine(text) {
28
+ return text.replace(/\s+/g, " ").trim();
29
+ }
30
+ //# sourceMappingURL=skill-render.js.map
package/lib/skill.d.ts CHANGED
@@ -1,10 +1,15 @@
1
- import type { HarnessEntry, RefinementResult } from "./types.js";
2
- /** Convert a harness entry id (underscore slug) to a kebab-case skill name. */
3
- export declare function skillNameOf(id: string): string;
1
+ import type { RefinementResult } from "./types.js";
2
+ export { renderSkillMarkdown, skillNameOf } from "./skill-render.js";
4
3
  /** Resolve and defend the skill directory for an entry id. */
5
4
  export declare function skillDir(skillsRoot: string, id: string): string;
6
- /** Render a harness skill entry as a discoverable SKILL.md document. */
7
- export declare function renderSkillMarkdown(entry: HarnessEntry): string;
8
- /** Apply the skill-kind edits of an applied refinement to the skills root. */
9
- export declare function syncSkillsFromResult(skillsRoot: string, result: RefinementResult): void;
5
+ /**
6
+ * Apply the skill-kind edits of an applied refinement to the skills root.
7
+ * Returns materialization warnings (rendered-SKILL.md mechanical problems
8
+ * and dangling resource references) — the file is still written, but the
9
+ * caller should surface them: a rendered file that fails the platform's
10
+ * frontmatter rules would be IGNORED by the skill loader, and a body
11
+ * referencing resources the entry does not ship would load with broken
12
+ * links.
13
+ */
14
+ export declare function syncSkillsFromResult(skillsRoot: string, result: RefinementResult): string[];
10
15
  //# sourceMappingURL=skill.d.ts.map
package/lib/skill.js CHANGED
@@ -9,10 +9,9 @@
9
9
  */
10
10
  import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
11
11
  import { join, resolve, sep } from "node:path";
12
- /** Convert a harness entry id (underscore slug) to a kebab-case skill name. */
13
- export function skillNameOf(id) {
14
- return id.toLowerCase().replace(/_/g, "-");
15
- }
12
+ import { renderSkillMarkdown, skillNameOf } from "./skill-render.js";
13
+ import { skillResourceRefs, validateRenderedSkill } from "./skillquality.js";
14
+ export { renderSkillMarkdown, skillNameOf } from "./skill-render.js";
16
15
  /** Resolve and defend the skill directory for an entry id. */
17
16
  export function skillDir(skillsRoot, id) {
18
17
  const root = resolve(skillsRoot);
@@ -22,30 +21,17 @@ export function skillDir(skillsRoot, id) {
22
21
  }
23
22
  return dir;
24
23
  }
25
- /** Render a harness skill entry as a discoverable SKILL.md document. */
26
- export function renderSkillMarkdown(entry) {
27
- const lines = [
28
- "---",
29
- `name: ${skillNameOf(entry.id)}`,
30
- `description: ${oneLine(entry.title)}`,
31
- "---",
32
- "",
33
- entry.content.trim(),
34
- ];
35
- const reference = entry.reference;
36
- if (reference && typeof reference === "object" && Object.keys(reference).length > 0) {
37
- lines.push("", "## Invocation");
38
- for (const [key, value] of Object.entries(reference)) {
39
- lines.push(`- ${key}: ${JSON.stringify(value)}`);
40
- }
41
- }
42
- if (Object.keys(entry.arguments).length > 0) {
43
- lines.push("", "## Arguments", "```json", JSON.stringify(entry.arguments, null, 2), "```");
44
- }
45
- return `${lines.join("\n").trimEnd()}\n`;
46
- }
47
- /** Apply the skill-kind edits of an applied refinement to the skills root. */
24
+ /**
25
+ * Apply the skill-kind edits of an applied refinement to the skills root.
26
+ * Returns materialization warnings (rendered-SKILL.md mechanical problems
27
+ * and dangling resource references) — the file is still written, but the
28
+ * caller should surface them: a rendered file that fails the platform's
29
+ * frontmatter rules would be IGNORED by the skill loader, and a body
30
+ * referencing resources the entry does not ship would load with broken
31
+ * links.
32
+ */
48
33
  export function syncSkillsFromResult(skillsRoot, result) {
34
+ const warnings = [];
49
35
  for (const edit of result.appliedEdits) {
50
36
  if (edit.kind !== "skill" || !edit.applied)
51
37
  continue;
@@ -53,15 +39,37 @@ export function syncSkillsFromResult(skillsRoot, result) {
53
39
  removeSkill(skillsRoot, edit.id);
54
40
  continue;
55
41
  }
56
- writeSkill(skillsRoot, edit.after);
42
+ warnings.push(...writeSkill(skillsRoot, edit.after));
57
43
  }
44
+ return warnings;
58
45
  }
46
+ /** Write one skill entry as a SKILL.md; returns materialization warnings. */
59
47
  function writeSkill(skillsRoot, entry) {
60
48
  const dir = skillDir(skillsRoot, entry.id);
61
49
  mkdirSync(dir, { recursive: true });
62
50
  const temp = join(dir, `SKILL.md.${process.pid}.tmp`);
63
51
  writeFileSync(temp, renderSkillMarkdown(entry), "utf8");
64
52
  renameSync(temp, join(dir, "SKILL.md"));
53
+ return materializationWarnings(dir, entry);
54
+ }
55
+ /** Post-write checks on the exact file that landed on disk. */
56
+ function materializationWarnings(dir, entry) {
57
+ const warnings = [];
58
+ for (const problem of validateRenderedSkill(entry)) {
59
+ warnings.push(`skill ${entry.id}: rendered SKILL.md would be ignored by the platform: ${problem}`);
60
+ }
61
+ const root = resolve(dir);
62
+ for (const ref of skillResourceRefs(entry.content)) {
63
+ const target = resolve(root, ref);
64
+ if (target !== root && !target.startsWith(`${root}${sep}`)) {
65
+ warnings.push(`skill ${entry.id}: body resource reference escapes the skill directory: ${ref}`);
66
+ continue;
67
+ }
68
+ if (!existsSync(target)) {
69
+ warnings.push(`skill ${entry.id}: body references missing resource ${ref} (expected at ${target})`);
70
+ }
71
+ }
72
+ return warnings;
65
73
  }
66
74
  function removeSkill(skillsRoot, id) {
67
75
  const dir = skillDir(skillsRoot, id);
@@ -69,7 +77,4 @@ function removeSkill(skillsRoot, id) {
69
77
  rmSync(dir, { recursive: true, force: true });
70
78
  }
71
79
  }
72
- function oneLine(text) {
73
- return text.replace(/\s+/g, " ").trim();
74
- }
75
80
  //# sourceMappingURL=skill.js.map
@@ -0,0 +1,80 @@
1
+ import type { HarnessEntry } from "./types.js";
2
+ /** Relative location of the skill-creator template facts. */
3
+ export declare const SKILL_CREATOR_TEMPLATE_REL: string;
4
+ /**
5
+ * Read the skill-creator template facts
6
+ * (`<skillsRoot>/skill-creator/references/template.md`; facts distilled
7
+ * from the official deepseek-harness skills). Returns null when the skills
8
+ * are not installed — callers fall back to the builtin distilled guide.
9
+ * Reading is a runtime reference, never a copy: template updates in the
10
+ * skill are picked up automatically.
11
+ */
12
+ export declare function readSkillCreatorTemplate(skillsRoot: string): string | null;
13
+ /**
14
+ * Builtin distilled skill-quality guide (fallback when the skill-creator
15
+ * template is not installed). Condenses the template facts — frontmatter
16
+ * schema, the 7 structural features, paragraph skeleton, and the
17
+ * no-duplication / real-trigger rules — so a planner still authors skills
18
+ * to the standard on installs without the skill-creator / skill-audit
19
+ * skills.
20
+ */
21
+ export declare const BUILTIN_SKILL_QUALITY_GUIDE = "DSH skill quality standard (distilled by the author from the official deepseek-harness 11 skills; the full facts live in <skillsRoot>/skill-creator/references/template.md when installed):\n\nFrontmatter schema (platform-enforced; violations make the platform IGNORE the whole file):\n- name: required, kebab-case only (^[a-z0-9]+(?:-[a-z0-9]+)*$)\n- description: required, non-empty; write \"use when / do not use when\" routing so the model can select it correctly\n- invocation booleans accept true/false/yes/no/on/off/1/0; legacy camelCase keys (disableModelInvocation / modelInvocable / userInvocable) are rejected\n- whenToUse (optional): non-empty string; metadata (optional): object\n\nThe 7 structural features of the official deepseek-harness skills:\n1. Frontmatter is routing metadata, not a summary (description = when to use / when not to use)\n2. Opens with a boundary declaration (guidance, not a script; mechanical flow skills may omit the disclaimer)\n3. Prerequisites + exclusions: explicit required input, stop when missing (report the required input and stop), excluded scenarios\n4. Layered information: Sources of truth (link only, do not re-summarize) -> numbered blocking requirements -> manual checks -> verification commands -> report format; all executable, no slogans\n5. Skill interlinks: reference a single source of truth instead of duplicating it\n6. Verifiable completion criteria: explicit verification commands and report format\n7. Real use + iteration: a real trigger scenario must exist; calibration conclusions distill into references/\n\nParagraph skeleton (writing order): frontmatter -> H1 + boundary declaration -> Sources of truth -> numbered requirements / workflow (full commands) -> exclusions / stop conditions -> verification and report.\n\nCreation rules: only create a skill for a REAL trigger scenario (who, in what real task, what signal) grounded in the trajectory \u2014 never invent one to pad the store; do not duplicate the official 11 skills or existing entries; skill bodies should be a SKILL.md document (this is what materializes under <skillsRoot>/<kebab-name>/SKILL.md).";
22
+ export interface SkillQualityGuide {
23
+ /** Where the guide text came from: the on-disk template or the builtin guide. */
24
+ source: "template" | "builtin";
25
+ text: string;
26
+ }
27
+ /**
28
+ * The quality guide handed to the planner: the skill-creator template facts
29
+ * when the skills are installed, otherwise the builtin distilled guide.
30
+ * Never throws — a missing/unreadable template degrades to the builtin.
31
+ */
32
+ export declare function skillQualityGuide(skillsRoot: string | undefined): SkillQualityGuide;
33
+ /** Split frontmatter out of a raw SKILL.md. Returns { yaml, body } or null when delimiters are missing. */
34
+ export declare function splitFrontmatter(raw: string): {
35
+ yaml: string;
36
+ body: string;
37
+ } | null;
38
+ /**
39
+ * Mechanical frontmatter validation of a rendered SKILL.md, mirroring
40
+ * `skill-creator/scripts/validate-frontmatter.mjs` (and the platform's
41
+ * skill-filesystem rules): delimiter structure, name kebab-case, non-empty
42
+ * description, invocation-boolean spellings, legacy camelCase key rejection,
43
+ * whenToUse/metadata types. Returns human-readable problems; an empty array
44
+ * means the file would load.
45
+ */
46
+ export declare function validateRenderedSkillMarkdown(markdown: string): string[];
47
+ /**
48
+ * Mechanical validation of a skill entry's raw `content` (the SKILL.md body
49
+ * that materializes under the generated frontmatter). Code-enforced at
50
+ * apply time so a bad entry never reaches the store:
51
+ * - empty content is rejected;
52
+ * - content must not open with a `---` block: the materializer generates
53
+ * its own frontmatter, and a second frontmatter in the body would be
54
+ * parsed instead of the generated one (the platform reads the FIRST
55
+ * closing `---`), so the file could be ignored or routed wrongly;
56
+ * - resource references (`references/…`, `scripts/…`) must be skill-local
57
+ * relative paths — parent-relative (`../`) or absolute targets escape the
58
+ * skill directory and are rejected.
59
+ * Returns human-readable problems; an empty array means the content is
60
+ * mechanically acceptable.
61
+ */
62
+ export declare function validateSkillEntryContent(content: string): string[];
63
+ /**
64
+ * Validate the FULL rendered SKILL.md of an entry (generated frontmatter +
65
+ * body) — the exact bytes that materialize on disk. Used as the final
66
+ * code-enforced line after materialization; problems here mean the platform
67
+ * would refuse to load the file.
68
+ */
69
+ export declare function validateRenderedSkill(entry: HarnessEntry): string[];
70
+ /**
71
+ * Resource references (`references/…`, `scripts/…`) found in a skill body —
72
+ * the same scanning policy as validate-frontmatter.mjs: markdown link
73
+ * targets starting with the category, plus backticked/prose paths carrying
74
+ * a filename extension. Used after materialization to warn about dangling
75
+ * references (a body referencing a resource the entry never ships).
76
+ */
77
+ export declare function skillResourceRefs(content: string): string[];
78
+ /** Kebab-case name under which the entry materializes (exported for diagnostics). */
79
+ export { skillNameOf } from "./skill-render.js";
80
+ //# sourceMappingURL=skillquality.d.ts.map