@gobing-ai/superskill 0.1.8 → 0.2.1

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
@@ -9348,6 +9348,11 @@ function resolveContentPath(type, name, opts) {
9348
9348
  const asIs = join(base, name);
9349
9349
  if (existsSync(asIs) && statSync(asIs).isFile())
9350
9350
  return asIs;
9351
+ if (type === "skill") {
9352
+ const skillDirForm = join(base, name, "SKILL.md");
9353
+ if (existsSync(skillDirForm))
9354
+ return skillDirForm;
9355
+ }
9351
9356
  const direct = join(base, `${name}.md`);
9352
9357
  if (existsSync(direct))
9353
9358
  return direct;
@@ -9366,6 +9371,11 @@ function resolveContentPath(type, name, opts) {
9366
9371
  }
9367
9372
  return null;
9368
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
+ }
9369
9379
  var init_identity = () => {};
9370
9380
 
9371
9381
  // ../../packages/core/src/content/paths.ts
@@ -9809,11 +9819,6 @@ function mapPluginToRulesync(pluginPath, pluginName, outputDir) {
9809
9819
  }
9810
9820
  return result;
9811
9821
  }
9812
- function assertSafePathSegment(value, label) {
9813
- if (!value || value === "." || value === ".." || value.includes("/") || value.includes("\\") || value.includes("\x00")) {
9814
- throw new Error(`Invalid ${label} '${value}': must be a single path segment`);
9815
- }
9816
- }
9817
9822
  function deepMergeJsonFile(sourcePath, targetPath) {
9818
9823
  const source = readJsonObject(sourcePath);
9819
9824
  const target = existsSync3(targetPath) ? readJsonObject(targetPath) : {};
@@ -9853,6 +9858,7 @@ function copyAndRewriteDirectory(source, destination, pluginName) {
9853
9858
  }
9854
9859
  }
9855
9860
  var init_mapper = __esm(() => {
9861
+ init_identity();
9856
9862
  init_adapt_command();
9857
9863
  init_adapt_subagent();
9858
9864
  init_rewrite_references();
@@ -14084,45 +14090,106 @@ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as rea
14084
14090
  import { homedir as homedir2 } from "os";
14085
14091
  import { join as join6 } from "path";
14086
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
+ }
14087
14104
  function substituteVars(template, vars) {
14088
- 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));
14106
+ }
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);
14089
14128
  }
14090
- function resolveTemplate(type) {
14129
+ function resolveTemplate(type, tier) {
14091
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
+ }
14092
14145
  const userPath = join6(homeDir, ".superskill", "templates", type, "default.md");
14093
14146
  if (existsSync7(userPath)) {
14094
14147
  return readFileSync5(userPath, "utf-8");
14095
14148
  }
14096
- const devPath = join6(import.meta.dir, "..", "..", "..", "..", "apps", "cli", "src", "templates", type, "default.md");
14097
- if (existsSync7(devPath)) {
14098
- 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
+ }
14099
14154
  }
14100
- const prodPath = join6(import.meta.dir, "..", "templates", type, "default.md");
14101
- return readFileSync5(prodPath, "utf-8");
14155
+ throw new Error(`No built-in default template found for type "${type}".`);
14102
14156
  }
14103
14157
  async function scaffold(type, name, opts = {}) {
14104
- const validTypes = ["skill", "command", "agent", "hook", "magent"];
14158
+ const validTypes = ["skill", "command", "agent", "magent"];
14105
14159
  if (!validTypes.includes(type)) {
14106
14160
  throw new Error(`Unknown content type: "${type}". Expected one of: ${validTypes.join(", ")}`);
14107
14161
  }
14108
- const template = resolveTemplate(type);
14109
- const content = substituteVars(template, {
14162
+ assertSafePathSegment(name, "content name");
14163
+ const template = resolveTemplate(type, opts.template);
14164
+ let content = substituteVars(template, {
14110
14165
  name,
14111
14166
  description: opts.description ?? "",
14112
14167
  target: opts.target ?? "claude",
14113
- body: ""
14168
+ body: "",
14169
+ tools: parseList(opts.tools)
14114
14170
  });
14171
+ const toolField = type === "agent" ? "tools" : "allowed-tools";
14172
+ content = mergeFrontmatterList(content, toolField, parseList(opts.tools));
14115
14173
  const outDir = opts.output ?? cwd4();
14116
14174
  mkdirSync4(outDir, { recursive: true });
14117
- 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
+ }
14118
14179
  if (existsSync7(filePath) && !opts.force) {
14119
14180
  throw new Error(`${filePath} already exists \u2014 pass --force to overwrite`);
14120
14181
  }
14121
14182
  writeFileSync3(filePath, content, "utf-8");
14122
14183
  return filePath;
14123
14184
  }
14124
- var PLACEHOLDER_NAME = "<!-- NAME -->", PLACEHOLDER_DESCRIPTION = "<!-- DESCRIPTION -->", PLACEHOLDER_TARGET = "<!-- TARGET -->", PLACEHOLDER_BODY = "<!-- BODY -->";
14125
- 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
+ });
14126
14193
 
