@gobing-ai/superskill 0.2.9 → 0.2.11

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/README.md CHANGED
@@ -25,8 +25,8 @@ Requires [Bun](https://bun.sh/) ≥ 1.3.14. See [Installation guide](docs/help/i
25
25
  ## Quick start
26
26
 
27
27
  ```bash
28
- # Distribute a plugin to every supported target
29
- superskill install my-plugin --targets all
28
+ # Distribute a plugin cc in current repo to every supported target
29
+ superskill install cc --targets all
30
30
 
31
31
  # Author a skill: scaffold → validate → evaluate → refine
32
32
  superskill skill scaffold my-skill --description "Deploy a Cloudflare Worker"
package/dist/index.js CHANGED
@@ -14269,6 +14269,25 @@ function mergeFrontmatterList(content, key, items) {
14269
14269
  }
14270
14270
  return content.slice(0, fmStart) + newFmBody + content.slice(closePos);
14271
14271
  }
14272
+ function mergeFrontmatterScalar(content, key, value) {
14273
+ const fenceRe = /^---$/gm;
14274
+ const matches = [...content.matchAll(fenceRe)];
14275
+ if (matches.length < 2)
14276
+ return content;
14277
+ const openPos = matches[0]?.index ?? 0;
14278
+ const closePos = matches[1]?.index ?? 0;
14279
+ const fmStart = openPos + 3;
14280
+ const fmBody = content.slice(fmStart, closePos);
14281
+ const pattern = new RegExp(`^${key}:.*$`, "m");
14282
+ let newFmBody;
14283
+ if (pattern.test(fmBody)) {
14284
+ newFmBody = fmBody.replace(pattern, `${key}: ${value}`);
14285
+ } else {
14286
+ newFmBody = `${fmBody}${key}: ${value}
14287
+ `;
14288
+ }
14289
+ return content.slice(0, fmStart) + newFmBody + content.slice(closePos);
14290
+ }
14272
14291
  function resolveTemplate(type, tier) {
14273
14292
  const homeDir = process.env.HOME ?? homedir3();
14274
14293
  const tierName = tier?.trim();
@@ -14315,6 +14334,14 @@ async function scaffold(type, name, opts = {}) {
14315
14334
  });
14316
14335
  const toolField = type === "agent" ? "tools" : "allowed-tools";
14317
14336
  content = mergeFrontmatterList(content, toolField, parseList(opts.tools));
14337
+ if (type === "skill" && opts.invocationMode === "user") {
14338
+ content = mergeFrontmatterScalar(content, "disable-model-invocation", "true");
14339
+ if (!opts.description) {
14340
+ content = content.replace(/^description: *$/m, "description: # One-line, human-facing: what this does, no trigger phrasing (user picks this skill directly)");
14341
+ }
14342
+ } else if (type === "skill" && !opts.description) {
14343
+ content = content.replace(/^description: *$/m, 'description: # Trigger-rich: front-load the identity phrase, one branch per genuine "use when" case');
14344
+ }
14318
14345
  const outDir = opts.output ?? cwd4();
14319
14346
  mkdirSync4(outDir, { recursive: true });
14320
14347
  const filePath = type === "skill" ? join6(outDir, name, "SKILL.md") : join6(outDir, `${name}.md`);
@@ -14434,6 +14461,89 @@ function hasPattern(text, patterns) {
14434
14461
  }
14435
14462
  return clamp(matched / patterns.length);
14436
14463
  }
14464
+ function scoreDescriptionBudget(description, min = 20, max = 500) {
14465
+ return scoreLength(description, min, max);
14466
+ }
14467
+ function noOpDensity(text) {
14468
+ const lower = text.toLowerCase();
14469
+ const noOpHits = NO_OP_PHRASES.filter((p) => lower.includes(p)).length;
14470
+ const imperativeHits = IMPERATIVE_KEYWORDS.filter((k) => lower.includes(k)).length;
14471
+ const denominator = noOpHits + imperativeHits;
14472
+ if (denominator === 0)
14473
+ return 0;
14474
+ return clamp(noOpHits / denominator);
14475
+ }
14476
+ function shingles(text, size) {
14477
+ const words = text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter(Boolean);
14478
+ if (words.length < size)
14479
+ return [];
14480
+ const result = [];
14481
+ for (let i = 0;i <= words.length - size; i++) {
14482
+ result.push(words.slice(i, i + size).join(" "));
14483
+ }
14484
+ return result;
14485
+ }
14486
+ function duplicationRatio(text, other, size = 8) {
14487
+ const own = shingles(text, size);
14488
+ if (own.length === 0)
14489
+ return 0;
14490
+ if (other !== undefined) {
14491
+ const otherSet = new Set(shingles(other, size));
14492
+ if (otherSet.size === 0)
14493
+ return 0;
14494
+ const dup = own.filter((s) => otherSet.has(s)).length;
14495
+ return clamp(dup / own.length);
14496
+ }
14497
+ const counts = new Map;
14498
+ for (const s of own)
14499
+ counts.set(s, (counts.get(s) ?? 0) + 1);
14500
+ const repeated = own.filter((s) => (counts.get(s) ?? 0) > 1).length;
14501
+ return clamp(repeated / own.length);
14502
+ }
14503
+ function countTriggerBranches(phrases) {
14504
+ const wordSets = phrases.map((p) => new Set(p.toLowerCase().split(/\s+/).filter(Boolean)));
14505
+ const branchOf = new Array(phrases.length).fill(-1);
14506
+ let nextBranch = 0;
14507
+ for (let i = 0;i < wordSets.length; i++) {
14508
+ if (branchOf[i] !== -1)
14509
+ continue;
14510
+ branchOf[i] = nextBranch;
14511
+ for (let j = i + 1;j < wordSets.length; j++) {
14512
+ if (branchOf[j] !== -1)
14513
+ continue;
14514
+ if (jaccard(wordSets[i] ?? new Set, wordSets[j] ?? new Set) >= 0.5) {
14515
+ branchOf[j] = branchOf[i] ?? nextBranch;
14516
+ }
14517
+ }
14518
+ nextBranch++;
14519
+ }
14520
+ return nextBranch;
14521
+ }
14522
+ function jaccard(a, b) {
14523
+ if (a.size === 0 && b.size === 0)
14524
+ return 1;
14525
+ let intersection = 0;
14526
+ for (const w of a)
14527
+ if (b.has(w))
14528
+ intersection++;
14529
+ const union = a.size + b.size - intersection;
14530
+ return union === 0 ? 0 : intersection / union;
14531
+ }
14532
+ function completionCheckability(body) {
14533
+ const stepLines = body.split(`
14534
+ `).filter((l) => /^\s*\d+[.)]\s/.test(l) || /^\s*-\s*\[[ x]\]/i.test(l));
14535
+ if (stepLines.length === 0)
14536
+ return 1;
14537
+ const lower = body.toLowerCase();
14538
+ const vagueHits = VAGUE_COMPLETION_BOUNDS.filter((v) => lower.includes(v)).length;
14539
+ const penalty = clamp(vagueHits / Math.max(3, VAGUE_COMPLETION_BOUNDS.length));
14540
+ return clamp(1 - penalty);
14541
+ }
14542
+ function progressiveDisclosureShape(body, budget = 8000) {
14543
+ if (body.length <= budget)
14544
+ return true;
14545
+ return /references\//i.test(body) || /##\s*(see also|additional resources)/i.test(body);
14546
+ }
14437
14547
  function extractBody(content) {
14438
14548
  if (!content.startsWith(`---
14439
14549
  `))
@@ -14443,15 +14553,52 @@ function extractBody(content) {
14443
14553
  return content.slice(4);
14444
14554
  return content.slice(closerMatch.index + 4 + 4);
14445
14555
  }
14556
+ function descriptionTriggerRichness(description) {
14557
+ if (!description)
14558
+ return 0;
14559
+ const branchDelimiters = (description.match(/[,;]|\bor\b/gi) ?? []).length;
14560
+ const dispatchCues = (description.match(/\b(use (?:this|it) when|whenever|triggers? on|use when)\b/gi) ?? []).length;
14561
+ const lengthSignal = clamp(description.length / 300);
14562
+ const branchSignal = clamp(branchDelimiters / 3);
14563
+ const cueSignal = clamp(dispatchCues);
14564
+ return clamp(branchSignal * 0.5 + cueSignal * 0.3 + lengthSignal * 0.2);
14565
+ }
14446
14566
  function clamp(n) {
14447
14567
  return Math.max(0, Math.min(1, n));
14448
14568
  }
14449
14569
  function escapeRegex(s) {
14450
14570
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14451
14571
  }
14572
+ var NO_OP_PHRASES, VAGUE_COMPLETION_BOUNDS;
14452
14573
  var init_heuristics = __esm(() => {
14453
14574
  init_frontmatter();
14454
14575
  init_types2();
14576
+ NO_OP_PHRASES = [
14577
+ "be helpful",
14578
+ "be concise",
14579
+ "be accurate",
14580
+ "do your best",
14581
+ "think carefully",
14582
+ "think step by step",
14583
+ "try your best",
14584
+ "work hard",
14585
+ "pay attention",
14586
+ "be thorough",
14587
+ "take your time",
14588
+ "be careful",
14589
+ "stay focused",
14590
+ "use good judgment"
14591
+ ];
14592
+ VAGUE_COMPLETION_BOUNDS = [
14593
+ "understanding reached",
14594
+ "as needed",
14595
+ "as appropriate",
14596
+ "when ready",
14597
+ "until satisfied",
14598
+ "good enough",
14599
+ "as necessary",
14600
+ "when done"
14601
+ ];
14455
14602
  });
14456
14603
 
14457
14604
  // ../../packages/core/src/quality/hook.ts
@@ -14776,6 +14923,9 @@ function _validateContent(type, content, opts) {
14776
14923
  findings.push(...checkLinkValidity(type, data, opts?.referenceChecker));
14777
14924
  if (opts?.strict) {
14778
14925
  findings.push(...strictChecks(type, data, body));
14926
+ if (type === "skill") {
14927
+ findings.push(...checkInvocationModeMismatch(data));
14928
+ }
14779
14929
  }
14780
14930
  const valid = !findings.some((f) => f.severity === "error");
14781
14931
  return { valid, findings };
@@ -14894,6 +15044,28 @@ function checkLinkValidity(type, data, referenceChecker) {
14894
15044
  }
14895
15045
  return findings;
14896
15046
  }
15047
+ function checkInvocationModeMismatch(data) {
15048
+ const findings = [];
15049
+ const description = typeof data.description === "string" ? data.description : "";
15050
+ if (!description)
15051
+ return findings;
15052
+ const userInvoked = data["disable-model-invocation"] === true;
15053
+ const richness = descriptionTriggerRichness(description);
15054
+ if (userInvoked && richness >= TRIGGER_RICH_THRESHOLD) {
15055
+ findings.push({
15056
+ severity: "warning",
15057
+ field: "invocation-mode",
15058
+ message: "disable-model-invocation is true (user-invoked) but the description reads " + "trigger-rich (branch/dispatch phrasing). A user-invoked skill cannot be fired " + "by other skills or commands \u2014 rewrite the description as a one-line, " + "human-facing summary instead."
15059
+ });
15060
+ } else if (!userInvoked && richness <= ONE_LINE_THRESHOLD) {
15061
+ findings.push({
15062
+ severity: "warning",
15063
+ field: "invocation-mode",
15064
+ message: "This skill is model-invoked (disable-model-invocation is absent/false) but the " + "description reads like a bare one-liner with no trigger phrasing. The dispatching " + 'orchestrator needs distinct "use when" branches to select this skill reliably.'
15065
+ });
15066
+ }
15067
+ return findings;
15068
+ }
14897
15069
  function strictChecks(type, data, body) {
14898
15070
  const findings = [];
14899
15071
  const desc = data.description;
@@ -14959,10 +15131,11 @@ function sentinelResult(message) {
14959
15131
  findings: [{ severity: "error", field: "_file", message }]
14960
15132
  };
14961
15133
  }
14962
- var MODEL_ALIASES, FIELD_TYPES, DEPRECATED_FIELDS, KNOWN_OPTIONAL;
15134
+ var MODEL_ALIASES, FIELD_TYPES, DEPRECATED_FIELDS, KNOWN_OPTIONAL, TRIGGER_RICH_THRESHOLD = 0.4, ONE_LINE_THRESHOLD = 0.15;
14963
15135
  var init_validate = __esm(() => {
14964
15136
  init_frontmatter();
14965
15137
  init_identity();
15138
+ init_heuristics();
14966
15139
  init_hook();
14967
15140
  init_types2();
14968
15141
  MODEL_ALIASES = ["inherit", "sonnet", "opus", "haiku"];
@@ -14970,7 +15143,8 @@ var init_validate = __esm(() => {
14970
15143
  skill: {
14971
15144
  name: { type: "string" },
14972
15145
  description: { type: "string" },
14973
- "allowed-tools": { type: "array" }
15146
+ "allowed-tools": { type: "array" },
15147
+ "disable-model-invocation": { type: "boolean" }
14974
15148
  },
14975
15149
  command: {
14976
15150
  name: { type: "string" },
@@ -40845,8 +41019,8 @@ var init_targets = __esm(() => {
40845
41019
  codex: "codexcli",
40846
41020
  pi: "codexcli",
40847
41021
  opencode: "opencode",
40848
- "antigravity-cli": "codexcli",
40849
- "antigravity-ide": "codexcli"
41022
+ "antigravity-cli": "antigravity-cli",
41023
+ "antigravity-ide": "antigravity-ide"
40850
41024
  };
40851
41025
  TARGET_TO_RULESYNC_HOOKS = {
40852
41026
  codex: "codexcli",
@@ -40886,18 +41060,39 @@ var init_slash_command2 = __esm(() => {
40886
41060
  });
40887
41061
 
40888
41062
  // ../../packages/core/src/quality/agent.ts
40889
- function scoreCompleteness(data) {
41063
+ function scoreCompleteness(data, body) {
40890
41064
  const fieldsPresent = Object.keys(data);
40891
41065
  const required2 = REQUIRED_FIELDS.agent;
40892
- const score = scorePresence(fieldsPresent, required2);
41066
+ const presence = scorePresence(fieldsPresent, required2);
40893
41067
  const missing = required2.filter((f) => !fieldsPresent.includes(f));
40894
- const findings = missing.length > 0 ? [`Missing required fields: ${missing.join(", ")}`] : undefined;
40895
- const recs = missing.length > 0 ? ["Add the missing frontmatter fields"] : undefined;
41068
+ const description = typeof data.description === "string" ? data.description : "";
41069
+ const budgetScore = description ? scoreDescriptionBudget(description, 20, 2000) : 1;
41070
+ const noOp = noOpDensity(description);
41071
+ const descDup = description ? duplicationRatio(description, body, 12) : 0;
41072
+ const score = clamp(presence * budgetScore * (1 - noOp) * (1 - descDup));
41073
+ const findings = [];
41074
+ const recs = [];
41075
+ if (missing.length > 0) {
41076
+ findings.push(`Missing required fields: ${missing.join(", ")}`);
41077
+ recs.push("Add the missing frontmatter fields");
41078
+ }
41079
+ if (budgetScore < 1) {
41080
+ findings.push(`Description is ${description.length} chars, outside the 20\u20132000 char budget.`);
41081
+ recs.push("Tighten the description to the agent\u2019s role and dispatch trigger.");
41082
+ }
41083
+ if (noOp > 0.2) {
41084
+ findings.push("Description contains default-behavior phrases that add no signal (no-op candidates).");
41085
+ recs.push("Delete no-op phrasing from the description rather than trimming it.");
41086
+ }
41087
+ if (descDup > 0.3) {
41088
+ findings.push("Description restates body text near-verbatim (duplication).");
41089
+ recs.push("State the agent\u2019s role once in the description; do not repeat it in the body.");
41090
+ }
40896
41091
  return {
40897
- score: clamp(score),
41092
+ score,
40898
41093
  note: missing.length > 0 ? `Missing: ${missing.join(", ")}` : "All required fields present",
40899
- findings,
40900
- recommendations: recs
41094
+ findings: findings.length > 0 ? findings : undefined,
41095
+ recommendations: recs.length > 0 ? recs : undefined
40901
41096
  };
40902
41097
  }
40903
41098
  function scoreRoleClarity(body) {
@@ -41006,7 +41201,7 @@ function evaluateAgent(content, target) {
41006
41201
  const data = parseFrontmatterSafe(content) ?? {};
41007
41202
  const body = extractBody(content);
41008
41203
  const dimensions = {
41009
- completeness: scoreCompleteness(data),
41204
+ completeness: scoreCompleteness(data, body),
41010
41205
  "role-clarity": scoreRoleClarity(body),
41011
41206
  "tool-selection": scoreToolSelection(data),
41012
41207
  "skill-linkage": scoreSkillLinkage(body),
@@ -41043,8 +41238,27 @@ function scoreCompleteness2(data) {
41043
41238
  const recs = missing.includes("description") ? ["Add a description field to the command frontmatter"] : undefined;
41044
41239
  return { score, note, findings, recommendations: recs };
41045
41240
  }
41046
- function scoreClarity(body) {
41047
- return scoreClarityFromDensities(body);
41241
+ function scoreClarity(body, description) {
41242
+ const base2 = scoreClarityFromDensities(body);
41243
+ const budgetScore = description ? scoreDescriptionBudget(description) : 1;
41244
+ const noOp = noOpDensity(description);
41245
+ const descDup = description ? duplicationRatio(description, body, 12) : 0;
41246
+ const score = clamp(base2.score * budgetScore * (1 - noOp) * (1 - descDup));
41247
+ const findings = [...base2.findings ?? []];
41248
+ const recommendations = [...base2.recommendations ?? []];
41249
+ if (budgetScore < 1) {
41250
+ findings.push(`Description is ${description.length} chars, outside the 20\u2013500 char budget.`);
41251
+ recommendations.push("Tighten the description to what the command does and when to use it.");
41252
+ }
41253
+ if (noOp > 0.2) {
41254
+ findings.push("Description contains default-behavior phrases that add no signal (no-op candidates).");
41255
+ recommendations.push("Delete no-op phrasing from the description rather than trimming it.");
41256
+ }
41257
+ if (descDup > 0.3) {
41258
+ findings.push("Description restates body text near-verbatim (duplication).");
41259
+ recommendations.push("State the command purpose once in the description; do not repeat it in the body.");
41260
+ }
41261
+ return { score, note: base2.note, findings, recommendations };
41048
41262
  }
41049
41263
  function scoreArgumentHints(data) {
41050
41264
  const hasKey = "argument-hint" in data;
@@ -41146,7 +41360,7 @@ function evaluateCommand(content, target) {
41146
41360
  const body = extractBody(content);
41147
41361
  const dimensions = {
41148
41362
  completeness: scoreCompleteness2(data),
41149
- clarity: scoreClarity(body),
41363
+ clarity: scoreClarity(body, typeof data.description === "string" ? data.description : ""),
41150
41364
  "argument-hints": scoreArgumentHints(data),
41151
41365
  "tool-references": scoreToolReferences(body, data),
41152
41366
  "slash-syntax": scoreSlashSyntax(body, target)
@@ -41335,9 +41549,25 @@ function scorePlatformCoverage(data, body) {
41335
41549
  return { score, note: `${platforms.length} platforms covered`, findings, recommendations: recs };
41336
41550
  }
41337
41551
  function scoreConciseness(body) {
41552
+ const lengthScore = scoreLength(body, 1000, 8000);
41553
+ const noOp = noOpDensity(body);
41554
+ const bodyDup = duplicationRatio(body, undefined, 12);
41555
+ const score = clamp(lengthScore * (1 - noOp) * (1 - bodyDup * 0.3));
41556
+ const findings = [];
41557
+ const recs = [];
41558
+ if (noOp > 0.2) {
41559
+ findings.push("Body contains default-behavior phrases that do not change model behavior (no-op candidates).");
41560
+ recs.push("Delete no-op instructions rather than trimming them \u2014 they add no signal.");
41561
+ }
41562
+ if (bodyDup > 0.15) {
41563
+ findings.push("Body repeats the same phrasing (n-gram duplication) in multiple places.");
41564
+ recs.push("Collapse duplicated phrasing into one authoritative section; cite it elsewhere.");
41565
+ }
41338
41566
  return {
41339
- score: scoreLength(body, 1000, 8000),
41340
- note: `Body length: ${body.length} chars`
41567
+ score,
41568
+ note: `Body length: ${body.length} chars`,
41569
+ findings: findings.length > 0 ? findings : undefined,
41570
+ recommendations: recs.length > 0 ? recs : undefined
41341
41571
  };
41342
41572
  }
41343
41573
  function scoreToneConsistency(body) {
@@ -41411,12 +41641,13 @@ var init_magent = __esm(() => {
41411
41641
  function evaluateSkill(content, target) {
41412
41642
  const data = parseFrontmatterSafe(content);
41413
41643
  const body = extractBody(content);
41644
+ const description = typeof data?.description === "string" ? data.description : "";
41414
41645
  const dimensions = {
41415
41646
  completeness: scoreCompleteness4(content, data, body),
41416
41647
  clarity: scoreClarity2(body),
41417
- "trigger-accuracy": scoreTriggerAccuracy(body),
41648
+ "trigger-accuracy": scoreTriggerAccuracy(body, description, data),
41418
41649
  "anti-hallucination": scoreAntiHallucination(body),
41419
- conciseness: scoreConciseness2(body)
41650
+ conciseness: scoreConciseness2(body, description)
41420
41651
  };
41421
41652
  return {
41422
41653
  type: "skill",
@@ -41435,7 +41666,8 @@ function scoreCompleteness4(content, data, body) {
41435
41666
  const required2 = REQUIRED_FIELDS.skill;
41436
41667
  const presence = scorePresence(presentKeys, required2);
41437
41668
  const structure = hasPattern(body, [/^# /m, /^## /m, /^### /m]);
41438
- const score = clamp(presence * structure);
41669
+ const disclosed = progressiveDisclosureShape(body) ? 1 : 0.7;
41670
+ const score = clamp(presence * structure * disclosed);
41439
41671
  const keySet = new Set(presentKeys);
41440
41672
  const missing = required2.filter((f) => !keySet.has(f));
41441
41673
  const note = missing.length > 0 ? `Missing fields: ${missing.join(", ")}` : "All required fields present";
@@ -41449,13 +41681,31 @@ function scoreCompleteness4(content, data, body) {
41449
41681
  findings.push("Body lacks section headings (# / ## / ###). Structure aids navigation.");
41450
41682
  recommendations.push("Organize content with markdown headings for progressive disclosure");
41451
41683
  }
41684
+ if (disclosed < 1) {
41685
+ findings.push("Body is over the disclosure budget with no references/ (or See Also) link.");
41686
+ recommendations.push("Move supporting detail into references/*.md and link it, rather than growing the body.");
41687
+ }
41452
41688
  return { score, note, findings, recommendations };
41453
41689
  }
41454
41690
  function scoreClarity2(body) {
41455
- return scoreClarityFromDensities(body);
41456
- }
41457
- function scoreTriggerAccuracy(body) {
41458
- const count2 = countTriggerPhrases(body);
41691
+ const base2 = scoreClarityFromDensities(body);
41692
+ const checkability = completionCheckability(body);
41693
+ const score = clamp(base2.score * checkability);
41694
+ const findings = [...base2.findings ?? []];
41695
+ const recommendations = [...base2.recommendations ?? []];
41696
+ if (checkability < 1) {
41697
+ findings.push('Step-shaped content uses vague completion bounds (e.g. "as needed", "when ready").');
41698
+ recommendations.push("Replace vague bounds with a decidable done-condition per step.");
41699
+ }
41700
+ return { score, note: base2.note, findings, recommendations };
41701
+ }
41702
+ function scoreTriggerAccuracy(body, description, data) {
41703
+ const userInvoked = data?.["disable-model-invocation"] === true;
41704
+ if (userInvoked) {
41705
+ return scoreUserInvokedDescription(description);
41706
+ }
41707
+ const phrases = collectTriggerPhrases(body, description);
41708
+ const count2 = countTriggerBranches(phrases);
41459
41709
  let score;
41460
41710
  if (count2 >= 3 && count2 <= 10) {
41461
41711
  score = 1;
@@ -41467,13 +41717,38 @@ function scoreTriggerAccuracy(body) {
41467
41717
  const findings = [];
41468
41718
  const recommendations = [];
41469
41719
  if (count2 < 3) {
41470
- findings.push(`Only ${count2} trigger phrase(s) found; aim for 3\u201310 for reliable skill activation.`);
41471
- recommendations.push("Add 1\u20132 more When-to-Use scenarios or trigger phrase patterns to the description.");
41720
+ findings.push(`Only ${count2} distinct trigger branch(es) found; aim for 3\u201310 for reliable skill activation.`);
41721
+ recommendations.push("Add 1\u20132 more When-to-Use scenarios covering genuinely distinct branches.");
41472
41722
  } else if (count2 > 10) {
41473
- findings.push(`${count2} trigger phrases may cause overlap with adjacent skills.`);
41723
+ findings.push(`${count2} distinct trigger branches may cause overlap with adjacent skills.`);
41474
41724
  recommendations.push("Consolidate overlapping triggers or narrow the activation scope.");
41475
41725
  }
41476
- return { score, note: `${count2} trigger phrases found`, findings, recommendations };
41726
+ if (phrases.length > count2) {
41727
+ findings.push(`${phrases.length} trigger phrase(s) collapse to ${count2} distinct branch(es) \u2014 some are synonym clusters.`);
41728
+ recommendations.push("Collapse synonym-cluster triggers into one phrase per genuine branch.");
41729
+ }
41730
+ return {
41731
+ score,
41732
+ note: `${count2} distinct trigger branch(es) (${phrases.length} phrases)`,
41733
+ findings,
41734
+ recommendations
41735
+ };
41736
+ }
41737
+ function scoreUserInvokedDescription(description) {
41738
+ const richness = descriptionTriggerRichness(description);
41739
+ const score = clamp(1 - richness);
41740
+ const findings = [];
41741
+ const recommendations = [];
41742
+ if (richness > 0.4) {
41743
+ findings.push("This skill is user-invoked (disable-model-invocation: true) but the description reads " + "trigger-rich. A user-invoked skill cannot be fired by other skills or commands.");
41744
+ recommendations.push("Rewrite the description as a one-line, human-facing summary \u2014 no branch list.");
41745
+ }
41746
+ return {
41747
+ score,
41748
+ note: `User-invoked: description trigger-richness ${richness.toFixed(2)} (lower is better)`,
41749
+ findings,
41750
+ recommendations
41751
+ };
41477
41752
  }
41478
41753
  function scoreAntiHallucination(body) {
41479
41754
  const density = keywordDensity(body, [
@@ -41495,16 +41770,45 @@ function scoreAntiHallucination(body) {
41495
41770
  }
41496
41771
  return { score: density, note, findings, recommendations };
41497
41772
  }
41498
- function scoreConciseness2(body) {
41499
- const score = scoreLength(body, 500, 15000);
41500
- return { score, note: `Body length: ${body.length} chars` };
41773
+ function scoreConciseness2(body, description) {
41774
+ const lengthScore = scoreLength(body, 500, 15000);
41775
+ const budgetScore = description ? scoreDescriptionBudget(description) : 1;
41776
+ const noOp = noOpDensity(body);
41777
+ const descDup = description ? duplicationRatio(description, body, 12) : 0;
41778
+ const bodyDup = duplicationRatio(body, undefined, 12);
41779
+ const score = clamp(lengthScore * budgetScore * (1 - noOp) * (1 - descDup) * (1 - bodyDup * 0.3));
41780
+ const findings = [];
41781
+ const recommendations = [];
41782
+ if (budgetScore < 1) {
41783
+ findings.push(`Description is ${description.length} chars, outside the 20\u2013500 char budget.`);
41784
+ recommendations.push("Tighten the description to the identity phrase plus distinct trigger branches.");
41785
+ }
41786
+ if (noOp > 0.2) {
41787
+ findings.push("Body contains default-behavior phrases that do not change model behavior (no-op candidates).");
41788
+ recommendations.push("Delete no-op instructions rather than trimming them \u2014 they add no signal.");
41789
+ }
41790
+ if (descDup > 0.3) {
41791
+ findings.push("Body restates the description near-verbatim (duplication).");
41792
+ recommendations.push("State identity once in the description; do not repeat it in the body.");
41793
+ }
41794
+ if (bodyDup > 0.15) {
41795
+ findings.push("Body repeats the same phrasing (n-gram duplication) in multiple places.");
41796
+ recommendations.push("Collapse duplicated phrasing into one authoritative section; cite it elsewhere.");
41797
+ }
41798
+ return { score, note: `Body length: ${body.length} chars`, findings, recommendations };
41799
+ }
41800
+ function collectTriggerPhrases(body, description) {
41801
+ const fromSection = triggerSectionPhrases(body);
41802
+ if (fromSection.length > 0)
41803
+ return fromSection;
41804
+ return descriptionTriggerPhrases(description);
41501
41805
  }
41502
- function countTriggerPhrases(body) {
41806
+ function triggerSectionPhrases(body) {
41503
41807
  const lines = body.split(`
41504
41808
  `);
41505
41809
  let inTriggerSection = false;
41506
41810
  let sectionDepth = 0;
41507
- let count2 = 0;
41811
+ const phrases = [];
41508
41812
  for (const line of lines) {
41509
41813
  const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
41510
41814
  if (headingMatch) {
@@ -41519,19 +41823,18 @@ function countTriggerPhrases(body) {
41519
41823
  continue;
41520
41824
  }
41521
41825
  if (inTriggerSection) {
41522
- if (/^\s*[-*+]\s/.test(line) || /^\s*\d+[.)]\s/.test(line)) {
41523
- count2++;
41826
+ const listMatch = line.match(/^\s*(?:[-*+]|\d+[.)])\s+(.+)/);
41827
+ if (listMatch?.[1]) {
41828
+ phrases.push(listMatch[1].replace(/^["']|["']$/g, "").trim());
41524
41829
  }
41525
41830
  }
41526
41831
  }
41527
- if (count2 === 0) {
41528
- for (const line of lines) {
41529
- if (/^\s*[-*+]\s/.test(line) || /^\s*\d+[.)]\s/.test(line)) {
41530
- count2++;
41531
- }
41532
- }
41533
- }
41534
- return count2;
41832
+ return phrases;
41833
+ }
41834
+ function descriptionTriggerPhrases(description) {
41835
+ if (!description)
41836
+ return [];
41837
+ return description.split(/[,;]|(?:\bor\b)/i).map((s) => s.trim()).filter((s) => s.length > 3);
41535
41838
  }
41536
41839
  var init_skill = __esm(() => {
41537
41840
  init_heuristics();
@@ -95937,6 +96240,7 @@ async function replaySplit(backend, skill2, cases, split, toolsCalled) {
95937
96240
  init_src();
95938
96241
 
95939
96242
  // src/operations/evolve.ts
96243
+ var FAILURE_MODES = ["sprawl", "sediment", "duplication", "no-op", "premature-completion"];
95940
96244
  function isHookApplyCapableOpt(opts) {
95941
96245
  if (!opts)
95942
96246
  return false;
@@ -96306,6 +96610,9 @@ async function ingestProposal(db2, type, name, resolvedPath, ingestPath, opts, b
96306
96610
  if (!change.dimension || !change.location || !change.proposed || !change.reason) {
96307
96611
  throw Object.assign(new Error(`Invalid ProposedChange: missing required field (dimension, location, proposed, reason)`), { code: 1 });
96308
96612
  }
96613
+ if (change.failure_mode !== undefined && !FAILURE_MODES.includes(change.failure_mode)) {
96614
+ throw Object.assign(new Error(`Invalid failure_mode "${change.failure_mode}". Expected one of: ${FAILURE_MODES.join(", ")}`), { code: 1 });
96615
+ }
96309
96616
  }
96310
96617
  const proposalDao = new ProposalDao(db2);
96311
96618
  const existingProposals = await proposalDao.getProposals(type, name);
@@ -96872,7 +97179,7 @@ function classifyFix(finding) {
96872
97179
  if (finding.severity === "error") {
96873
97180
  return "auto-apply";
96874
97181
  }
96875
- if (["description", "trigger-accuracy", "clarity", "conciseness"].includes(finding.field)) {
97182
+ if (["description", "trigger-accuracy", "clarity", "conciseness", "invocation-mode"].includes(finding.field)) {
96876
97183
  return "suggest";
96877
97184
  }
96878
97185
  if (["skill-linkage", "tool-selection", "model-fit", "platform-coverage"].includes(finding.field)) {
@@ -97261,7 +97568,7 @@ function addTargetOption(cmd) {
97261
97568
  return cmd.option("-t, --target <agent>", "Target agent platform", "claude");
97262
97569
  }
97263
97570
  function addScaffoldOptions(cmd) {
97264
- return cmd.option("-d, --description <text>", "Content description").option("-t, --target <agent>", "Target agent platform", "claude").option("-o, --output <dir>", "Output directory (default: cwd)").option("--template <tier>", "Template tier (e.g. minimal / standard / specialist)").option("--tools <list>", "Comma-separated tool names to pre-populate frontmatter").option("--force", "Overwrite existing file if present");
97571
+ return cmd.option("-d, --description <text>", "Content description").option("-t, --target <agent>", "Target agent platform", "claude").option("-o, --output <dir>", "Output directory (default: cwd)").option("--template <tier>", "Template tier (e.g. minimal / standard / specialist)").option("--tools <list>", "Comma-separated tool names to pre-populate frontmatter").option("--invocation-mode <mode>", "Skill invocation axis: 'user' (disable-model-invocation + one-line description) " + "or 'model' (trigger-rich description, default)").option("--force", "Overwrite existing file if present");
97265
97572
  }
97266
97573
  function addEvolveOptions(cmd) {
97267
97574
  return cmd.option("-t, --target <agent>", "Target agent platform", "claude").option("--from <date>", "Analyze evaluations since date (ISO 8601)").option("--propose-only", "Generate proposal without applying").option("--accept <id>", "Accept a specific proposal by ID").option("--reject <id>", "Reject a specific proposal").option("--json", "Output machine-readable JSON (envelope-out with --propose-only)").option("--ingest <file>", "Agent-authored proposal JSON (ingest-in mode)").option("--margin <n>", "\u0394-margin gate threshold for accept (default 0.05)", Number.parseFloat, 0.05).option("--eval-gate", "Enable empirical behavior gate (requires skills/<name>/eval/cases.yaml)").option("--analyze", "Print analysis summary (trends, score, data sources) without writing a proposal").option("--history", "List applied proposal versions from the store").option("--rollback <id>", "Rollback to a prior version by proposal_id (requires --confirm)").option("--confirm", "Confirm a destructive operation (required for --rollback)");
@@ -98451,6 +98758,13 @@ async function migrateSkills(sources, dest, opts) {
98451
98758
  init_src();
98452
98759
 
98453
98760
  // src/commands/skill.ts
98761
+ function parseInvocationMode(value) {
98762
+ if (value === undefined)
98763
+ return;
98764
+ if (value === "user" || value === "model")
98765
+ return value;
98766
+ throw new Error(`Invalid --invocation-mode "${value}". Expected 'user' or 'model'.`);
98767
+ }
98454
98768
  async function skillScaffold(opts) {
98455
98769
  const target = resolveTarget(opts);
98456
98770
  const createdPath = await scaffold("skill", opts.name, {
@@ -98459,7 +98773,8 @@ async function skillScaffold(opts) {
98459
98773
  output: opts.output,
98460
98774
  force: opts.force,
98461
98775
  template: opts.template,
98462
- tools: opts.tools
98776
+ tools: opts.tools,
98777
+ invocationMode: parseInvocationMode(opts.invocationMode)
98463
98778
  });
98464
98779
  echo(`Created: ${createdPath}`);
98465
98780
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/superskill",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "A manager for multi-agent skill, slash command, subagent, hook, MCP and etc.",
5
5
  "keywords": [
6
6
  "cli",
@@ -1,4 +1,4 @@
1
- version: 1
1
+ version: 2
2
2
  type: agent
3
3
  dimensions:
4
4
  - name: completeness
@@ -6,10 +6,21 @@ dimensions:
6
6
  criterion: >
7
7
  Does the agent cover its stated scope end-to-end? Penalize missing workflow
8
8
  phases, undocumented edge cases, and gaps between the stated trigger and
9
- the actual execution path.
9
+ the actual execution path. Measurable proxies (folded in, not a new
10
+ dimension): the frontmatter description — the dispatch-time signal a
11
+ Task-tool orchestrator reads to pick this agent — sits within a 20-2000
12
+ char context-load budget (wider than skill’s 20-500: Claude Code's own
13
+ convention embeds few-shot <example> blocks directly in an agent's
14
+ description, unlike a skill's single-sentence trigger), is free of curated
15
+ no-op phrases, and does not restate the body near-verbatim. Progressive
16
+ disclosure (references/*.md) does NOT apply here — agents in this repo
17
+ are flat single-file documents with no companion references/ directory
18
+ convention, unlike skills. Whether the description's wording will actually
19
+ read well to a dispatching orchestrator is genuinely subjective — judge
20
+ that here, in the two-call seam, not via a deterministic proxy.
10
21
  anchors:
11
- excellent: "Full workflow from intake to delivery; every phase has explicit steps and exit conditions."
12
- poor: "Scope claims coverage but execution path skips phases or leaves edge cases unhandled."
22
+ excellent: "Full workflow from intake to delivery; every phase has explicit steps and exit conditions; description is tight, within budget, and not duplicated from the body."
23
+ poor: "Scope claims coverage but execution path skips phases, leaves edge cases unhandled, or the description is vague, no-op-heavy, or duplicated from the body."
13
24
 
14
25
  - name: role-clarity
15
26
  weight: 0.25
@@ -1,4 +1,4 @@
1
- version: 1
1
+ version: 2
2
2
  type: command
3
3
  dimensions:
4
4
  - name: completeness
@@ -16,10 +16,17 @@ dimensions:
16
16
  criterion: >
17
17
  Is the command's purpose and usage unambiguous? The description should
18
18
  tell the user what it does and when to use it. Penalize descriptions
19
- that restate the command name without adding information.
19
+ that restate the command name without adding information. Measurable
20
+ proxies (folded in, not a new dimension): description sits within a
21
+ 20-500 char context-load budget; description is free of curated no-op
22
+ phrases ("be helpful", "think carefully") that add no signal; description
23
+ does not restate the command body near-verbatim (n-gram duplication).
24
+ Whether the description's actual WORDING is well-chosen for the intended
25
+ audience is genuinely subjective — judge that here, in the two-call seam,
26
+ not via a deterministic proxy.
20
27
  anchors:
21
- excellent: "Description states what the command does, when to use it, and what the output means."
22
- poor: "Description restates the command name; user cannot tell when to use it."
28
+ excellent: "Description states what the command does, when to use it, and what the output means, within budget, with no no-op or duplicated phrasing."
29
+ poor: "Description restates the command name, exceeds the context-load budget, or duplicates the body verbatim; user cannot tell when to use it."
23
30
 
24
31
  - name: argument-hints
25
32
  weight: 0.20
package/rubrics/hook.yaml CHANGED
@@ -1,5 +1,10 @@
1
1
  version: 1
2
2
  type: hook
3
+ # R2 scoping note (task 0070): description-budget / no-op-density / duplication proxies
4
+ # do NOT transfer to hooks. A hook definition is a JSON matcher/command table with no
5
+ # frontmatter description field and no markdown prose body — there is nothing for those
6
+ # proxies to measure. The trigger-equivalent concept here is the JSON `matcher` field,
7
+ # already scored by pattern-match-quality. This is a deliberate N/A, not a gap.
3
8
  dimensions:
4
9
  - name: correctness
5
10
  weight: 0.25
@@ -1,4 +1,4 @@
1
- version: 1
1
+ version: 2
2
2
  type: magent
3
3
  dimensions:
4
4
  - name: completeness
@@ -27,10 +27,17 @@ dimensions:
27
27
  criterion: >
28
28
  Is the config as short as possible while remaining complete? Penalize
29
29
  redundant rule restating, boilerplate that adds no information, and
30
- rules that could be merged without loss.
30
+ rules that could be merged without loss. Measurable proxies (folded in,
31
+ not a new dimension): body is free of curated no-op phrases ("be
32
+ helpful", "think carefully") that restate default model behavior without
33
+ changing it; body has low within-body n-gram duplication. A magent
34
+ config (AGENTS.md/CLAUDE.md) has no separate frontmatter description
35
+ field — it is read wholesale rather than dispatch-selected — so the
36
+ description-budget check that applies to skill/command/agent does not
37
+ apply here.
31
38
  anchors:
32
- excellent: "Minimal length with no redundancy; every rule carries unique information."
33
- poor: "Redundant rules and boilerplate; rules could be merged without loss."
39
+ excellent: "Minimal length with no redundancy; every rule carries unique information; no no-op phrasing."
40
+ poor: "Redundant rules, boilerplate, or no-op phrasing that adds no signal; rules could be merged without loss."
34
41
 
35
42
  - name: tone-consistency
36
43
  weight: 0.20
@@ -1,4 +1,4 @@
1
- version: 1
1
+ version: 2
2
2
  type: skill
3
3
  dimensions:
4
4
  - name: completeness
@@ -6,31 +6,45 @@ dimensions:
6
6
  criterion: >
7
7
  Does the skill cover its stated purpose end-to-end? Penalize missing
8
8
  steps, undocumented preconditions, and gaps between the trigger and the
9
- delivered outcome. A complete skill leaves no implicit step.
9
+ delivered outcome. A complete skill leaves no implicit step. Also check
10
+ progressive-disclosure shape: a body that has grown past a reasonable
11
+ length budget without moving supporting detail into references/*.md (or
12
+ linking a "See Also"/"Additional Resources" section) is incomplete in a
13
+ structural sense — coverage exists but isn't disclosed in a navigable way.
10
14
  anchors:
11
- excellent: "Every phase from trigger to outcome is explicit; preconditions and exit conditions stated."
12
- poor: "Purpose claims coverage but steps are implicit or preconditions unstated."
15
+ excellent: "Every phase from trigger to outcome is explicit; preconditions and exit conditions stated; a body over the length budget discloses detail via references/."
16
+ poor: "Purpose claims coverage but steps are implicit or preconditions unstated; a long body with no references/ link buries detail instead of disclosing it."
13
17
 
14
18
  - name: clarity
15
19
  weight: 0.25
16
20
  criterion: >
17
21
  Is the skill's instruction unambiguous to a fresh agent? Penalize
18
22
  vague verbs ('handle', 'process'), undefined terms, and instructions
19
- that require external context the skill does not provide.
23
+ that require external context the skill does not provide. For
24
+ step/workflow-shaped content, also check completion-criteria
25
+ checkability: each step should read as a decidable done-condition.
26
+ Penalize vague bounds ("understanding reached", "as needed", "as
27
+ appropriate") that leave a reader unable to tell when a step is done.
20
28
  anchors:
21
- excellent: "Concrete verbs, defined terms, self-contained instructions an agent can execute without guessing."
22
- poor: "Vague verbs and undefined terms; agent must guess the intended behavior."
29
+ excellent: "Concrete verbs, defined terms, self-contained instructions an agent can execute without guessing; every step has a decidable done-condition."
30
+ poor: "Vague verbs and undefined terms; agent must guess the intended behavior; steps end on vague bounds like 'as needed' with no checkable condition."
23
31
 
24
32
  - name: trigger-accuracy
25
33
  weight: 0.20
26
34
  criterion: >
27
35
  Does the skill fire on the right inputs and not on adjacent ones? The
28
- description must precisely scope when the skill activates. Penalize
29
- triggers that are too broad (fires on unrelated requests) or too narrow
30
- (misses the intended case).
36
+ description must precisely scope when the skill activates. Score by
37
+ DISTINCT trigger branches, not raw phrase count a description with 8
38
+ near-synonymous phrasings of the same branch ("review this code" /
39
+ "review the code" / "review code") is still one branch, not eight.
40
+ Reward description phrasing that covers 3-10 genuinely distinct
41
+ branches (one phrase per branch); penalize triggers that are too broad
42
+ (fires on unrelated requests), too narrow (misses the intended case),
43
+ or padded with synonym clusters that inflate the apparent count without
44
+ adding coverage.
31
45
  anchors:
32
- excellent: "Trigger description scopes activation precisely; no overlap with adjacent skills."
33
- poor: "Trigger is broad enough to fire on unrelated requests or narrow enough to miss the intended case."
46
+ excellent: "Trigger description scopes activation precisely across 3-10 genuinely distinct branches; no synonym-cluster padding; no overlap with adjacent skills."
47
+ poor: "Trigger is broad enough to fire on unrelated requests, narrow enough to miss the intended case, or pads the same branch with several near-identical phrasings."
34
48
 
35
49
  - name: anti-hallucination
36
50
  weight: 0.15
@@ -47,7 +61,17 @@ dimensions:
47
61
  criterion: >
48
62
  Is the skill as short as possible while remaining complete? Penalize
49
63
  redundant restating, boilerplate that adds no information, and steps
50
- that could be merged without loss.
64
+ that could be merged without loss. Specifically check: (1) description
65
+ char budget — a model-invoked description sits in context every turn it
66
+ is a candidate, so it should be no longer than the identity phrase plus
67
+ its distinct trigger branches require; (2) no-op density — instructions
68
+ that restate default model behavior ("be helpful", "think carefully")
69
+ change nothing and should be deleted, not trimmed; (3) duplication —
70
+ the body should not restate the description near-verbatim, and should
71
+ not repeat the same phrasing in multiple places within itself. Leading-
72
+ word quality (is this the RIGHT tight/imperative word for the described
73
+ action, for this specific model) is genuinely subjective — judge it
74
+ here, in the two-call seam, not via a deterministic proxy.
51
75
  anchors:
52
- excellent: "Minimal length with no redundancy; every line carries unique information."
53
- poor: "Redundant restating and boilerplate; steps could be merged without loss."
76
+ excellent: "Minimal length with no redundancy; description stays within its context-load budget; no no-op instructions; no restated or duplicated phrasing; leading words are precise and model-appropriate."
77
+ poor: "Redundant restating and boilerplate; steps could be merged without loss; description is bloated; body pads out no-op instructions or duplicates the description verbatim."
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  name: <!-- NAME -->
3
+ # Description rules: front-load the leading identity phrase; one trigger per genuine
4
+ # branch (collapse synonym triggers into one); never restate the body's identity line.
3
5
  description: <!-- DESCRIPTION -->
4
6
  ---
5
7
 
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  name: <!-- NAME -->
3
+ # Description rules: front-load the leading identity phrase; one trigger per genuine
4
+ # branch (collapse synonym triggers into one); never restate the body's identity line.
3
5
  description: <!-- DESCRIPTION -->
4
6
  license: Apache-2.0
5
7
  metadata:
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  name: <!-- NAME -->
3
+ # Description rules: front-load the leading identity phrase; one trigger per genuine
4
+ # branch (collapse synonym triggers into one); never restate the body's identity line.
3
5
  description: <!-- DESCRIPTION -->
4
6
  license: Apache-2.0
5
7
  metadata:
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  name: <!-- NAME -->
3
+ # Description rules: front-load the leading identity phrase; one trigger per genuine
4
+ # branch (collapse synonym triggers into one); never restate the body's identity line.
3
5
  description: <!-- DESCRIPTION -->
4
6
  license: Apache-2.0
5
7
  metadata: