@gobing-ai/superskill 0.1.7 → 0.2.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/index.js CHANGED
@@ -9317,7 +9317,7 @@ function hashContent(filePath) {
9317
9317
  var init_hash = () => {};
9318
9318
 
9319
9319
  // ../../packages/core/src/content/identity.ts
9320
- import { existsSync } from "fs";
9320
+ import { existsSync, statSync } from "fs";
9321
9321
  import { basename, dirname, extname, join } from "path";
9322
9322
  import { cwd } from "process";
9323
9323
  function resolveContentName(path) {
@@ -9333,10 +9333,26 @@ function resolveContentName(path) {
9333
9333
  }
9334
9334
  function resolveContentPath(type, name, opts) {
9335
9335
  if (name.includes("/") || name.includes("\\")) {
9336
- if (existsSync(name))
9337
- return name;
9336
+ if (existsSync(name)) {
9337
+ const st = statSync(name);
9338
+ if (st.isDirectory()) {
9339
+ const skillMd = join(name, "SKILL.md");
9340
+ if (existsSync(skillMd))
9341
+ return skillMd;
9342
+ } else {
9343
+ return name;
9344
+ }
9345
+ }
9338
9346
  }
9339
9347
  const base = opts?.baseDir ?? cwd();
9348
+ const asIs = join(base, name);
9349
+ if (existsSync(asIs) && statSync(asIs).isFile())
9350
+ return asIs;
9351
+ if (type === "skill") {
9352
+ const skillDirForm = join(base, name, "SKILL.md");
9353
+ if (existsSync(skillDirForm))
9354
+ return skillDirForm;
9355
+ }
9340
9356
  const direct = join(base, `${name}.md`);
9341
9357
  if (existsSync(direct))
9342
9358
  return direct;
@@ -9355,6 +9371,11 @@ function resolveContentPath(type, name, opts) {
9355
9371
  }
9356
9372
  return null;
9357
9373
  }
9374
+ function assertSafePathSegment(value, label) {
9375
+ if (!value || value === "." || value === ".." || value.includes("/") || value.includes("\\") || value.includes("\x00")) {
9376
+ throw new Error(`Invalid ${label} '${value}': must be a single path segment`);
9377
+ }
9378
+ }
9358
9379
  var init_identity = () => {};
9359
9380
 
9360
9381
  // ../../packages/core/src/content/paths.ts
@@ -9710,7 +9731,7 @@ var init_adapt_subagent = __esm(() => {
9710
9731
  });
9711
9732
 
9712
9733
  // ../../packages/core/src/mapper.ts
9713
- import { existsSync as existsSync3, mkdirSync, readdirSync, readFileSync as readFileSync2, statSync, writeFileSync } from "fs";
9734
+ import { existsSync as existsSync3, mkdirSync, readdirSync, readFileSync as readFileSync2, statSync as statSync2, writeFileSync } from "fs";
9714
9735
  import { join as join3 } from "path";
9715
9736
  function mapPluginToRulesync(pluginPath, pluginName, outputDir) {
9716
9737
  assertSafePathSegment(pluginName, "plugin name");
@@ -9798,11 +9819,6 @@ function mapPluginToRulesync(pluginPath, pluginName, outputDir) {
9798
9819
  }
9799
9820
  return result;
9800
9821
  }
9801
- function assertSafePathSegment(value, label) {
9802
- if (!value || value === "." || value === ".." || value.includes("/") || value.includes("\\") || value.includes("\x00")) {
9803
- throw new Error(`Invalid ${label} '${value}': must be a single path segment`);
9804
- }
9805
- }
9806
9822
  function deepMergeJsonFile(sourcePath, targetPath) {
9807
9823
  const source = readJsonObject(sourcePath);
9808
9824
  const target = existsSync3(targetPath) ? readJsonObject(targetPath) : {};
@@ -9833,7 +9849,7 @@ function copyAndRewriteDirectory(source, destination, pluginName) {
9833
9849
  for (const entry of readdirSync(source)) {
9834
9850
  const srcPath = join3(source, entry);
9835
9851
  const destPath = join3(destination, entry);
9836
- if (statSync(srcPath).isDirectory()) {
9852
+ if (statSync2(srcPath).isDirectory()) {
9837
9853
  copyAndRewriteDirectory(srcPath, destPath, pluginName);
9838
9854
  } else {
9839
9855
  const content = readFileSync2(srcPath, "utf-8");
@@ -9842,6 +9858,7 @@ function copyAndRewriteDirectory(source, destination, pluginName) {
9842
9858
  }
9843
9859
  }
9844
9860
  var init_mapper = __esm(() => {
9861
+ init_identity();
9845
9862
  init_adapt_command();
9846
9863
  init_adapt_subagent();
9847
9864
  init_rewrite_references();
@@ -13814,7 +13831,7 @@ var init_zod = __esm(() => {
13814
13831
  });
13815
13832
 
13816
13833
  // ../../packages/core/src/marketplace.ts
13817
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
13834
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
13818
13835
  import { join as join4, resolve } from "path";
13819
13836
  function resolvePlugin(marketplacePath, pluginName) {
13820
13837
  let manifestPath = null;
@@ -13861,8 +13878,19 @@ function resolvePlugin(marketplacePath, pluginName) {
13861
13878
  const marketplaceRoot = resolve(manifestPath, "..", "..");
13862
13879
  const pluginRootBase = manifest.metadata?.pluginRoot ?? "";
13863
13880
  const pluginRoot = resolve(marketplaceRoot, pluginRootBase, source);
13864
- if (!existsSync4(join4(pluginRoot, "plugin.json"))) {
13865
- throw new Error(`plugin.json not found in resolved plugin root: ${pluginRoot}`);
13881
+ let dirents;
13882
+ try {
13883
+ dirents = readdirSync2(pluginRoot);
13884
+ } catch {
13885
+ throw new Error(`Plugin root not found: ${pluginRoot}`);
13886
+ }
13887
+ const hasSkills = dirents.includes("skills");
13888
+ const hasCommands = dirents.includes("commands");
13889
+ const hasAgents = dirents.includes("agents");
13890
+ const hasHooksDir = dirents.includes("hooks");
13891
+ const hasHooksFile = dirents.includes("hooks.json");
13892
+ if (!(hasSkills || hasCommands || hasAgents || hasHooksDir || hasHooksFile)) {
13893
+ throw new Error(`No recognizable plugin content (skills/, commands/, agents/, hooks/, hooks.json) in: ${pluginRoot}`);
13866
13894
  }
13867
13895
  return { pluginRoot, marketplaceRoot, source };
13868
13896
  }
@@ -14013,7 +14041,7 @@ var init_migrate = __esm(() => {
14013
14041
  });
14014
14042
 
14015
14043
  // ../../packages/core/src/operations/package.ts
14016
- import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync3, statSync as statSync2 } from "fs";
14044
+ import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync3, statSync as statSync3 } from "fs";
14017
14045
  import { basename as basename2, dirname as dirname3, join as join5 } from "path";
14018
14046
  import { cwd as cwd3 } from "process";
14019
14047
  function resolveSkillDir(name) {
@@ -14021,7 +14049,7 @@ function resolveSkillDir(name) {
14021
14049
  if (!skillPath) {
14022
14050
  throw Object.assign(new Error(`Skill not found: ${name}`), { code: "ENOENT" });
14023
14051
  }
14024
- const dir = statSync2(skillPath).isDirectory() ? skillPath : dirname3(skillPath);
14052
+ const dir = statSync3(skillPath).isDirectory() ? skillPath : dirname3(skillPath);
14025
14053
  return { dir, name: basename2(dir) };
14026
14054
  }
14027
14055
  function copyDirIfExists(src, dest) {
@@ -14062,45 +14090,106 @@ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as rea
14062
14090
  import { homedir as homedir2 } from "os";
14063
14091
  import { join as join6 } from "path";
14064
14092
  import { cwd as cwd4 } from "process";
14093
+ function parseList(value) {
14094
+ if (value === undefined)
14095
+ return [];
14096
+ if (Array.isArray(value)) {
14097
+ return value.map((v) => v.trim()).filter((v) => v.length > 0);
14098
+ }
14099
+ return value.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
14100
+ }
14101
+ function toYamlArray(items) {
14102
+ return `[${items.join(", ")}]`;
14103
+ }
14065
14104
  function substituteVars(template, vars) {
14066
- return template.replaceAll(PLACEHOLDER_NAME, vars.name).replaceAll(PLACEHOLDER_DESCRIPTION, vars.description).replaceAll(PLACEHOLDER_TARGET, vars.target).replaceAll(PLACEHOLDER_BODY, vars.body);
14105
+ return template.replaceAll(PLACEHOLDER_NAME, vars.name).replaceAll(PLACEHOLDER_DESCRIPTION, vars.description).replaceAll(PLACEHOLDER_TARGET, vars.target).replaceAll(PLACEHOLDER_BODY, vars.body).replaceAll(PLACEHOLDER_TOOLS, toYamlArray(vars.tools));
14067
14106
  }
14068
- function resolveTemplate(type) {
14107
+ function mergeFrontmatterList(content, key, items) {
14108
+ if (items.length === 0)
14109
+ return content;
14110
+ const fenceRe = /^---$/gm;
14111
+ const matches = [...content.matchAll(fenceRe)];
14112
+ if (matches.length < 2)
14113
+ return content;
14114
+ const openPos = matches[0]?.index ?? 0;
14115
+ const closePos = matches[1]?.index ?? 0;
14116
+ const fmStart = openPos + 3;
14117
+ const fmBody = content.slice(fmStart, closePos);
14118
+ const yamlValue = toYamlArray(items);
14119
+ const pattern = new RegExp(`^${key}:.*$`, "m");
14120
+ let newFmBody;
14121
+ if (pattern.test(fmBody)) {
14122
+ newFmBody = fmBody.replace(pattern, `${key}: ${yamlValue}`);
14123
+ } else {
14124
+ newFmBody = `${fmBody}${key}: ${yamlValue}
14125
+ `;
14126
+ }
14127
+ return content.slice(0, fmStart) + newFmBody + content.slice(closePos);
14128
+ }
14129
+ function resolveTemplate(type, tier) {
14069
14130
  const homeDir = process.env.HOME ?? homedir2();
14131
+ const tierName = tier?.trim();
14132
+ if (tierName && tierName !== "default") {
14133
+ const userTierPath = join6(homeDir, ".superskill", "templates", type, `${tierName}.md`);
14134
+ if (existsSync7(userTierPath)) {
14135
+ return readFileSync5(userTierPath, "utf-8");
14136
+ }
14137
+ for (const base of TEMPLATE_BASE_DIRS) {
14138
+ const path = join6(base, type, `${tierName}.md`);
14139
+ if (existsSync7(path)) {
14140
+ return readFileSync5(path, "utf-8");
14141
+ }
14142
+ }
14143
+ throw new Error(`Unknown template tier "${tierName}" for type "${type}". ` + `No user override (~/.superskill/templates/${type}/${tierName}.md) ` + `or built-in template found. Omit --template to use the default tier.`);
14144
+ }
14070
14145
  const userPath = join6(homeDir, ".superskill", "templates", type, "default.md");
14071
14146
  if (existsSync7(userPath)) {
14072
14147
  return readFileSync5(userPath, "utf-8");
14073
14148
  }
14074
- const devPath = join6(import.meta.dir, "..", "..", "..", "..", "apps", "cli", "src", "templates", type, "default.md");
14075
- if (existsSync7(devPath)) {
14076
- return readFileSync5(devPath, "utf-8");
14149
+ for (const base of TEMPLATE_BASE_DIRS) {
14150
+ const path = join6(base, type, "default.md");
14151
+ if (existsSync7(path)) {
14152
+ return readFileSync5(path, "utf-8");
14153
+ }
14077
14154
  }
14078
- const prodPath = join6(import.meta.dir, "..", "templates", type, "default.md");
14079
- return readFileSync5(prodPath, "utf-8");
14155
+ throw new Error(`No built-in default template found for type "${type}".`);
14080
14156
  }
14081
14157
  async function scaffold(type, name, opts = {}) {
14082
- const validTypes = ["skill", "command", "agent", "hook", "magent"];
14158
+ const validTypes = ["skill", "command", "agent", "magent"];
14083
14159
  if (!validTypes.includes(type)) {
14084
14160
  throw new Error(`Unknown content type: "${type}". Expected one of: ${validTypes.join(", ")}`);
14085
14161
  }
14086
- const template = resolveTemplate(type);
14087
- const content = substituteVars(template, {
14162
+ assertSafePathSegment(name, "content name");
14163
+ const template = resolveTemplate(type, opts.template);
14164
+ let content = substituteVars(template, {
14088
14165
  name,
14089
14166
  description: opts.description ?? "",
14090
14167
  target: opts.target ?? "claude",
14091
- body: ""
14168
+ body: "",
14169
+ tools: parseList(opts.tools)
14092
14170
  });
14171
+ const toolField = type === "agent" ? "tools" : "allowed-tools";
14172
+ content = mergeFrontmatterList(content, toolField, parseList(opts.tools));
14093
14173
  const outDir = opts.output ?? cwd4();
14094
14174
  mkdirSync4(outDir, { recursive: true });
14095
- const filePath = join6(outDir, `${name}.md`);
14175
+ const filePath = type === "skill" ? join6(outDir, name, "SKILL.md") : join6(outDir, `${name}.md`);
14176
+ if (type === "skill") {
14177
+ mkdirSync4(join6(outDir, name), { recursive: true });
14178
+ }
14096
14179
  if (existsSync7(filePath) && !opts.force) {
14097
14180
  throw new Error(`${filePath} already exists \u2014 pass --force to overwrite`);
14098
14181
  }
14099
14182
  writeFileSync3(filePath, content, "utf-8");
14100
14183
  return filePath;
14101
14184
  }
14102
- var PLACEHOLDER_NAME = "<!-- NAME -->", PLACEHOLDER_DESCRIPTION = "<!-- DESCRIPTION -->", PLACEHOLDER_TARGET = "<!-- TARGET -->", PLACEHOLDER_BODY = "<!-- BODY -->";
14103
- var init_scaffold = () => {};
14185
+ var PLACEHOLDER_NAME = "<!-- NAME -->", PLACEHOLDER_DESCRIPTION = "<!-- DESCRIPTION -->", PLACEHOLDER_TARGET = "<!-- TARGET -->", PLACEHOLDER_BODY = "<!-- BODY -->", PLACEHOLDER_TOOLS = "<!-- TOOLS -->", TEMPLATE_BASE_DIRS;
14186
+ var init_scaffold = __esm(() => {
14187
+ init_identity();
14188
+ TEMPLATE_BASE_DIRS = [
14189
+ join6(import.meta.dir, "..", "..", "..", "..", "apps", "cli", "src", "templates"),
14190
+ join6(import.meta.dir, "..", "templates")
14191
+ ];
14192
+ });
14104
14193
 
14105
14194
  // ../../packages/core/src/quality/types.ts
14106
14195
  function computeAggregate(dimensions) {
@@ -14122,10 +14211,10 @@ var init_types2 = __esm(() => {
14122
14211
  VAGUE_KEYWORDS = ["maybe", "perhaps", "might", "could be", "probably"];
14123
14212
  REQUIRED_FIELDS = {
14124
14213
  skill: ["name", "description"],
14125
- command: ["name", "description"],
14214
+ command: ["description"],
14126
14215
  agent: ["name", "description", "model", "tools"],
14127
- hook: ["name", "description", "event"],
14128
- magent: ["name", "description"]
14216
+ hook: [],
14217
+ magent: []
14129
14218
  };
14130
14219
  });
14131
14220
 
@@ -14224,89 +14313,180 @@ var init_heuristics = __esm(() => {
14224
14313
  });
14225
14314
 
14226
14315
  // ../../packages/core/src/quality/hook.ts
14227
- function scoreCorrectness(data) {
14228
- const raw = data.event;
14229
- let base;
14230
- let note;
14231
- if (typeof raw === "string" && raw.trim().length > 0) {
14232
- const event = raw.trim();
14233
- if (KNOWN_HOOK_EVENTS.includes(event)) {
14234
- base = 1;
14235
- note = `Valid event: ${event}`;
14236
- } else {
14237
- base = 0.5;
14238
- note = `Unknown event: ${event}`;
14316
+ function parseHookEntries(raw) {
14317
+ if (typeof raw !== "object" || raw === null)
14318
+ return null;
14319
+ const data = raw;
14320
+ const hooks = data.hooks;
14321
+ if (typeof hooks !== "object" || hooks === null)
14322
+ return null;
14323
+ const entries = [];
14324
+ for (const [event, matcherBlocks] of Object.entries(hooks)) {
14325
+ if (!Array.isArray(matcherBlocks))
14326
+ continue;
14327
+ for (const block of matcherBlocks) {
14328
+ if (typeof block !== "object" || block === null)
14329
+ continue;
14330
+ const subHooks = block.hooks;
14331
+ if (!Array.isArray(subHooks))
14332
+ continue;
14333
+ for (const h of subHooks) {
14334
+ if (typeof h !== "object" || h === null)
14335
+ continue;
14336
+ entries.push({
14337
+ event,
14338
+ matcher: typeof block.matcher === "string" ? block.matcher : "",
14339
+ command: typeof h.command === "string" ? h.command : "",
14340
+ type: typeof h.type === "string" ? h.type : "",
14341
+ timeout: typeof h.timeout === "number" ? h.timeout : undefined
14342
+ });
14343
+ }
14239
14344
  }
14240
- } else {
14241
- base = 0;
14242
- note = "Missing event field";
14243
14345
  }
14244
- if (data.enabled === true) {
14245
- base = clamp(base + 0.1);
14246
- }
14247
- return { score: clamp(base), note };
14248
- }
14249
- function scoreEventCoverage(data, body) {
14250
- const eventName = typeof data.event === "string" ? data.event.trim() : "";
14251
- const mentionsEventName = eventName.length > 0 && body.toLowerCase().includes(eventName.toLowerCase()) ? 1 : 0;
14252
- const density = keywordDensity(body, ["event", "intercept", "trigger", "when", "condition", "match"]);
14253
- const score = 0.5 * mentionsEventName + 0.5 * density;
14254
- const note = score >= 0.5 ? "Event coverage described" : "Minimal event description";
14255
- return { score: clamp(score), note };
14346
+ return entries.length > 0 ? entries : null;
14256
14347
  }
14257
- function scoreSafety(body) {
14258
- const density = keywordDensity(body, [
14259
- "safety",
14260
- "secure",
14261
- "gated",
14262
- "approval",
14263
- "explicit",
14264
- "dangerous",
14265
- "destructive",
14266
- "block"
14267
- ]);
14268
- const note = density >= 0.2 ? "Includes safety considerations" : "No safety gates described";
14269
- return { score: clamp(density), note };
14270
- }
14271
- function scorePatternMatchQuality(body) {
14272
- const hasSpecificGlob = /\*\.[a-z]+\b/i.test(body);
14273
- const patternDensity = keywordDensity(body, ["match", "pattern", "regex", "glob"]);
14274
- let score;
14275
- let note;
14276
- if (hasSpecificGlob) {
14277
- score = 0.9;
14278
- note = "Specific match patterns";
14279
- } else if (patternDensity > 0) {
14280
- score = 0.3;
14281
- note = "Broad/unspecific patterns";
14282
- } else {
14283
- score = 0;
14284
- note = "No match patterns";
14348
+ function scoreCorrectness(entries) {
14349
+ let valid = 0;
14350
+ for (const e of entries) {
14351
+ if (e.type === "command" && e.command.length > 0 && e.matcher.length > 0)
14352
+ valid++;
14285
14353
  }
14354
+ const score = entries.length > 0 ? clamp(valid / entries.length) : 0;
14355
+ const note = `${valid}/${entries.length} entries valid`;
14286
14356
  return { score, note };
14287
14357
  }
14358
+ function scoreEventCoverage(entries) {
14359
+ const events = new Set(entries.map((e) => e.event));
14360
+ const covered = KNOWN_HOOK_EVENTS.filter((e) => events.has(e)).length;
14361
+ const score = clamp(Math.min(events.size / 3, 1));
14362
+ const note = `${covered} of ${KNOWN_HOOK_EVENTS.length} known events covered (${events.size} total)`;
14363
+ return { score, note };
14364
+ }
14365
+ function scoreSafety(entries) {
14366
+ const dangerousPatterns = [
14367
+ { re: /rm\s+-rf/i, label: "rm -rf" },
14368
+ { re: /\b(?:curl|wget|fetch)\b.*\|\s*(?:ba|z)?sh\b/i, label: "download pipe to shell" },
14369
+ { re: /--no-verify/i, label: "--no-verify bypass" },
14370
+ { re: /\beval\b/i, label: "eval" },
14371
+ { re: /sudo\b/i, label: "sudo" },
14372
+ { re: /chmod\s+777/i, label: "chmod 777" },
14373
+ { re: /\$\([^)]*\)/, label: "unquoted command substitution" },
14374
+ { re: /`[^`]+`/, label: "backtick execution" }
14375
+ ];
14376
+ const findings = [];
14377
+ let dangerous = 0;
14378
+ for (const e of entries) {
14379
+ for (const { re, label } of dangerousPatterns) {
14380
+ if (re.test(e.command)) {
14381
+ dangerous++;
14382
+ findings.push(`[${e.event}] ${label}: ${e.command.slice(0, 60)}`);
14383
+ }
14384
+ }
14385
+ }
14386
+ const base = entries.length > 0 ? 1 - dangerous / (entries.length * 2) : 0;
14387
+ const score = clamp(Math.max(base, 0.1));
14388
+ const note = dangerous === 0 ? "No dangerous command patterns found" : `${dangerous} dangerous pattern(s) found`;
14389
+ const recs = dangerous > 0 ? ["Replace dangerous patterns with safe alternatives; add explicit approval gates"] : undefined;
14390
+ return { score, note, findings: findings.length > 0 ? findings : undefined, recommendations: recs };
14391
+ }
14392
+ function scorePatternMatchQuality(entries) {
14393
+ let specificMatchers = 0;
14394
+ let hasTimeout = 0;
14395
+ let portable = 0;
14396
+ for (const e of entries) {
14397
+ if (e.matcher !== "*" && e.matcher.length > 0)
14398
+ specificMatchers++;
14399
+ if (e.timeout !== undefined && e.timeout > 0)
14400
+ hasTimeout++;
14401
+ if (/\$\{CLAUDE_PLUGIN_ROOT\}/.test(e.command)) {
14402
+ portable++;
14403
+ } else {
14404
+ const tokens = e.command.trim().split(/\s+/);
14405
+ const firstToken = tokens[0] ?? "";
14406
+ const bareBinary = firstToken.length > 0 && !firstToken.includes("/") && !firstToken.startsWith(".");
14407
+ const noPathTokens = tokens.every((t) => !t.includes("/") || /\$\{CLAUDE_PLUGIN_ROOT\}/.test(t));
14408
+ if (bareBinary && noPathTokens)
14409
+ portable++;
14410
+ }
14411
+ }
14412
+ const n = entries.length || 1;
14413
+ const matcherScore = specificMatchers / n;
14414
+ const timeoutScore = hasTimeout / n;
14415
+ const portableScore = portable / n;
14416
+ const score = clamp(0.3 * matcherScore + 0.4 * timeoutScore + 0.3 * portableScore);
14417
+ const noteParts = [];
14418
+ if (specificMatchers < n)
14419
+ noteParts.push(`${n - specificMatchers} broad matcher(s)`);
14420
+ if (hasTimeout < n)
14421
+ noteParts.push(`${n - hasTimeout} missing timeout`);
14422
+ if (portable < n)
14423
+ noteParts.push(`${n - portable} non-portable path(s)`);
14424
+ const findings = noteParts.length > 0 ? noteParts.map((p) => p) : undefined;
14425
+ const recs = noteParts.length > 0 ? [
14426
+ "Use specific matchers instead of *; add timeout to every hook; use CLAUDE_PLUGIN_ROOT env var for paths"
14427
+ ] : undefined;
14428
+ return {
14429
+ score,
14430
+ note: noteParts.length > 0 ? noteParts.join("; ") : "All entries specific, timed, and portable",
14431
+ findings,
14432
+ recommendations: recs
14433
+ };
14434
+ }
14288
14435
  function evaluateHook(content, target) {
14289
- const parsed = parseFrontmatterSafe(content);
14290
- const data = parsed ?? {};
14291
- const body = extractBody(content);
14292
- const isParseError = parsed === null;
14293
- const dimensions = (() => {
14294
- if (isParseError) {
14295
- const errNote = parseErrorNote(content, "Frontmatter parse error");
14436
+ const isJson = target.endsWith(".json") || /^\s*\{/.test(content);
14437
+ if (isJson) {
14438
+ let parsed;
14439
+ try {
14440
+ parsed = JSON.parse(content);
14441
+ } catch {
14296
14442
  return {
14297
- correctness: { score: 0.1, note: errNote },
14298
- "event-coverage": { score: 0, note: errNote },
14299
- safety: { score: 0, note: errNote },
14300
- "pattern-match-quality": { score: 0, note: errNote }
14443
+ type: "hook",
14444
+ target,
14445
+ content: "",
14446
+ aggregate: 0,
14447
+ dimensions: {
14448
+ correctness: { score: 0, note: "Invalid JSON: parse error" },
14449
+ "event-coverage": { score: 0, note: "Invalid JSON: parse error" },
14450
+ safety: { score: 0, note: "Invalid JSON: parse error" },
14451
+ "pattern-match-quality": { score: 0, note: "Invalid JSON: parse error" }
14452
+ }
14301
14453
  };
14302
14454
  }
14455
+ const entries = parseHookEntries(parsed);
14456
+ if (!entries || entries.length === 0) {
14457
+ return {
14458
+ type: "hook",
14459
+ target,
14460
+ content: "",
14461
+ aggregate: 0,
14462
+ dimensions: {
14463
+ correctness: { score: 0, note: "No hook entries found in JSON" },
14464
+ "event-coverage": { score: 0, note: "No hook entries found in JSON" },
14465
+ safety: { score: 0, note: "No hook entries found in JSON" },
14466
+ "pattern-match-quality": { score: 0, note: "No hook entries found in JSON" }
14467
+ }
14468
+ };
14469
+ }
14470
+ const dimensions2 = {
14471
+ correctness: scoreCorrectness(entries),
14472
+ "event-coverage": scoreEventCoverage(entries),
14473
+ safety: scoreSafety(entries),
14474
+ "pattern-match-quality": scorePatternMatchQuality(entries)
14475
+ };
14303
14476
  return {
14304
- correctness: scoreCorrectness(data),
14305
- "event-coverage": scoreEventCoverage(data, body),
14306
- safety: scoreSafety(body),
14307
- "pattern-match-quality": scorePatternMatchQuality(body)
14477
+ type: "hook",
14478
+ target,
14479
+ content: "",
14480
+ aggregate: computeAggregate(dimensions2),
14481
+ dimensions: dimensions2
14308
14482
  };
14309
- })();
14483
+ }
14484
+ const dimensions = {
14485
+ correctness: { score: 0, note: "Markdown hook definitions not supported; use hooks.json" },
14486
+ "event-coverage": { score: 0, note: "Markdown hook definitions not supported; use hooks.json" },
14487
+ safety: { score: 0, note: "Markdown hook definitions not supported; use hooks.json" },
14488
+ "pattern-match-quality": { score: 0, note: "Markdown hook definitions not supported; use hooks.json" }
14489
+ };
14310
14490
  return {
14311
14491
  type: "hook",
14312
14492
  target,
@@ -14333,8 +14513,34 @@ var init_hook = __esm(() => {
14333
14513
  });
14334
14514
 
14335
14515
  // ../../packages/core/src/operations/validate.ts
14336
- import { existsSync as existsSync8, statSync as statSync3 } from "fs";
14337
- import { dirname as dirname4 } from "path";
14516
+ import { existsSync as existsSync8, statSync as statSync4 } from "fs";
14517
+ import { dirname as dirname4, join as join7 } from "path";
14518
+ function checkBodyLinks(body, baseDir) {
14519
+ const findings = [];
14520
+ const linkRe = /\[([^\]]*)\]\(([^)]+)\)/g;
14521
+ for (const match of body.matchAll(linkRe)) {
14522
+ const linkText = match[1];
14523
+ const target = match[2];
14524
+ if (!target)
14525
+ continue;
14526
+ if (/^#/.test(target))
14527
+ continue;
14528
+ if (/^[a-z][a-z0-9+.-]*:/i.test(target))
14529
+ continue;
14530
+ const filePart = target.split(/[#?]/)[0];
14531
+ if (!filePart)
14532
+ continue;
14533
+ const resolved = join7(baseDir, filePart);
14534
+ if (!existsSync8(resolved)) {
14535
+ findings.push({
14536
+ severity: "warning",
14537
+ field: "_links",
14538
+ message: `Broken body link: [${linkText}](${target}) \u2192 target not found: ${resolved}`
14539
+ });
14540
+ }
14541
+ }
14542
+ return findings;
14543
+ }
14338
14544
  async function validate(type, nameOrPath, opts) {
14339
14545
  const resolvedPath = resolveContentPath(type, nameOrPath);
14340
14546
  if (!resolvedPath) {
@@ -14345,7 +14551,7 @@ async function validate(type, nameOrPath, opts) {
14345
14551
  }
14346
14552
  let stat;
14347
14553
  try {
14348
- stat = statSync3(resolvedPath);
14554
+ stat = statSync4(resolvedPath);
14349
14555
  } catch {
14350
14556
  return sentinelResult(`File not found: ${resolvedPath}`);
14351
14557
  }
@@ -14365,19 +14571,39 @@ async function validate(type, nameOrPath, opts) {
14365
14571
  return sentinelResult(`Cannot read file: ${resolvedPath}`);
14366
14572
  }
14367
14573
  const baseDir = dirname4(resolvedPath);
14368
- return _validateContent(type, content, {
14574
+ const result = _validateContent(type, content, {
14369
14575
  ...opts,
14370
14576
  referenceChecker: (refType, refName) => resolveContentPath(refType, refName, { baseDir }) !== null
14371
14577
  });
14578
+ const bodyStart = content.indexOf(`
14579
+ ---
14580
+ `);
14581
+ const body = bodyStart >= 0 ? content.slice(bodyStart + 5) : content;
14582
+ const linkFindings = checkBodyLinks(body, baseDir);
14583
+ result.findings.push(...linkFindings);
14584
+ return result;
14372
14585
  }
14373
14586
  function _validateContent(type, content, opts) {
14374
14587
  const findings = [];
14375
14588
  let data;
14376
14589
  let body;
14590
+ const frontmatterPresent = /^---\s*$/m.test(content);
14377
14591
  try {
14378
- const parsed = parseFrontmatter(content);
14379
- data = parsed.data;
14380
- body = parsed.body;
14592
+ if (frontmatterPresent) {
14593
+ const parsed = parseFrontmatter(content);
14594
+ data = parsed.data;
14595
+ body = parsed.body;
14596
+ } else if (type === "magent") {
14597
+ data = {};
14598
+ body = content;
14599
+ } else {
14600
+ findings.push({
14601
+ severity: "error",
14602
+ field: "frontmatter",
14603
+ message: "YAML parse error: Missing frontmatter: content must start with ---"
14604
+ });
14605
+ return { valid: false, findings };
14606
+ }
14381
14607
  } catch (e) {
14382
14608
  const msg = e instanceof Error ? e.message : String(e);
14383
14609
  findings.push({ severity: "error", field: "frontmatter", message: `YAML parse error: ${msg}` });
@@ -14523,7 +14749,7 @@ function checkLinkValidity(type, data, referenceChecker) {
14523
14749
  }
14524
14750
  return findings;
14525
14751
  }
14526
- function strictChecks(_type, data, body) {
14752
+ function strictChecks(type, data, body) {
14527
14753
  const findings = [];
14528
14754
  const desc = data.description;
14529
14755
  if (typeof desc === "string" && desc.length < 40) {
@@ -14560,6 +14786,22 @@ function strictChecks(_type, data, body) {
14560
14786
  });
14561
14787
  }
14562
14788
  }
14789
+ const recognized = new Set([
14790
+ ...Object.keys(FIELD_TYPES[type] ?? {}),
14791
+ ...REQUIRED_FIELDS[type],
14792
+ ...KNOWN_OPTIONAL[type] ?? []
14793
+ ]);
14794
+ for (const key of Object.keys(data)) {
14795
+ if (recognized.has(key))
14796
+ continue;
14797
+ if (DEPRECATED_FIELDS[key] !== undefined)
14798
+ continue;
14799
+ findings.push({
14800
+ severity: "warning",
14801
+ field: key,
14802
+ message: `Unknown frontmatter key '${key}'`
14803
+ });
14804
+ }
14563
14805
  return findings;
14564
14806
  }
14565
14807
  function formatValidationResult(result, json) {
@@ -14578,7 +14820,7 @@ function sentinelResult(message) {
14578
14820
  findings: [{ severity: "error", field: "_file", message }]
14579
14821
  };
14580
14822
  }
14581
- var MODEL_ALIASES, FIELD_TYPES, DEPRECATED_FIELDS;
14823
+ var MODEL_ALIASES, FIELD_TYPES, DEPRECATED_FIELDS, KNOWN_OPTIONAL;
14582
14824
  var init_validate = __esm(() => {
14583
14825
  init_frontmatter();
14584
14826
  init_identity();
@@ -14619,12 +14861,13 @@ var init_validate = __esm(() => {
14619
14861
  author: "No longer used; remove.",
14620
14862
  version: "Version is derived from the source repository."
14621
14863
  };
14622
- });
14623
-
14624
- // ../../packages/core/src/pipeline/pi-subagent.ts
14625
- var init_pi_subagent = __esm(() => {
14626
- init_frontmatter();
14627
- init_pi_tools();
14864
+ KNOWN_OPTIONAL = {
14865
+ skill: ["license", "metadata"],
14866
+ command: ["argument-hint", "allowed-tools", "target"],
14867
+ agent: [],
14868
+ hook: [],
14869
+ magent: []
14870
+ };
14628
14871
  });
14629
14872
 
14630
14873
  // ../../node_modules/.bun/@logtape+logtape@2.1.5/node_modules/@logtape/logtape/dist/context.js
@@ -32208,7 +32451,7 @@ var init_config = __esm(() => {
32208
32451
  });
32209
32452
 
32210
32453
  // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.3.21/node_modules/@gobing-ai/ts-runtime/dist/file-system-node.js
32211
- import { appendFileSync, cpSync as cpSync2, createWriteStream, existsSync as existsSync9, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync6, renameSync, rmSync as rmSync2, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
32454
+ import { appendFileSync, cpSync as cpSync2, createWriteStream, existsSync as existsSync9, mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync as readFileSync6, renameSync, rmSync as rmSync2, statSync as statSync5, writeFileSync as writeFileSync4 } from "fs";
32212
32455
  import { dirname as dirname5, resolve as resolvePath } from "path";
32213
32456
  function createNodeFileSystem(root) {
32214
32457
  const projectRoot = root ?? findProjectRoot(process.cwd());
@@ -32228,7 +32471,7 @@ function createNodeFileSystem(root) {
32228
32471
  ensureDir: (path) => {
32229
32472
  mkdirSync5(path, { recursive: true });
32230
32473
  },
32231
- readDir: (path) => readdirSync2(path),
32474
+ readDir: (path) => readdirSync3(path),
32232
32475
  deleteFile: (path) => {
32233
32476
  rmSync2(path, { recursive: true, force: true });
32234
32477
  },
@@ -32244,7 +32487,7 @@ function createNodeFileSystem(root) {
32244
32487
  },
32245
32488
  stat: (path) => {
32246
32489
  try {
32247
- const s = statSync4(path);
32490
+ const s = statSync5(path);
32248
32491
  return {
32249
32492
  isFile: () => s.isFile(),
32250
32493
  isDirectory: () => s.isDirectory(),
@@ -34872,7 +35115,7 @@ var init_encoding_option = __esm(() => {
34872
35115
  });
34873
35116
 
34874
35117
  // ../../node_modules/.bun/execa@9.6.1/node_modules/execa/lib/arguments/cwd.js
34875
- import { statSync as statSync5 } from "fs";
35118
+ import { statSync as statSync6 } from "fs";
34876
35119
  import path4 from "path";
34877
35120
  import process6 from "process";
34878
35121
  var normalizeCwd = (cwd5 = getDefaultCwd()) => {
@@ -34892,7 +35135,7 @@ ${error51.message}`;
34892
35135
  }
34893
35136
  let cwdStat;
34894
35137
  try {
34895
- cwdStat = statSync5(cwd5);
35138
+ cwdStat = statSync6(cwd5);
34896
35139
  } catch (error51) {
34897
35140
  return `The "cwd" option is invalid: ${cwd5}.
34898
35141
  ${error51.message}
@@ -40376,8 +40619,14 @@ function scoreCompleteness(data) {
40376
40619
  const required2 = REQUIRED_FIELDS.agent;
40377
40620
  const score = scorePresence(fieldsPresent, required2);
40378
40621
  const missing = required2.filter((f) => !fieldsPresent.includes(f));
40379
- const note = missing.length > 0 ? `Missing: ${missing.join(", ")}` : "All required fields present";
40380
- return { score: clamp(score), note };
40622
+ const findings = missing.length > 0 ? [`Missing required fields: ${missing.join(", ")}`] : undefined;
40623
+ const recs = missing.length > 0 ? ["Add the missing frontmatter fields"] : undefined;
40624
+ return {
40625
+ score: clamp(score),
40626
+ note: missing.length > 0 ? `Missing: ${missing.join(", ")}` : "All required fields present",
40627
+ findings,
40628
+ recommendations: recs
40629
+ };
40381
40630
  }
40382
40631
  function scoreRoleClarity(body) {
40383
40632
  const patterns = [/you are/i, /role/i, /specialist/i, /persona/i];
@@ -40404,7 +40653,9 @@ function scoreRoleClarity(body) {
40404
40653
  }
40405
40654
  const score = specificity ? clamp(base2 + 0.2) : base2;
40406
40655
  const note = matchCount >= 2 ? "Clear role defined" : matchCount === 1 ? "Role definition vague/generic" : "No role definition found";
40407
- return { score, note };
40656
+ const findings = matchCount < 2 ? ["Role definition is vague, generic, or absent"] : undefined;
40657
+ const recs = matchCount === 0 ? ['Add a role statement (e.g. "You are a ... specialist")'] : matchCount === 1 ? ["Add more role signals (specialist, persona, role scope)"] : undefined;
40658
+ return { score, note, findings, recommendations: recs };
40408
40659
  }
40409
40660
  function scoreToolSelection(data) {
40410
40661
  const tools = data.tools;
@@ -40417,7 +40668,9 @@ function scoreToolSelection(data) {
40417
40668
  } else {
40418
40669
  score = 0.1;
40419
40670
  }
40420
- return { score, note: `${count2} tools selected` };
40671
+ const findings = count2 < 3 ? [`Only ${count2} tool(s) selected \u2014 3+ recommended`] : undefined;
40672
+ const recs = count2 === 0 ? ["Add at least 3 tools to the agent frontmatter"] : count2 < 3 ? ["Add more tools to cover the agent workflow"] : undefined;
40673
+ return { score, note: `${count2} tools selected`, findings, recommendations: recs };
40421
40674
  }
40422
40675
  function scoreSkillLinkage(body) {
40423
40676
  const patterns = [/skill:/i, /skills:/i, /skill/i];
@@ -40437,7 +40690,13 @@ function scoreSkillLinkage(body) {
40437
40690
  score = 0;
40438
40691
  note = "No skill references";
40439
40692
  }
40440
- return { score, note };
40693
+ let findings;
40694
+ let recs;
40695
+ if (score < 1) {
40696
+ findings = ["Skill linkage is weak or missing"];
40697
+ recs = score === 0.5 ? ["Use structured skill references (skill: or skills:) in the agent body"] : ["Add skill references to the agent body for delegated workflows"];
40698
+ }
40699
+ return { score, note, findings, recommendations: recs };
40441
40700
  }
40442
40701
  function scoreModelFit(data) {
40443
40702
  const model = data.model;
@@ -40460,7 +40719,16 @@ function scoreModelFit(data) {
40460
40719
  score = 0;
40461
40720
  note = "Model: missing";
40462
40721
  }
40463
- return { score, note };
40722
+ let findings;
40723
+ let recs;
40724
+ if (score < 0.5) {
40725
+ findings = ["Model field is missing or unrecognized"];
40726
+ recs = ["Specify a valid model (e.g. inherit, claude-sonnet-4, claude-opus-4)"];
40727
+ } else if (score === 0.5) {
40728
+ findings = ["Model name appears ambiguous"];
40729
+ recs = ["Use a recognized model alias (inherit, sonnet, opus, haiku) or well-formed name (claude-sonnet-4)"];
40730
+ }
40731
+ return { score, note, findings, recommendations: recs };
40464
40732
  }
40465
40733
  function evaluateAgent(content, target) {
40466
40734
  const data = parseFrontmatterSafe(content) ?? {};
@@ -40489,53 +40757,99 @@ var init_agent = __esm(() => {
40489
40757
  function scoreCompleteness2(data) {
40490
40758
  const fieldsPresent = Object.keys(data);
40491
40759
  const base2 = scorePresence(fieldsPresent, REQUIRED_FIELDS.command);
40492
- const argsFactor = Array.isArray(data.arguments) ? 1 : 0;
40493
- const score = clamp(base2 * (0.5 + 0.5 * argsFactor));
40760
+ const hasArgHint = typeof data["argument-hint"] === "string" && data["argument-hint"].length > 0;
40761
+ const hasAllowedTools = Array.isArray(data["allowed-tools"]) && data["allowed-tools"].length > 0;
40762
+ const optBonus = (hasArgHint ? 0.1 : 0) + (hasAllowedTools ? 0.1 : 0);
40763
+ const score = clamp(Math.min(base2 + optBonus, 1));
40494
40764
  const missing = [];
40495
40765
  for (const f of REQUIRED_FIELDS.command) {
40496
40766
  if (!(f in data))
40497
40767
  missing.push(f);
40498
40768
  }
40499
40769
  const note = missing.length > 0 ? `Missing fields: ${missing.join(", ")}` : "All required fields present";
40500
- return { score, note };
40770
+ const findings = missing.length > 0 ? [`Missing required fields: ${missing.join(", ")}`] : undefined;
40771
+ const recs = missing.includes("description") ? ["Add a description field to the command frontmatter"] : undefined;
40772
+ return { score, note, findings, recommendations: recs };
40501
40773
  }
40502
40774
  function scoreClarity(body) {
40503
40775
  return scoreClarityFromDensities(body);
40504
40776
  }
40505
40777
  function scoreArgumentHints(data) {
40506
- const args = data.arguments;
40507
- if (!Array.isArray(args)) {
40508
- return { score: 0, note: "No arguments array found" };
40509
- }
40510
- if (args.length === 0) {
40511
- return { score: 0, note: "0/0 arguments have hints" };
40778
+ const hasKey = "argument-hint" in data;
40779
+ const argHint = data["argument-hint"];
40780
+ if (!hasKey) {
40781
+ return { score: 1, note: "No argument-hint (command takes no parameters)" };
40512
40782
  }
40513
- let withHints = 0;
40514
- for (const arg of args) {
40515
- if (typeof arg === "object" && arg !== null && "name" in arg && "description" in arg) {
40516
- withHints++;
40517
- }
40783
+ if (typeof argHint !== "string" || argHint.trim().length === 0) {
40784
+ return {
40785
+ score: 0.4,
40786
+ note: "Argument-hint declared but empty",
40787
+ findings: ["Argument-hint is declared but empty"],
40788
+ recommendations: [
40789
+ "Add a descriptive argument-hint string with placeholder syntax, or remove the empty field"
40790
+ ]
40791
+ };
40518
40792
  }
40519
- const score = clamp(withHints / args.length);
40520
- return { score, note: `${withHints}/${args.length} arguments have hints` };
40521
- }
40522
- function scoreToolReferences(body) {
40793
+ const hint = argHint.trim();
40794
+ const hasPositional = /<[a-z][^>]*>/i.test(hint);
40795
+ const hasFlags = /\[?--[a-z][^\s\]]*\]?/i.test(hint);
40796
+ let score;
40797
+ let note;
40798
+ if (hasPositional && hasFlags) {
40799
+ score = 1;
40800
+ note = "Rich argument-hint with positional args and flags";
40801
+ } else if (hasPositional || hasFlags) {
40802
+ score = 0.75;
40803
+ note = "Argument-hint present but could be more descriptive";
40804
+ } else {
40805
+ score = 0.4;
40806
+ note = "Argument-hint is vague or placeholder-only";
40807
+ }
40808
+ let findings;
40809
+ let recs;
40810
+ if (score < 1 && score > 0.5) {
40811
+ findings = ["Argument-hint could be more descriptive"];
40812
+ recs = ["Add both positional args (<name>) and flags ([--flag <value>]) to the argument-hint"];
40813
+ } else if (score <= 0.5) {
40814
+ findings = ["Argument-hint is vague or missing for a parameterized command"];
40815
+ recs = ["Add a descriptive argument-hint string with placeholder syntax"];
40816
+ }
40817
+ return { score, note, findings, recommendations: recs };
40818
+ }
40819
+ function scoreToolReferences(body, data) {
40820
+ const allowedTools = data["allowed-tools"];
40821
+ const toolCount = Array.isArray(allowedTools) ? allowedTools.length : 0;
40523
40822
  const structuredCount = (body.match(/\btools?:/gi) ?? []).length;
40524
40823
  const backtickCount = (body.match(/`[a-z][a-z0-9_-]*`/g) ?? []).length;
40525
- const weightedCount = structuredCount + Math.min(backtickCount, 1);
40824
+ const bodyWeighted = structuredCount + Math.min(backtickCount, 1);
40526
40825
  let score;
40527
40826
  let note;
40528
- if (structuredCount >= 1 || weightedCount >= 2) {
40827
+ if (toolCount >= 5) {
40529
40828
  score = 1;
40530
- note = "Uses tool references";
40531
- } else if (weightedCount === 1) {
40532
- score = 0.6;
40829
+ note = `${toolCount} allowed-tools declared`;
40830
+ } else if (toolCount >= 3) {
40831
+ score = 0.9;
40832
+ note = `${toolCount} allowed-tools declared`;
40833
+ } else if (toolCount >= 1 || structuredCount >= 1 || bodyWeighted >= 2) {
40834
+ score = 0.7;
40835
+ note = toolCount > 0 ? `${toolCount} allowed-tools declared` : "Uses tool references";
40836
+ } else if (bodyWeighted === 1) {
40837
+ score = 0.4;
40533
40838
  note = "Limited tool references";
40534
40839
  } else {
40535
40840
  score = 0.1;
40536
40841
  note = "No tool references found";
40537
40842
  }
40538
- return { score, note };
40843
+ let findings;
40844
+ let recs;
40845
+ if (score < 0.5) {
40846
+ findings = ["No tool references found"];
40847
+ recs = ["Add allowed-tools to frontmatter or reference tools in the command body"];
40848
+ } else if (score < 0.8) {
40849
+ findings = ["Limited tool references"];
40850
+ recs = ["Declare all required tools in the allowed-tools frontmatter array"];
40851
+ }
40852
+ return { score, note, findings, recommendations: recs };
40539
40853
  }
40540
40854
  function scoreSlashSyntax(body, target) {
40541
40855
  const slashPattern = /\/[a-z][a-z-]*/g;
@@ -40562,7 +40876,7 @@ function evaluateCommand(content, target) {
40562
40876
  completeness: scoreCompleteness2(data),
40563
40877
  clarity: scoreClarity(body),
40564
40878
  "argument-hints": scoreArgumentHints(data),
40565
- "tool-references": scoreToolReferences(body),
40879
+ "tool-references": scoreToolReferences(body, data),
40566
40880
  "slash-syntax": scoreSlashSyntax(body, target)
40567
40881
  };
40568
40882
  return {
@@ -40581,16 +40895,21 @@ var init_command3 = __esm(() => {
40581
40895
  // ../../packages/core/src/quality/magent.ts
40582
40896
  function scoreCompleteness3(body) {
40583
40897
  let found = 0;
40584
- for (const re of MAGENT_SECTIONS) {
40898
+ for (const { re } of MAGENT_SECTIONS) {
40585
40899
  if (re.test(body))
40586
40900
  found++;
40587
40901
  }
40902
+ const score = clamp(found / MAGENT_SECTIONS.length);
40903
+ const findings = found < MAGENT_SECTIONS.length / 2 ? ["Config is missing key governance sections"] : undefined;
40904
+ const recs = found < MAGENT_SECTIONS.length ? ["Add more governance sections (commands, verification, conventions, safety, docs)"] : undefined;
40588
40905
  return {
40589
- score: clamp(found / MAGENT_SECTIONS.length),
40590
- note: `${found}/${MAGENT_SECTIONS.length} sections present`
40906
+ score,
40907
+ note: `${found}/${MAGENT_SECTIONS.length} governance sections present`,
40908
+ findings,
40909
+ recommendations: recs
40591
40910
  };
40592
40911
  }
40593
- function scorePlatformCoverage(data) {
40912
+ function scorePlatformCoverage(data, body) {
40594
40913
  const raw = data.platforms;
40595
40914
  let platforms = [];
40596
40915
  if (Array.isArray(raw)) {
@@ -40598,8 +40917,29 @@ function scorePlatformCoverage(data) {
40598
40917
  } else if (typeof raw === "string") {
40599
40918
  platforms = raw.split(",").map((s) => s.trim()).filter(Boolean);
40600
40919
  }
40920
+ if (platforms.length === 0) {
40921
+ const detected = [];
40922
+ const platformPatterns = [
40923
+ [/claude.?code/i, "claude-code"],
40924
+ [/codex/i, "codex"],
40925
+ [/gemini/i, "gemini"],
40926
+ [/cursor/i, "cursor"],
40927
+ [/windsurf/i, "windsurf"],
40928
+ [/opencode/i, "opencode"],
40929
+ [/openclaw/i, "openclaw"],
40930
+ [/antigravity/i, "antigravity"],
40931
+ [/pi\b/i, "pi"]
40932
+ ];
40933
+ for (const [re, name] of platformPatterns) {
40934
+ if (re.test(body))
40935
+ detected.push(name);
40936
+ }
40937
+ platforms = detected;
40938
+ }
40601
40939
  const score = clamp(Math.min(platforms.length / 5, 1));
40602
- return { score, note: `${platforms.length} platforms covered` };
40940
+ const findings = platforms.length === 0 ? ["No platforms declared or detected"] : platforms.length < 3 ? ["Limited platform coverage"] : undefined;
40941
+ const recs = platforms.length < 3 ? ["Declare supported platforms in frontmatter (platforms:) or mention them in prose"] : undefined;
40942
+ return { score, note: `${platforms.length} platforms covered`, findings, recommendations: recs };
40603
40943
  }
40604
40944
  function scoreConciseness(body) {
40605
40945
  return {
@@ -40627,16 +40967,19 @@ function scoreSafety2(body) {
40627
40967
  if (lower.includes(kw.toLowerCase()))
40628
40968
  count2++;
40629
40969
  }
40630
- return { score: density, note: `${count2} safety markers found` };
40970
+ const findings = count2 < 3 ? ["Limited safety markers in config"] : undefined;
40971
+ const recs = count2 < 3 ? ["Add [CRITICAL] markers, safety rules, NEVER directives, and security validation"] : undefined;
40972
+ return { score: density, note: `${count2} safety markers found`, findings, recommendations: recs };
40631
40973
  }
40632
40974
  function evaluateMagent(content, target) {
40633
- const fmResult = parseFrontmatterSafe(content);
40634
- const data = fmResult ?? {};
40635
- const fmNote = fmResult === null ? parseErrorNote(content, "unknown parse error") : null;
40636
40975
  const body = extractBody(content);
40976
+ const hasFrontmatter = /^---\s*$/m.test(content);
40977
+ const fmResult = hasFrontmatter ? parseFrontmatterSafe(content) : undefined;
40978
+ const data = fmResult ?? {};
40979
+ const fmNote = hasFrontmatter && fmResult === null ? parseErrorNote(content, "Frontmatter parse error") : null;
40637
40980
  const dimensions = {
40638
40981
  completeness: scoreCompleteness3(body),
40639
- "platform-coverage": scorePlatformCoverage(data),
40982
+ "platform-coverage": scorePlatformCoverage(data, body),
40640
40983
  conciseness: scoreConciseness(body),
40641
40984
  "tone-consistency": scoreToneConsistency(body),
40642
40985
  safety: scoreSafety2(body)
@@ -40659,7 +41002,14 @@ var MAGENT_SECTIONS;
40659
41002
  var init_magent = __esm(() => {
40660
41003
  init_heuristics();
40661
41004
  init_types2();
40662
- MAGENT_SECTIONS = [/^## IDENTITY/m, /^## SOUL/m, /^## AGENTS/m, /^## USER/m];
41005
+ MAGENT_SECTIONS = [
41006
+ { re: /^## .*[Pp]roject|^## .*[Ss]tack/m, label: "project" },
41007
+ { re: /^## .*[Cc]ommand|^## .*[Tt]ool/m, label: "commands" },
41008
+ { re: /^## .*[Vv]erif|^## .*[Tt]est|^## .*[Gg]ate/m, label: "verification" },
41009
+ { re: /^## .*[Cc]onvention|^## .*[Ss]tyle|^## .*[Bb]oundar/m, label: "conventions" },
41010
+ { re: /^## .*[Ss]afety|^## .*[Ss]ecurity|^## .*[Cc]ritical/m, label: "safety" },
41011
+ { re: /^## .*[Dd]oc|^## .*[Rr]eference|^## .*[Rr]outing/m, label: "docs" }
41012
+ ];
40663
41013
  });
40664
41014
 
40665
41015
  // ../../packages/core/src/quality/skill.ts
@@ -40694,7 +41044,17 @@ function scoreCompleteness4(content, data, body) {
40694
41044
  const keySet = new Set(presentKeys);
40695
41045
  const missing = required2.filter((f) => !keySet.has(f));
40696
41046
  const note = missing.length > 0 ? `Missing fields: ${missing.join(", ")}` : "All required fields present";
40697
- return { score, note };
41047
+ const findings = [];
41048
+ const recommendations = [];
41049
+ if (missing.length > 0) {
41050
+ findings.push(`Missing required frontmatter: ${missing.join(", ")}`);
41051
+ recommendations.push(`Add \`${missing.join("`, `")}\` to YAML frontmatter`);
41052
+ }
41053
+ if (structure < 1) {
41054
+ findings.push("Body lacks section headings (# / ## / ###). Structure aids navigation.");
41055
+ recommendations.push("Organize content with markdown headings for progressive disclosure");
41056
+ }
41057
+ return { score, note, findings, recommendations };
40698
41058
  }
40699
41059
  function scoreClarity2(body) {
40700
41060
  return scoreClarityFromDensities(body);
@@ -40709,7 +41069,16 @@ function scoreTriggerAccuracy(body) {
40709
41069
  } else {
40710
41070
  score = clamp(1 - (count2 - 10) / 10);
40711
41071
  }
40712
- return { score, note: `${count2} trigger phrases found` };
41072
+ const findings = [];
41073
+ const recommendations = [];
41074
+ if (count2 < 3) {
41075
+ findings.push(`Only ${count2} trigger phrase(s) found; aim for 3\u201310 for reliable skill activation.`);
41076
+ recommendations.push("Add 1\u20132 more When-to-Use scenarios or trigger phrase patterns to the description.");
41077
+ } else if (count2 > 10) {
41078
+ findings.push(`${count2} trigger phrases may cause overlap with adjacent skills.`);
41079
+ recommendations.push("Consolidate overlapping triggers or narrow the activation scope.");
41080
+ }
41081
+ return { score, note: `${count2} trigger phrases found`, findings, recommendations };
40713
41082
  }
40714
41083
  function scoreAntiHallucination(body) {
40715
41084
  const density = keywordDensity(body, [
@@ -40723,10 +41092,16 @@ function scoreAntiHallucination(body) {
40723
41092
  "evidence"
40724
41093
  ]);
40725
41094
  const note = density > 0 ? "Includes verification language" : "Missing verification instructions";
40726
- return { score: density, note };
41095
+ const findings = [];
41096
+ const recommendations = [];
41097
+ if (density < 0.3) {
41098
+ findings.push("Verification/citation language sparse or absent. Skill may invite fabrication.");
41099
+ recommendations.push('Add explicit "verify with source", "cross-check against docs", or "cite the reference" instructions.');
41100
+ }
41101
+ return { score: density, note, findings, recommendations };
40727
41102
  }
40728
41103
  function scoreConciseness2(body) {
40729
- const score = scoreLength(body, 500, 5000);
41104
+ const score = scoreLength(body, 500, 15000);
40730
41105
  return { score, note: `Body length: ${body.length} chars` };
40731
41106
  }
40732
41107
  function countTriggerPhrases(body) {
@@ -40791,7 +41166,7 @@ var init_evaluate = __esm(() => {
40791
41166
  // ../../packages/core/src/quality/rubric.ts
40792
41167
  import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
40793
41168
  import { homedir as homedir3 } from "os";
40794
- import { join as join7 } from "path";
41169
+ import { join as join8 } from "path";
40795
41170
  function resolveRubricContent(type, opts) {
40796
41171
  if (opts?.path) {
40797
41172
  if (!existsSync10(opts.path)) {
@@ -40800,15 +41175,15 @@ function resolveRubricContent(type, opts) {
40800
41175
  return readFileSync9(opts.path, "utf-8");
40801
41176
  }
40802
41177
  const homeDir = process.env.HOME ?? homedir3();
40803
- const userPath = join7(homeDir, ".superskill", "rubrics", `${type}.yaml`);
41178
+ const userPath = join8(homeDir, ".superskill", "rubrics", `${type}.yaml`);
40804
41179
  if (existsSync10(userPath)) {
40805
41180
  return readFileSync9(userPath, "utf-8");
40806
41181
  }
40807
- const devPath = join7(import.meta.dir, "..", "rubrics", `${type}.yaml`);
41182
+ const devPath = join8(import.meta.dir, "..", "rubrics", `${type}.yaml`);
40808
41183
  if (existsSync10(devPath)) {
40809
41184
  return readFileSync9(devPath, "utf-8");
40810
41185
  }
40811
- const prodPath = join7(import.meta.dir, "..", "..", "rubrics", `${type}.yaml`);
41186
+ const prodPath = join8(import.meta.dir, "..", "..", "rubrics", `${type}.yaml`);
40812
41187
  if (existsSync10(prodPath)) {
40813
41188
  return readFileSync9(prodPath, "utf-8");
40814
41189
  }
@@ -46639,11 +47014,11 @@ var require_out = __commonJS((exports) => {
46639
47014
  async.read(path7, getSettings(optionsOrSettingsOrCallback), callback);
46640
47015
  }
46641
47016
  exports.stat = stat;
46642
- function statSync6(path7, optionsOrSettings) {
47017
+ function statSync7(path7, optionsOrSettings) {
46643
47018
  const settings = getSettings(optionsOrSettings);
46644
47019
  return sync.read(path7, settings);
46645
47020
  }
46646
- exports.statSync = statSync6;
47021
+ exports.statSync = statSync7;
46647
47022
  function getSettings(settingsOrOptions = {}) {
46648
47023
  if (settingsOrOptions instanceof settings_1.default) {
46649
47024
  return settingsOrOptions;
@@ -57813,7 +58188,7 @@ import { join as join52 } from "path";
57813
58188
  import path10, { relative as relative2, resolve as resolve4 } from "path";
57814
58189
  import { basename as basename4, join as join9 } from "path";
57815
58190
  import { join as join72 } from "path";
57816
- import { join as join8 } from "path";
58191
+ import { join as join82 } from "path";
57817
58192
  import { join as join10 } from "path";
57818
58193
  import { basename as basename22, join as join11 } from "path";
57819
58194
  import { join as join13 } from "path";
@@ -57886,7 +58261,7 @@ import { join as join79 } from "path";
57886
58261
  import { join as join81 } from "path";
57887
58262
  import { join as join80 } from "path";
57888
58263
  import { join as join84 } from "path";
57889
- import { join as join82 } from "path";
58264
+ import { join as join822 } from "path";
57890
58265
  import { join as join83 } from "path";
57891
58266
  import { join as join85 } from "path";
57892
58267
  import { join as join86 } from "path";
@@ -63432,7 +63807,7 @@ var init_chunk_QKQNZ6C3 = __esm(() => {
63432
63807
  constructor({ frontmatter, body, ...rest2 }) {
63433
63808
  const parseResult = RulesyncCommandFrontmatterSchema.safeParse(frontmatter);
63434
63809
  if (!parseResult.success && rest2.validate) {
63435
- throw new Error(`Invalid frontmatter in ${join8(rest2.outputRoot ?? process.cwd(), rest2.relativeDirPath, rest2.relativeFilePath)}: ${formatError2(parseResult.error)}`);
63810
+ throw new Error(`Invalid frontmatter in ${join82(rest2.outputRoot ?? process.cwd(), rest2.relativeDirPath, rest2.relativeFilePath)}: ${formatError2(parseResult.error)}`);
63436
63811
  }
63437
63812
  const parsedFrontmatter = parseResult.success ? { ...frontmatter, ...parseResult.data } : { ...frontmatter, targets: frontmatter.targets ?? ["*"] };
63438
63813
  super({
@@ -63473,7 +63848,7 @@ var init_chunk_QKQNZ6C3 = __esm(() => {
63473
63848
  } else {
63474
63849
  return {
63475
63850
  success: false,
63476
- error: new Error(`Invalid frontmatter in ${join8(this.relativeDirPath, this.relativeFilePath)}: ${formatError2(result.error)}`)
63851
+ error: new Error(`Invalid frontmatter in ${join82(this.relativeDirPath, this.relativeFilePath)}: ${formatError2(result.error)}`)
63477
63852
  };
63478
63853
  }
63479
63854
  }
@@ -63481,7 +63856,7 @@ var init_chunk_QKQNZ6C3 = __esm(() => {
63481
63856
  outputRoot = process.cwd(),
63482
63857
  relativeFilePath
63483
63858
  }) {
63484
- const filePath = join8(outputRoot, _RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath);
63859
+ const filePath = join82(outputRoot, _RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath);
63485
63860
  const fileContent = await readFileContent(filePath);
63486
63861
  const { frontmatter, body: content } = parseFrontmatter2(fileContent, filePath);
63487
63862
  const result = RulesyncCommandFrontmatterSchema.safeParse(frontmatter);
@@ -69803,9 +70178,9 @@ prompt = ""`;
69803
70178
  return ignoreProcessorToolTargets;
69804
70179
  }
69805
70180
  };
69806
- AMP_GLOBAL_DIR = join82(".config", "amp");
69807
- AMP_SKILLS_PROJECT_DIR = join82(AMP_AGENTS_DIR, "skills");
69808
- AMP_SKILLS_GLOBAL_DIR = join82(".config", "agents", "skills");
70181
+ AMP_GLOBAL_DIR = join822(".config", "amp");
70182
+ AMP_SKILLS_PROJECT_DIR = join822(AMP_AGENTS_DIR, "skills");
70183
+ AMP_SKILLS_GLOBAL_DIR = join822(".config", "agents", "skills");
69809
70184
  EnvVarNameSchema = exports_external3.string().check(refine2((value) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(value), "envVars entries must be valid environment variable names"));
69810
70185
  McpServerSchema = exports_external3.looseObject({
69811
70186
  type: exports_external3.optional(exports_external3.enum(["local", "stdio", "sse", "http", "ws", "streamable-http"])),
@@ -88916,7 +89291,6 @@ var init_src = __esm(() => {
88916
89291
  init_validate();
88917
89292
  init_adapt_command();
88918
89293
  init_adapt_subagent();
88919
- init_pi_subagent();
88920
89294
  init_pi_tools();
88921
89295
  init_rewrite_references();
88922
89296
  init_slash_command2();
@@ -94660,11 +95034,39 @@ function deserializeEvaluation(row) {
94660
95034
  }
94661
95035
 
94662
95036
  // src/operations/evaluate.ts
95037
+ async function showHistory(type, contentName, opts) {
95038
+ const adapter = opts.adapter ?? await openStore();
95039
+ const dao = new EvaluationDao(adapter);
95040
+ const rows = await dao.getEvaluations(type, contentName);
95041
+ if (rows.length === 0)
95042
+ return;
95043
+ const cols = { date: 19, agg: 9, verdict: 7 };
95044
+ const lines = [];
95045
+ lines.push(`Evaluation history for ${contentName} (${rows.length} entries):`);
95046
+ lines.push("");
95047
+ const header = `${"Date".padEnd(cols.date)} ${"Aggregate".padEnd(cols.agg)} ${"Verdict".padEnd(cols.verdict)} Scorer`;
95048
+ lines.push(header);
95049
+ lines.push("-".repeat(header.length));
95050
+ for (const row of rows) {
95051
+ const date9 = new Date(row.created_at).toISOString().slice(0, 19).replace("T", " ");
95052
+ const agg = row.aggregate.toFixed(2).padEnd(cols.agg);
95053
+ const verdict = (row.aggregate >= 0.7 ? "PASS" : "FAIL").padEnd(cols.verdict);
95054
+ const scorer = row.scorer ?? "heuristic";
95055
+ const rv = row.rubric_version ? ` v${row.rubric_version}` : "";
95056
+ lines.push(`${date9.padEnd(cols.date)} ${agg} ${verdict} ${scorer}${rv}`);
95057
+ }
95058
+ echo(lines.join(`
95059
+ `));
95060
+ }
94663
95061
  async function evaluate3(type, nameOrPath, opts) {
94664
95062
  const resolvedPath = resolveContentPath(type, nameOrPath);
94665
95063
  if (!resolvedPath) {
94666
95064
  throw Object.assign(new Error(`File not found: ${nameOrPath}`), { code: 2 });
94667
95065
  }
95066
+ if (opts?.history) {
95067
+ await showHistory(type, resolveContentName(resolvedPath), opts);
95068
+ return null;
95069
+ }
94668
95070
  let content;
94669
95071
  try {
94670
95072
  content = await Bun.file(resolvedPath).text();
@@ -94680,8 +95082,24 @@ async function evaluate3(type, nameOrPath, opts) {
94680
95082
  }
94681
95083
  const report = evaluate(type, content, resolvedTarget);
94682
95084
  report.content = resolveContentName(resolvedPath);
95085
+ applyRubricWeightingAndVerdict(type, report, opts);
94683
95086
  if (opts?.save) {
94684
- await persistEvaluation(type, resolvedPath, resolvedTarget, report, opts);
95087
+ try {
95088
+ const adapter = opts.adapter ?? await openStore();
95089
+ await new EvaluationDao(adapter).insertEvaluation({
95090
+ content_type: type,
95091
+ content_name: resolveContentName(resolvedPath),
95092
+ target_agent: resolvedTarget,
95093
+ operation: opts.operation ?? "evaluate",
95094
+ aggregate: report.aggregate,
95095
+ dimensions: report.dimensions,
95096
+ file_hash: hashContent(resolvedPath),
95097
+ scorer: "heuristic"
95098
+ });
95099
+ } catch (err) {
95100
+ const msg = err instanceof Error ? err.message : String(err);
95101
+ echoError(`Warning: failed to save evaluation: ${msg}`);
95102
+ }
94685
95103
  }
94686
95104
  return report;
94687
95105
  }
@@ -94695,11 +95113,30 @@ function computeWeightedAggregate(scores, rubric2) {
94695
95113
  }
94696
95114
  return sum2;
94697
95115
  }
95116
+ function applyRubricWeightingAndVerdict(type, report, opts) {
95117
+ try {
95118
+ const rubric2 = loadRubric(type, opts?.rubric ? { path: opts.rubric } : undefined);
95119
+ report.aggregate = computeWeightedAggregate(report.dimensions, rubric2);
95120
+ } catch {}
95121
+ const agg = report.aggregate;
95122
+ report.verdict = agg >= 0.7 ? "PASS" : "FAIL";
95123
+ if (agg >= 0.9)
95124
+ report.grade = "A";
95125
+ else if (agg >= 0.75)
95126
+ report.grade = "B";
95127
+ else if (agg >= 0.6)
95128
+ report.grade = "C";
95129
+ else if (agg >= 0.45)
95130
+ report.grade = "D";
95131
+ else
95132
+ report.grade = "F";
95133
+ }
94698
95134
  function emitEnvelope(type, resolvedPath, content, resolvedTarget, opts) {
94699
95135
  const rubric2 = loadRubric(type, { path: opts.rubric });
94700
95136
  const contentName = resolveContentName(resolvedPath);
94701
95137
  const baseline = evaluate(type, content, resolvedTarget);
94702
95138
  baseline.content = contentName;
95139
+ applyRubricWeightingAndVerdict(type, baseline, opts);
94703
95140
  const envelope = {
94704
95141
  type,
94705
95142
  content_name: contentName,
@@ -94769,31 +95206,26 @@ async function ingestScores(type, resolvedPath, resolvedTarget, opts) {
94769
95206
  dimensions
94770
95207
  };
94771
95208
  if (opts.save) {
94772
- await persistEvaluation(type, resolvedPath, resolvedTarget, report, opts, "rubric", rubric2.version);
95209
+ try {
95210
+ const adapter = opts.adapter ?? await openStore();
95211
+ await new EvaluationDao(adapter).insertEvaluation({
95212
+ content_type: type,
95213
+ content_name: resolveContentName(resolvedPath),
95214
+ target_agent: resolvedTarget,
95215
+ operation: opts.operation ?? "evaluate",
95216
+ aggregate: report.aggregate,
95217
+ dimensions: report.dimensions,
95218
+ file_hash: hashContent(resolvedPath),
95219
+ scorer: "rubric",
95220
+ rubric_version: rubric2.version
95221
+ });
95222
+ } catch (err) {
95223
+ const msg = err instanceof Error ? err.message : String(err);
95224
+ echoError(`Warning: failed to save evaluation: ${msg}`);
95225
+ }
94773
95226
  }
94774
95227
  return report;
94775
95228
  }
94776
- async function persistEvaluation(type, resolvedPath, resolvedTarget, report, opts, scorer, rubricVersion) {
94777
- try {
94778
- const fileHash = hashContent(resolvedPath);
94779
- const adapter = opts.adapter ?? await openStore();
94780
- const dao = new EvaluationDao(adapter);
94781
- await dao.insertEvaluation({
94782
- content_type: type,
94783
- content_name: resolveContentName(resolvedPath),
94784
- target_agent: resolvedTarget,
94785
- operation: opts.operation ?? "evaluate",
94786
- aggregate: report.aggregate,
94787
- dimensions: report.dimensions,
94788
- file_hash: fileHash,
94789
- scorer: scorer ?? "heuristic",
94790
- ...rubricVersion !== undefined ? { rubric_version: rubricVersion } : {}
94791
- });
94792
- } catch (err) {
94793
- const msg = err instanceof Error ? err.message : String(err);
94794
- echoError(`Warning: failed to save evaluation: ${msg}`);
94795
- }
94796
- }
94797
95229
  function formatEvaluationReport(report, json3) {
94798
95230
  if (json3) {
94799
95231
  return JSON.stringify(report);
@@ -94811,6 +95243,33 @@ function formatEvaluationReport(report, json3) {
94811
95243
  }
94812
95244
  lines.push(` ${"\u2500".repeat(maxNameLen)}${"\u2500".repeat(30)}`);
94813
95245
  lines.push(` ${"AGGREGATE".padEnd(maxNameLen)} ${report.aggregate.toFixed(2)}`);
95246
+ if (report.verdict || report.grade) {
95247
+ lines.push("");
95248
+ lines.push(` Verdict: ${report.verdict ?? ""} Grade: ${report.grade ?? ""}`);
95249
+ }
95250
+ const findings = [];
95251
+ const recommendations = [];
95252
+ for (const name of dimNames) {
95253
+ const dim2 = report.dimensions[name];
95254
+ if (!dim2)
95255
+ continue;
95256
+ if (dim2.findings)
95257
+ findings.push(...dim2.findings.map((f) => `[${name}] ${f}`));
95258
+ if (dim2.recommendations)
95259
+ recommendations.push(...dim2.recommendations.map((r) => `[${name}] ${r}`));
95260
+ }
95261
+ if (findings.length > 0) {
95262
+ lines.push("");
95263
+ lines.push("Findings:");
95264
+ for (const f of findings)
95265
+ lines.push(` \u2022 ${f}`);
95266
+ }
95267
+ if (recommendations.length > 0) {
95268
+ lines.push("");
95269
+ lines.push("Recommendations:");
95270
+ for (const r of recommendations)
95271
+ lines.push(` \u2192 ${r}`);
95272
+ }
94814
95273
  return lines.join(`
94815
95274
  `);
94816
95275
  }
@@ -94890,6 +95349,11 @@ function deserializeProposal(row) {
94890
95349
  init_src();
94891
95350
 
94892
95351
  // src/operations/evolve.ts
95352
+ function isHookApplyCapableOpt(opts) {
95353
+ if (!opts)
95354
+ return false;
95355
+ return Boolean(opts.proposeOnly || opts.acceptId || opts.rejectId || opts.ingest || opts.history || opts.rollback);
95356
+ }
94893
95357
  function computeTrends(evaluations2) {
94894
95358
  if (evaluations2.length < 2)
94895
95359
  return [];
@@ -94951,6 +95415,56 @@ function computeTrends(evaluations2) {
94951
95415
  });
94952
95416
  return trends;
94953
95417
  }
95418
+ function hasFrontmatter(content) {
95419
+ if (!content.startsWith(`---
95420
+ `))
95421
+ return false;
95422
+ return /\n---(?=\n|$)/.test(content.slice(4));
95423
+ }
95424
+ function firstBodyLine(content) {
95425
+ for (const line of content.split(`
95426
+ `)) {
95427
+ const trimmed = line.trim();
95428
+ if (trimmed)
95429
+ return trimmed;
95430
+ }
95431
+ return "";
95432
+ }
95433
+ function generateChanges(report, trends, content) {
95434
+ const changes = [];
95435
+ const dimMap = new Map(Object.entries(report.dimensions));
95436
+ const withFrontmatter = content === undefined ? true : hasFrontmatter(content);
95437
+ const bodyAnchor = withFrontmatter ? "" : firstBodyLine(content ?? "");
95438
+ for (const trend of trends) {
95439
+ if (trend.trend === "declining" || trend.trend === "flat" && trend.latest < 0.7) {
95440
+ const dimData = dimMap.get(trend.dimension);
95441
+ const note = dimData?.note ?? "";
95442
+ const scoreLine = `Score: ${trend.latest.toFixed(2)} (trend: ${trend.trend}, \u0394${trend.delta >= 0 ? "+" : ""}${trend.delta.toFixed(2)}).`;
95443
+ if (withFrontmatter) {
95444
+ const suggestion = note ? `[Improve ${trend.dimension}]: ${note}` : `[Improve ${trend.dimension}]: review and enhance the description for better ${trend.dimension}.`;
95445
+ changes.push({
95446
+ dimension: trend.dimension,
95447
+ location: "frontmatter.description",
95448
+ current: `${trend.dimension} score: ${trend.latest.toFixed(2)}`,
95449
+ proposed: suggestion,
95450
+ reason: note ? `${scoreLine} Note: "${note}".` : `${scoreLine} Score below threshold.`
95451
+ });
95452
+ } else {
95453
+ const suggestion = note ? `[Improve ${trend.dimension}]: ${note}` : `[Improve ${trend.dimension}]: review and enhance this config for better ${trend.dimension}.`;
95454
+ changes.push({
95455
+ dimension: trend.dimension,
95456
+ location: "body",
95457
+ current: bodyAnchor,
95458
+ proposed: bodyAnchor ? `${bodyAnchor}
95459
+
95460
+ ${suggestion}` : suggestion,
95461
+ reason: `${scoreLine} Config has no frontmatter; suggesting a body addition rather than a frontmatter.description edit.`
95462
+ });
95463
+ }
95464
+ }
95465
+ }
95466
+ return changes;
95467
+ }
94954
95468
  function generateProposalId(type, _name, existingProposals) {
94955
95469
  const date9 = new Date().toISOString().slice(0, 10);
94956
95470
  const seq = String(existingProposals.length + 1).padStart(3, "0");
@@ -94980,8 +95494,14 @@ function computeAnchorHash(anchor) {
94980
95494
  return hasher.digest("hex").slice(0, 16);
94981
95495
  }
94982
95496
  function computeBaselineAnchorHash(content) {
94983
- const parsed = parseFrontmatter(content);
94984
- const frontmatter2 = parsed.data;
95497
+ let frontmatter2 = {};
95498
+ if (hasFrontmatter(content)) {
95499
+ try {
95500
+ frontmatter2 = parseFrontmatter(content).data;
95501
+ } catch {
95502
+ frontmatter2 = {};
95503
+ }
95504
+ }
94985
95505
  return computeAnchorHash({
94986
95506
  frontmatter: frontmatter2,
94987
95507
  rubric_criteria: "",
@@ -95027,8 +95547,14 @@ async function runGate(input) {
95027
95547
  }
95028
95548
  function emitGenerationEnvelope(type, resolvedPath, content, baseline, trends) {
95029
95549
  const rubric2 = loadRubric(type);
95030
- const parsed = parseFrontmatter(content);
95031
- const frontmatter2 = parsed.data;
95550
+ let frontmatter2 = {};
95551
+ if (hasFrontmatter(content)) {
95552
+ try {
95553
+ frontmatter2 = parseFrontmatter(content).data;
95554
+ } catch {
95555
+ frontmatter2 = {};
95556
+ }
95557
+ }
95032
95558
  const description = frontmatter2.description;
95033
95559
  const negativeConstraints = extractNegativeConstraints(description);
95034
95560
  const contentName = resolveContentName(resolvedPath);
@@ -95122,6 +95648,9 @@ async function ingestProposal(db2, type, name, resolvedPath, ingestPath, opts, b
95122
95648
  ingestedAnchorHash: parsed.anchor_hash,
95123
95649
  skeptic: parsed.skeptic
95124
95650
  });
95651
+ if (!verdict.rejected && verdict.backupPath) {
95652
+ await persistVersionSnapshot(verdict.backupPath, resolvedPath, proposalId);
95653
+ }
95125
95654
  return {
95126
95655
  baselineScore,
95127
95656
  postScore: verdict.postScore,
@@ -95158,8 +95687,8 @@ async function stepAnalyze(db2, type, name, opts) {
95158
95687
  const baselineDate = new Date(newest.created_at).toISOString();
95159
95688
  return { evaluations: evaluations2, trends, baselineScore, baselineDate };
95160
95689
  }
95161
- async function stepPropose(db2, type, name, _report, trends, evaluations2, baselineScore, baselineDate) {
95162
- const changes = [];
95690
+ async function stepPropose(db2, type, name, report, trends, evaluations2, baselineScore, baselineDate, content) {
95691
+ const changes = generateChanges(report, trends, content);
95163
95692
  const proposalDao = new ProposalDao(db2);
95164
95693
  const existingProposals = await proposalDao.getProposals(type, name);
95165
95694
  const proposalId = generateProposalId(type, name, existingProposals);
@@ -95360,12 +95889,58 @@ async function stepVerify(type, name, filePath, baselineScore, proposalDbId, opt
95360
95889
  } catch {
95361
95890
  echoError("Cannot link verify evaluation.");
95362
95891
  }
95363
- if (gate) {
95364
- rmSync3(gate.backupPath, { force: true });
95365
- }
95892
+ const survivingBackupPath = gate?.backupPath;
95366
95893
  const pctStr = baselineScore > 0 ? `, ${delta >= 0 ? "+" : ""}${(delta / baselineScore * 100).toFixed(1)}%` : "";
95367
95894
  echo(`Score: ${baselineScore.toFixed(2)} \u2192 ${postScore.toFixed(2)} (${delta >= 0 ? "+" : ""}${delta.toFixed(2)}${pctStr})`);
95368
- return { postScore, delta };
95895
+ return { postScore, delta, ...survivingBackupPath ? { backupPath: survivingBackupPath } : {} };
95896
+ }
95897
+ function scoreToGrade(score) {
95898
+ if (score >= 0.9)
95899
+ return "A";
95900
+ if (score >= 0.75)
95901
+ return "B";
95902
+ if (score >= 0.6)
95903
+ return "C";
95904
+ if (score >= 0.45)
95905
+ return "D";
95906
+ return "F";
95907
+ }
95908
+ function formatAnalyze(name, analysis) {
95909
+ const { trends, baselineScore, baselineDate, evaluations: evaluations2 } = analysis;
95910
+ const grade = scoreToGrade(baselineScore);
95911
+ const verdict = baselineScore >= 0.7 ? "PASS" : "FAIL";
95912
+ const gitAvailable = existsSync12(join220(process.cwd(), ".git"));
95913
+ const lines = [];
95914
+ lines.push("=== Evolution Analysis ===");
95915
+ lines.push(`Target: ${name} Score: ${(baselineScore * 100).toFixed(0)}% (${grade}) Status: ${verdict}`);
95916
+ lines.push(`Available data sources: evaluation-history (${evaluations2.length}) \xB7 git-history (${gitAvailable ? "\u2713" : "\u2717"})`);
95917
+ if (trends.length > 0) {
95918
+ lines.push("");
95919
+ lines.push("| Dimension | Earliest | Latest | Trend |");
95920
+ lines.push("|-----------|----------|--------|-------|");
95921
+ for (const t of trends) {
95922
+ const arrow = t.trend === "improving" ? "\u2191" : t.trend === "declining" ? "\u2193" : "\u2192";
95923
+ lines.push(`| ${t.dimension} | ${t.earliest.toFixed(2)} | ${t.latest.toFixed(2)} | ${arrow} ${t.trend} |`);
95924
+ }
95925
+ }
95926
+ const declining = trends.filter((t) => t.trend === "declining");
95927
+ const flatLow = trends.filter((t) => t.trend === "flat" && t.latest < 0.7);
95928
+ if (declining.length > 0) {
95929
+ lines.push(`Patterns: [warning] declining dimensions: ${declining.map((t) => t.dimension).join(", ")}`);
95930
+ } else if (flatLow.length > 0) {
95931
+ lines.push(`Patterns: [warning] flat-low dimensions: ${flatLow.map((t) => t.dimension).join(", ")}`);
95932
+ } else {
95933
+ lines.push(`Patterns: [success] ${name} is currently stable at ${(baselineScore * 100).toFixed(0)}%`);
95934
+ }
95935
+ lines.push(`Baseline date: ${baselineDate}`);
95936
+ return lines.join(`
95937
+ `);
95938
+ }
95939
+ async function persistVersionSnapshot(backupPath, resolvedPath, proposalId) {
95940
+ const versionPath = `${resolvedPath}.version-${proposalId}`;
95941
+ await Bun.write(versionPath, Bun.file(backupPath));
95942
+ rmSync3(backupPath, { force: true });
95943
+ return versionPath;
95369
95944
  }
95370
95945
  async function evolve(type, name, opts) {
95371
95946
  const resolvedPath = resolveContentPath(type, name);
@@ -95374,6 +95949,10 @@ async function evolve(type, name, opts) {
95374
95949
  return { baselineScore: 0, postScore: 0, delta: 0, changesApplied: 0, proposalPath: "" };
95375
95950
  }
95376
95951
  const contentName = resolveContentName(resolvedPath);
95952
+ if (type === "hook" && isHookApplyCapableOpt(opts)) {
95953
+ echoError("hook evolve is analyze-only \u2014 apply/history/rollback are not supported for hooks.");
95954
+ return { baselineScore: 0, postScore: 0, delta: 0, changesApplied: 0, proposalPath: "" };
95955
+ }
95377
95956
  let db2;
95378
95957
  try {
95379
95958
  db2 = await openDb(opts);
@@ -95382,6 +95961,45 @@ async function evolve(type, name, opts) {
95382
95961
  echoError(`Could not open the evaluation store. ${msg}`);
95383
95962
  return { baselineScore: 0, postScore: 0, delta: 0, changesApplied: 0, proposalPath: "" };
95384
95963
  }
95964
+ if (opts?.history) {
95965
+ const proposals2 = await new ProposalDao(db2).getProposals(type, contentName);
95966
+ const accepted = proposals2.filter((p) => p.status === "accepted");
95967
+ if (accepted.length === 0) {
95968
+ echo(`No applied versions found for ${type}/${contentName}.`);
95969
+ return { baselineScore: 0, postScore: 0, delta: 0, changesApplied: 0, proposalPath: "" };
95970
+ }
95971
+ const lines = [];
95972
+ lines.push(`=== Version History: ${contentName} ===`);
95973
+ lines.push("");
95974
+ lines.push("| Version | Applied At | Snapshot |");
95975
+ lines.push("|---------|------------|----------|");
95976
+ for (const p of accepted) {
95977
+ const json3 = typeof p.proposal_json === "string" ? JSON.parse(p.proposal_json) : p.proposal_json;
95978
+ const proposalId2 = json3?.proposal_id;
95979
+ const versionId = proposalId2 ?? `#${p.id}`;
95980
+ const appliedAt = p.applied_at ?? "unknown";
95981
+ const versionPath = `${resolvedPath}.version-${versionId}`;
95982
+ const hasSnapshot = existsSync12(versionPath) ? "\u2713" : "\u2717";
95983
+ lines.push(`| ${versionId} | ${appliedAt} | ${hasSnapshot} |`);
95984
+ }
95985
+ echo(lines.join(`
95986
+ `));
95987
+ return { baselineScore: 0, postScore: 0, delta: 0, changesApplied: 0, proposalPath: "" };
95988
+ }
95989
+ if (opts?.rollback) {
95990
+ if (!opts.confirm) {
95991
+ echoError("--rollback requires --confirm to proceed.");
95992
+ return { baselineScore: 0, postScore: 0, delta: 0, changesApplied: 0, proposalPath: "" };
95993
+ }
95994
+ const versionPath = `${resolvedPath}.version-${opts.rollback}`;
95995
+ if (!existsSync12(versionPath)) {
95996
+ echoError(`Version snapshot not found: ${opts.rollback}`);
95997
+ return { baselineScore: 0, postScore: 0, delta: 0, changesApplied: 0, proposalPath: "" };
95998
+ }
95999
+ await Bun.write(resolvedPath, Bun.file(versionPath));
96000
+ echo(`Rolled back ${contentName} to version ${opts.rollback}.`);
96001
+ return { baselineScore: 0, postScore: 0, delta: 0, changesApplied: 0, proposalPath: "" };
96002
+ }
95385
96003
  let analysis;
95386
96004
  try {
95387
96005
  analysis = await stepAnalyze(db2, type, contentName, opts);
@@ -95391,6 +96009,10 @@ async function evolve(type, name, opts) {
95391
96009
  return { baselineScore: 0, postScore: 0, delta: 0, changesApplied: 0, proposalPath: "" };
95392
96010
  }
95393
96011
  const { trends, baselineScore, baselineDate, evaluations: evaluations2 } = analysis;
96012
+ if (opts?.analyze) {
96013
+ echo(formatAnalyze(contentName, analysis));
96014
+ return { baselineScore, postScore: baselineScore, delta: 0, changesApplied: 0, proposalPath: "" };
96015
+ }
95394
96016
  if (evaluations2.length < 2) {
95395
96017
  echo("Only one evaluation found \u2014 need at least two for trend analysis. Running evaluation-based proposal instead.");
95396
96018
  }
@@ -95448,13 +96070,16 @@ async function evolve(type, name, opts) {
95448
96070
  } catch {
95449
96071
  acceptedFromStore = [];
95450
96072
  }
95451
- const backupPath = await backupFile(resolvedPath);
96073
+ const backupPath2 = await backupFile(resolvedPath);
95452
96074
  const appliedCount = await stepApply(acceptedFromStore, resolvedPath, target.id, db2);
95453
96075
  const verdict = await stepVerify(type, contentName, resolvedPath, baselineScore, target.id, opts, db2, {
95454
- backupPath,
96076
+ backupPath: backupPath2,
95455
96077
  ingestedAnchorHash: storedAnchorHash,
95456
96078
  skeptic: storedSkeptic
95457
96079
  });
96080
+ if (!verdict.rejected && verdict.backupPath) {
96081
+ await persistVersionSnapshot(verdict.backupPath, resolvedPath, opts.acceptId);
96082
+ }
95458
96083
  return {
95459
96084
  baselineScore,
95460
96085
  postScore: verdict.postScore,
@@ -95464,20 +96089,24 @@ async function evolve(type, name, opts) {
95464
96089
  ...verdict.rejected ? { rejected: true, rejectionReason: verdict.reason } : {}
95465
96090
  };
95466
96091
  }
95467
- const { proposalDbId, proposalPath, changes } = await stepPropose(db2, type, contentName, baselineReport, trends, evaluations2, baselineScore, baselineDate);
96092
+ const proposeContent = await Bun.file(resolvedPath).text();
96093
+ const { proposalId, proposalDbId, proposalPath, changes } = await stepPropose(db2, type, contentName, baselineReport, trends, evaluations2, baselineScore, baselineDate, proposeContent);
95468
96094
  if (opts?.proposeOnly) {
95469
96095
  echo(`Proposal written to: ${proposalPath}`);
95470
96096
  return { baselineScore, postScore: baselineScore, delta: 0, changesApplied: 0, proposalPath };
95471
96097
  }
95472
96098
  const { accepted: acceptedChanges } = await interactiveReview(changes, trends);
96099
+ const backupPath = await backupFile(resolvedPath);
95473
96100
  const changesApplied = await stepApply(acceptedChanges, resolvedPath, proposalDbId, db2);
95474
96101
  const { postScore, delta } = await stepVerify(type, contentName, resolvedPath, baselineScore, proposalDbId, opts, db2);
96102
+ await persistVersionSnapshot(backupPath, resolvedPath, proposalId);
95475
96103
  return { baselineScore, postScore, delta, changesApplied, proposalPath };
95476
96104
  }
95477
96105
 
95478
96106
  // src/operations/refine.ts
95479
96107
  init_src();
95480
96108
  init_dist();
96109
+ init_db2();
95481
96110
  import { createInterface as createInterface2 } from "readline";
95482
96111
  class RefineAbortedError extends Error {
95483
96112
  constructor(message = "User quit interactive mode") {
@@ -95485,6 +96114,11 @@ class RefineAbortedError extends Error {
95485
96114
  this.name = "RefineAbortedError";
95486
96115
  }
95487
96116
  }
96117
+ function isHookApplyCapableOpt2(opts) {
96118
+ if (!opts)
96119
+ return false;
96120
+ return Boolean(opts.auto || opts.save);
96121
+ }
95488
96122
  function classifyFix(finding) {
95489
96123
  if (finding.severity === "error") {
95490
96124
  return "auto-apply";
@@ -95497,17 +96131,59 @@ function classifyFix(finding) {
95497
96131
  }
95498
96132
  return "auto-apply";
95499
96133
  }
95500
- function getDefaultForField(field) {
95501
- const DEFAULTS = {
95502
- name: "TODO",
95503
- description: "TODO",
95504
- model: "default"
95505
- };
95506
- return DEFAULTS[field] ?? "TODO";
96134
+ function getDefaultForField(field, content) {
96135
+ if (field === "model")
96136
+ return "inherit";
96137
+ if (field === "tools")
96138
+ return [];
96139
+ const name = readFrontmatterName(content);
96140
+ const h1 = extractFirstHeading(content);
96141
+ if (field === "description") {
96142
+ if (name)
96143
+ return humanize(name);
96144
+ if (h1)
96145
+ return h1;
96146
+ return null;
96147
+ }
96148
+ if (field === "name") {
96149
+ if (h1)
96150
+ return slugify2(h1);
96151
+ return null;
96152
+ }
96153
+ return null;
96154
+ }
96155
+ function readFrontmatterName(content) {
96156
+ try {
96157
+ const parsed = parseFrontmatter(content);
96158
+ const name = parsed.data.name;
96159
+ return typeof name === "string" && name.trim() ? name.trim() : null;
96160
+ } catch {
96161
+ return null;
96162
+ }
96163
+ }
96164
+ function extractFirstHeading(content) {
96165
+ let body = content;
96166
+ if (content.startsWith(`---
96167
+ `)) {
96168
+ const closer = content.slice(4).match(/\n---(?=\n|$)/);
96169
+ if (closer?.index !== undefined)
96170
+ body = content.slice(closer.index + 4 + 4);
96171
+ }
96172
+ const headingMatch = /^\s*#\s+(.+?)\s*$/m.exec(body);
96173
+ return headingMatch?.[1] ? headingMatch[1].trim() : null;
96174
+ }
96175
+ function humanize(slug) {
96176
+ return slug.replace(/[-_]+/g, " ").replace(/\b\w/g, (c3) => c3.toUpperCase()).trim();
96177
+ }
96178
+ function slugify2(label) {
96179
+ return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
95507
96180
  }
95508
96181
  function generateAutoChange(finding, content) {
95509
96182
  if (finding.message.toLowerCase().includes("missing")) {
95510
- return { kind: "frontmatter", key: finding.field, value: getDefaultForField(finding.field) };
96183
+ const value = getDefaultForField(finding.field, content);
96184
+ if (value === null)
96185
+ return null;
96186
+ return { kind: "frontmatter", key: finding.field, value };
95511
96187
  }
95512
96188
  if (finding.message.includes("must be an array")) {
95513
96189
  let currentValue;
@@ -95653,30 +96329,30 @@ function applyAutoFixes(findings, content) {
95653
96329
  async function refine3(type, nameOrPath, opts) {
95654
96330
  const resolvedPath = resolveContentPath(type, nameOrPath);
95655
96331
  const resolvedTarget = opts?.target ?? "claude";
95656
- const validation = await validate(type, resolvedPath ?? nameOrPath, {
96332
+ const filePath = resolvedPath ?? nameOrPath;
96333
+ if (type === "hook" && isHookApplyCapableOpt2(opts)) {
96334
+ echoError("hook refine is suggest-only \u2014 auto-apply and save are not supported for hooks. Use --dry-run to preview findings, then hand-fix and `hook validate`.");
96335
+ return { preScore: 0, postScore: 0, delta: 0, fixesApplied: [], fixesSkipped: [] };
96336
+ }
96337
+ const validation = await validate(type, filePath, {
95657
96338
  target: resolvedTarget,
95658
96339
  strict: opts?.auto === true
95659
96340
  });
95660
- if (!validation.valid) {
95661
- for (const f of validation.findings) {
95662
- if (f.severity === "error") {
95663
- echoError(`[ERROR] ${f.field}: ${f.message}`);
95664
- }
96341
+ let preScore = 0;
96342
+ let preDimensions = {};
96343
+ try {
96344
+ const report = await evaluate3(type, filePath, { target: resolvedTarget });
96345
+ if (report) {
96346
+ preScore = report.aggregate;
96347
+ preDimensions = report.dimensions;
95665
96348
  }
95666
- echoError("Fix validation errors before refining.");
95667
- return { preScore: 0, postScore: 0, delta: 0, fixesApplied: [], fixesSkipped: [] };
95668
- }
95669
- let preScore;
95670
- let preDimensions;
96349
+ } catch {}
96350
+ let content;
95671
96351
  try {
95672
- const report = await evaluate3(type, resolvedPath ?? nameOrPath, { target: resolvedTarget });
95673
- if (!report)
95674
- throw new Error("evaluate returned null in heuristic mode");
95675
- preScore = report.aggregate;
95676
- preDimensions = report.dimensions;
96352
+ content = await Bun.file(filePath).text();
95677
96353
  } catch {
95678
- echoError("Cannot evaluate: file not found or unreadable.");
95679
- return { preScore: 0, postScore: 0, delta: 0, fixesApplied: [], fixesSkipped: [] };
96354
+ echoError("Cannot read content file for editing.");
96355
+ return { preScore, postScore: preScore, delta: 0, fixesApplied: [], fixesSkipped: [] };
95680
96356
  }
95681
96357
  const findings = [];
95682
96358
  for (const f of validation.findings) {
@@ -95684,26 +96360,23 @@ async function refine3(type, nameOrPath, opts) {
95684
96360
  }
95685
96361
  for (const [dimName, dimScore] of Object.entries(preDimensions)) {
95686
96362
  if (dimScore.score < 0.7 && dimScore.note?.trim()) {
95687
- const finding = {
95688
- severity: "warning",
95689
- field: dimName,
95690
- message: dimScore.note
95691
- };
95692
- findings.push({ finding, strategy: "suggest" });
96363
+ findings.push({
96364
+ finding: { severity: "warning", field: dimName, message: dimScore.note },
96365
+ strategy: "suggest"
96366
+ });
95693
96367
  }
95694
96368
  }
96369
+ if (opts?.dryRun || type === "hook") {
96370
+ if (type === "hook") {
96371
+ echo("Hook refine is suggest-only (task 0061 decision C). Findings are recommendations; hooks.json is not modified. Hand-fix, then `hook validate`.");
96372
+ }
96373
+ return dryRunPreview(type, content, resolvedTarget, findings);
96374
+ }
95695
96375
  if (findings.length === 0) {
95696
96376
  echo(`No issues found. Score: ${preScore.toFixed(2)}`);
95697
96377
  return { preScore, postScore: preScore, delta: 0, fixesApplied: [], fixesSkipped: [] };
95698
96378
  }
95699
- let content;
95700
- try {
95701
- content = await Bun.file(resolvedPath ?? nameOrPath).text();
95702
- } catch {
95703
- echoError("Cannot read content file for editing.");
95704
- return { preScore, postScore: preScore, delta: 0, fixesApplied: [], fixesSkipped: [] };
95705
- }
95706
- const backupPath = await backupFile(resolvedPath ?? nameOrPath);
96379
+ const backupPath = await backupFile(filePath);
95707
96380
  let fixesApplied = [];
95708
96381
  let fixesSkipped = [];
95709
96382
  try {
@@ -95712,10 +96385,10 @@ async function refine3(type, nameOrPath, opts) {
95712
96385
  fixesApplied = result.fixesApplied;
95713
96386
  fixesSkipped = result.fixesSkipped;
95714
96387
  if (fixesApplied.length > 0) {
95715
- await Bun.write(resolvedPath ?? nameOrPath, result.content);
96388
+ await Bun.write(filePath, result.content);
95716
96389
  }
95717
96390
  } else {
95718
- const result = await runInteractive(findings, content, resolvedPath ?? nameOrPath, backupPath);
96391
+ const result = await runInteractive(findings, content, filePath, backupPath);
95719
96392
  fixesApplied = result.fixesApplied;
95720
96393
  fixesSkipped = result.fixesSkipped;
95721
96394
  }
@@ -95725,9 +96398,24 @@ async function refine3(type, nameOrPath, opts) {
95725
96398
  }
95726
96399
  throw err;
95727
96400
  }
96401
+ if (!validation.valid) {
96402
+ const revalidation = await validate(type, filePath, {
96403
+ target: resolvedTarget,
96404
+ strict: opts?.auto === true
96405
+ });
96406
+ if (!revalidation.valid) {
96407
+ for (const f of revalidation.findings) {
96408
+ if (f.severity === "error")
96409
+ echoError(`[ERROR] ${f.field}: ${f.message}`);
96410
+ }
96411
+ echoError("Validation errors remain after structural fixes; refine aborted.");
96412
+ return { preScore, postScore: preScore, delta: 0, fixesApplied, fixesSkipped };
96413
+ }
96414
+ }
95728
96415
  let postScore;
96416
+ let postReport = null;
95729
96417
  try {
95730
- const postReport = await evaluate3(type, resolvedPath ?? nameOrPath, { target: resolvedTarget });
96418
+ postReport = await evaluate3(type, filePath, { target: resolvedTarget });
95731
96419
  if (!postReport)
95732
96420
  throw new Error("evaluate returned null in heuristic mode");
95733
96421
  postScore = postReport.aggregate;
@@ -95735,6 +96423,14 @@ async function refine3(type, nameOrPath, opts) {
95735
96423
  echoError("Cannot re-evaluate after fixes.");
95736
96424
  return { preScore, postScore: preScore, delta: 0, fixesApplied, fixesSkipped };
95737
96425
  }
96426
+ if (postScore < preScore) {
96427
+ await restoreFromBackup(backupPath, filePath);
96428
+ echo("Refine skipped: changes would have lowered the score; original restored.");
96429
+ for (const f of fixesApplied)
96430
+ fixesSkipped.push({ ...f, applied: false });
96431
+ fixesApplied = [];
96432
+ postScore = preScore;
96433
+ }
95738
96434
  const delta = postScore - preScore;
95739
96435
  if (delta === 0 && preScore === postScore) {
95740
96436
  echo(`Score: ${preScore.toFixed(2)} (no change)`);
@@ -95742,12 +96438,20 @@ async function refine3(type, nameOrPath, opts) {
95742
96438
  const pctStr = preScore > 0 ? `, +${(delta / preScore * 100).toFixed(1)}%` : "";
95743
96439
  echo(`Score: ${preScore.toFixed(2)} \u2192 ${postScore.toFixed(2)} (${delta >= 0 ? "+" : ""}${delta.toFixed(2)}${pctStr})`);
95744
96440
  }
95745
- if (opts?.save) {
96441
+ if (opts?.save && postReport) {
95746
96442
  try {
95747
- await evaluate3(type, resolvedPath ?? nameOrPath, {
95748
- target: resolvedTarget,
95749
- save: true,
95750
- operation: "refine"
96443
+ const fileHash = hashContent(filePath);
96444
+ const adapter = await openStore();
96445
+ const dao = new EvaluationDao(adapter);
96446
+ await dao.insertEvaluation({
96447
+ content_type: type,
96448
+ content_name: resolveContentName(filePath),
96449
+ target_agent: resolvedTarget,
96450
+ operation: "refine",
96451
+ aggregate: postReport.aggregate,
96452
+ dimensions: postReport.dimensions,
96453
+ file_hash: fileHash,
96454
+ scorer: "heuristic"
95751
96455
  });
95752
96456
  } catch {
95753
96457
  echoError("Warning: failed to save evaluation results.");
@@ -95755,6 +96459,48 @@ async function refine3(type, nameOrPath, opts) {
95755
96459
  }
95756
96460
  return { preScore, postScore, delta, fixesApplied, fixesSkipped };
95757
96461
  }
96462
+ function dryRunPreview(type, content, target, findings) {
96463
+ let preScore = 0;
96464
+ try {
96465
+ preScore = evaluate(type, content, target).aggregate;
96466
+ } catch {}
96467
+ const autoFindings = findings.filter((f) => f.strategy === "auto-apply");
96468
+ const projected = applyAutoFixes(autoFindings, content);
96469
+ let projectedPost = preScore;
96470
+ try {
96471
+ projectedPost = evaluate(type, projected.content, target).aggregate;
96472
+ } catch {}
96473
+ echo("Dry run \u2014 no changes written.");
96474
+ if (findings.length === 0) {
96475
+ echo(`No issues found. Score: ${preScore.toFixed(2)}`);
96476
+ } else {
96477
+ const hookSuggestOnly2 = type === "hook";
96478
+ for (const { finding, strategy } of findings) {
96479
+ const effectiveStrategy = hookSuggestOnly2 ? "suggest" : strategy;
96480
+ const tag = effectiveStrategy.toUpperCase();
96481
+ const change = strategy === "auto-apply" ? generateAutoChange(finding, content) : null;
96482
+ const proposed = !hookSuggestOnly2 && change && change.kind === "frontmatter" ? ` \u2192 would set ${change.key} = ${formatValue(change.value)}` : "";
96483
+ echo(`[${tag}] ${finding.field}: ${finding.message}${proposed}`);
96484
+ }
96485
+ const delta = projectedPost - preScore;
96486
+ const pctStr = preScore > 0 ? `, +${(delta / preScore * 100).toFixed(1)}%` : "";
96487
+ echo(`Projected: ${preScore.toFixed(2)} \u2192 ${projectedPost.toFixed(2)} (${delta >= 0 ? "+" : ""}${delta.toFixed(2)}${pctStr})`);
96488
+ }
96489
+ const hookSuggestOnly = type === "hook";
96490
+ const fixesSkipped = findings.map(({ finding, strategy }) => ({
96491
+ severity: finding.severity,
96492
+ field: finding.field,
96493
+ message: finding.message,
96494
+ strategy: hookSuggestOnly ? "suggest" : strategy,
96495
+ applied: false
96496
+ }));
96497
+ return { preScore, postScore: projectedPost, delta: projectedPost - preScore, fixesApplied: [], fixesSkipped };
96498
+ }
96499
+ function formatValue(value) {
96500
+ if (Array.isArray(value))
96501
+ return value.length === 0 ? "[]" : `[${value.join(", ")}]`;
96502
+ return String(value);
96503
+ }
95758
96504
 
95759
96505
  // src/operations/scaffold.ts
95760
96506
  init_src();
@@ -95766,10 +96512,16 @@ function addTargetOption(cmd) {
95766
96512
  return cmd.option("-t, --target <agent>", "Target agent platform", "claude");
95767
96513
  }
95768
96514
  function addScaffoldOptions(cmd) {
95769
- 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("--force", "Overwrite existing file if present");
96515
+ 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");
95770
96516
  }
95771
96517
  function addEvolveOptions(cmd) {
95772
- 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);
96518
+ 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("--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)");
96519
+ }
96520
+ function addHookEvolveOptions(cmd) {
96521
+ return cmd.option("-t, --target <agent>", "Target agent platform", "claude").option("--from <date>", "Analyze evaluations since date (ISO 8601)").option("--analyze", "Print analysis summary (trends, score, data sources) \u2014 analyze-only, no apply").option("--json", "Output machine-readable JSON");
96522
+ }
96523
+ function addHookRefineOptions(cmd) {
96524
+ return cmd.option("-t, --target <agent>", "Target agent platform", "claude").option("--dry-run", "Preview classified findings and recommendations without writing");
95773
96525
  }
95774
96526
  function addJsonOption(cmd) {
95775
96527
  return cmd.option("--json", "Output machine-readable JSON");
@@ -95786,6 +96538,9 @@ function addStrictOption(cmd) {
95786
96538
  function addAutoOption(cmd) {
95787
96539
  return cmd.option("--auto", "Apply low-risk fixes automatically");
95788
96540
  }
96541
+ function addDryRunOption(cmd) {
96542
+ return cmd.option("--dry-run", "Preview classified fixes and projected delta without writing");
96543
+ }
95789
96544
  function resolveTarget(opts) {
95790
96545
  const raw = opts.target || "claude";
95791
96546
  if (!TARGETS.includes(raw)) {
@@ -95819,7 +96574,9 @@ async function agentScaffold(opts) {
95819
96574
  description: opts.description,
95820
96575
  target,
95821
96576
  output: opts.output,
95822
- force: opts.force
96577
+ force: opts.force,
96578
+ template: opts.template,
96579
+ tools: opts.tools
95823
96580
  });
95824
96581
  echo(`Created: ${createdPath}`);
95825
96582
  return;
@@ -95850,7 +96607,7 @@ async function agentEvaluate(opts) {
95850
96607
  }
95851
96608
  async function agentRefine(opts) {
95852
96609
  const target = resolveTarget(opts);
95853
- await refine3("agent", opts.nameOrPath, { target, auto: opts.auto, save: opts.save });
96610
+ await refine3("agent", opts.nameOrPath, { target, auto: opts.auto, save: opts.save, dryRun: opts.dryRun });
95854
96611
  return;
95855
96612
  }
95856
96613
  async function agentEvolve(opts) {
@@ -95863,7 +96620,11 @@ async function agentEvolve(opts) {
95863
96620
  rejectId: opts.reject,
95864
96621
  json: opts.json,
95865
96622
  ingest: opts.ingest,
95866
- margin: opts.margin
96623
+ margin: opts.margin,
96624
+ analyze: opts.analyze,
96625
+ history: opts.history,
96626
+ rollback: opts.rollback,
96627
+ confirm: opts.confirm
95867
96628
  });
95868
96629
  return;
95869
96630
  }
@@ -95893,7 +96654,7 @@ function registerAgent(program2) {
95893
96654
  addEvaluateOptions(addSaveOption(addTargetOption(addJsonOption(cmd.command("evaluate <nameOrPath>").description("Evaluate agent quality (use --rubric --json for envelope, --ingest --save to persist scores)"))))).action(async (nameOrPath, opts) => {
95894
96655
  await handleAgentEvaluate({ nameOrPath, ...opts });
95895
96656
  });
95896
- addSaveOption(addTargetOption(addAutoOption(cmd.command("refine <nameOrPath>").description("Evaluate and auto-fix an agent")))).action(async (nameOrPath, opts) => {
96657
+ addDryRunOption(addSaveOption(addTargetOption(addAutoOption(cmd.command("refine <nameOrPath>").description("Evaluate and auto-fix an agent"))))).action(async (nameOrPath, opts) => {
95897
96658
  await handleAgentRefine({ nameOrPath, ...opts });
95898
96659
  });
95899
96660
  addEvolveOptions(cmd.command("evolve <name>").description("Longitudinal improvement from evaluation history")).action(async (name, opts) => {
@@ -95909,7 +96670,9 @@ async function commandScaffold(opts) {
95909
96670
  description: opts.description,
95910
96671
  target,
95911
96672
  output: opts.output,
95912
- force: opts.force
96673
+ force: opts.force,
96674
+ template: opts.template,
96675
+ tools: opts.tools
95913
96676
  });
95914
96677
  echo(`Created: ${createdPath}`);
95915
96678
  return;
@@ -95940,7 +96703,7 @@ async function commandEvaluate(opts) {
95940
96703
  }
95941
96704
  async function commandRefine(opts) {
95942
96705
  const target = resolveTarget(opts);
95943
- await refine3("command", opts.nameOrPath, { target, auto: opts.auto, save: opts.save });
96706
+ await refine3("command", opts.nameOrPath, { target, auto: opts.auto, save: opts.save, dryRun: opts.dryRun });
95944
96707
  return;
95945
96708
  }
95946
96709
  async function commandEvolve(opts) {
@@ -95953,7 +96716,11 @@ async function commandEvolve(opts) {
95953
96716
  rejectId: opts.reject,
95954
96717
  json: opts.json,
95955
96718
  ingest: opts.ingest,
95956
- margin: opts.margin
96719
+ margin: opts.margin,
96720
+ analyze: opts.analyze,
96721
+ history: opts.history,
96722
+ rollback: opts.rollback,
96723
+ confirm: opts.confirm
95957
96724
  });
95958
96725
  return;
95959
96726
  }
@@ -95983,7 +96750,7 @@ function registerCommand(program2) {
95983
96750
  addEvaluateOptions(addSaveOption(addTargetOption(addJsonOption(cmd.command("evaluate <nameOrPath>").description("Evaluate command quality (use --rubric --json for envelope, --ingest --save to persist scores)"))))).action(async (nameOrPath, opts) => {
95984
96751
  await handleCommandEvaluate({ nameOrPath, ...opts });
95985
96752
  });
95986
- addSaveOption(addTargetOption(addAutoOption(cmd.command("refine <nameOrPath>").description("Evaluate and auto-fix a command")))).action(async (nameOrPath, opts) => {
96753
+ addDryRunOption(addSaveOption(addTargetOption(addAutoOption(cmd.command("refine <nameOrPath>").description("Evaluate and auto-fix a command"))))).action(async (nameOrPath, opts) => {
95987
96754
  await handleCommandRefine({ nameOrPath, ...opts });
95988
96755
  });
95989
96756
  addEvolveOptions(cmd.command("evolve <name>").description("Longitudinal improvement from evaluation history")).action(async (name, opts) => {
@@ -96004,10 +96771,10 @@ import {
96004
96771
  copyFileSync as copyFileSync2,
96005
96772
  existsSync as existsSync14,
96006
96773
  mkdirSync as mkdirSync9,
96007
- readdirSync as readdirSync3,
96774
+ readdirSync as readdirSync4,
96008
96775
  readFileSync as readFileSync13,
96009
96776
  rmSync as rmSync4,
96010
- statSync as statSync6,
96777
+ statSync as statSync7,
96011
96778
  writeFileSync as writeFileSync8
96012
96779
  } from "fs";
96013
96780
  import { homedir as homedir5 } from "os";
@@ -96267,7 +97034,7 @@ async function executeInstall(plugin, targets2, options2, dependencies = {}) {
96267
97034
  if (existsSync14(agentsDir) && !options2.dryRun) {
96268
97035
  const piAgentsDir = join223(outputRoot, ".pi", "agent", "agents");
96269
97036
  mkdirSync9(piAgentsDir, { recursive: true });
96270
- for (const entry of readdirSync3(agentsDir)) {
97037
+ for (const entry of readdirSync4(agentsDir)) {
96271
97038
  if (!entry.endsWith(".md"))
96272
97039
  continue;
96273
97040
  const agentName = entry.replace(/\.md$/, "");
@@ -96332,7 +97099,7 @@ function resolvePluginRoot(plugin, marketplacePath) {
96332
97099
  return { pluginRoot: resolved.pluginRoot, marketplaceRoot: manifestRoot, marketplaceName };
96333
97100
  }
96334
97101
  const fallback = join223("plugins", plugin);
96335
- if (existsSync14(join223(fallback, "plugin.json")))
97102
+ if (existsSync14(fallback) && readdirSync4(fallback).some((d) => ["skills", "commands", "agents", "hooks", "hooks.json"].includes(d)))
96336
97103
  return { pluginRoot: fallback };
96337
97104
  const available = listResolvablePlugins(marketplacePath);
96338
97105
  const msg = available.length > 0 ? `Available: ${available.join(", ")}` : "No marketplace manifest found and no plugins/<name>/ directory.";
@@ -96369,9 +97136,9 @@ function transformRulesyncMarkdown(root, target, pluginName) {
96369
97136
  function transformMarkdownDirectory(dir, target, pluginName) {
96370
97137
  if (!existsSync14(dir))
96371
97138
  return;
96372
- for (const entry of readdirSync3(dir)) {
97139
+ for (const entry of readdirSync4(dir)) {
96373
97140
  const path11 = join223(dir, entry);
96374
- const stats = statSync6(path11);
97141
+ const stats = statSync7(path11);
96375
97142
  if (stats.isDirectory()) {
96376
97143
  transformMarkdownDirectory(path11, target, pluginName);
96377
97144
  continue;
@@ -96388,12 +97155,12 @@ function copyDirectory(source, destination, options2 = {}) {
96388
97155
  if (!existsSync14(source))
96389
97156
  return;
96390
97157
  mkdirSync9(destination, { recursive: true });
96391
- for (const entry of readdirSync3(source)) {
97158
+ for (const entry of readdirSync4(source)) {
96392
97159
  if (options2.skipDirectoryNames?.has(entry))
96393
97160
  continue;
96394
97161
  const sourcePath = join223(source, entry);
96395
97162
  const destinationPath = join223(destination, entry);
96396
- if (statSync6(sourcePath).isDirectory()) {
97163
+ if (statSync7(sourcePath).isDirectory()) {
96397
97164
  copyDirectory(sourcePath, destinationPath, options2);
96398
97165
  } else {
96399
97166
  copyFileSync2(sourcePath, destinationPath);
@@ -96402,15 +97169,6 @@ function copyDirectory(source, destination, options2 = {}) {
96402
97169
  }
96403
97170
 
96404
97171
  // src/commands/hook.ts
96405
- async function scaffoldHook(name, opts) {
96406
- const target = resolveTarget(opts);
96407
- return scaffold("hook", name, {
96408
- description: opts.description,
96409
- target,
96410
- output: opts.output,
96411
- force: opts.force
96412
- });
96413
- }
96414
97172
  async function validateHook(nameOrPath, opts) {
96415
97173
  const target = resolveTarget(opts);
96416
97174
  return validate("hook", nameOrPath, { target, strict: opts.strict });
@@ -96426,19 +97184,15 @@ async function evaluateHook2(nameOrPath, opts) {
96426
97184
  }
96427
97185
  async function refineHook(nameOrPath, opts) {
96428
97186
  const target = resolveTarget(opts);
96429
- return refine3("hook", nameOrPath, { target, auto: opts.auto, save: opts.save });
97187
+ return refine3("hook", nameOrPath, { target, dryRun: opts.dryRun });
96430
97188
  }
96431
97189
  async function evolveHook(name, opts) {
96432
97190
  const target = resolveTarget(opts);
96433
97191
  return evolve("hook", name, {
96434
97192
  target,
96435
97193
  from: opts.from,
96436
- proposeOnly: opts.proposeOnly,
96437
- acceptId: opts.accept,
96438
- rejectId: opts.reject,
96439
97194
  json: opts.json,
96440
- ingest: opts.ingest,
96441
- margin: opts.margin
97195
+ analyze: opts.analyze
96442
97196
  });
96443
97197
  }
96444
97198
  async function emitHook(name, opts) {
@@ -96466,11 +97220,6 @@ async function emitHook(name, opts) {
96466
97220
  }
96467
97221
  return { count: hookResult.count, message: hookResult.message };
96468
97222
  }
96469
- async function hookScaffold(opts) {
96470
- const createdPath = await scaffoldHook(opts.name, opts);
96471
- echo(`Created: ${createdPath}`);
96472
- return;
96473
- }
96474
97223
  async function hookValidate(opts) {
96475
97224
  const result = await validateHook(opts.nameOrPath, opts);
96476
97225
  const output2 = formatValidationResult(result, opts.json);
@@ -96503,19 +97252,16 @@ async function hookEmit(opts) {
96503
97252
  }
96504
97253
  function registerHook(program2) {
96505
97254
  const cmd = program2.command("hook").description("Manage hook definitions");
96506
- addScaffoldOptions(cmd.command("scaffold <name>").description("Create a new hook from template")).action(async (name, opts) => {
96507
- await runOperation(() => hookScaffold({ name, ...opts }));
96508
- });
96509
97255
  addStrictOption(addTargetOption(addJsonOption(cmd.command("validate <nameOrPath>").description("Validate a hook file")))).action(async (nameOrPath, opts) => {
96510
97256
  await runOperation(() => hookValidate({ nameOrPath, ...opts }));
96511
97257
  });
96512
97258
  addEvaluateOptions(addSaveOption(addTargetOption(addJsonOption(cmd.command("evaluate <nameOrPath>").description("Evaluate hook quality (use --rubric --json for envelope, --ingest --save to persist scores)"))))).action(async (nameOrPath, opts) => {
96513
97259
  await runOperation(() => hookEvaluate({ nameOrPath, ...opts }));
96514
97260
  });
96515
- addSaveOption(addTargetOption(addAutoOption(cmd.command("refine <nameOrPath>").description("Evaluate and auto-fix a hook")))).action(async (nameOrPath, opts) => {
97261
+ addHookRefineOptions(cmd.command("refine <nameOrPath>").description("Surface hook quality findings as suggestions (suggest-only, no auto-apply)")).action(async (nameOrPath, opts) => {
96516
97262
  await runOperation(() => hookRefine({ nameOrPath, ...opts }));
96517
97263
  });
96518
- addEvolveOptions(cmd.command("evolve <name>").description("Longitudinal improvement from evaluation history")).action(async (name, opts) => {
97264
+ addHookEvolveOptions(cmd.command("evolve <name>").description("Analyze hook evaluation trends (analyze-only, no apply)")).action(async (name, opts) => {
96519
97265
  await runOperation(() => hookEvolve({ name, ...opts }));
96520
97266
  });
96521
97267
  addTargetOption(cmd.command("emit <name>").description("Emit a hook definition to a single target agent (thin wrapper over the install hook path)")).option("--global", "Install to user-level global directory (default)", true).option("--dry-run", "Preview without writing files", false).action(async (name, opts) => {
@@ -96531,7 +97277,9 @@ async function magentScaffold(opts) {
96531
97277
  description: opts.description,
96532
97278
  target,
96533
97279
  output: opts.output,
96534
- force: opts.force
97280
+ force: opts.force,
97281
+ template: opts.template,
97282
+ tools: opts.tools
96535
97283
  });
96536
97284
  echo(`Created: ${createdPath}`);
96537
97285
  return;
@@ -96562,7 +97310,7 @@ async function magentEvaluate(opts) {
96562
97310
  }
96563
97311
  async function magentRefine(opts) {
96564
97312
  const target = resolveTarget(opts);
96565
- await refine3("magent", opts.nameOrPath, { target, auto: opts.auto, save: opts.save });
97313
+ await refine3("magent", opts.nameOrPath, { target, auto: opts.auto, save: opts.save, dryRun: opts.dryRun });
96566
97314
  return;
96567
97315
  }
96568
97316
  async function magentEvolve(opts) {
@@ -96575,7 +97323,11 @@ async function magentEvolve(opts) {
96575
97323
  rejectId: opts.reject,
96576
97324
  json: opts.json,
96577
97325
  ingest: opts.ingest,
96578
- margin: opts.margin
97326
+ margin: opts.margin,
97327
+ analyze: opts.analyze,
97328
+ history: opts.history,
97329
+ rollback: opts.rollback,
97330
+ confirm: opts.confirm
96579
97331
  });
96580
97332
  return;
96581
97333
  }
@@ -96605,7 +97357,7 @@ function registerMagent(program2) {
96605
97357
  addEvaluateOptions(addSaveOption(addTargetOption(addJsonOption(cmd.command("evaluate <nameOrPath>").description("Evaluate magent quality (use --rubric --json for envelope, --ingest --save to persist scores)"))))).action(async (nameOrPath, opts) => {
96606
97358
  await handleMagentEvaluate({ nameOrPath, ...opts });
96607
97359
  });
96608
- addSaveOption(addTargetOption(addAutoOption(cmd.command("refine <nameOrPath>").description("Evaluate and auto-fix a magent")))).action(async (nameOrPath, opts) => {
97360
+ addDryRunOption(addSaveOption(addTargetOption(addAutoOption(cmd.command("refine <nameOrPath>").description("Evaluate and auto-fix a magent"))))).action(async (nameOrPath, opts) => {
96609
97361
  await handleMagentRefine({ nameOrPath, ...opts });
96610
97362
  });
96611
97363
  addEvolveOptions(cmd.command("evolve <name>").description("Longitudinal improvement from evaluation history")).action(async (name, opts) => {
@@ -96675,7 +97427,9 @@ async function skillScaffold(opts) {
96675
97427
  description: opts.description,
96676
97428
  target,
96677
97429
  output: opts.output,
96678
- force: opts.force
97430
+ force: opts.force,
97431
+ template: opts.template,
97432
+ tools: opts.tools
96679
97433
  });
96680
97434
  echo(`Created: ${createdPath}`);
96681
97435
  return;
@@ -96695,10 +97449,11 @@ async function skillEvaluate(opts) {
96695
97449
  const report = await evaluate3("skill", opts.nameOrPath, {
96696
97450
  target,
96697
97451
  save: opts.save,
97452
+ history: opts.history,
96698
97453
  ...opts.rubric ? { rubric: opts.rubric } : {},
96699
97454
  ...opts.ingest ? { ingest: opts.ingest } : {}
96700
97455
  });
96701
- if (report) {
97456
+ if (report && !opts.history) {
96702
97457
  const output2 = formatEvaluationReport(report, opts.json);
96703
97458
  echo(`${output2}`);
96704
97459
  }
@@ -96706,7 +97461,7 @@ async function skillEvaluate(opts) {
96706
97461
  }
96707
97462
  async function skillRefine(opts) {
96708
97463
  const target = resolveTarget(opts);
96709
- await refine3("skill", opts.nameOrPath, { target, auto: opts.auto, save: opts.save });
97464
+ await refine3("skill", opts.nameOrPath, { target, auto: opts.auto, save: opts.save, dryRun: opts.dryRun });
96710
97465
  return;
96711
97466
  }
96712
97467
  async function skillEvolve(opts) {
@@ -96719,7 +97474,11 @@ async function skillEvolve(opts) {
96719
97474
  rejectId: opts.reject,
96720
97475
  json: opts.json,
96721
97476
  ingest: opts.ingest,
96722
- margin: opts.margin
97477
+ margin: opts.margin,
97478
+ analyze: opts.analyze,
97479
+ history: opts.history,
97480
+ rollback: opts.rollback,
97481
+ confirm: opts.confirm
96723
97482
  });
96724
97483
  return;
96725
97484
  }
@@ -96770,8 +97529,8 @@ function registerSkill(program2) {
96770
97529
  const cmd = program2.command("skill").description("Manage skill definitions");
96771
97530
  addScaffoldOptions(cmd.command("scaffold <name>").description("Create a new skill from template")).action(handleSkillScaffold);
96772
97531
  addStrictOption(addTargetOption(addJsonOption(cmd.command("validate <nameOrPath>").description("Validate a skill file")))).action(handleSkillValidate);
96773
- addEvaluateOptions(addSaveOption(addTargetOption(addJsonOption(cmd.command("evaluate <nameOrPath>").description("Evaluate skill quality (use --rubric --json for envelope, --ingest --save to persist scores)"))))).action(handleSkillEvaluate);
96774
- addSaveOption(addTargetOption(addAutoOption(cmd.command("refine <nameOrPath>").description("Evaluate and auto-fix a skill")))).action(handleSkillRefine);
97532
+ addEvaluateOptions(addSaveOption(addTargetOption(addJsonOption(cmd.command("evaluate <nameOrPath>").description("Evaluate skill quality (use --rubric --json for envelope, --ingest --save to persist scores)").option("--history", "Show prior evaluation rows from the store"))))).action(handleSkillEvaluate);
97533
+ addDryRunOption(addSaveOption(addTargetOption(addAutoOption(cmd.command("refine <nameOrPath>").description("Evaluate and auto-fix a skill"))))).action(handleSkillRefine);
96775
97534
  addEvolveOptions(cmd.command("evolve <name>").description("Longitudinal improvement from evaluation history")).action(handleSkillEvolve);
96776
97535
  cmd.command("package <name>").description("Package a skill for distribution").option("-o, --output <dir>", "Output directory (default: cwd)").option("--include-companions", "Include companion configs (metadata.openclaw, agents/)").action(handleSkillPackage);
96777
97536
  cmd.command("migrate <sources...>").description("Merge/migrate skills into a destination").option("--refine", "Route through the generation seam (F023) for content refinement").option("--ingest <file>", "Agent-authored proposal JSON (apply through the double-loop gate)").option("-t, --target <agent>", "Target agent platform", "claude").option("--margin <n>", "\u0394-margin gate threshold (default 0.05)", Number.parseFloat, 0.05).action(handleSkillMigrate);