14127
14194
  // ../../packages/core/src/quality/types.ts
14128
14195
  function computeAggregate(dimensions) {
@@ -14331,8 +14398,16 @@ function scorePatternMatchQuality(entries) {
14331
14398
  specificMatchers++;
14332
14399
  if (e.timeout !== undefined && e.timeout > 0)
14333
14400
  hasTimeout++;
14334
- if (/\$\{CLAUDE_PLUGIN_ROOT\}/.test(e.command) || !/^\//.test(e.command.trim()))
14401
+ if (/\$\{CLAUDE_PLUGIN_ROOT\}/.test(e.command)) {
14335
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
+ }
14336
14411
  }
14337
14412
  const n = entries.length || 1;
14338
14413
  const matcherScore = specificMatchers / n;
@@ -14512,10 +14587,23 @@ function _validateContent(type, content, opts) {
14512
14587
  const findings = [];
14513
14588
  let data;
14514
14589
  let body;
14590
+ const frontmatterPresent = /^---\s*$/m.test(content);
14515
14591
  try {
14516
- const parsed = parseFrontmatter(content);
14517
- data = parsed.data;
14518
- 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
+ }
14519
14607
  } catch (e) {
14520
14608
  const msg = e instanceof Error ? e.message : String(e);
14521
14609
  findings.push({ severity: "error", field: "frontmatter", message: `YAML parse error: ${msg}` });
@@ -14661,7 +14749,7 @@ function checkLinkValidity(type, data, referenceChecker) {
14661
14749
  }
14662
14750
  return findings;
14663
14751
  }
14664
- function strictChecks(_type, data, body) {
14752
+ function strictChecks(type, data, body) {
14665
14753
  const findings = [];
14666
14754
  const desc = data.description;
14667
14755
  if (typeof desc === "string" && desc.length < 40) {
@@ -14698,6 +14786,22 @@ function strictChecks(_type, data, body) {
14698
14786
  });
14699
14787
  }
14700
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
+ }
14701
14805
  return findings;
14702
14806
  }
14703
14807
  function formatValidationResult(result, json) {
@@ -14716,7 +14820,7 @@ function sentinelResult(message) {
14716
14820
  findings: [{ severity: "error", field: "_file", message }]
14717
14821
  };
14718
14822
  }
14719
- var MODEL_ALIASES, FIELD_TYPES, DEPRECATED_FIELDS;
14823
+ var MODEL_ALIASES, FIELD_TYPES, DEPRECATED_FIELDS, KNOWN_OPTIONAL;
14720
14824
  var init_validate = __esm(() => {
14721
14825
  init_frontmatter();
14722
14826
  init_identity();
@@ -14757,12 +14861,13 @@ var init_validate = __esm(() => {
14757
14861
  author: "No longer used; remove.",
14758
14862
  version: "Version is derived from the source repository."
14759
14863
  };
14760
- });
14761
-
14762
- // ../../packages/core/src/pipeline/pi-subagent.ts
14763
- var init_pi_subagent = __esm(() => {
14764
- init_frontmatter();
14765
- init_pi_tools();
14864
+ KNOWN_OPTIONAL = {
14865
+ skill: ["license", "metadata"],
14866
+ command: ["argument-hint", "allowed-tools", "target"],
14867
+ agent: [],
14868
+ hook: [],
14869
+ magent: []
14870
+ };
14766
14871
  });
14767
14872
 
14768
14873
  // ../../node_modules/.bun/@logtape+logtape@2.1.5/node_modules/@logtape/logtape/dist/context.js
@@ -89186,7 +89291,6 @@ var init_src = __esm(() => {
89186
89291
  init_validate();
89187
89292
  init_adapt_command();
89188
89293
  init_adapt_subagent();
89189
- init_pi_subagent();
89190
89294
  init_pi_tools();
89191
89295
  init_rewrite_references();
89192
89296
  init_slash_command2();
@@ -94980,7 +95084,22 @@ async function evaluate3(type, nameOrPath, opts) {
94980
95084
  report.content = resolveContentName(resolvedPath);
94981
95085
  applyRubricWeightingAndVerdict(type, report, opts);
94982
95086
  if (opts?.save) {
94983
- 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
+ }
94984
95103
  }
94985
95104
  return report;
94986
95105
  }
@@ -95087,31 +95206,26 @@ async function ingestScores(type, resolvedPath, resolvedTarget, opts) {
95087
95206
  dimensions
95088
95207
  };
95089
95208
  if (opts.save) {
95090
- 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
+ }
95091
95226
  }
95092
95227
  return report;
95093
95228
  }
95094
- async function persistEvaluation(type, resolvedPath, resolvedTarget, report, opts, scorer, rubricVersion) {
95095
- try {
95096
- const fileHash = hashContent(resolvedPath);
95097
- const adapter = opts.adapter ?? await openStore();
95098
- const dao = new EvaluationDao(adapter);
95099
- await dao.insertEvaluation({
95100
- content_type: type,
95101
- content_name: resolveContentName(resolvedPath),
95102
- target_agent: resolvedTarget,
95103
- operation: opts.operation ?? "evaluate",
95104
- aggregate: report.aggregate,
95105
- dimensions: report.dimensions,
95106
- file_hash: fileHash,
95107
- scorer: scorer ?? "heuristic",
95108
- ...rubricVersion !== undefined ? { rubric_version: rubricVersion } : {}
95109
- });
95110
- } catch (err) {
95111
- const msg = err instanceof Error ? err.message : String(err);
95112
- echoError(`Warning: failed to save evaluation: ${msg}`);
95113
- }
95114
- }
95115
95229
  function formatEvaluationReport(report, json3) {
95116
95230
  if (json3) {
95117
95231
  return JSON.stringify(report);
@@ -95235,6 +95349,11 @@ function deserializeProposal(row) {
95235
95349
  init_src();
95236
95350
 
95237
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
+ }
95238
95357
  function computeTrends(evaluations2) {
95239
95358
  if (evaluations2.length < 2)
95240
95359
  return [];
@@ -95296,6 +95415,56 @@ function computeTrends(evaluations2) {
95296
95415
  });
95297
95416
  return trends;
95298
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
+ }
95299
95468
  function generateProposalId(type, _name, existingProposals) {
95300
95469
  const date9 = new Date().toISOString().slice(0, 10);
95301
95470
  const seq = String(existingProposals.length + 1).padStart(3, "0");
@@ -95325,8 +95494,14 @@ function computeAnchorHash(anchor) {
95325
95494
  return hasher.digest("hex").slice(0, 16);
95326
95495
  }
95327
95496
  function computeBaselineAnchorHash(content) {
95328
- const parsed = parseFrontmatter(content);
95329
- const frontmatter2 = parsed.data;
95497
+ let frontmatter2 = {};
95498
+ if (hasFrontmatter(content)) {
95499
+ try {
95500
+ frontmatter2 = parseFrontmatter(content).data;
95501
+ } catch {
95502
+ frontmatter2 = {};
95503
+ }
95504
+ }
95330
95505
  return computeAnchorHash({
95331
95506
  frontmatter: frontmatter2,
95332
95507
  rubric_criteria: "",
@@ -95372,8 +95547,14 @@ async function runGate(input) {
95372
95547
  }
95373
95548
  function emitGenerationEnvelope(type, resolvedPath, content, baseline, trends) {
95374
95549
  const rubric2 = loadRubric(type);
95375
- const parsed = parseFrontmatter(content);
95376
- const frontmatter2 = parsed.data;
95550
+ let frontmatter2 = {};
95551
+ if (hasFrontmatter(content)) {
95552
+ try {
95553
+ frontmatter2 = parseFrontmatter(content).data;
95554
+ } catch {
95555
+ frontmatter2 = {};
95556
+ }
95557
+ }
95377
95558
  const description = frontmatter2.description;
95378
95559
  const negativeConstraints = extractNegativeConstraints(description);
95379
95560
  const contentName = resolveContentName(resolvedPath);
@@ -95467,6 +95648,9 @@ async function ingestProposal(db2, type, name, resolvedPath, ingestPath, opts, b
95467
95648
  ingestedAnchorHash: parsed.anchor_hash,
95468
95649
  skeptic: parsed.skeptic
95469
95650
  });
95651
+ if (!verdict.rejected && verdict.backupPath) {
95652
+ await persistVersionSnapshot(verdict.backupPath, resolvedPath, proposalId);
95653
+ }
95470
95654
  return {
95471
95655
  baselineScore,
95472
95656
  postScore: verdict.postScore,
@@ -95503,8 +95687,8 @@ async function stepAnalyze(db2, type, name, opts) {
95503
95687
  const baselineDate = new Date(newest.created_at).toISOString();
95504
95688
  return { evaluations: evaluations2, trends, baselineScore, baselineDate };
95505
95689
  }
95506
- async function stepPropose(db2, type, name, _report, trends, evaluations2, baselineScore, baselineDate) {
95507
- const changes = [];
95690
+ async function stepPropose(db2, type, name, report, trends, evaluations2, baselineScore, baselineDate, content) {
95691
+ const changes = generateChanges(report, trends, content);
95508
95692
  const proposalDao = new ProposalDao(db2);
95509
95693
  const existingProposals = await proposalDao.getProposals(type, name);
95510
95694
  const proposalId = generateProposalId(type, name, existingProposals);
@@ -95705,12 +95889,58 @@ async function stepVerify(type, name, filePath, baselineScore, proposalDbId, opt
95705
95889
  } catch {
95706
95890
  echoError("Cannot link verify evaluation.");
95707
95891
  }
95708
- if (gate) {
95709
- rmSync3(gate.backupPath, { force: true });
95710
- }
95892
+ const survivingBackupPath = gate?.backupPath;
95711
95893
  const pctStr = baselineScore > 0 ? `, ${delta >= 0 ? "+" : ""}${(delta / baselineScore * 100).toFixed(1)}%` : "";
95712
95894
  echo(`Score: ${baselineScore.toFixed(2)} \u2192 ${postScore.toFixed(2)} (${delta >= 0 ? "+" : ""}${delta.toFixed(2)}${pctStr})`);
95713
- 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;
95714
95944
  }
95715
95945
  async function evolve(type, name, opts) {
95716
95946
  const resolvedPath = resolveContentPath(type, name);
@@ -95719,6 +95949,10 @@ async function evolve(type, name, opts) {
95719
95949
  return { baselineScore: 0, postScore: 0, delta: 0, changesApplied: 0, proposalPath: "" };
95720
95950
  }
95721
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
+ }
95722
95956
  let db2;
95723
95957
  try {
95724
95958
  db2 = await openDb(opts);
@@ -95727,6 +95961,45 @@ async function evolve(type, name, opts) {
95727
95961
  echoError(`Could not open the evaluation store. ${msg}`);
95728
95962
  return { baselineScore: 0, postScore: 0, delta: 0, changesApplied: 0, proposalPath: "" };
95729
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
+ }
95730
96003
  let analysis;
95731
96004
  try {
95732
96005
  analysis = await stepAnalyze(db2, type, contentName, opts);
@@ -95736,6 +96009,10 @@ async function evolve(type, name, opts) {
95736
96009
  return { baselineScore: 0, postScore: 0, delta: 0, changesApplied: 0, proposalPath: "" };
95737
96010
  }
95738
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
+ }
95739
96016
  if (evaluations2.length < 2) {
95740
96017
  echo("Only one evaluation found \u2014 need at least two for trend analysis. Running evaluation-based proposal instead.");
95741
96018
  }
@@ -95793,13 +96070,16 @@ async function evolve(type, name, opts) {
95793
96070
  } catch {
95794
96071
  acceptedFromStore = [];
95795
96072
  }
95796
- const backupPath = await backupFile(resolvedPath);
96073
+ const backupPath2 = await backupFile(resolvedPath);
95797
96074
  const appliedCount = await stepApply(acceptedFromStore, resolvedPath, target.id, db2);
95798
96075
  const verdict = await stepVerify(type, contentName, resolvedPath, baselineScore, target.id, opts, db2, {
95799
- backupPath,
96076
+ backupPath: backupPath2,
95800
96077
  ingestedAnchorHash: storedAnchorHash,
95801
96078
  skeptic: storedSkeptic
95802
96079
  });
96080
+ if (!verdict.rejected && verdict.backupPath) {
96081
+ await persistVersionSnapshot(verdict.backupPath, resolvedPath, opts.acceptId);
96082
+ }
95803
96083
  return {
95804
96084
  baselineScore,
95805
96085
  postScore: verdict.postScore,
@@ -95809,20 +96089,24 @@ async function evolve(type, name, opts) {
95809
96089
  ...verdict.rejected ? { rejected: true, rejectionReason: verdict.reason } : {}
95810
96090
  };
95811
96091
  }
95812
- 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);
95813
96094
  if (opts?.proposeOnly) {
95814
96095
  echo(`Proposal written to: ${proposalPath}`);
95815
96096
  return { baselineScore, postScore: baselineScore, delta: 0, changesApplied: 0, proposalPath };
95816
96097
  }
95817
96098
  const { accepted: acceptedChanges } = await interactiveReview(changes, trends);
96099
+ const backupPath = await backupFile(resolvedPath);
95818
96100
  const changesApplied = await stepApply(acceptedChanges, resolvedPath, proposalDbId, db2);
95819
96101
  const { postScore, delta } = await stepVerify(type, contentName, resolvedPath, baselineScore, proposalDbId, opts, db2);
96102
+ await persistVersionSnapshot(backupPath, resolvedPath, proposalId);
95820
96103
  return { baselineScore, postScore, delta, changesApplied, proposalPath };
95821
96104
  }
95822
96105
 
95823
96106
  // src/operations/refine.ts
95824
96107
  init_src();
95825
96108
  init_dist();
96109
+ init_db2();
95826
96110
  import { createInterface as createInterface2 } from "readline";
95827
96111
  class RefineAbortedError extends Error {
95828
96112
  constructor(message = "User quit interactive mode") {
@@ -95830,6 +96114,11 @@ class RefineAbortedError extends Error {
95830
96114
  this.name = "RefineAbortedError";
95831
96115
  }
95832
96116
  }
96117
+ function isHookApplyCapableOpt2(opts) {
96118
+ if (!opts)
96119
+ return false;
96120
+ return Boolean(opts.auto || opts.save);
96121
+ }
95833
96122
  function classifyFix(finding) {
95834
96123
  if (finding.severity === "error") {
95835
96124
  return "auto-apply";
@@ -95842,17 +96131,59 @@ function classifyFix(finding) {
95842
96131
  }
95843
96132
  return "auto-apply";
95844
96133
  }
95845
- function getDefaultForField(field) {
95846
- const DEFAULTS = {
95847
- name: "TODO",
95848
- description: "TODO",
95849
- model: "default"
95850
- };
95851
- 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, "");
95852
96180
  }
95853
96181
  function generateAutoChange(finding, content) {
95854
96182
  if (finding.message.toLowerCase().includes("missing")) {
95855
- 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 };
95856
96187
  }
95857
96188
  if (finding.message.includes("must be an array")) {
95858
96189
  let currentValue;
@@ -95998,30 +96329,30 @@ function applyAutoFixes(findings, content) {
95998
96329
  async function refine3(type, nameOrPath, opts) {
95999
96330
  const resolvedPath = resolveContentPath(type, nameOrPath);
96000
96331
  const resolvedTarget = opts?.target ?? "claude";
96001
- 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, {
96002
96338
  target: resolvedTarget,
96003
96339
  strict: opts?.auto === true
96004
96340
  });
96005
- if (!validation.valid) {
96006
- for (const f of validation.findings) {
96007
- if (f.severity === "error") {
96008
- echoError(`[ERROR] ${f.field}: ${f.message}`);
96009
- }
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;
96010
96348
  }
96011
- echoError("Fix validation errors before refining.");
96012
- return { preScore: 0, postScore: 0, delta: 0, fixesApplied: [], fixesSkipped: [] };
96013
- }
96014
- let preScore;
96015
- let preDimensions;
96349
+ } catch {}
96350
+ let content;
96016
96351
  try {
96017
- const report = await evaluate3(type, resolvedPath ?? nameOrPath, { target: resolvedTarget });
96018
- if (!report)
96019
- throw new Error("evaluate returned null in heuristic mode");
96020
- preScore = report.aggregate;
96021
- preDimensions = report.dimensions;
96352
+ content = await Bun.file(filePath).text();
96022
96353
  } catch {
96023
- echoError("Cannot evaluate: file not found or unreadable.");
96024
- 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: [] };
96025
96356
  }
96026
96357
  const findings = [];
96027
96358
  for (const f of validation.findings) {
@@ -96029,26 +96360,23 @@ async function refine3(type, nameOrPath, opts) {
96029
96360
  }
96030
96361
  for (const [dimName, dimScore] of Object.entries(preDimensions)) {
96031
96362
  if (dimScore.score < 0.7 && dimScore.note?.trim()) {
96032
- const finding = {
96033
- severity: "warning",
96034
- field: dimName,
96035
- message: dimScore.note
96036
- };
96037
- findings.push({ finding, strategy: "suggest" });
96363
+ findings.push({
96364
+ finding: { severity: "warning", field: dimName, message: dimScore.note },
96365
+ strategy: "suggest"
96366
+ });
96038
96367
  }
96039
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
+ }
96040
96375
  if (findings.length === 0) {
96041
96376
  echo(`No issues found. Score: ${preScore.toFixed(2)}`);
96042
96377
  return { preScore, postScore: preScore, delta: 0, fixesApplied: [], fixesSkipped: [] };
96043
96378
  }
96044
- let content;
96045
- try {
96046
- content = await Bun.file(resolvedPath ?? nameOrPath).text();
96047
- } catch {
96048
- echoError("Cannot read content file for editing.");
96049
- return { preScore, postScore: preScore, delta: 0, fixesApplied: [], fixesSkipped: [] };
96050
- }
96051
- const backupPath = await backupFile(resolvedPath ?? nameOrPath);
96379
+ const backupPath = await backupFile(filePath);
96052
96380
  let fixesApplied = [];
96053
96381
  let fixesSkipped = [];
96054
96382
  try {
@@ -96057,10 +96385,10 @@ async function refine3(type, nameOrPath, opts) {
96057
96385
  fixesApplied = result.fixesApplied;
96058
96386
  fixesSkipped = result.fixesSkipped;
96059
96387
  if (fixesApplied.length > 0) {
96060
- await Bun.write(resolvedPath ?? nameOrPath, result.content);
96388
+ await Bun.write(filePath, result.content);
96061
96389
  }
96062
96390
  } else {
96063
- const result = await runInteractive(findings, content, resolvedPath ?? nameOrPath, backupPath);
96391
+ const result = await runInteractive(findings, content, filePath, backupPath);
96064
96392
  fixesApplied = result.fixesApplied;
96065
96393
  fixesSkipped = result.fixesSkipped;
96066
96394
  }
@@ -96070,9 +96398,24 @@ async function refine3(type, nameOrPath, opts) {
96070
96398
  }
96071
96399
  throw err;
96072
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
+ }
96073
96415
  let postScore;
96416
+ let postReport = null;
96074
96417
  try {
96075
- const postReport = await evaluate3(type, resolvedPath ?? nameOrPath, { target: resolvedTarget });
96418
+ postReport = await evaluate3(type, filePath, { target: resolvedTarget });
96076
96419
  if (!postReport)
96077
96420
  throw new Error("evaluate returned null in heuristic mode");
96078
96421
  postScore = postReport.aggregate;
@@ -96080,6 +96423,14 @@ async function refine3(type, nameOrPath, opts) {
96080
96423
  echoError("Cannot re-evaluate after fixes.");
96081
96424
  return { preScore, postScore: preScore, delta: 0, fixesApplied, fixesSkipped };
96082
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
+ }
96083
96434
  const delta = postScore - preScore;
96084
96435
  if (delta === 0 && preScore === postScore) {
96085
96436
  echo(`Score: ${preScore.toFixed(2)} (no change)`);
@@ -96087,12 +96438,20 @@ async function refine3(type, nameOrPath, opts) {
96087
96438
  const pctStr = preScore > 0 ? `, +${(delta / preScore * 100).toFixed(1)}%` : "";
96088
96439
  echo(`Score: ${preScore.toFixed(2)} \u2192 ${postScore.toFixed(2)} (${delta >= 0 ? "+" : ""}${delta.toFixed(2)}${pctStr})`);
96089
96440
  }
96090
- if (opts?.save) {
96441
+ if (opts?.save && postReport) {
96091
96442
  try {
96092
- await evaluate3(type, resolvedPath ?? nameOrPath, {
96093
- target: resolvedTarget,
96094
- save: true,
96095
- 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"
96096
96455
  });
96097
96456
  } catch {
96098
96457
  echoError("Warning: failed to save evaluation results.");
@@ -96100,6 +96459,48 @@ async function refine3(type, nameOrPath, opts) {
96100
96459
  }
96101
96460
  return { preScore, postScore, delta, fixesApplied, fixesSkipped };
96102
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
+ }
96103
96504
 
96104
96505
  // src/operations/scaffold.ts
96105
96506
  init_src();
@@ -96111,10 +96512,16 @@ function addTargetOption(cmd) {
96111
96512
  return cmd.option("-t, --target <agent>", "Target agent platform", "claude");
96112
96513
  }
96113
96514
  function addScaffoldOptions(cmd) {
96114
- 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");
96115
96516
  }
96116
96517
  function addEvolveOptions(cmd) {
96117
- 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");
96118
96525
  }
96119
96526
  function addJsonOption(cmd) {
96120
96527
  return cmd.option("--json", "Output machine-readable JSON");
@@ -96131,6 +96538,9 @@ function addStrictOption(cmd) {
96131
96538
  function addAutoOption(cmd) {
96132
96539
  return cmd.option("--auto", "Apply low-risk fixes automatically");
96133
96540
  }
96541
+ function addDryRunOption(cmd) {
96542
+ return cmd.option("--dry-run", "Preview classified fixes and projected delta without writing");
96543
+ }
96134
96544
  function resolveTarget(opts) {
96135
96545
  const raw = opts.target || "claude";
96136
96546
  if (!TARGETS.includes(raw)) {
@@ -96164,7 +96574,9 @@ async function agentScaffold(opts) {
96164
96574
  description: opts.description,
96165
96575
  target,
96166
96576
  output: opts.output,
96167
- force: opts.force
96577
+ force: opts.force,
96578
+ template: opts.template,
96579
+ tools: opts.tools
96168
96580
  });
96169
96581
  echo(`Created: ${createdPath}`);
96170
96582
  return;
@@ -96195,7 +96607,7 @@ async function agentEvaluate(opts) {
96195
96607
  }
96196
96608
  async function agentRefine(opts) {
96197
96609
  const target = resolveTarget(opts);
96198
- 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 });
96199
96611
  return;
96200
96612
  }
96201
96613
  async function agentEvolve(opts) {
@@ -96208,7 +96620,11 @@ async function agentEvolve(opts) {
96208
96620
  rejectId: opts.reject,
96209
96621
  json: opts.json,
96210
96622
  ingest: opts.ingest,
96211
- margin: opts.margin
96623
+ margin: opts.margin,
96624
+ analyze: opts.analyze,
96625
+ history: opts.history,
96626
+ rollback: opts.rollback,
96627
+ confirm: opts.confirm
96212
96628
  });
96213
96629
  return;
96214
96630
  }
@@ -96238,7 +96654,7 @@ function registerAgent(program2) {
96238
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) => {
96239
96655
  await handleAgentEvaluate({ nameOrPath, ...opts });
96240
96656
  });
96241
- 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) => {
96242
96658
  await handleAgentRefine({ nameOrPath, ...opts });
96243
96659
  });
96244
96660
  addEvolveOptions(cmd.command("evolve <name>").description("Longitudinal improvement from evaluation history")).action(async (name, opts) => {
@@ -96254,7 +96670,9 @@ async function commandScaffold(opts) {
96254
96670
  description: opts.description,
96255
96671
  target,
96256
96672
  output: opts.output,
96257
- force: opts.force
96673
+ force: opts.force,
96674
+ template: opts.template,
96675
+ tools: opts.tools
96258
96676
  });
96259
96677
  echo(`Created: ${createdPath}`);
96260
96678
  return;
@@ -96285,7 +96703,7 @@ async function commandEvaluate(opts) {
96285
96703
  }
96286
96704
  async function commandRefine(opts) {
96287
96705
  const target = resolveTarget(opts);
96288
- 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 });
96289
96707
  return;
96290
96708
  }
96291
96709
  async function commandEvolve(opts) {
@@ -96298,7 +96716,11 @@ async function commandEvolve(opts) {
96298
96716
  rejectId: opts.reject,
96299
96717
  json: opts.json,
96300
96718
  ingest: opts.ingest,
96301
- margin: opts.margin
96719
+ margin: opts.margin,
96720
+ analyze: opts.analyze,
96721
+ history: opts.history,
96722
+ rollback: opts.rollback,
96723
+ confirm: opts.confirm
96302
96724
  });
96303
96725
  return;
96304
96726
  }
@@ -96328,7 +96750,7 @@ function registerCommand(program2) {
96328
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) => {
96329
96751
  await handleCommandEvaluate({ nameOrPath, ...opts });
96330
96752
  });
96331
- 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) => {
96332
96754
  await handleCommandRefine({ nameOrPath, ...opts });
96333
96755
  });
96334
96756
  addEvolveOptions(cmd.command("evolve <name>").description("Longitudinal improvement from evaluation history")).action(async (name, opts) => {
@@ -96747,15 +97169,6 @@ function copyDirectory(source, destination, options2 = {}) {
96747
97169
  }
96748
97170
 
96749
97171
  // src/commands/hook.ts
96750
- async function scaffoldHook(name, opts) {
96751
- const target = resolveTarget(opts);
96752
- return scaffold("hook", name, {
96753
- description: opts.description,
96754
- target,
96755
- output: opts.output,
96756
- force: opts.force
96757
- });
96758
- }
96759
97172
  async function validateHook(nameOrPath, opts) {
96760
97173
  const target = resolveTarget(opts);
96761
97174
  return validate("hook", nameOrPath, { target, strict: opts.strict });
@@ -96771,19 +97184,15 @@ async function evaluateHook2(nameOrPath, opts) {
96771
97184
  }
96772
97185
  async function refineHook(nameOrPath, opts) {
96773
97186
  const target = resolveTarget(opts);
96774
- return refine3("hook", nameOrPath, { target, auto: opts.auto, save: opts.save });
97187
+ return refine3("hook", nameOrPath, { target, dryRun: opts.dryRun });
96775
97188
  }
96776
97189
  async function evolveHook(name, opts) {
96777
97190
  const target = resolveTarget(opts);
96778
97191
  return evolve("hook", name, {
96779
97192
  target,
96780
97193
  from: opts.from,
96781
- proposeOnly: opts.proposeOnly,
96782
- acceptId: opts.accept,
96783
- rejectId: opts.reject,
96784
97194
  json: opts.json,
96785
- ingest: opts.ingest,
96786
- margin: opts.margin
97195
+ analyze: opts.analyze
96787
97196
  });
96788
97197
  }
96789
97198
  async function emitHook(name, opts) {
@@ -96811,11 +97220,6 @@ async function emitHook(name, opts) {
96811
97220
  }
96812
97221
  return { count: hookResult.count, message: hookResult.message };
96813
97222
  }
96814
- async function hookScaffold(opts) {
96815
- const createdPath = await scaffoldHook(opts.name, opts);
96816
- echo(`Created: ${createdPath}`);
96817
- return;
96818
- }
96819
97223
  async function hookValidate(opts) {
96820
97224
  const result = await validateHook(opts.nameOrPath, opts);
96821
97225
  const output2 = formatValidationResult(result, opts.json);
@@ -96848,19 +97252,16 @@ async function hookEmit(opts) {
96848
97252
  }
96849
97253
  function registerHook(program2) {
96850
97254
  const cmd = program2.command("hook").description("Manage hook definitions");
96851
- addScaffoldOptions(cmd.command("scaffold <name>").description("Create a new hook from template")).action(async (name, opts) => {
96852
- await runOperation(() => hookScaffold({ name, ...opts }));
96853
- });
96854
97255
  addStrictOption(addTargetOption(addJsonOption(cmd.command("validate <nameOrPath>").description("Validate a hook file")))).action(async (nameOrPath, opts) => {
96855
97256
  await runOperation(() => hookValidate({ nameOrPath, ...opts }));
96856
97257
  });
96857
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) => {
96858
97259
  await runOperation(() => hookEvaluate({ nameOrPath, ...opts }));
96859
97260
  });
96860
- 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) => {
96861
97262
  await runOperation(() => hookRefine({ nameOrPath, ...opts }));
96862
97263
  });
96863
- 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) => {
96864
97265
  await runOperation(() => hookEvolve({ name, ...opts }));
96865
97266
  });
96866
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) => {
@@ -96876,7 +97277,9 @@ async function magentScaffold(opts) {
96876
97277
  description: opts.description,
96877
97278
  target,
96878
97279
  output: opts.output,
96879
- force: opts.force
97280
+ force: opts.force,
97281
+ template: opts.template,
97282
+ tools: opts.tools
96880
97283
  });
96881
97284
  echo(`Created: ${createdPath}`);
96882
97285
  return;
@@ -96907,7 +97310,7 @@ async function magentEvaluate(opts) {
96907
97310
  }
96908
97311
  async function magentRefine(opts) {
96909
97312
  const target = resolveTarget(opts);
96910
- 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 });
96911
97314
  return;
96912
97315
  }
96913
97316
  async function magentEvolve(opts) {
@@ -96920,7 +97323,11 @@ async function magentEvolve(opts) {
96920
97323
  rejectId: opts.reject,
96921
97324
  json: opts.json,
96922
97325
  ingest: opts.ingest,
96923
- margin: opts.margin
97326
+ margin: opts.margin,
97327
+ analyze: opts.analyze,
97328
+ history: opts.history,
97329
+ rollback: opts.rollback,
97330
+ confirm: opts.confirm
96924
97331
  });
96925
97332
  return;
96926
97333
  }
@@ -96950,7 +97357,7 @@ function registerMagent(program2) {
96950
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) => {
96951
97358
  await handleMagentEvaluate({ nameOrPath, ...opts });
96952
97359
  });
96953
- 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) => {
96954
97361
  await handleMagentRefine({ nameOrPath, ...opts });
96955
97362
  });
96956
97363
  addEvolveOptions(cmd.command("evolve <name>").description("Longitudinal improvement from evaluation history")).action(async (name, opts) => {
@@ -97020,7 +97427,9 @@ async function skillScaffold(opts) {
97020
97427
  description: opts.description,
97021
97428
  target,
97022
97429
  output: opts.output,
97023
- force: opts.force
97430
+ force: opts.force,
97431
+ template: opts.template,
97432
+ tools: opts.tools
97024
97433
  });
97025
97434
  echo(`Created: ${createdPath}`);
97026
97435
  return;
@@ -97052,7 +97461,7 @@ async function skillEvaluate(opts) {
97052
97461
  }
97053
97462
  async function skillRefine(opts) {
97054
97463
  const target = resolveTarget(opts);
97055
- 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 });
97056
97465
  return;
97057
97466
  }
97058
97467
  async function skillEvolve(opts) {
@@ -97065,7 +97474,11 @@ async function skillEvolve(opts) {
97065
97474
  rejectId: opts.reject,
97066
97475
  json: opts.json,
97067
97476
  ingest: opts.ingest,
97068
- margin: opts.margin
97477
+ margin: opts.margin,
97478
+ analyze: opts.analyze,
97479
+ history: opts.history,
97480
+ rollback: opts.rollback,
97481
+ confirm: opts.confirm
97069
97482
  });
97070
97483
  return;
97071
97484
  }
@@ -97117,7 +97530,7 @@ function registerSkill(program2) {
97117
97530
  addScaffoldOptions(cmd.command("scaffold <name>").description("Create a new skill from template")).action(handleSkillScaffold);
97118
97531
  addStrictOption(addTargetOption(addJsonOption(cmd.command("validate <nameOrPath>").description("Validate a skill file")))).action(handleSkillValidate);
97119
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);
97120
- addSaveOption(addTargetOption(addAutoOption(cmd.command("refine <nameOrPath>").description("Evaluate and auto-fix a skill")))).action(handleSkillRefine);
97533
+ addDryRunOption(addSaveOption(addTargetOption(addAutoOption(cmd.command("refine <nameOrPath>").description("Evaluate and auto-fix a skill"))))).action(handleSkillRefine);
97121
97534
  addEvolveOptions(cmd.command("evolve <name>").description("Longitudinal improvement from evaluation history")).action(handleSkillEvolve);
97122
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);
97123
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);