skillset 0.13.5 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +1765 -554
  2. package/dist/create.js +1765 -554
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -7024,10 +7024,11 @@ var require_dist = __commonJS((exports) => {
7024
7024
  });
7025
7025
 
7026
7026
  // apps/skillset/src/cli-core.ts
7027
- import { mkdir as mkdir11, writeFile as writeFile11 } from "fs/promises";
7028
- import { basename as basename8, dirname as dirname15, resolve as resolve10 } from "path";
7027
+ import { mkdir as mkdir11, writeFile as writeFile12 } from "fs/promises";
7028
+ import { basename as basename9, dirname as dirname16, resolve as resolve10 } from "path";
7029
7029
 
7030
7030
  // packages/core/src/authoring.ts
7031
+ import { readFile as readFile10, writeFile as writeFile4 } from "fs/promises";
7031
7032
  import { resolve as resolve3, relative as relative11 } from "path";
7032
7033
 
7033
7034
  // packages/core/src/path.ts
@@ -7096,7 +7097,7 @@ var skillsetFeatureRegistry = defineFeatureRegistry([
7096
7097
  id: "activation-probes",
7097
7098
  kind: "workflow",
7098
7099
  renderOwner: "apps/skillset/src/test-runner.ts",
7099
- sourceShape: ".skillset/config.yaml tests.<name>.activation[]",
7100
+ sourceShape: "workspace skillset.yaml tests.<name>.activation[]",
7100
7101
  status: "implemented",
7101
7102
  summary: "Compiles target-aware manual activation probe assets inside isolated test runs.",
7102
7103
  targetSupport: notTargetRuntime(),
@@ -7109,7 +7110,7 @@ var skillsetFeatureRegistry = defineFeatureRegistry([
7109
7110
  id: "changes",
7110
7111
  kind: "change-management",
7111
7112
  renderOwner: "apps/skillset/src/change-entries.ts",
7112
- sourceShape: ".skillset/changes/pending/*.yaml and .skillset/changes/history/*.jsonl",
7113
+ sourceShape: "workspace changes/*.md and changes/history.jsonl",
7113
7114
  status: "implemented",
7114
7115
  summary: "Tracks source-unit changes, evidence hashes, pending entries, and applied history.",
7115
7116
  targetSupport: notTargetRuntime(),
@@ -7146,7 +7147,7 @@ var skillsetFeatureRegistry = defineFeatureRegistry([
7146
7147
  id: "distributions",
7147
7148
  kind: "workflow",
7148
7149
  renderOwner: "packages/core/src/distribution.ts",
7149
- sourceShape: ".skillset/config.yaml distributions",
7150
+ sourceShape: "workspace skillset.yaml distributions",
7150
7151
  status: "implemented",
7151
7152
  summary: "Plans post-build distribution of generated projections without writing or activating runtimes.",
7152
7153
  targetSupport: notTargetRuntime(),
@@ -7550,7 +7551,7 @@ var skillsetFeatureRegistry = defineFeatureRegistry([
7550
7551
  id: "releases",
7551
7552
  kind: "change-management",
7552
7553
  renderOwner: "apps/skillset/src/release.ts",
7553
- sourceShape: ".skillset/releases/*.jsonl, .skillset/release-state.json, generated changelogs",
7554
+ sourceShape: "workspace changes/releases.jsonl, changes/state.json, generated changelogs",
7554
7555
  status: "implemented",
7555
7556
  summary: "Applies pending changes into versions, release history, changelogs, and generated metadata.",
7556
7557
  targetSupport: bothTargets("metadata_only"),
@@ -8190,7 +8191,7 @@ function validateWorkspaceConfigDocument(record, label) {
8190
8191
  rejectTargetsKey(record, label);
8191
8192
  for (const key of Object.keys(record)) {
8192
8193
  if (!WORKSPACE_CONFIG_TOP_LEVEL_KEYS.has(key)) {
8193
- throw new Error(`skillset: unsupported workspace config key ${key} in ${label}; ` + "move source identity and compatibility metadata to .skillset/src/skillset.yaml");
8194
+ throw new Error(`skillset: unsupported workspace config key ${key} in ${label}; ` + "move source identity and compatibility metadata to the workspace manifest");
8194
8195
  }
8195
8196
  }
8196
8197
  }
@@ -9631,7 +9632,7 @@ function sourceWarningDiagnostic(message) {
9631
9632
  }
9632
9633
 
9633
9634
  // packages/core/src/build.ts
9634
- import { mkdir as mkdir3, readdir as readdir4, readFile as readFile8, rm, stat as stat7, writeFile as writeFile3 } from "fs/promises";
9635
+ import { mkdir as mkdir3, readdir as readdir5, readFile as readFile8, rm, stat as stat7, writeFile as writeFile3 } from "fs/promises";
9635
9636
  import { dirname as dirname7, join as join9, relative as relative9 } from "path";
9636
9637
 
9637
9638
  // packages/core/src/render-result-policy.ts
@@ -10183,10 +10184,10 @@ function isRecord(value) {
10183
10184
  }
10184
10185
 
10185
10186
  // packages/core/src/render.ts
10186
- import { readFileSync, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
10187
- import { readdir as readdir2, readFile as readFile5, stat as stat4 } from "fs/promises";
10187
+ import { readFileSync as readFileSync2, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
10188
+ import { readdir as readdir3, readFile as readFile5, stat as stat4 } from "fs/promises";
10188
10189
  import { createHash as createHash3 } from "crypto";
10189
- import { basename, dirname as dirname4, join as join6, relative as relative7 } from "path";
10190
+ import { basename as basename2, dirname as dirname4, join as join6, relative as relative7 } from "path";
10190
10191
 
10191
10192
  // packages/transforms/src/registry.ts
10192
10193
  var transformEntries = new Map;
@@ -11339,29 +11340,71 @@ function isResourceReference(value) {
11339
11340
  }
11340
11341
 
11341
11342
  // packages/core/src/preprocess.ts
11342
- import { readFile as readFile3 } from "fs/promises";
11343
- import { dirname as dirname2, isAbsolute, join as join3, relative as relative5, resolve as resolve2, sep as sep2 } from "path";
11343
+ import { readdirSync as readdirSync2, readFileSync } from "fs";
11344
+ import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
11345
+ import {
11346
+ basename,
11347
+ dirname as dirname2,
11348
+ isAbsolute,
11349
+ join as join3,
11350
+ relative as relative5,
11351
+ resolve as resolve2,
11352
+ sep as sep2
11353
+ } from "path";
11344
11354
  async function preprocessText(content, context) {
11345
11355
  if (isPreprocessDisabled(context.frontmatter)) {
11346
11356
  return normalizeText(content);
11347
11357
  }
11348
- let expanded = normalizeText(content);
11358
+ const escapedTokens = [];
11359
+ let expanded = escapeTripleBraceTokens(normalizeText(content), escapedTokens);
11349
11360
  expanded = await expandPartials(expanded, context);
11350
- expanded = expandVariables(expanded, context);
11351
- return expanded;
11361
+ assertNoRetiredPartialSyntax(expanded, context);
11362
+ expanded = escapeTripleBraceTokens(expanded, escapedTokens);
11363
+ expanded = await expandVariables(expanded, context);
11364
+ return restoreTripleBraceTokens(expanded, escapedTokens);
11365
+ }
11366
+ function formatPreprocessDependency(rootPath, dependency) {
11367
+ if (isTreePreprocessDependency(dependency))
11368
+ return dependency;
11369
+ return normalizePath2(relative5(rootPath, dependency));
11370
+ }
11371
+ function readPreprocessDependencySync(rootPath, dependency) {
11372
+ const tree = parseTreePreprocessDependency(dependency);
11373
+ if (tree !== undefined) {
11374
+ return renderDirectoryTreeSync(join3(rootPath, tree.path), tree.depth);
11375
+ }
11376
+ return readFileSync(join3(rootPath, dependency));
11377
+ }
11378
+ function isTreePreprocessDependency(dependency) {
11379
+ return parseTreePreprocessDependency(dependency) !== undefined;
11380
+ }
11381
+ function parseTreePreprocessDependency(dependency) {
11382
+ if (!dependency.startsWith("tree:"))
11383
+ return;
11384
+ const separator = dependency.lastIndexOf(":");
11385
+ if (separator <= "tree:".length)
11386
+ return;
11387
+ const path = dependency.slice("tree:".length, separator);
11388
+ const depth = Number(dependency.slice(separator + 1));
11389
+ if (!Number.isInteger(depth) || depth < 0 || depth > 8)
11390
+ return;
11391
+ return { depth, path };
11352
11392
  }
11353
11393
  function isPreprocessDisabled(frontmatter) {
11354
11394
  const skillset = frontmatter.skillset;
11355
11395
  return typeof skillset === "object" && skillset !== null && !Array.isArray(skillset) && skillset.preprocess === false;
11356
11396
  }
11357
11397
  async function expandPartials(content, context) {
11358
- const partialPattern = /\{\{\s*>\s*([^}\s]+)\s*\}\}/g;
11398
+ assertNoRetiredPartialSyntax(content, context);
11399
+ const partialPattern = /\{\{\s*([^}\s]+)\s*\}\}/g;
11359
11400
  let expanded = "";
11360
11401
  let cursor = 0;
11361
11402
  for (const match of content.matchAll(partialPattern)) {
11362
11403
  const [token, specifier] = match;
11363
11404
  if (specifier === undefined)
11364
11405
  continue;
11406
+ if (!isPartialSpecifier(specifier))
11407
+ continue;
11365
11408
  expanded += content.slice(cursor, match.index);
11366
11409
  expanded += normalizeText(await readPartial(specifier, context));
11367
11410
  cursor = match.index + token.length;
@@ -11377,21 +11420,227 @@ async function readPartial(specifier, context) {
11377
11420
  throw new Error(`skillset: failed to read partial ${specifier} in ${source2}: ${message}`);
11378
11421
  }
11379
11422
  }
11380
- function expandVariables(content, context) {
11381
- return content.replace(/\{\{\s*([^}\s]+)\s*\}\}/g, (token, key) => {
11382
- if (key.startsWith("this.")) {
11383
- const field = key.slice("this.".length);
11384
- const value2 = readString(context.frontmatter, field);
11385
- if (value2 === undefined) {
11386
- throw new Error(`skillset: missing this.${field} reference in ${relative5(context.rootPath, context.sourcePath)}`);
11387
- }
11388
- return value2;
11423
+ async function expandVariables(content, context) {
11424
+ const variablePattern = /\{\{\s*([^{}]*?)\s*\}\}/g;
11425
+ let expanded = "";
11426
+ let cursor = 0;
11427
+ for (const match of content.matchAll(variablePattern)) {
11428
+ const [token, key] = match;
11429
+ if (key === undefined)
11430
+ continue;
11431
+ expanded += content.slice(cursor, match.index);
11432
+ expanded += await resolveVariable(token, key.trim(), context, {
11433
+ inMarkdownCodeBlock: isMarkdownSource(context.sourcePath) && isInsideFencedCodeBlock(content, match.index),
11434
+ markdown: isMarkdownSource(context.sourcePath)
11435
+ });
11436
+ cursor = match.index + token.length;
11437
+ }
11438
+ return `${expanded}${content.slice(cursor)}`;
11439
+ }
11440
+ async function resolveVariable(token, key, context, renderContext) {
11441
+ if (key.startsWith("this.")) {
11442
+ const field = key.slice("this.".length);
11443
+ const value = readPathValue(context.frontmatter, field);
11444
+ if (value === undefined) {
11445
+ throw new Error(`skillset: missing this.${field} reference in ${relative5(context.rootPath, context.sourcePath)}`);
11389
11446
  }
11390
- const value = context.variables?.[key];
11447
+ return stringifyPreprocessValue(value, `this.${field}`, context, renderContext);
11448
+ }
11449
+ const explicitValue = context.variables?.[key];
11450
+ if (explicitValue !== undefined)
11451
+ return explicitValue;
11452
+ if (key.startsWith("skillset.")) {
11453
+ const value = skillsetVariable(key, context);
11454
+ if (value !== undefined)
11455
+ return value;
11456
+ }
11457
+ if (key.startsWith("parent.")) {
11458
+ const value = await parentVariable(key, context);
11391
11459
  if (value !== undefined)
11392
11460
  return value;
11393
- throw new Error(`skillset: unknown preprocess variable ${token} in ${relative5(context.rootPath, context.sourcePath)}`);
11461
+ }
11462
+ throw new Error(`skillset: unknown preprocess variable ${token} in ${relative5(context.rootPath, context.sourcePath)}`);
11463
+ }
11464
+ function readPathValue(record, path) {
11465
+ let current = record;
11466
+ for (const segment of path.split(".")) {
11467
+ if (segment.length === 0)
11468
+ return;
11469
+ if (!isJsonRecord2(current))
11470
+ return;
11471
+ current = current[segment];
11472
+ }
11473
+ return current;
11474
+ }
11475
+ function stringifyPreprocessValue(value, key, context, renderContext) {
11476
+ if (typeof value === "string")
11477
+ return value;
11478
+ if (typeof value === "number" || typeof value === "boolean")
11479
+ return String(value);
11480
+ if (value === null)
11481
+ return "null";
11482
+ try {
11483
+ const json = JSON.stringify(value);
11484
+ if (isJsonRecord2(value) || Array.isArray(value)) {
11485
+ return renderJsonValue(json, JSON.stringify(value, null, 2), renderContext);
11486
+ }
11487
+ return json;
11488
+ } catch {
11489
+ throw new Error(`skillset: cannot stringify ${key} reference in ${relative5(context.rootPath, context.sourcePath)}`);
11490
+ }
11491
+ }
11492
+ function renderJsonValue(compactJson, prettyJson, renderContext) {
11493
+ if (!renderContext.markdown)
11494
+ return compactJson;
11495
+ if (renderContext.inMarkdownCodeBlock)
11496
+ return prettyJson;
11497
+ return `\`\`\`json
11498
+ ${prettyJson}
11499
+ \`\`\``;
11500
+ }
11501
+ function skillsetVariable(key, context) {
11502
+ if (key === "skillset.source_path") {
11503
+ return normalizePath2(relative5(context.rootPath, context.sourcePath));
11504
+ }
11505
+ if (key === "skillset.source_dir") {
11506
+ return normalizePath2(relative5(context.rootPath, dirname2(context.sourcePath)));
11507
+ }
11508
+ if (key === "skillset.source_root")
11509
+ return normalizePath2(context.sourceRoot);
11510
+ return;
11511
+ }
11512
+ async function parentVariable(key, context) {
11513
+ const parentDir = dirname2(context.sourcePath);
11514
+ if (key === "parent.dir")
11515
+ return normalizePath2(relative5(context.rootPath, parentDir));
11516
+ if (key === "parent.name")
11517
+ return basename(parentDir);
11518
+ if (key === "parent.tree")
11519
+ return renderParentTree(parentDir, 2, context);
11520
+ const parsed = parsePreprocessInvocation(key);
11521
+ if (parsed.name === "parent.tree") {
11522
+ return renderParentTree(parentDir, parseParentTreeDepth(parsed, context), context);
11523
+ }
11524
+ return;
11525
+ }
11526
+ function parsePreprocessInvocation(key) {
11527
+ const [name = "", ...rawArgs] = key.trim().split(/\s+/);
11528
+ const args = new Map;
11529
+ const duplicateArgs = new Set;
11530
+ for (const rawArg of rawArgs) {
11531
+ const separator = rawArg.indexOf(":");
11532
+ if (separator <= 0) {
11533
+ args.set(rawArg, "");
11534
+ continue;
11535
+ }
11536
+ const argName = rawArg.slice(0, separator);
11537
+ if (args.has(argName))
11538
+ duplicateArgs.add(argName);
11539
+ args.set(argName, rawArg.slice(separator + 1));
11540
+ }
11541
+ return { args, duplicateArgs, name };
11542
+ }
11543
+ function parseParentTreeDepth(invocation, context) {
11544
+ const depth = invocation.args.get("depth");
11545
+ if (invocation.args.size !== 1 || invocation.duplicateArgs.size > 0 || depth === undefined || depth.length === 0) {
11546
+ throw new Error(`skillset: parent.tree in ${relative5(context.rootPath, context.sourcePath)} supports only depth:<0-8>`);
11547
+ }
11548
+ const rawDepth = Number(depth);
11549
+ if (!Number.isInteger(rawDepth) || rawDepth < 0 || rawDepth > 8) {
11550
+ throw new Error(`skillset: parent.tree depth in ${relative5(context.rootPath, context.sourcePath)} must be between 0 and 8`);
11551
+ }
11552
+ return rawDepth;
11553
+ }
11554
+ async function renderParentTree(parentDir, depth, context) {
11555
+ context.preprocessDependencies?.add(`tree:${normalizePath2(relative5(context.rootPath, parentDir))}:${depth}`);
11556
+ return renderDirectoryTree(parentDir, depth);
11557
+ }
11558
+ async function renderDirectoryTree(root, depth) {
11559
+ const lines = ["."];
11560
+ if (depth > 0) {
11561
+ lines.push(...await directoryTreeLines(root, depth, ""));
11562
+ }
11563
+ return lines.join(`
11564
+ `);
11565
+ }
11566
+ async function directoryTreeLines(root, depth, indent) {
11567
+ if (depth <= 0)
11568
+ return [];
11569
+ const entries = (await readdir2(root, { withFileTypes: true })).filter((entry) => entry.name !== ".DS_Store").sort((left, right) => {
11570
+ if (left.name < right.name)
11571
+ return -1;
11572
+ if (left.name > right.name)
11573
+ return 1;
11574
+ return 0;
11394
11575
  });
11576
+ const lines = [];
11577
+ for (const entry of entries) {
11578
+ const isDirectory = entry.isDirectory();
11579
+ lines.push(`${indent}- ${entry.name}${isDirectory ? "/" : ""}`);
11580
+ if (isDirectory) {
11581
+ lines.push(...await directoryTreeLines(join3(root, entry.name), depth - 1, `${indent} `));
11582
+ }
11583
+ }
11584
+ return lines;
11585
+ }
11586
+ function renderDirectoryTreeSync(root, depth) {
11587
+ const lines = ["."];
11588
+ if (depth > 0) {
11589
+ lines.push(...directoryTreeLinesSync(root, depth, ""));
11590
+ }
11591
+ return lines.join(`
11592
+ `);
11593
+ }
11594
+ function directoryTreeLinesSync(root, depth, indent) {
11595
+ if (depth <= 0)
11596
+ return [];
11597
+ const entries = readdirSync2(root, { withFileTypes: true }).filter((entry) => entry.name !== ".DS_Store").sort((left, right) => {
11598
+ if (left.name < right.name)
11599
+ return -1;
11600
+ if (left.name > right.name)
11601
+ return 1;
11602
+ return 0;
11603
+ });
11604
+ const lines = [];
11605
+ for (const entry of entries) {
11606
+ const isDirectory = entry.isDirectory();
11607
+ lines.push(`${indent}- ${entry.name}${isDirectory ? "/" : ""}`);
11608
+ if (isDirectory) {
11609
+ lines.push(...directoryTreeLinesSync(join3(root, entry.name), depth - 1, `${indent} `));
11610
+ }
11611
+ }
11612
+ return lines;
11613
+ }
11614
+ function isJsonRecord2(value) {
11615
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11616
+ }
11617
+ function isPartialSpecifier(specifier) {
11618
+ if (specifier.startsWith("this.") || specifier.startsWith("skillset.") || specifier.startsWith("parent.")) {
11619
+ return false;
11620
+ }
11621
+ if (specifier.startsWith("shared:") || specifier.startsWith("root:") || specifier.startsWith("plugin:")) {
11622
+ return true;
11623
+ }
11624
+ return specifier.includes("/") || /^[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+$/.test(specifier);
11625
+ }
11626
+ function assertNoRetiredPartialSyntax(content, context) {
11627
+ if (!/\{\{\s*>/.test(content))
11628
+ return;
11629
+ throw new Error(`skillset: partials in ${relative5(context.rootPath, context.sourcePath)} use {{shared:path.md}}, {{plugin:path.md}}, or {{relative/path.md}} syntax`);
11630
+ }
11631
+ function escapeTripleBraceTokens(content, escapedTokens) {
11632
+ return content.replace(/\{\{\{\s*([^{}]+?)\s*\}\}\}/g, (_token, key) => {
11633
+ const marker = `\x00skillset-escaped-${escapedTokens.length}\x00`;
11634
+ escapedTokens.push(`{{${key.trim()}}}`);
11635
+ return marker;
11636
+ });
11637
+ }
11638
+ function restoreTripleBraceTokens(content, escapedTokens) {
11639
+ let restored = content;
11640
+ for (const [index, value] of escapedTokens.entries()) {
11641
+ restored = restored.replaceAll(`\x00skillset-escaped-${index}\x00`, value);
11642
+ }
11643
+ return restored;
11395
11644
  }
11396
11645
  function resolvePartial(specifier, context) {
11397
11646
  const [scheme, path] = splitSpecifier(specifier);
@@ -11423,6 +11672,35 @@ function normalizeText(content) {
11423
11672
  return content.replaceAll(/\r\n?/g, `
11424
11673
  `);
11425
11674
  }
11675
+ function normalizePath2(path) {
11676
+ return path.replaceAll("\\", "/") || ".";
11677
+ }
11678
+ function isMarkdownSource(path) {
11679
+ return path.endsWith(".md");
11680
+ }
11681
+ function isInsideFencedCodeBlock(content, index) {
11682
+ const before = content.slice(0, index);
11683
+ let fence;
11684
+ for (const line of before.split(`
11685
+ `)) {
11686
+ const match = line.match(/^( {0,3})(`{3,}|~{3,})(.*)$/);
11687
+ if (match === null)
11688
+ continue;
11689
+ const marker = match[2];
11690
+ if (marker === undefined)
11691
+ continue;
11692
+ const char = marker[0];
11693
+ const length = marker.length;
11694
+ if (fence === undefined) {
11695
+ fence = { char, length };
11696
+ continue;
11697
+ }
11698
+ const trailing = match[3] ?? "";
11699
+ if (char === fence.char && length >= fence.length && /^[ \t]*$/.test(trailing))
11700
+ fence = undefined;
11701
+ }
11702
+ return fence !== undefined;
11703
+ }
11426
11704
  function resolveInsideScoped(root, candidate, specifier, context) {
11427
11705
  const resolvedRoot = resolve2(root);
11428
11706
  const resolved = resolve2(resolvedRoot, candidate);
@@ -11452,6 +11730,7 @@ import { dirname as dirname3, join as join5, relative as relative6 } from "path"
11452
11730
  import { readFile as readFile4, stat as stat3 } from "fs/promises";
11453
11731
  import { join as join4 } from "path";
11454
11732
  var HISTORY_FILE = "changes/history.jsonl";
11733
+ var AMENDMENTS_FILE = "changes/amendments.jsonl";
11455
11734
  async function readAppliedChangeRecords(rootPath, options = {}) {
11456
11735
  const sourceDir = options.sourceDir ?? ".skillset";
11457
11736
  const path = join4(sourceDir, HISTORY_FILE).replaceAll("\\", "/");
@@ -11489,7 +11768,8 @@ async function readAppliedChangeRecords(rootPath, options = {}) {
11489
11768
  sourceHashes: readHistoryEvidence(parsed.evidence, scopes)
11490
11769
  });
11491
11770
  }
11492
- return entries.sort((left, right) => compareStrings(left.id, right.id));
11771
+ const amended = await applyHistoryAmendments(rootPath, sourceDir, entries);
11772
+ return [...amended].sort((left, right) => compareStrings(left.id, right.id));
11493
11773
  }
11494
11774
  function groupRef(group) {
11495
11775
  if (group === undefined)
@@ -11522,6 +11802,42 @@ function readHistoryGroup(value) {
11522
11802
  const provider = readString(value, "provider");
11523
11803
  return id === undefined ? undefined : { id, ...provider === undefined ? {} : { provider } };
11524
11804
  }
11805
+ async function applyHistoryAmendments(rootPath, sourceDir, entries) {
11806
+ const amendments = await readHistoryAmendments(rootPath, sourceDir);
11807
+ if (amendments.size === 0)
11808
+ return entries;
11809
+ return entries.map((entry) => {
11810
+ const amendment = amendments.get(entry.id);
11811
+ return amendment === undefined ? entry : { ...entry, reason: amendment.reason };
11812
+ });
11813
+ }
11814
+ async function readHistoryAmendments(rootPath, sourceDir) {
11815
+ const path = join4(sourceDir, AMENDMENTS_FILE).replaceAll("\\", "/");
11816
+ const absolutePath = resolveInside(rootPath, path);
11817
+ if (!await exists2(absolutePath))
11818
+ return new Map;
11819
+ const amendments = new Map;
11820
+ const lines = (await readFile4(absolutePath, "utf8")).split(`
11821
+ `);
11822
+ for (const [index, line] of lines.entries()) {
11823
+ if (line.trim().length === 0)
11824
+ continue;
11825
+ let parsed;
11826
+ try {
11827
+ parsed = JSON.parse(line);
11828
+ } catch {
11829
+ throw new Error(`skillset: invalid JSON in ${path}:${index + 1}`);
11830
+ }
11831
+ if (!isJsonRecord(parsed))
11832
+ throw new Error(`skillset: expected ${path}:${index + 1} to contain a JSON object`);
11833
+ const id = readString(parsed, "id");
11834
+ const reason = readString(parsed, "reason");
11835
+ if (id === undefined || reason === undefined)
11836
+ continue;
11837
+ amendments.set(id, { reason });
11838
+ }
11839
+ return amendments;
11840
+ }
11525
11841
  function readHistoryEvidence(raw, scopes) {
11526
11842
  const evidence = new Map;
11527
11843
  const add = (scope, hash) => {
@@ -12001,13 +12317,13 @@ async function renderPluginSkillFiles(graph, plugin, skill, target, basePath, ou
12001
12317
  const targetSkillFile = join6(targetSkillDir, "SKILL.md");
12002
12318
  const generatedCodexAgentFile = await renderCodexSkillAgentFile(graph, plugin, skill, target, sourceDir, targetSkillDir);
12003
12319
  const generatedCodexToolsFile = renderCodexSkillToolsFile(graph, skill, target, targetSkillDir);
12004
- const generatedCodexRelativeFiles = new Set([generatedCodexAgentFile, generatedCodexToolsFile].filter((file) => file !== undefined).map((file) => relative7(targetSkillDir, file.path)));
12320
+ const generatedCodexRelativeFiles = new Set([generatedCodexAgentFile?.file, generatedCodexToolsFile].filter((file) => file !== undefined).map((file) => relative7(targetSkillDir, file.path)));
12005
12321
  const rendered = [];
12006
12322
  const renderedRelativeFiles = new Set;
12007
12323
  const skillMarkdown = await renderSkillMarkdown(graph, plugin, skill, target);
12008
12324
  pushSkillRenderedFile(rendered, textFile(targetSkillFile, skillMarkdown.content, relative7(graph.rootPath, skill.sourcePath)), targetSkillDir, renderedRelativeFiles, `${skill.sourcePath}.SKILL.md`);
12009
12325
  if (generatedCodexAgentFile !== undefined) {
12010
- pushSkillRenderedFile(rendered, generatedCodexAgentFile, targetSkillDir, renderedRelativeFiles, `${skill.sourcePath}.agents/openai.yaml`);
12326
+ pushSkillRenderedFile(rendered, generatedCodexAgentFile.file, targetSkillDir, renderedRelativeFiles, `${skill.sourcePath}.agents/openai.yaml`);
12011
12327
  }
12012
12328
  if (generatedCodexToolsFile !== undefined) {
12013
12329
  pushSkillRenderedFile(rendered, generatedCodexToolsFile, targetSkillDir, renderedRelativeFiles, `${skill.sourcePath}.tools`);
@@ -12032,6 +12348,7 @@ async function renderPluginSkillFiles(graph, plugin, skill, target, basePath, ou
12032
12348
  kind: "plugin-skill",
12033
12349
  outputRoot,
12034
12350
  plugin,
12351
+ preprocessDependencies: skillPreprocessDependencies(skillMarkdown, generatedCodexAgentFile),
12035
12352
  skill,
12036
12353
  sourceDir,
12037
12354
  transforms: skillMarkdown.transforms
@@ -12075,7 +12392,7 @@ async function renderClaudeProjectAgent(graph, agent) {
12075
12392
  preprocessDependencies,
12076
12393
  rootPath: graph.rootPath,
12077
12394
  sourcePath: agent.sourcePath,
12078
- sourceRoot: join6(graph.sourceDir, "src")
12395
+ sourceRoot: graph.sourceRoot
12079
12396
  });
12080
12397
  const targetPath = join6(targetProjectRoot2(graph, "claude"), "agents", `${agent.outputName}.md`);
12081
12398
  return {
@@ -12111,7 +12428,7 @@ async function renderCodexProjectAgentInstructions(graph, agent, targetOptions,
12111
12428
  preprocessDependencies,
12112
12429
  rootPath: graph.rootPath,
12113
12430
  sourcePath: agent.sourcePath,
12114
- sourceRoot: join6(graph.sourceDir, "src")
12431
+ sourceRoot: graph.sourceRoot
12115
12432
  });
12116
12433
  const sections = [];
12117
12434
  if (skills !== undefined && skills.length > 0) {
@@ -12124,7 +12441,7 @@ async function renderCodexProjectAgentInstructions(graph, agent, targetOptions,
12124
12441
  preprocessDependencies,
12125
12442
  rootPath: graph.rootPath,
12126
12443
  sourcePath: agent.sourcePath,
12127
- sourceRoot: join6(graph.sourceDir, "src")
12444
+ sourceRoot: graph.sourceRoot
12128
12445
  });
12129
12446
  if (renderedPrompt.includes("</initial_prompt>")) {
12130
12447
  throw new Error(`skillset: ${relative7(graph.rootPath, agent.sourcePath)} initialPrompt must not contain </initial_prompt>`);
@@ -12139,7 +12456,7 @@ ${renderedPrompt.trimEnd()}
12139
12456
  `;
12140
12457
  }
12141
12458
  function projectAgentPreprocessDependencies(graph, dependencies) {
12142
- return [...dependencies].sort(compareStrings).map((path) => relative7(graph.rootPath, path));
12459
+ return formattedPreprocessDependencies(graph, dependencies);
12143
12460
  }
12144
12461
  function renderCodexSkillsPreface(targetOptions, skills) {
12145
12462
  const bullets = skills.map((skill) => `- ${skill}`).join(`
@@ -12192,7 +12509,7 @@ async function renderIslandFile(graph, island, targetPath) {
12192
12509
  const content = await renderTextIslandFile(graph, island, targetPath, preprocessDependencies);
12193
12510
  return {
12194
12511
  file: textFile(targetPath, content, relative7(graph.rootPath, island.sourcePath)),
12195
- preprocessDependencies: [...preprocessDependencies].sort(compareStrings).map((path) => relative7(graph.rootPath, path)),
12512
+ preprocessDependencies: formattedPreprocessDependencies(graph, preprocessDependencies),
12196
12513
  validation: "structured"
12197
12514
  };
12198
12515
  }
@@ -12215,7 +12532,7 @@ async function renderTextIslandFile(graph, island, targetPath, preprocessDepende
12215
12532
  preprocessDependencies,
12216
12533
  rootPath: graph.rootPath,
12217
12534
  sourcePath: island.sourcePath,
12218
- sourceRoot: join6(graph.sourceDir, "src")
12535
+ sourceRoot: graph.sourceRoot
12219
12536
  });
12220
12537
  return renderValidatedMarkdown(stripSourceFrontmatter(parsed.frontmatter, island.sourcePath), body, `${relative7(graph.rootPath, island.sourcePath)} -> ${targetPath}`);
12221
12538
  }
@@ -12224,7 +12541,7 @@ async function renderTextIslandFile(graph, island, targetPath, preprocessDepende
12224
12541
  preprocessDependencies,
12225
12542
  rootPath: graph.rootPath,
12226
12543
  sourcePath: island.sourcePath,
12227
- sourceRoot: join6(graph.sourceDir, "src")
12544
+ sourceRoot: graph.sourceRoot
12228
12545
  });
12229
12546
  }
12230
12547
  function rejectIslandTargetEscape(frontmatter, island) {
@@ -12251,13 +12568,13 @@ async function renderStandaloneSkill(graph, skill, target, lockRoots) {
12251
12568
  const targetSkillFile = join6(targetSkillDir, "SKILL.md");
12252
12569
  const generatedCodexAgentFile = await renderCodexSkillAgentFile(graph, undefined, skill, target, sourceDir, targetSkillDir);
12253
12570
  const generatedCodexToolsFile = renderCodexSkillToolsFile(graph, skill, target, targetSkillDir);
12254
- const generatedCodexRelativeFiles = new Set([generatedCodexAgentFile, generatedCodexToolsFile].filter((file) => file !== undefined).map((file) => relative7(targetSkillDir, file.path)));
12571
+ const generatedCodexRelativeFiles = new Set([generatedCodexAgentFile?.file, generatedCodexToolsFile].filter((file) => file !== undefined).map((file) => relative7(targetSkillDir, file.path)));
12255
12572
  const rendered = [];
12256
12573
  const renderedRelativeFiles = new Set;
12257
12574
  const skillMarkdown = await renderSkillMarkdown(graph, undefined, skill, target);
12258
12575
  pushSkillRenderedFile(rendered, textFile(targetSkillFile, skillMarkdown.content, relative7(graph.rootPath, skill.sourcePath)), targetSkillDir, renderedRelativeFiles, `${skill.sourcePath}.SKILL.md`);
12259
12576
  if (generatedCodexAgentFile !== undefined) {
12260
- pushSkillRenderedFile(rendered, generatedCodexAgentFile, targetSkillDir, renderedRelativeFiles, `${skill.sourcePath}.agents/openai.yaml`);
12577
+ pushSkillRenderedFile(rendered, generatedCodexAgentFile.file, targetSkillDir, renderedRelativeFiles, `${skill.sourcePath}.agents/openai.yaml`);
12261
12578
  }
12262
12579
  if (generatedCodexToolsFile !== undefined) {
12263
12580
  pushSkillRenderedFile(rendered, generatedCodexToolsFile, targetSkillDir, renderedRelativeFiles, `${skill.sourcePath}.tools`);
@@ -12281,6 +12598,7 @@ async function renderStandaloneSkill(graph, skill, target, lockRoots) {
12281
12598
  graph,
12282
12599
  kind: "standalone-skill",
12283
12600
  outputRoot,
12601
+ preprocessDependencies: skillPreprocessDependencies(skillMarkdown, generatedCodexAgentFile),
12284
12602
  skill,
12285
12603
  sourceDir,
12286
12604
  transforms: skillMarkdown.transforms
@@ -12312,6 +12630,15 @@ function pushSkillRenderedFile(rendered, file, targetSkillDir, renderedRelativeF
12312
12630
  function normalizeRenderedRelativePath(path) {
12313
12631
  return path.replaceAll("\\", "/");
12314
12632
  }
12633
+ function formattedPreprocessDependencies(graph, dependencies) {
12634
+ return [...dependencies].map((dependency) => formatPreprocessDependency(graph.rootPath, dependency)).sort(compareStrings);
12635
+ }
12636
+ function skillPreprocessDependencies(markdown, auxiliary) {
12637
+ return [...new Set([
12638
+ ...markdown.preprocessDependencies,
12639
+ ...auxiliary?.preprocessDependencies ?? []
12640
+ ])].sort(compareStrings);
12641
+ }
12315
12642
  async function renderSkillMarkdown(graph, plugin, skill, target) {
12316
12643
  const metadata = skill.metadata;
12317
12644
  const targetOptions = skill.targets[target].options;
@@ -12332,11 +12659,13 @@ async function renderSkillMarkdown(graph, plugin, skill, target) {
12332
12659
  version
12333
12660
  }
12334
12661
  }) : withTargetFrontmatter;
12662
+ const preprocessDependencies = new Set;
12335
12663
  const preprocessedBody = await preprocessText(skill.body, {
12336
12664
  frontmatter: skill.frontmatter,
12665
+ preprocessDependencies,
12337
12666
  rootPath: graph.rootPath,
12338
12667
  sourcePath: skill.sourcePath,
12339
- sourceRoot: join6(graph.sourceDir, "src"),
12668
+ sourceRoot: graph.sourceRoot,
12340
12669
  ...plugin === undefined ? {} : { pluginPath: plugin.path }
12341
12670
  });
12342
12671
  const dependencyNotice = target === "codex" && plugin !== undefined ? renderCodexDependencyNotice(graph, plugin) : undefined;
@@ -12347,6 +12676,7 @@ ${preprocessedBody}`;
12347
12676
  const translated = target === "codex" && skill.dialect === "claude" ? translateClaudeDialect(linkedBody) : { text: linkedBody, transforms: [] };
12348
12677
  return {
12349
12678
  content: renderValidatedMarkdown(frontmatter, translated.text, `${relative7(graph.rootPath, skill.sourcePath)} -> ${target}`),
12679
+ preprocessDependencies: formattedPreprocessDependencies(graph, preprocessDependencies),
12350
12680
  transforms: translated.transforms
12351
12681
  };
12352
12682
  }
@@ -12380,15 +12710,20 @@ async function renderCodexSkillAgentFile(graph, plugin, skill, target, sourceDir
12380
12710
  return;
12381
12711
  const sourceOpenAiPath = join6(sourceDir, "agents/openai.yaml");
12382
12712
  const hasSourceOpenAi = await exists3(sourceOpenAiPath);
12713
+ const preprocessDependencies = new Set;
12383
12714
  const source2 = hasSourceOpenAi ? parseYamlRecord(await preprocessText(await readFile5(sourceOpenAiPath, "utf8"), {
12384
12715
  frontmatter: skill.frontmatter,
12716
+ preprocessDependencies,
12385
12717
  rootPath: graph.rootPath,
12386
12718
  sourcePath: sourceOpenAiPath,
12387
- sourceRoot: join6(graph.sourceDir, "src"),
12719
+ sourceRoot: graph.sourceRoot,
12388
12720
  ...plugin === undefined ? {} : { pluginPath: plugin.path }
12389
12721
  }), sourceOpenAiPath) : {};
12390
12722
  const merged = mergeRecords(source2, generated);
12391
- return textFile(join6(targetSkillDir, "agents/openai.yaml"), renderValidatedYaml(merged, `${relative7(graph.rootPath, sourceOpenAiPath)} -> ${join6(targetSkillDir, "agents/openai.yaml")}`), relative7(graph.rootPath, sourceOpenAiPath));
12723
+ return {
12724
+ file: textFile(join6(targetSkillDir, "agents/openai.yaml"), renderValidatedYaml(merged, `${relative7(graph.rootPath, sourceOpenAiPath)} -> ${join6(targetSkillDir, "agents/openai.yaml")}`), relative7(graph.rootPath, sourceOpenAiPath)),
12725
+ preprocessDependencies: formattedPreprocessDependencies(graph, preprocessDependencies)
12726
+ };
12392
12727
  }
12393
12728
  function renderCodexSkillAgentConfig(skill, label) {
12394
12729
  const implicitInvocation = readImplicitInvocation(skill.frontmatter, "codex", label);
@@ -12424,7 +12759,8 @@ async function renderClaudeRules(graph, lockRoots) {
12424
12759
  const rendered = [];
12425
12760
  for (const rule of graph.rules.filter((sourceRule) => sourceRule.targets.claude.enabled)) {
12426
12761
  const targetFile = join6(CLAUDE_RULES_OUTPUT_ROOT, rule.relativePath);
12427
- const file = textFile(targetFile, await renderClaudeRuleMarkdown(graph, rule, targetFile), relative7(graph.rootPath, rule.sourcePath));
12762
+ const markdown = await renderClaudeRuleMarkdown(graph, rule, targetFile);
12763
+ const file = textFile(targetFile, markdown.content, relative7(graph.rootPath, rule.sourcePath));
12428
12764
  rendered.push(file);
12429
12765
  lockRootsFor(lockRoots, CLAUDE_RULES_OUTPUT_ROOT, "claude").items.push(lockItemForRule({
12430
12766
  files: [file],
@@ -12432,7 +12768,8 @@ async function renderClaudeRules(graph, lockRoots) {
12432
12768
  name: rule.id,
12433
12769
  outputRoot: CLAUDE_RULES_OUTPUT_ROOT,
12434
12770
  outputPath: targetFile,
12435
- sourceHash: hashTextRule(rule),
12771
+ preprocessDependencies: markdown.preprocessDependencies,
12772
+ sourceHash: hashTextRule(rule, markdown.preprocessDependencies, graph.rootPath),
12436
12773
  ...rule.sourceOrigin === undefined ? {} : { sourceOrigin: rule.sourceOrigin },
12437
12774
  sourcePath: relative7(graph.rootPath, rule.sourcePath)
12438
12775
  }));
@@ -12450,7 +12787,8 @@ async function renderCodexAgentsFiles(graph, lockRoots) {
12450
12787
  const rendered = [];
12451
12788
  for (const [destination, rules] of [...destinations.entries()].sort(([left], [right]) => compareStrings(left, right))) {
12452
12789
  const markdown = await renderCodexAgentsMarkdown(graph, rules, destination);
12453
- const file = textFile(destination, markdown.content, `${graph.sourceDir}/${graph.instructionsDir}`);
12790
+ const sourcePath = workspaceRelativeSourcePath(graph, graph.instructionsDir);
12791
+ const file = textFile(destination, markdown.content, sourcePath);
12454
12792
  rendered.push(file);
12455
12793
  lockRootsFor(lockRoots, WORKSPACE_LOCK_ROOT, "workspace").items.push(lockItemForRule({
12456
12794
  files: [file],
@@ -12458,9 +12796,10 @@ async function renderCodexAgentsFiles(graph, lockRoots) {
12458
12796
  name: destination,
12459
12797
  outputRoot: WORKSPACE_LOCK_ROOT,
12460
12798
  outputPath: destination,
12461
- sourceHash: hashRules(rules),
12462
- sourcePath: `${graph.sourceDir}/${graph.instructionsDir}`,
12463
- transforms: markdown.transforms
12799
+ preprocessDependencies: markdown.preprocessDependencies,
12800
+ sourceHash: hashRules(rules, markdown.preprocessDependencies, graph.rootPath),
12801
+ sourcePath,
12802
+ ...markdown.transforms === undefined ? {} : { transforms: markdown.transforms }
12464
12803
  }));
12465
12804
  }
12466
12805
  return rendered;
@@ -12468,12 +12807,18 @@ async function renderCodexAgentsFiles(graph, lockRoots) {
12468
12807
  async function renderClaudeRuleMarkdown(graph, rule, outputPath) {
12469
12808
  const paths = readRulePaths(rule);
12470
12809
  const frontmatter = paths.length === 0 ? {} : { paths: [...paths] };
12471
- return stringifyOptionalMarkdown(frontmatter, await renderRuleBody(graph, rule, outputPath));
12810
+ const preprocessDependencies = new Set;
12811
+ const body = await renderRuleBody(graph, rule, outputPath, preprocessDependencies);
12812
+ return {
12813
+ content: stringifyOptionalMarkdown(frontmatter, body),
12814
+ preprocessDependencies: formattedPreprocessDependencies(graph, preprocessDependencies)
12815
+ };
12472
12816
  }
12473
12817
  async function renderCodexAgentsMarkdown(graph, rules, outputPath) {
12474
12818
  const counts = new Map;
12819
+ const preprocessDependencies = new Set;
12475
12820
  const sections = rules.map(async (rule) => {
12476
- const body = await renderRuleBody(graph, rule, outputPath);
12821
+ const body = await renderRuleBody(graph, rule, outputPath, preprocessDependencies);
12477
12822
  if (rule.dialect !== "claude")
12478
12823
  return { rule, body };
12479
12824
  const translated = translateClaudeDialect(body);
@@ -12486,7 +12831,7 @@ async function renderCodexAgentsMarkdown(graph, rules, outputPath) {
12486
12831
  const renderedSections = resolvedSections.filter((section) => section.body.length > 0).map((section) => `<!-- source: ${relative7(graph.rootPath, section.rule.sourcePath)} -->
12487
12832
  ${section.body}`);
12488
12833
  const content = [
12489
- `<!-- Generated by ${GENERATED_BY2} from ${graph.sourceDir}/${graph.instructionsDir}. Do not edit directly. -->`,
12834
+ `<!-- Generated by ${GENERATED_BY2} from ${workspaceRelativeSourcePath(graph, graph.instructionsDir)}. Do not edit directly. -->`,
12490
12835
  "",
12491
12836
  renderedSections.join(`
12492
12837
 
@@ -12495,7 +12840,11 @@ ${section.body}`);
12495
12840
  ].join(`
12496
12841
  `);
12497
12842
  const transforms = [...counts.entries()].sort(([left], [right]) => compareStrings(left, right)).map(([intent, count]) => ({ count, intent }));
12498
- return { content, transforms };
12843
+ return {
12844
+ content,
12845
+ preprocessDependencies: formattedPreprocessDependencies(graph, preprocessDependencies),
12846
+ transforms
12847
+ };
12499
12848
  }
12500
12849
  async function codexRuleDestinations(graph, rule) {
12501
12850
  const paths = readRulePaths(rule);
@@ -12534,10 +12883,16 @@ async function matchingRepoFiles(graph, pattern) {
12534
12883
  function isIgnoredRuleMatch(graph, path) {
12535
12884
  if (path.startsWith(".git/") || path.startsWith("node_modules/"))
12536
12885
  return true;
12537
- if (path === graph.sourceDir || path.startsWith(`${graph.sourceDir}/`))
12886
+ if (graph.sourceDir !== "." && (path === graph.sourceDir || path.startsWith(`${graph.sourceDir}/`))) {
12887
+ return true;
12888
+ }
12889
+ if (path === graph.sourceRoot || path.startsWith(`${graph.sourceRoot}/`))
12538
12890
  return true;
12539
12891
  return graph.outputRoots.some((outputRoot) => path === outputRoot || path.startsWith(`${outputRoot}/`));
12540
12892
  }
12893
+ function workspaceRelativeSourcePath(graph, sourcePath) {
12894
+ return graph.sourceDir === "." ? sourcePath : join6(graph.sourceDir, sourcePath);
12895
+ }
12541
12896
  function readRulePaths(rule) {
12542
12897
  const value = rule.frontmatter.paths;
12543
12898
  if (value === undefined)
@@ -12563,12 +12918,13 @@ function stringifyOptionalMarkdown(frontmatter, body) {
12563
12918
  `;
12564
12919
  return renderValidatedMarkdown(frontmatter, normalizedBody, "generated instruction markdown");
12565
12920
  }
12566
- async function renderRuleBody(graph, rule, outputPath) {
12921
+ async function renderRuleBody(graph, rule, outputPath, preprocessDependencies) {
12567
12922
  return preprocessText(normalizeRuleBody(rule.body), {
12568
12923
  frontmatter: rule.frontmatter,
12924
+ preprocessDependencies,
12569
12925
  rootPath: graph.rootPath,
12570
12926
  sourcePath: rule.sourcePath,
12571
- sourceRoot: join6(graph.sourceDir, "src"),
12927
+ sourceRoot: graph.sourceRoot,
12572
12928
  variables: ruleVariables(graph, rule, outputPath)
12573
12929
  });
12574
12930
  }
@@ -12747,7 +13103,7 @@ function renderLockFiles(graph, lockRoots) {
12747
13103
  skillsetMetadata: graph.root.compile.skillset.metadata,
12748
13104
  outputRoot,
12749
13105
  schemaVersion: 1,
12750
- sourceRoot: join6(graph.sourceDir, "src"),
13106
+ sourceRoot: graph.sourceRoot,
12751
13107
  target: lock.target
12752
13108
  };
12753
13109
  rendered.push(textFile(join6(outputRoot, ".skillset.lock"), renderValidatedJson(value, `${outputRoot}/.skillset.lock`)));
@@ -12833,6 +13189,7 @@ function lockItemForRule(args) {
12833
13189
  name: args.name,
12834
13190
  outputHash: hashRenderedFiles(args.outputRoot, args.files),
12835
13191
  outputPath: relative7(args.outputRoot, args.outputPath),
13192
+ ...args.preprocessDependencies.length === 0 ? {} : { preprocessDependencies: args.preprocessDependencies },
12836
13193
  sourceHash: args.sourceHash,
12837
13194
  ...args.sourceOrigin === undefined ? {} : { sourceOrigin: args.sourceOrigin },
12838
13195
  sourcePath: args.sourcePath,
@@ -12878,7 +13235,8 @@ async function lockItemForSkill(args) {
12878
13235
  name: args.skill.id,
12879
13236
  outputHash: hashRenderedFiles(args.outputRoot, args.files),
12880
13237
  outputPath: files.find((file) => file.endsWith("/SKILL.md")) ?? files[0] ?? "",
12881
- sourceHash: await hashSkillSource(args.sourceDir, args.skill.resources),
13238
+ ...args.preprocessDependencies.length === 0 ? {} : { preprocessDependencies: args.preprocessDependencies },
13239
+ sourceHash: await hashSkillSource(args.sourceDir, args.skill.resources, args.preprocessDependencies, args.graph.rootPath),
12882
13240
  ...args.skill.sourceOrigin === undefined ? {} : { sourceOrigin: args.skill.sourceOrigin },
12883
13241
  sourcePath: relative7(args.graph.rootPath, args.skill.sourcePath),
12884
13242
  ...args.transforms.length === 0 ? {} : { transforms: args.transforms },
@@ -12901,7 +13259,7 @@ function hashIslandSource(island, preprocessDependencies, rootPath) {
12901
13259
  hash.update("dependency\x00");
12902
13260
  hash.update(dependency);
12903
13261
  hash.update("\x00");
12904
- hash.update(readFileSyncBytes(join6(rootPath, dependency)));
13262
+ hash.update(readPreprocessDependencySync(rootPath, dependency));
12905
13263
  hash.update("\x00");
12906
13264
  }
12907
13265
  return `sha256:${hash.digest("hex")}`;
@@ -12923,7 +13281,7 @@ function hashProjectAgentSource(agent, preprocessDependencies, rootPath) {
12923
13281
  hash.update("dependency\x00");
12924
13282
  hash.update(dependency);
12925
13283
  hash.update("\x00");
12926
- hash.update(readFileSyncBytes(join6(rootPath, dependency)));
13284
+ hash.update(readPreprocessDependencySync(rootPath, dependency));
12927
13285
  hash.update("\x00");
12928
13286
  }
12929
13287
  return `sha256:${hash.digest("hex")}`;
@@ -13010,7 +13368,7 @@ async function hashPluginFeatureSource(feature2) {
13010
13368
  }
13011
13369
  return `sha256:${hash.digest("hex")}`;
13012
13370
  }
13013
- async function hashSkillSource(sourceDir, resources) {
13371
+ async function hashSkillSource(sourceDir, resources, preprocessDependencies, rootPath) {
13014
13372
  const hash = createHash3("sha256");
13015
13373
  hash.update("skillset-skill-source-v2\x00");
13016
13374
  for (const file of await collectFiles2(sourceDir)) {
@@ -13031,6 +13389,13 @@ async function hashSkillSource(sourceDir, resources) {
13031
13389
  hash.update("\x00");
13032
13390
  await hashResourceSource(hash, resource);
13033
13391
  }
13392
+ for (const dependency of preprocessDependencies) {
13393
+ hash.update("preprocess-dependency\x00");
13394
+ hash.update(dependency);
13395
+ hash.update("\x00");
13396
+ hash.update(readPreprocessDependencySync(rootPath, dependency));
13397
+ hash.update("\x00");
13398
+ }
13034
13399
  return `sha256:${hash.digest("hex")}`;
13035
13400
  }
13036
13401
  async function hashResourceSource(hash, resource) {
@@ -13049,10 +13414,10 @@ async function hashResourceSource(hash, resource) {
13049
13414
  hash.update("\x00");
13050
13415
  }
13051
13416
  }
13052
- function hashTextRule(rule) {
13053
- return hashRules([rule]);
13417
+ function hashTextRule(rule, preprocessDependencies, rootPath) {
13418
+ return hashRules([rule], preprocessDependencies, rootPath);
13054
13419
  }
13055
- function hashRules(rules) {
13420
+ function hashRules(rules, preprocessDependencies, rootPath) {
13056
13421
  const hash = createHash3("sha256");
13057
13422
  hash.update("skillset-rule-source-v1\x00");
13058
13423
  for (const rule of [...rules].sort((left, right) => compareStrings(left.sourcePath, right.sourcePath))) {
@@ -13063,6 +13428,13 @@ function hashRules(rules) {
13063
13428
  hash.update(rule.body);
13064
13429
  hash.update("\x00");
13065
13430
  }
13431
+ for (const dependency of preprocessDependencies) {
13432
+ hash.update("preprocess-dependency\x00");
13433
+ hash.update(dependency);
13434
+ hash.update("\x00");
13435
+ hash.update(readPreprocessDependencySync(rootPath, dependency));
13436
+ hash.update("\x00");
13437
+ }
13066
13438
  return `sha256:${hash.digest("hex")}`;
13067
13439
  }
13068
13440
  function hashRenderedFiles(outputRoot, files) {
@@ -13077,7 +13449,7 @@ function hashRenderedFiles(outputRoot, files) {
13077
13449
  return `sha256:${hash.digest("hex")}`;
13078
13450
  }
13079
13451
  async function collectFiles2(root) {
13080
- const entries = await readdir2(root, { withFileTypes: true });
13452
+ const entries = await readdir3(root, { withFileTypes: true });
13081
13453
  const files = [];
13082
13454
  for (const entry of entries.sort((left, right) => compareStrings(left.name, right.name))) {
13083
13455
  const path = join6(root, entry.name);
@@ -13111,14 +13483,14 @@ function hasRenderableContent(path) {
13111
13483
  return !isIgnoredCompanionFile(path);
13112
13484
  if (!stats.isDirectory())
13113
13485
  return false;
13114
- for (const entry of readdirSync2(path)) {
13486
+ for (const entry of readdirSync3(path)) {
13115
13487
  if (hasRenderableContent(join6(path, entry)))
13116
13488
  return true;
13117
13489
  }
13118
13490
  return false;
13119
13491
  }
13120
13492
  function isIgnoredCompanionFile(path) {
13121
- const name = basename(path);
13493
+ const name = basename2(path);
13122
13494
  return name === ".DS_Store" || name === ".gitkeep";
13123
13495
  }
13124
13496
  function bytesEqual2(left, right) {
@@ -13158,12 +13530,12 @@ function titleize(value) {
13158
13530
  return value.split("-").filter((part) => part.length > 0).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
13159
13531
  }
13160
13532
  function readFileSyncBytes(path) {
13161
- return readFileSync(path);
13533
+ return readFileSync2(path);
13162
13534
  }
13163
13535
 
13164
13536
  // packages/core/src/resolver.ts
13165
- import { readdir as readdir3, readFile as readFile7, stat as stat6 } from "fs/promises";
13166
- import { basename as basename2, dirname as dirname6, join as join8, relative as relative8, sep as sep3 } from "path";
13537
+ import { readdir as readdir4, readFile as readFile7, stat as stat6 } from "fs/promises";
13538
+ import { basename as basename3, dirname as dirname6, join as join8, relative as relative8, sep as sep3 } from "path";
13167
13539
 
13168
13540
  // packages/core/src/release-state.ts
13169
13541
  import { mkdir as mkdir2, readFile as readFile6, stat as stat5, writeFile as writeFile2 } from "fs/promises";
@@ -13259,6 +13631,7 @@ var SOURCE_ROOT_DIR = "src";
13259
13631
  var RULES_DIR = "rules";
13260
13632
  var SKILLS_DIR = "skills";
13261
13633
  var SHARED_DIR = "shared";
13634
+ var DEDICATED_STATE_DIR = "changes";
13262
13635
  var SKILL_FILE = "SKILL.md";
13263
13636
  var RULES_OUTPUT_ROOT = ".claude/rules";
13264
13637
  var PROJECT_AGENTS_DIR = "agents";
@@ -13268,26 +13641,30 @@ var PROVIDER_SOURCE_DIRS = {
13268
13641
  };
13269
13642
  var PLUGIN_FEATURE_KEYS = ["bin", "mcp"];
13270
13643
  async function loadBuildGraph(rootPath, options = {}) {
13271
- const sourceDir = options.sourceDir ?? DEFAULT_SOURCE_DIR;
13272
- const sourcePath = resolveInside(rootPath, sourceDir);
13273
- const rootConfigPath = join8(sourcePath, ROOT_CONFIG_FILE);
13274
- const rootSourceManifestPath = join8(sourcePath, SOURCE_ROOT_DIR, ROOT_SOURCE_MANIFEST_FILE);
13275
- const rootConfig = parseYamlRecord(await readFile7(rootConfigPath, "utf8"), rootConfigPath);
13276
- validateWorkspaceConfigDocument(rootConfig, rootConfigPath);
13277
- const sourceManifest = parseYamlRecord(await readFile7(rootSourceManifestPath, "utf8"), rootSourceManifestPath);
13278
- validateRootSourceManifestDocument(sourceManifest, rootSourceManifestPath);
13279
- const metadata = readSkillsetMetadata(sourceManifest, rootSourceManifestPath);
13280
- validateSchemaField(metadata, `${rootSourceManifestPath}.skillset.schema`);
13281
- validateVersionField(metadata, `${rootSourceManifestPath}.skillset.version`);
13282
- readSkillsetName(metadata, basename2(rootPath), rootSourceManifestPath);
13283
- const outputs = readOutputConfig(rootConfig, {}, options.distDir === undefined ? {} : { distDir: options.distDir });
13284
- const distributions = readDistributionConfig(rootConfig, rootConfigPath);
13285
- const rootTargets = resolveTargets(readCompileTargets(rootConfig, rootConfigPath), rootConfig, rootConfigPath, {
13644
+ const workspace = await resolveWorkspaceLayout(rootPath, options);
13645
+ const { sourceDir, sourcePath, sourceRoot, sourceRootDir, sourceRootPath } = workspace;
13646
+ const rootConfig = parseYamlRecord(await readFile7(workspace.configPath, "utf8"), workspace.configPath);
13647
+ const sourceManifest = workspace.splitRootManifestPath === undefined ? rootConfig : parseYamlRecord(await readFile7(workspace.splitRootManifestPath, "utf8"), workspace.splitRootManifestPath);
13648
+ if (workspace.splitRootManifestPath === undefined) {
13649
+ validateConfigDocument(rootConfig, workspace.configPath, { allowCompile: true });
13650
+ } else {
13651
+ validateWorkspaceConfigDocument(rootConfig, workspace.configPath);
13652
+ validateRootSourceManifestDocument(sourceManifest, workspace.splitRootManifestPath);
13653
+ }
13654
+ const metadataLabel = workspace.splitRootManifestPath ?? workspace.configPath;
13655
+ const metadata = readSkillsetMetadata(sourceManifest, metadataLabel);
13656
+ validateSchemaField(metadata, `${metadataLabel}.skillset.schema`);
13657
+ validateVersionField(metadata, `${metadataLabel}.skillset.version`);
13658
+ readSkillsetName(metadata, basename3(rootPath), metadataLabel);
13659
+ const outputMetadata = workspace.splitRootManifestPath === undefined ? metadata : {};
13660
+ const outputs = readOutputConfig(rootConfig, outputMetadata, options.distDir === undefined ? {} : { distDir: options.distDir });
13661
+ const distributions = readDistributionConfig(rootConfig, workspace.configPath);
13662
+ const rootTargets = resolveTargets(readCompileTargets(rootConfig, workspace.configPath), rootConfig, workspace.configPath, {
13286
13663
  allowDefaults: true,
13287
13664
  objectInheritsEnabled: true
13288
13665
  });
13289
- const compileConfig = readCompileConfig(rootConfig, rootConfigPath);
13290
- const filteredTargets = applyTargetFilter(rootTargets, options.targetFilter, rootConfigPath);
13666
+ const compileConfig = readCompileConfig(rootConfig, workspace.configPath);
13667
+ const filteredTargets = applyTargetFilter(rootTargets, options.targetFilter, workspace.configPath);
13291
13668
  const compile = {
13292
13669
  ...compileConfig,
13293
13670
  build: options.buildMode ?? compileConfig.build,
@@ -13301,29 +13678,33 @@ async function loadBuildGraph(rootPath, options = {}) {
13301
13678
  targets: filteredTargets
13302
13679
  };
13303
13680
  const warnings = [];
13304
- await rejectLegacySourceLayout(rootPath, sourceDir);
13305
- await validateSupports(sourceManifest.supports, { label: rootSourceManifestPath, rootPath, warnings });
13306
- const releaseState = await readReleaseState(rootPath, options);
13307
- const plugins = await loadPlugins(rootPath, sourceDir, filteredTargets, warnings, outputs);
13681
+ await rejectLegacySourceLayout(rootPath, sourceDir, sourceRootDir);
13682
+ await validateSupports(sourceManifest.supports, { label: metadataLabel, rootPath, warnings });
13683
+ const releaseState = await readReleaseState(rootPath, { ...options, sourceDir });
13684
+ const plugins = await loadPlugins(rootPath, sourceDir, sourceRootDir, filteredTargets, warnings, outputs);
13308
13685
  try {
13309
13686
  validatePluginDependencyGraph(plugins);
13310
13687
  } catch (error) {
13311
13688
  throw featureDiagnosticError(error, {
13312
13689
  code: "plugin-dependencies-invalid",
13313
13690
  featureId: "dependencies",
13314
- path: join8(sourceDir, SOURCE_ROOT_DIR, PLUGINS_DIR)
13691
+ path: join8(sourceRoot, PLUGINS_DIR)
13315
13692
  });
13316
13693
  }
13317
- const standaloneSkills = await loadStandaloneSkills(rootPath, sourceDir, filteredTargets, warnings);
13318
- const { rules, instructionsDir } = await loadInstructions(rootPath, sourceDir, filteredTargets, warnings);
13319
- const projectAgents = await loadProjectAgents(rootPath, sourceDir, filteredTargets, warnings);
13320
- const projectIslands = await loadProjectIslands(rootPath, sourceDir, plugins);
13694
+ const standaloneSkills = await loadStandaloneSkills(rootPath, sourceDir, sourceRootDir, filteredTargets, warnings);
13695
+ const { rules, instructionsDir } = await loadInstructions(rootPath, sourceDir, sourceRootDir, filteredTargets, warnings);
13696
+ const projectAgents = await loadProjectAgents(rootPath, sourceDir, sourceRootDir, filteredTargets, warnings);
13697
+ const projectIslands = await loadProjectIslands(rootPath, sourceDir, sourceRootDir, plugins);
13321
13698
  if (plugins.length === 0 && standaloneSkills.length === 0 && rules.length === 0 && projectAgents.length === 0 && projectIslands.length === 0) {
13322
- throw new Error(`skillset: no source plugins, skills, rules, project agents, or provider source found under ${sourceDir}/${SOURCE_ROOT_DIR}/`);
13699
+ throw new Error(`skillset: no source plugins, skills, rules, project agents, or provider source found under ${sourceRoot}/`);
13323
13700
  }
13324
13701
  const outputRoots = await outputRootsFor(rootPath, outputs, plugins, standaloneSkills, rules);
13325
- validateOutputRoots(rootPath, sourcePath, outputRoots);
13326
- validateProjectRoots(rootPath, sourcePath, outputRoots, filteredTargets, projectAgents, projectIslands);
13702
+ const protectedRoots = sourceDir === "." ? [
13703
+ { label: "source root", path: sourceRootPath },
13704
+ { label: "change state", path: resolveInside(rootPath, DEDICATED_STATE_DIR) }
13705
+ ] : [{ label: "source root", path: sourcePath }];
13706
+ validateOutputRoots(rootPath, protectedRoots, outputRoots);
13707
+ validateProjectRoots(rootPath, protectedRoots, outputRoots, filteredTargets, projectAgents, projectIslands);
13327
13708
  return {
13328
13709
  instructionsDir,
13329
13710
  outputRoots: outputRoots.map((outputRoot) => outputRoot.path),
@@ -13333,13 +13714,140 @@ async function loadBuildGraph(rootPath, options = {}) {
13333
13714
  releaseState,
13334
13715
  rules,
13335
13716
  root,
13717
+ rootConfigPath: workspace.configPath,
13718
+ rootManifestPath: workspace.splitRootManifestPath ?? workspace.configPath,
13336
13719
  rootPath,
13337
13720
  sourceDir,
13338
13721
  sourcePath,
13722
+ sourceRoot,
13723
+ sourceRootPath,
13339
13724
  standaloneSkills,
13340
13725
  warnings
13341
13726
  };
13342
13727
  }
13728
+ async function detectWorkspaceSourceDir(rootPath, options = {}) {
13729
+ return (await resolveWorkspaceLayout(rootPath, options)).sourceDir;
13730
+ }
13731
+ async function resolveWorkspaceLayout(rootPath, options) {
13732
+ if (options.sourceDir !== undefined) {
13733
+ if (options.sourceDir === ".")
13734
+ return dedicatedWorkspace(rootPath);
13735
+ const ordinary2 = await ordinaryWorkspace(rootPath, options.sourceDir);
13736
+ if (await workspaceExists(ordinary2))
13737
+ return ordinary2;
13738
+ return legacyOrdinaryWorkspace(rootPath, options.sourceDir);
13739
+ }
13740
+ const ordinary = await ordinaryWorkspace(rootPath);
13741
+ const legacy = await legacyOrdinaryWorkspace(rootPath, DEFAULT_SOURCE_DIR);
13742
+ const dedicated = await dedicatedWorkspace(rootPath);
13743
+ const ordinaryExists = await workspaceExists(ordinary);
13744
+ const legacyExists = await workspaceExists(legacy);
13745
+ const dedicatedExists = await workspaceExists(dedicated);
13746
+ const dedicatedSourceMarkerExists = await hasDedicatedSourceMarker(dedicated.sourceRootPath);
13747
+ if (dedicatedExists && dedicatedSourceMarkerExists && (ordinaryExists || legacyExists)) {
13748
+ throw new Error("skillset: ambiguous workspace layout; found both dedicated root skillset.yaml/skillset and ordinary .skillset workspace");
13749
+ }
13750
+ if (ordinaryExists && legacyExists) {
13751
+ throw new Error("skillset: ambiguous workspace layout; found both .skillset/skillset.yaml and legacy .skillset/config.yaml");
13752
+ }
13753
+ if (dedicatedExists && dedicatedSourceMarkerExists)
13754
+ return dedicated;
13755
+ if (ordinaryExists)
13756
+ return ordinary;
13757
+ if (legacyExists)
13758
+ return legacy;
13759
+ if (dedicatedExists)
13760
+ return dedicated;
13761
+ return legacy;
13762
+ }
13763
+ async function hasDedicatedSourceMarker(sourceRootPath) {
13764
+ if (await hasSkillSource(join8(sourceRootPath, SKILLS_DIR)))
13765
+ return true;
13766
+ if (await hasPluginSource(join8(sourceRootPath, PLUGINS_DIR)))
13767
+ return true;
13768
+ if (await hasMarkdownSource(join8(sourceRootPath, RULES_DIR)))
13769
+ return true;
13770
+ if (await hasProjectAgentSource(join8(sourceRootPath, PROJECT_AGENTS_DIR)))
13771
+ return true;
13772
+ for (const providerDir of Object.values(PROVIDER_SOURCE_DIRS)) {
13773
+ if (await hasAnyFile(join8(sourceRootPath, providerDir)))
13774
+ return true;
13775
+ }
13776
+ return false;
13777
+ }
13778
+ async function hasSkillSource(path) {
13779
+ return await exists5(path) && (await findSkillFiles(path)).length > 0;
13780
+ }
13781
+ async function hasPluginSource(path) {
13782
+ if (!await exists5(path))
13783
+ return false;
13784
+ for (const entry of await readdir4(path, { withFileTypes: true })) {
13785
+ if (!entry.isDirectory())
13786
+ continue;
13787
+ const pluginPath = join8(path, entry.name);
13788
+ if (await exists5(join8(pluginPath, "skillset.yaml")))
13789
+ return true;
13790
+ if (await exists5(join8(pluginPath, "config.yaml")))
13791
+ return true;
13792
+ }
13793
+ return false;
13794
+ }
13795
+ async function hasMarkdownSource(path) {
13796
+ return await exists5(path) && (await findMarkdownFiles(path)).some(isNonReadmeFile);
13797
+ }
13798
+ async function hasProjectAgentSource(path) {
13799
+ if (!await exists5(path))
13800
+ return false;
13801
+ const entries = await readdir4(path, { withFileTypes: true });
13802
+ return entries.some((entry) => entry.isFile() && entry.name.endsWith(".md") && entry.name.toLowerCase() !== "readme.md");
13803
+ }
13804
+ async function hasAnyFile(path) {
13805
+ return await exists5(path) && (await collectFiles3(path)).some(isNonReadmeFile);
13806
+ }
13807
+ function isNonReadmeFile(path) {
13808
+ return basename3(path).toLowerCase() !== "readme.md";
13809
+ }
13810
+ async function workspaceExists(workspace) {
13811
+ return exists5(workspace.configPath);
13812
+ }
13813
+ async function ordinaryWorkspace(rootPath, sourceDir = DEFAULT_SOURCE_DIR) {
13814
+ return {
13815
+ configPath: resolveInside(rootPath, join8(sourceDir, ROOT_SOURCE_MANIFEST_FILE)),
13816
+ configRelativePath: join8(sourceDir, ROOT_SOURCE_MANIFEST_FILE),
13817
+ mode: "ordinary",
13818
+ sourceDir,
13819
+ sourcePath: resolveInside(rootPath, sourceDir),
13820
+ sourceRoot: join8(sourceDir, SOURCE_ROOT_DIR),
13821
+ sourceRootDir: SOURCE_ROOT_DIR,
13822
+ sourceRootPath: resolveInside(rootPath, join8(sourceDir, SOURCE_ROOT_DIR))
13823
+ };
13824
+ }
13825
+ function dedicatedWorkspace(rootPath) {
13826
+ return {
13827
+ configPath: resolveInside(rootPath, ROOT_SOURCE_MANIFEST_FILE),
13828
+ configRelativePath: ROOT_SOURCE_MANIFEST_FILE,
13829
+ mode: "dedicated",
13830
+ sourceDir: ".",
13831
+ sourcePath: rootPath,
13832
+ sourceRoot: "skillset",
13833
+ sourceRootDir: "skillset",
13834
+ sourceRootPath: resolveInside(rootPath, "skillset")
13835
+ };
13836
+ }
13837
+ function legacyOrdinaryWorkspace(rootPath, sourceDir) {
13838
+ return {
13839
+ configPath: resolveInside(rootPath, join8(sourceDir, ROOT_CONFIG_FILE)),
13840
+ configRelativePath: join8(sourceDir, ROOT_CONFIG_FILE),
13841
+ mode: "legacy-ordinary",
13842
+ sourceDir,
13843
+ sourcePath: resolveInside(rootPath, sourceDir),
13844
+ sourceRoot: join8(sourceDir, SOURCE_ROOT_DIR),
13845
+ sourceRootDir: SOURCE_ROOT_DIR,
13846
+ sourceRootPath: resolveInside(rootPath, join8(sourceDir, SOURCE_ROOT_DIR)),
13847
+ splitRootManifestPath: resolveInside(rootPath, join8(sourceDir, SOURCE_ROOT_DIR, ROOT_SOURCE_MANIFEST_FILE)),
13848
+ splitRootManifestRelativePath: join8(sourceDir, SOURCE_ROOT_DIR, ROOT_SOURCE_MANIFEST_FILE)
13849
+ };
13850
+ }
13343
13851
  function applyTargetFilter(targets, filter, label) {
13344
13852
  if (filter === undefined)
13345
13853
  return targets;
@@ -13364,15 +13872,18 @@ function featureDiagnosticError(error, args) {
13364
13872
  ...args.path === undefined ? {} : { path: args.path }
13365
13873
  });
13366
13874
  }
13367
- async function rejectLegacySourceLayout(rootPath, sourceDir) {
13368
- const moves = [
13369
- ["instructions", `${SOURCE_ROOT_DIR}/${RULES_DIR}`],
13370
- ["rules", `${SOURCE_ROOT_DIR}/${RULES_DIR}`],
13371
- [SKILLS_DIR, `${SOURCE_ROOT_DIR}/${SKILLS_DIR}`],
13372
- [PLUGINS_DIR, `${SOURCE_ROOT_DIR}/${PLUGINS_DIR}`],
13373
- [SHARED_DIR, `${SOURCE_ROOT_DIR}/${SHARED_DIR}`],
13374
- [`${SOURCE_ROOT_DIR}/claude`, `${SOURCE_ROOT_DIR}/${PROVIDER_SOURCE_DIRS.claude}`],
13375
- [`${SOURCE_ROOT_DIR}/codex`, `${SOURCE_ROOT_DIR}/${PROVIDER_SOURCE_DIRS.codex}`]
13875
+ async function rejectLegacySourceLayout(rootPath, sourceDir, sourceRootDir) {
13876
+ const moves = sourceDir === "." ? [
13877
+ [`${sourceRootDir}/claude`, `${sourceRootDir}/${PROVIDER_SOURCE_DIRS.claude}`],
13878
+ [`${sourceRootDir}/codex`, `${sourceRootDir}/${PROVIDER_SOURCE_DIRS.codex}`]
13879
+ ] : [
13880
+ ["instructions", `${sourceRootDir}/${RULES_DIR}`],
13881
+ ["rules", `${sourceRootDir}/${RULES_DIR}`],
13882
+ [SKILLS_DIR, `${sourceRootDir}/${SKILLS_DIR}`],
13883
+ [PLUGINS_DIR, `${sourceRootDir}/${PLUGINS_DIR}`],
13884
+ [SHARED_DIR, `${sourceRootDir}/${SHARED_DIR}`],
13885
+ [`${sourceRootDir}/claude`, `${sourceRootDir}/${PROVIDER_SOURCE_DIRS.claude}`],
13886
+ [`${sourceRootDir}/codex`, `${sourceRootDir}/${PROVIDER_SOURCE_DIRS.codex}`]
13376
13887
  ];
13377
13888
  for (const [oldPath, newPath] of moves) {
13378
13889
  const absoluteOldPath = resolveInside(rootPath, join8(sourceDir, oldPath));
@@ -13380,24 +13891,24 @@ async function rejectLegacySourceLayout(rootPath, sourceDir) {
13380
13891
  throw new Error(`skillset: ${join8(sourceDir, oldPath)} uses the retired source layout; move it to ${join8(sourceDir, newPath)}`);
13381
13892
  }
13382
13893
  }
13383
- const pluginsPath = resolveInside(rootPath, join8(sourceDir, SOURCE_ROOT_DIR, PLUGINS_DIR));
13894
+ const pluginsPath = resolveInside(rootPath, join8(sourceDir, sourceRootDir, PLUGINS_DIR));
13384
13895
  if (!await exists5(pluginsPath))
13385
13896
  return;
13386
- for (const entry of await readdir3(pluginsPath, { withFileTypes: true })) {
13897
+ for (const entry of await readdir4(pluginsPath, { withFileTypes: true })) {
13387
13898
  if (!entry.isDirectory())
13388
13899
  continue;
13389
13900
  for (const [oldProviderDir, newProviderDir] of Object.entries(PROVIDER_SOURCE_DIRS)) {
13390
- const oldPath = join8(SOURCE_ROOT_DIR, PLUGINS_DIR, entry.name, oldProviderDir);
13901
+ const oldPath = join8(sourceRootDir, PLUGINS_DIR, entry.name, oldProviderDir);
13391
13902
  const absoluteOldPath = resolveInside(rootPath, join8(sourceDir, oldPath));
13392
13903
  if (!await exists5(absoluteOldPath))
13393
13904
  continue;
13394
- const newPath = join8(SOURCE_ROOT_DIR, PLUGINS_DIR, entry.name, newProviderDir);
13905
+ const newPath = join8(sourceRootDir, PLUGINS_DIR, entry.name, newProviderDir);
13395
13906
  throw new Error(`skillset: ${join8(sourceDir, oldPath)} uses the retired provider source layout; move it to ${join8(sourceDir, newPath)}`);
13396
13907
  }
13397
13908
  }
13398
13909
  }
13399
- async function loadProjectIslands(rootPath, sourceDir, plugins) {
13400
- const srcPath = resolveInside(rootPath, join8(sourceDir, SOURCE_ROOT_DIR));
13910
+ async function loadProjectIslands(rootPath, sourceDir, sourceRootDir, plugins) {
13911
+ const srcPath = resolveInside(rootPath, join8(sourceDir, sourceRootDir));
13401
13912
  if (!await exists5(srcPath))
13402
13913
  return [];
13403
13914
  const islands = [];
@@ -13427,7 +13938,7 @@ async function loadProjectIslands(rootPath, sourceDir, plugins) {
13427
13938
  throw new SkillsetFeatureDiagnosticError({
13428
13939
  code: "target-native-island-unsupported",
13429
13940
  featureId: "target-native-islands",
13430
- message: `skillset: ${path} targets Codex .rules outside .skillset/src/_codex/rules/; ` + "Codex .rules are project-only command policy",
13941
+ message: `skillset: ${path} targets Codex .rules outside ${join8(sourceDir, sourceRootDir, PROVIDER_SOURCE_DIRS.codex, "rules")}/; ` + "Codex .rules are project-only command policy",
13431
13942
  path
13432
13943
  });
13433
13944
  }
@@ -13436,7 +13947,7 @@ async function loadProjectIslands(rootPath, sourceDir, plugins) {
13436
13947
  }
13437
13948
  async function validatePluginIslandOwners(rootPath, pluginsPath, plugins) {
13438
13949
  const pluginIds = new Set(plugins.map((plugin) => plugin.id));
13439
- for (const entry of await readdir3(pluginsPath, { withFileTypes: true })) {
13950
+ for (const entry of await readdir4(pluginsPath, { withFileTypes: true })) {
13440
13951
  if (!entry.isDirectory())
13441
13952
  continue;
13442
13953
  if (pluginIds.has(entry.name))
@@ -13455,11 +13966,11 @@ async function loadTargetIsland(rootPath, islandPath, target, plugin) {
13455
13966
  ...plugin === undefined ? {} : { plugin }
13456
13967
  }));
13457
13968
  }
13458
- async function loadProjectAgents(rootPath, sourceDir, rootTargets, warnings) {
13459
- const agentsPath = resolveInside(rootPath, join8(sourceDir, SOURCE_ROOT_DIR, PROJECT_AGENTS_DIR));
13969
+ async function loadProjectAgents(rootPath, sourceDir, sourceRootDir, rootTargets, warnings) {
13970
+ const agentsPath = resolveInside(rootPath, join8(sourceDir, sourceRootDir, PROJECT_AGENTS_DIR));
13460
13971
  if (!await exists5(agentsPath))
13461
13972
  return [];
13462
- const entries = await readdir3(agentsPath, { withFileTypes: true });
13973
+ const entries = await readdir4(agentsPath, { withFileTypes: true });
13463
13974
  const agents = [];
13464
13975
  for (const entry of entries.sort((left, right) => compareStrings(left.name, right.name))) {
13465
13976
  if (!entry.isFile() || !entry.name.endsWith(".md"))
@@ -13475,7 +13986,7 @@ async function loadProjectAgent(rootPath, sourceDir, agentsPath, sourcePath, par
13475
13986
  const sourceLabel = relative8(rootPath, sourcePath);
13476
13987
  rejectUnsupportedPortableFrontmatter(parts.frontmatter, sourceLabel);
13477
13988
  await validateSupports(parts.frontmatter.supports, { label: sourceLabel, rootPath, warnings });
13478
- const name = readString(parts.frontmatter, "name") ?? basename2(sourcePath, ".md");
13989
+ const name = readString(parts.frontmatter, "name") ?? basename3(sourcePath, ".md");
13479
13990
  const outputName = sanitizeProjectAgentName(name, sourcePath);
13480
13991
  const description = readString(parts.frontmatter, "description");
13481
13992
  if (description === undefined) {
@@ -13493,7 +14004,7 @@ async function loadProjectAgent(rootPath, sourceDir, agentsPath, sourcePath, par
13493
14004
  warnPortableModel(parts.frontmatter, targets, rootPath, sourcePath, warnings);
13494
14005
  return {
13495
14006
  body: parts.body,
13496
- filename: basename2(sourcePath),
14007
+ filename: basename3(sourcePath),
13497
14008
  frontmatter: parts.frontmatter,
13498
14009
  name,
13499
14010
  outputName,
@@ -13537,11 +14048,11 @@ function validateProjectAgentCollisions(agents) {
13537
14048
  }
13538
14049
  }
13539
14050
  }
13540
- async function loadInstructions(rootPath, sourceDir, rootTargets, warnings) {
13541
- const canonicalPath = resolveInside(rootPath, join8(sourceDir, SOURCE_ROOT_DIR, RULES_DIR));
14051
+ async function loadInstructions(rootPath, sourceDir, sourceRootDir, rootTargets, warnings) {
14052
+ const canonicalPath = resolveInside(rootPath, join8(sourceDir, sourceRootDir, RULES_DIR));
13542
14053
  const canonicalFiles = await exists5(canonicalPath) ? await findMarkdownFiles(canonicalPath) : [];
13543
14054
  if (canonicalFiles.length === 0) {
13544
- return { rules: [], instructionsDir: join8(SOURCE_ROOT_DIR, RULES_DIR) };
14055
+ return { rules: [], instructionsDir: join8(sourceRootDir, RULES_DIR) };
13545
14056
  }
13546
14057
  const ruleFiles = canonicalFiles;
13547
14058
  const rules = [];
@@ -13568,7 +14079,7 @@ async function loadInstructions(rootPath, sourceDir, rootTargets, warnings) {
13568
14079
  }
13569
14080
  return {
13570
14081
  rules: rules.sort((left, right) => compareStrings(left.relativePath, right.relativePath)),
13571
- instructionsDir: join8(SOURCE_ROOT_DIR, RULES_DIR)
14082
+ instructionsDir: join8(sourceRootDir, RULES_DIR)
13572
14083
  };
13573
14084
  }
13574
14085
  function readDialect(frontmatter, label) {
@@ -13586,22 +14097,22 @@ function normalizeRuleFrontmatter(frontmatter, label) {
13586
14097
  }
13587
14098
  return frontmatter;
13588
14099
  }
13589
- async function loadPlugins(rootPath, sourceDir, rootTargets, warnings, outputs) {
13590
- const pluginsPath = resolveInside(rootPath, join8(sourceDir, SOURCE_ROOT_DIR, PLUGINS_DIR));
14100
+ async function loadPlugins(rootPath, sourceDir, sourceRootDir, rootTargets, warnings, outputs) {
14101
+ const pluginsPath = resolveInside(rootPath, join8(sourceDir, sourceRootDir, PLUGINS_DIR));
13591
14102
  if (!await exists5(pluginsPath))
13592
14103
  return [];
13593
- const entries = await readdir3(pluginsPath, { withFileTypes: true });
14104
+ const entries = await readdir4(pluginsPath, { withFileTypes: true });
13594
14105
  const plugins = [];
13595
14106
  for (const entry of entries.sort((left, right) => compareStrings(left.name, right.name))) {
13596
14107
  if (!entry.isDirectory())
13597
14108
  continue;
13598
14109
  const id = validateSlug(entry.name, "plugin directory");
13599
- plugins.push(await loadPlugin(rootPath, sourceDir, id, rootTargets, warnings, outputs));
14110
+ plugins.push(await loadPlugin(rootPath, sourceDir, sourceRootDir, id, rootTargets, warnings, outputs));
13600
14111
  }
13601
14112
  return plugins;
13602
14113
  }
13603
- async function loadPlugin(rootPath, sourceDir, id, parentTargets, warnings, outputs) {
13604
- const pluginPath = resolveInside(rootPath, join8(sourceDir, SOURCE_ROOT_DIR, PLUGINS_DIR, id));
14114
+ async function loadPlugin(rootPath, sourceDir, sourceRootDir, id, parentTargets, warnings, outputs) {
14115
+ const pluginPath = resolveInside(rootPath, join8(sourceDir, sourceRootDir, PLUGINS_DIR, id));
13605
14116
  const configPath = await resolvePluginConfigPath(pluginPath);
13606
14117
  const configRelativePath = relative8(rootPath, configPath);
13607
14118
  const config = parseYamlRecord(await readFile7(configPath, "utf8"), configPath);
@@ -13636,7 +14147,7 @@ async function loadPlugin(rootPath, sourceDir, id, parentTargets, warnings, outp
13636
14147
  });
13637
14148
  }
13638
14149
  const features = await loadPluginFeatures(rootPath, pluginPath, config, configPath, targets, id, configuredOutputRoots(outputs));
13639
- const skills = await loadSkills(rootPath, sourceDir, pluginPath, inheritedTargets, warnings);
14150
+ const skills = await loadSkills(rootPath, sourceDir, sourceRootDir, pluginPath, inheritedTargets, warnings);
13640
14151
  if (await exists5(join8(pluginPath, "hooks.json"))) {
13641
14152
  const path = relative8(rootPath, join8(pluginPath, "hooks.json"));
13642
14153
  throw new SkillsetFeatureDiagnosticError({
@@ -13752,13 +14263,13 @@ async function resolvePluginConfigPath(pluginPath) {
13752
14263
  }
13753
14264
  return candidates[0] ?? join8(pluginPath, "skillset.yaml");
13754
14265
  }
13755
- async function loadSkills(rootPath, sourceDir, pluginPath, parentTargets, warnings) {
14266
+ async function loadSkills(rootPath, sourceDir, sourceRootDir, pluginPath, parentTargets, warnings) {
13756
14267
  const skillsPath = join8(pluginPath, SKILLS_DIR);
13757
14268
  if (!await exists5(skillsPath))
13758
14269
  return [];
13759
- return loadSkillsFromDirectory(rootPath, sourceDir, skillsPath, pluginPath, parentTargets, warnings, pluginPath);
14270
+ return loadSkillsFromDirectory(rootPath, sourceDir, sourceRootDir, skillsPath, pluginPath, parentTargets, warnings, pluginPath);
13760
14271
  }
13761
- async function loadSkillsFromDirectory(rootPath, sourceDir, skillsPath, relativeBasePath, parentTargets, warnings, pluginPath) {
14272
+ async function loadSkillsFromDirectory(rootPath, sourceDir, sourceRootDir, skillsPath, relativeBasePath, parentTargets, warnings, pluginPath) {
13762
14273
  const skillFiles = await findSkillFiles(skillsPath);
13763
14274
  const skills = [];
13764
14275
  for (const sourcePath of skillFiles) {
@@ -13777,14 +14288,14 @@ async function loadSkillsFromDirectory(rootPath, sourceDir, skillsPath, relative
13777
14288
  throw new Error(`skillset: ${sourcePath} uses unsupported skillset.version; use top-level version`);
13778
14289
  }
13779
14290
  const sourceOrigin = readSourceOrigin(metadata, sourcePath);
13780
- const id = validateSlug(readString(parts.frontmatter, "name") ?? basename2(dirname6(sourcePath)), `skill id in ${sourcePath}`);
14291
+ const id = validateSlug(readString(parts.frontmatter, "name") ?? basename3(dirname6(sourcePath)), `skill id in ${sourcePath}`);
13781
14292
  const targets = resolveFeatureTargets(parentTargets, parts.frontmatter, sourcePath, "skills");
13782
14293
  warnPortableModel(parts.frontmatter, targets, rootPath, sourcePath, warnings);
13783
14294
  const relativePath = relative8(relativeBasePath, sourcePath);
13784
14295
  const resources = await readSkillResources(parts.frontmatter.resources, {
13785
14296
  label: sourcePath,
13786
14297
  ...pluginPath === undefined ? {} : { pluginSharedPath: join8(pluginPath, "shared") },
13787
- sharedPath: resolveInside(rootPath, join8(sourceDir, SOURCE_ROOT_DIR, SHARED_DIR))
14298
+ sharedPath: resolveInside(rootPath, join8(sourceDir, sourceRootDir, SHARED_DIR))
13788
14299
  });
13789
14300
  const dialect = readDialect(parts.frontmatter, relative8(rootPath, sourcePath));
13790
14301
  skills.push({
@@ -13802,11 +14313,11 @@ async function loadSkillsFromDirectory(rootPath, sourceDir, skillsPath, relative
13802
14313
  }
13803
14314
  return skills.sort((left, right) => compareStrings(left.relativePath, right.relativePath));
13804
14315
  }
13805
- async function loadStandaloneSkills(rootPath, sourceDir, rootTargets, warnings) {
13806
- const skillsPath = resolveInside(rootPath, join8(sourceDir, SOURCE_ROOT_DIR, SKILLS_DIR));
14316
+ async function loadStandaloneSkills(rootPath, sourceDir, sourceRootDir, rootTargets, warnings) {
14317
+ const skillsPath = resolveInside(rootPath, join8(sourceDir, sourceRootDir, SKILLS_DIR));
13807
14318
  if (!await exists5(skillsPath))
13808
14319
  return [];
13809
- const skills = await loadSkillsFromDirectory(rootPath, sourceDir, skillsPath, skillsPath, rootTargets, warnings);
14320
+ const skills = await loadSkillsFromDirectory(rootPath, sourceDir, sourceRootDir, skillsPath, skillsPath, rootTargets, warnings);
13810
14321
  return skills;
13811
14322
  }
13812
14323
  function warnPortableModel(frontmatter, targets, rootPath, sourcePath, warnings) {
@@ -13819,7 +14330,7 @@ function warnPortableModel(frontmatter, targets, rootPath, sourcePath, warnings)
13819
14330
  }
13820
14331
  async function findSkillFiles(root) {
13821
14332
  const files = [];
13822
- const entries = await readdir3(root, { withFileTypes: true });
14333
+ const entries = await readdir4(root, { withFileTypes: true });
13823
14334
  for (const entry of entries.sort((left, right) => compareStrings(left.name, right.name))) {
13824
14335
  const path = join8(root, entry.name);
13825
14336
  if (entry.isDirectory()) {
@@ -13834,7 +14345,7 @@ async function findSkillFiles(root) {
13834
14345
  }
13835
14346
  async function findMarkdownFiles(root) {
13836
14347
  const files = [];
13837
- const entries = await readdir3(root, { withFileTypes: true });
14348
+ const entries = await readdir4(root, { withFileTypes: true });
13838
14349
  for (const entry of entries.sort((left, right) => compareStrings(left.name, right.name))) {
13839
14350
  const path = join8(root, entry.name);
13840
14351
  if (entry.isDirectory()) {
@@ -13849,7 +14360,7 @@ async function findMarkdownFiles(root) {
13849
14360
  }
13850
14361
  async function collectFiles3(root) {
13851
14362
  const files = [];
13852
- const entries = await readdir3(root, { withFileTypes: true });
14363
+ const entries = await readdir4(root, { withFileTypes: true });
13853
14364
  for (const entry of entries.sort((left, right) => compareStrings(left.name, right.name))) {
13854
14365
  const path = join8(root, entry.name);
13855
14366
  if (entry.isDirectory()) {
@@ -13947,7 +14458,7 @@ function isInsidePath(path, root) {
13947
14458
  const relativePath = relative8(root, path);
13948
14459
  return relativePath === "" || !relativePath.startsWith("..") && !relativePath.includes(`..${sep3}`);
13949
14460
  }
13950
- function validateProjectRoots(rootPath, sourcePath, outputRoots, rootTargets, projectAgents, projectIslands) {
14461
+ function validateProjectRoots(rootPath, protectedRoots, outputRoots, rootTargets, projectAgents, projectIslands) {
13951
14462
  for (const target of targetNames()) {
13952
14463
  const hasProjectAgentOutput = projectAgents.some((agent) => agent.targets[target].enabled);
13953
14464
  const hasProjectIslandOutput = projectIslands.some((island) => island.plugin === undefined && island.target === target);
@@ -13957,7 +14468,7 @@ function validateProjectRoots(rootPath, sourcePath, outputRoots, rootTargets, pr
13957
14468
  label: `${target}.projectRoot`,
13958
14469
  path: targetProjectRoot3(rootTargets, target)
13959
14470
  };
13960
- const absoluteProjectRoot = validateOutputRootNotInsideSource(rootPath, sourcePath, projectRoot);
14471
+ const absoluteProjectRoot = validateOutputRootNotInsideProtectedRoots(rootPath, protectedRoots, projectRoot);
13961
14472
  const targetProjectAgents = projectAgents.filter((agent) => agent.targets[target].enabled);
13962
14473
  const targetProjectIslands = projectIslands.filter((island) => island.plugin === undefined && island.target === target);
13963
14474
  for (const outputRoot of outputRoots) {
@@ -13982,10 +14493,10 @@ function targetProjectRoot3(rootTargets, target) {
13982
14493
  function projectAgentOutputPath(projectRoot, target, agent) {
13983
14494
  return join8(projectRoot, "agents", `${agent.outputName}.${target === "claude" ? "md" : "toml"}`);
13984
14495
  }
13985
- function validateOutputRoots(rootPath, sourcePath, outputRoots) {
14496
+ function validateOutputRoots(rootPath, protectedRoots, outputRoots) {
13986
14497
  const seen = new Map;
13987
14498
  for (const outputRoot of outputRoots) {
13988
- const absoluteOutputRoot = validateOutputRootNotInsideSource(rootPath, sourcePath, outputRoot);
14499
+ const absoluteOutputRoot = validateOutputRootNotInsideProtectedRoots(rootPath, protectedRoots, outputRoot);
13989
14500
  const existing = seen.get(absoluteOutputRoot);
13990
14501
  if (existing !== undefined) {
13991
14502
  throw new Error(`skillset: ${outputRoot.label} reuses output root ${outputRoot.path}; already used by ${existing}`);
@@ -13993,10 +14504,12 @@ function validateOutputRoots(rootPath, sourcePath, outputRoots) {
13993
14504
  seen.set(absoluteOutputRoot, outputRoot.label);
13994
14505
  }
13995
14506
  }
13996
- function validateOutputRootNotInsideSource(rootPath, sourcePath, outputRoot) {
14507
+ function validateOutputRootNotInsideProtectedRoots(rootPath, protectedRoots, outputRoot) {
13997
14508
  const absoluteOutputRoot = resolveInside(rootPath, outputRoot.path);
13998
- if (isSameOrInside(absoluteOutputRoot, sourcePath)) {
13999
- throw new Error(`skillset: ${outputRoot.label} must not point inside source root ${relative8(rootPath, sourcePath)}`);
14509
+ for (const protectedRoot of protectedRoots) {
14510
+ if (isSameOrInside(absoluteOutputRoot, protectedRoot.path)) {
14511
+ throw new Error(`skillset: ${outputRoot.label} must not point inside ${protectedRoot.label} ${relative8(rootPath, protectedRoot.path)}`);
14512
+ }
14000
14513
  }
14001
14514
  return absoluteOutputRoot;
14002
14515
  }
@@ -14428,7 +14941,7 @@ async function listGeneratedFiles(rootPath, outputRoots, rendered, previousManag
14428
14941
  return [...paths].sort();
14429
14942
  }
14430
14943
  async function collectFiles4(root) {
14431
- const entries = await readdir4(root, { withFileTypes: true });
14944
+ const entries = await readdir5(root, { withFileTypes: true });
14432
14945
  const files = [];
14433
14946
  for (const entry of entries.sort((left, right) => compareStrings(left.name, right.name))) {
14434
14947
  const path = join9(root, entry.name);
@@ -14557,7 +15070,7 @@ async function exists6(path) {
14557
15070
 
14558
15071
  // packages/core/src/lint.ts
14559
15072
  import { readFile as readFile9, stat as stat8 } from "fs/promises";
14560
- import { basename as basename3, dirname as dirname8, join as join10, relative as relative10 } from "path";
15073
+ import { basename as basename4, dirname as dirname8, join as join10, relative as relative10 } from "path";
14561
15074
 
14562
15075
  // packages/lint/src/registry.ts
14563
15076
  var lintRules = new Map;
@@ -15040,8 +15553,8 @@ async function lintSkillRules(graph) {
15040
15553
  async function lintSubjectForSkill(graph, skill) {
15041
15554
  return {
15042
15555
  body: skill.body,
15043
- directoryName: basename3(dirname8(skill.sourcePath)),
15044
- files: [basename3(skill.sourcePath)],
15556
+ directoryName: basename4(dirname8(skill.sourcePath)),
15557
+ files: [basename4(skill.sourcePath)],
15045
15558
  frontmatter: skill.frontmatter,
15046
15559
  kind: "skill",
15047
15560
  path: relative10(graph.rootPath, skill.sourcePath),
@@ -15323,7 +15836,7 @@ async function explainPath(rootPath, inputPath, options = {}) {
15323
15836
  features: [],
15324
15837
  renderResults: [],
15325
15838
  notes: [
15326
- `No lock entry references ${target}. Pass a source path under ${graph.sourceDir}/ or a generated output path.`
15839
+ `No lock entry references ${target}. Pass a source path under ${graph.sourceRoot}/ or a generated output path.`
15327
15840
  ]
15328
15841
  };
15329
15842
  }
@@ -15332,6 +15845,86 @@ async function listGeneratedEntries(rootPath, options = {}) {
15332
15845
  const rendered = scopedRenderedFiles(graph, await renderBuildGraph(graph), options.scopes);
15333
15846
  return collectLockItems(rendered).map((item) => item.entry);
15334
15847
  }
15848
+ async function suggestSource(rootPath, inputPath, options = {}) {
15849
+ const write = options.write === true;
15850
+ const explanation = await explainPath(rootPath, inputPath, options);
15851
+ const generatedPath = explanation.path;
15852
+ if (explanation.kind !== "generated" || explanation.entries.length === 0) {
15853
+ return refusedSourceSuggestion(generatedPath, explanation.entries, "No Skillset lock entry owns this generated path.", [
15854
+ "Run `skillset explain <path>` to inspect path provenance.",
15855
+ "Update the adaptive source directly if this file is not Skillset-managed."
15856
+ ]);
15857
+ }
15858
+ const uniqueSourcePaths = new Set(explanation.entries.map((entry) => entry.sourcePath));
15859
+ if (uniqueSourcePaths.size !== 1) {
15860
+ return refusedSourceSuggestion(generatedPath, explanation.entries, "Generated path has multiple source owners.", [
15861
+ "Update the owning source files manually, then run `skillset build --yes`."
15862
+ ]);
15863
+ }
15864
+ const [sourcePath] = [...uniqueSourcePaths];
15865
+ if (sourcePath === undefined) {
15866
+ return refusedSourceSuggestion(generatedPath, explanation.entries, "Generated path is missing source ownership.", [
15867
+ "Run `skillset explain <path>` to inspect the stale or corrupt lock entry."
15868
+ ]);
15869
+ }
15870
+ if (generatedPath.endsWith("/CHANGELOG.md") || generatedPath === "CHANGELOG.md") {
15871
+ return refusedSourceSuggestion(generatedPath, explanation.entries, "Generated changelogs are managed projections, not source edit surfaces.", [
15872
+ "Use `skillset change reason <@ref>` before release.",
15873
+ "Use `skillset change amend <@ref>` for applied-history wording after release.",
15874
+ "Use `skillset release amend <@ref>` for release-event metadata."
15875
+ ], sourcePath);
15876
+ }
15877
+ const blockingDependency = explanation.entries.find((entry) => (entry.dependencies?.length ?? 0) > 0 || (entry.preprocessDependencies?.length ?? 0) > 0);
15878
+ if (blockingDependency !== undefined) {
15879
+ return refusedSourceSuggestion(generatedPath, explanation.entries, "Generated path includes dependencies or partials that are not safe to reverse-patch.", [
15880
+ "Update the source file, resources, or partials manually, then run `skillset build --yes`."
15881
+ ], sourcePath);
15882
+ }
15883
+ const primaryEntry = explanation.entries[0];
15884
+ if (primaryEntry?.kind !== "standalone-skill" && primaryEntry?.kind !== "plugin-skill") {
15885
+ return refusedSourceSuggestion(generatedPath, explanation.entries, "Only generated skill Markdown body edits are suggestible in v1.", [
15886
+ "Update the adaptive source manually, then run `skillset build --yes`."
15887
+ ], sourcePath);
15888
+ }
15889
+ if (!generatedPath.endsWith(".md") || !sourcePath.endsWith(".md")) {
15890
+ return refusedSourceSuggestion(generatedPath, explanation.entries, "Only Markdown generated outputs are suggestible in v1.", [
15891
+ "Update the adaptive source manually, then run `skillset build --yes`."
15892
+ ], sourcePath);
15893
+ }
15894
+ const sourceAbsolute = resolve3(rootPath, sourcePath);
15895
+ const generatedAbsolute = resolve3(rootPath, generatedPath);
15896
+ const sourceParts = parseMarkdown(await readFile10(sourceAbsolute, "utf8"), sourcePath);
15897
+ const generatedParts = parseMarkdown(await readFile10(generatedAbsolute, "utf8"), generatedPath);
15898
+ if (sourceParts.body === generatedParts.body) {
15899
+ const lockPath2 = lockPathForEntry(primaryEntry);
15900
+ return {
15901
+ entries: explanation.entries,
15902
+ generatedPath,
15903
+ ...lockPath2 === undefined ? {} : { lockPath: lockPath2 },
15904
+ message: "Generated body matches the source body; no source edit is needed.",
15905
+ nextSteps: ["Run `skillset diff` or `skillset check` to inspect any remaining generated drift."],
15906
+ sourcePath,
15907
+ status: "suggestible",
15908
+ wouldWrite: false,
15909
+ wrote: false
15910
+ };
15911
+ }
15912
+ if (write) {
15913
+ await writeFile4(sourceAbsolute, stringifyMarkdown(sourceParts.frontmatter, generatedParts.body), "utf8");
15914
+ }
15915
+ const lockPath = lockPathForEntry(primaryEntry);
15916
+ return {
15917
+ entries: explanation.entries,
15918
+ generatedPath,
15919
+ ...lockPath === undefined ? {} : { lockPath },
15920
+ message: write ? "Updated the source Markdown body from the generated edit." : "Generated Markdown body can be moved back to the source file.",
15921
+ nextSteps: write ? ["Run `skillset build --yes` to refresh generated output.", "Run `skillset change add` or `skillset change check` if the source edit needs change coverage."] : ["Review the body change, then rerun with `--write --yes` to update source.", "After accepting, run `skillset build --yes`."],
15922
+ sourcePath,
15923
+ status: write ? "written" : "suggestible",
15924
+ wouldWrite: true,
15925
+ wrote: write
15926
+ };
15927
+ }
15335
15928
  function listFeatureCapabilities(featureId) {
15336
15929
  const features = featureId === undefined ? listSkillsetFeatures() : [getSkillsetFeature(featureId)].filter((feature2) => feature2 !== undefined);
15337
15930
  return features.map(featureCapability);
@@ -15509,6 +16102,25 @@ function joinOutputRoot2(outputRoot, file) {
15509
16102
  return file;
15510
16103
  return `${outputRoot}/${file}`;
15511
16104
  }
16105
+ function lockPathForEntry(entry) {
16106
+ if (entry === undefined)
16107
+ return;
16108
+ return joinOutputRoot2(entry.outputRoot, ".skillset.lock");
16109
+ }
16110
+ function refusedSourceSuggestion(generatedPath, entries, message, nextSteps, sourcePath) {
16111
+ const lockPath = lockPathForEntry(entries[0]);
16112
+ return {
16113
+ entries,
16114
+ generatedPath,
16115
+ ...lockPath === undefined ? {} : { lockPath },
16116
+ message,
16117
+ nextSteps,
16118
+ ...sourcePath === undefined ? {} : { sourcePath },
16119
+ status: "refused",
16120
+ wouldWrite: false,
16121
+ wrote: false
16122
+ };
16123
+ }
15512
16124
  function explainSourceKind(graph, target) {
15513
16125
  if (graph.rules.some((rule) => relative11(graph.rootPath, rule.sourcePath) === target)) {
15514
16126
  return "source-instruction";
@@ -15637,7 +16249,7 @@ var DEFAULT_STRUCTURED_JSON_BASENAMES = new Set([".skillset.lock"]);
15637
16249
  // packages/core/src/deterministic-projection.ts
15638
16250
  var textEncoder4 = new TextEncoder;
15639
16251
  // packages/core/src/destination-ownership.ts
15640
- import { basename as basename4 } from "path";
16252
+ import { basename as basename5 } from "path";
15641
16253
  var textDecoder5 = new TextDecoder;
15642
16254
  function classifyDestinationOwnership(args) {
15643
16255
  const file = classifyFileOwnership(args.path, args.target);
@@ -15743,7 +16355,7 @@ function codexInterfaceReason(owner, key) {
15743
16355
  return "Codex interface presentation can be explicitly overlaid by the distribution destination.";
15744
16356
  }
15745
16357
  function fieldSelector(path, ...parts) {
15746
- return `${basename4(path)}#/${parts.map(escapePointerSegment).join("/")}`;
16358
+ return `${basename5(path)}#/${parts.map(escapePointerSegment).join("/")}`;
15747
16359
  }
15748
16360
  function escapePointerSegment(segment) {
15749
16361
  return segment.replaceAll("~", "~0").replaceAll("/", "~1");
@@ -15796,7 +16408,7 @@ var claudeGeneratedFields = new Set([
15796
16408
  "version"
15797
16409
  ]);
15798
16410
  // packages/core/src/distribution.ts
15799
- import { readFile as readFile10 } from "fs/promises";
16411
+ import { readFile as readFile11 } from "fs/promises";
15800
16412
  import { createHash as createHash4 } from "crypto";
15801
16413
  import { resolve as resolve4 } from "path";
15802
16414
  async function planDistributions(rootPath, options = {}) {
@@ -15911,7 +16523,7 @@ async function distributionDestinationState(graph, config, destinationPath, cont
15911
16523
  if (root === undefined)
15912
16524
  return { status: "unknown" };
15913
16525
  try {
15914
- const current = await readFile10(resolve4(graph.rootPath, root, destinationPath));
16526
+ const current = await readFile11(resolve4(graph.rootPath, root, destinationPath));
15915
16527
  return { content: current, status: bytesEqual4(current, content) ? "unchanged" : "change" };
15916
16528
  } catch (error) {
15917
16529
  if (isNotFound(error))
@@ -16016,7 +16628,7 @@ function isNodeError(error) {
16016
16628
  return error instanceof Error && "code" in error;
16017
16629
  }
16018
16630
  // packages/core/src/version-audit.ts
16019
- import { readFile as readFile11 } from "fs/promises";
16631
+ import { readFile as readFile12 } from "fs/promises";
16020
16632
  async function auditVersions(rootPath, options = {}) {
16021
16633
  const graph = await loadBuildGraph(rootPath, options);
16022
16634
  const rendered = await renderBuildGraph(graph);
@@ -16031,7 +16643,7 @@ async function auditVersions(rootPath, options = {}) {
16031
16643
  async function auditVersionLocus(graph, expected) {
16032
16644
  let current;
16033
16645
  try {
16034
- current = await readFile11(resolveInside(graph.rootPath, expected.path));
16646
+ current = await readFile12(resolveInside(graph.rootPath, expected.path));
16035
16647
  } catch (error) {
16036
16648
  if (isNotFound2(error)) {
16037
16649
  return versionLocus(expected, {
@@ -16250,12 +16862,12 @@ function isNotFound2(error) {
16250
16862
  return error instanceof Error && "code" in error && error.code === "ENOENT";
16251
16863
  }
16252
16864
  // apps/skillset/src/change-entries.ts
16253
- import { readFile as readFile13, rm as rm3, stat as stat10 } from "fs/promises";
16865
+ import { readFile as readFile14, rm as rm3, stat as stat10 } from "fs/promises";
16254
16866
  import { join as join13 } from "path";
16255
16867
 
16256
16868
  // apps/skillset/src/change-status.ts
16257
16869
  import { createHash as createHash5 } from "crypto";
16258
- import { mkdir as mkdir4, mkdtemp, readdir as readdir5, readFile as readFile12, rename, rm as rm2, stat as stat9, writeFile as writeFile4 } from "fs/promises";
16870
+ import { mkdir as mkdir4, mkdtemp, readdir as readdir6, readFile as readFile13, rename, rm as rm2, stat as stat9, writeFile as writeFile5 } from "fs/promises";
16259
16871
  import { tmpdir } from "os";
16260
16872
  import { dirname as dirname9, join as join12, relative as relative12 } from "path";
16261
16873
  // apps/skillset/src/git-env.ts
@@ -16303,9 +16915,13 @@ var PLUGIN_COMPANION_PATHS = [
16303
16915
  async function changeStatus(rootPath, options = {}) {
16304
16916
  const stagedSnapshot = options.staged === true ? await snapshotGitIndex(rootPath) : undefined;
16305
16917
  try {
16306
- const current = await collectSourceInventory(stagedSnapshot ?? rootPath, options);
16307
- const baseline = await resolveBaselineInventory(rootPath, options);
16308
- const generatedDrift = await diffSkillset(stagedSnapshot ?? rootPath, options);
16918
+ const currentRoot = stagedSnapshot ?? rootPath;
16919
+ const graph = await loadBuildGraph(currentRoot, options);
16920
+ const releaseOptions = withDetectedSourceDir(options, graph);
16921
+ const baselineOptions = options.sourceDir === undefined ? options : releaseOptions;
16922
+ const current = await sourceInventoryForGraph(graph);
16923
+ const baseline = await resolveBaselineInventory(rootPath, baselineOptions, releaseOptions);
16924
+ const generatedDrift = await diffSkillset(currentRoot, releaseOptions);
16309
16925
  return {
16310
16926
  baseline: baseline.baseline,
16311
16927
  generatedDrift,
@@ -16322,6 +16938,12 @@ async function collectSourceInventory(rootPath, options = {}) {
16322
16938
  const graph = await loadBuildGraph(rootPath, options);
16323
16939
  return sourceInventoryForGraph(graph);
16324
16940
  }
16941
+ async function detectWorkspaceOptions(rootPath, options) {
16942
+ return { ...options, sourceDir: await detectWorkspaceSourceDir(rootPath, options) };
16943
+ }
16944
+ function withDetectedSourceDir(options, graph) {
16945
+ return { ...options, sourceDir: graph.sourceDir };
16946
+ }
16325
16947
  async function sourceInventoryForGraph(graph) {
16326
16948
  const units = [];
16327
16949
  units.push(await rootConfigUnit(graph));
@@ -16359,25 +16981,28 @@ async function sourceInventoryForGraph(graph) {
16359
16981
  };
16360
16982
  }
16361
16983
  async function rootConfigUnit(graph) {
16362
- const configPath = join12(graph.sourcePath, ROOT_CONFIG_FILE2);
16363
- const manifestPath = join12(graph.sourcePath, SOURCE_ROOT_DIR2, ROOT_SOURCE_MANIFEST_FILE2);
16364
- const sourcePaths = [relativePath(graph, configPath), relativePath(graph, manifestPath)];
16984
+ const configPath = graph.rootConfigPath;
16985
+ const manifestPath = graph.rootManifestPath;
16986
+ const sourcePaths = sortedUnique2([relativePath(graph, configPath), relativePath(graph, manifestPath)]);
16365
16987
  const hash = createSourceHash("root-config");
16366
16988
  hash.update("metadata\x00");
16367
- hash.update(stringifyJson({ id: "root-config", sourcePaths }));
16989
+ hash.update(stringifyJson({ id: "root-config", sourcePaths: [...sourcePaths] }));
16368
16990
  hash.update("\x00config\x00");
16369
16991
  await hashPathInto(hash, configPath);
16370
- hash.update("\x00manifest\x00");
16371
- await hashPathInto(hash, manifestPath);
16992
+ if (manifestPath !== configPath) {
16993
+ hash.update("\x00manifest\x00");
16994
+ await hashPathInto(hash, manifestPath);
16995
+ }
16996
+ const regions = manifestPath === configPath ? await regionsForYaml(configPath) : mergeRegions([
16997
+ ...await regionsForYaml(configPath),
16998
+ ...await regionsForYaml(manifestPath)
16999
+ ]);
16372
17000
  return {
16373
17001
  hash: digest(hash),
16374
17002
  hashSchema: SOURCE_HASH_SCHEMA,
16375
17003
  id: selectorForRootConfig(),
16376
17004
  kind: "root-config",
16377
- regions: mergeRegions([
16378
- ...await regionsForYaml(configPath),
16379
- ...await regionsForYaml(manifestPath)
16380
- ]),
17005
+ regions,
16381
17006
  sourcePath: sourcePaths[0] ?? "",
16382
17007
  sourcePaths
16383
17008
  };
@@ -16396,7 +17021,7 @@ async function fileUnit(graph, kind, id, sourcePath, regions = [], hashId = id)
16396
17021
  }
16397
17022
  async function skillUnit(graph, skill, kind, id, plugin) {
16398
17023
  const sourceDir = dirname9(skill.sourcePath);
16399
- const preprocessDependencies = await skillPreprocessDependencies(graph, skill, plugin);
17024
+ const preprocessDependencies = await skillPreprocessDependencies2(graph, skill, plugin);
16400
17025
  const sourcePaths = await sourcePathsForSkill(graph, sourceDir, skill.resources, preprocessDependencies);
16401
17026
  const hash = createSourceHash(kind);
16402
17027
  hash.update("id\x00");
@@ -16434,7 +17059,7 @@ async function ruleUnit(graph, rule) {
16434
17059
  kind: "instruction",
16435
17060
  regions: regionsForRecord(rule.frontmatter),
16436
17061
  sourcePath,
16437
- sourcePaths: sortedUnique2([sourcePath, ...preprocessDependencies.map((path2) => relativePath(graph, path2))])
17062
+ sourcePaths: sortedUnique2([sourcePath, ...preprocessDependencies])
16438
17063
  };
16439
17064
  }
16440
17065
  async function projectAgentUnit(graph, agent) {
@@ -16458,7 +17083,7 @@ async function projectAgentUnit(graph, agent) {
16458
17083
  kind: "project-agent",
16459
17084
  regions: regionsForRecord(agent.frontmatter),
16460
17085
  sourcePath,
16461
- sourcePaths: sortedUnique2([sourcePath, ...preprocessDependencies.map((path2) => relativePath(graph, path2))])
17086
+ sourcePaths: sortedUnique2([sourcePath, ...preprocessDependencies])
16462
17087
  };
16463
17088
  }
16464
17089
  async function islandUnit(graph, island) {
@@ -16475,7 +17100,7 @@ async function islandUnit(graph, island) {
16475
17100
  hash.update("\x00relativePath\x00");
16476
17101
  hash.update(island.relativePath);
16477
17102
  hash.update("\x00content\x00");
16478
- hash.update(await readFile12(island.sourcePath));
17103
+ hash.update(await readFile13(island.sourcePath));
16479
17104
  hash.update("\x00");
16480
17105
  await hashPreprocessDependencies(hash, graph, preprocessDependencies);
16481
17106
  const sourcePath = relativePath(graph, island.sourcePath);
@@ -16486,7 +17111,7 @@ async function islandUnit(graph, island) {
16486
17111
  kind: "target-native-island",
16487
17112
  regions: [{ name: "target-native", severityBearing: true }],
16488
17113
  sourcePath,
16489
- sourcePaths: sortedUnique2([sourcePath, ...preprocessDependencies.map((path2) => relativePath(graph, path2))])
17114
+ sourcePaths: sortedUnique2([sourcePath, ...preprocessDependencies])
16490
17115
  };
16491
17116
  }
16492
17117
  async function pluginFeatureUnit(graph, plugin, feature2) {
@@ -16562,7 +17187,7 @@ async function sourcePathsForSkill(graph, sourceDir, resources, preprocessDepend
16562
17187
  ...resources.flatMap((resource) => [
16563
17188
  relativePath(graph, resource.sourcePath)
16564
17189
  ]),
16565
- ...preprocessDependencies.map((path2) => relativePath(graph, path2))
17190
+ ...preprocessDependencies
16566
17191
  ]);
16567
17192
  }
16568
17193
  async function sourcePathsForPath(graph, sourcePath, shouldSkip) {
@@ -16583,7 +17208,7 @@ async function hashPathInto(hash, sourcePath) {
16583
17208
  const stats = await stat9(sourcePath);
16584
17209
  if (stats.isFile()) {
16585
17210
  hash.update("file\x00");
16586
- hash.update(await readFile12(sourcePath));
17211
+ hash.update(await readFile13(sourcePath));
16587
17212
  hash.update("\x00");
16588
17213
  return;
16589
17214
  }
@@ -16597,7 +17222,7 @@ async function hashDirectory(hash, sourceDir, shouldSkip) {
16597
17222
  continue;
16598
17223
  hash.update(relativeFile);
16599
17224
  hash.update("\x00");
16600
- hash.update(await readFile12(file));
17225
+ hash.update(await readFile13(file));
16601
17226
  hash.update("\x00");
16602
17227
  }
16603
17228
  }
@@ -16618,31 +17243,31 @@ async function hashResources(hash, resources) {
16618
17243
  async function hashPreprocessDependencies(hash, graph, dependencies) {
16619
17244
  for (const dependency of [...dependencies].sort(compareStrings)) {
16620
17245
  hash.update("preprocess\x00");
16621
- hash.update(relativePath(graph, dependency));
17246
+ hash.update(dependency);
16622
17247
  hash.update("\x00");
16623
- hash.update(await readFile12(dependency));
17248
+ hash.update(readPreprocessDependencySync(graph.rootPath, dependency));
16624
17249
  hash.update("\x00");
16625
17250
  }
16626
17251
  }
16627
- async function skillPreprocessDependencies(graph, skill, plugin) {
17252
+ async function skillPreprocessDependencies2(graph, skill, plugin) {
16628
17253
  const dependencies = new Set;
16629
17254
  const context = {
16630
17255
  frontmatter: skill.frontmatter,
16631
17256
  preprocessDependencies: dependencies,
16632
17257
  rootPath: graph.rootPath,
16633
17258
  sourcePath: skill.sourcePath,
16634
- sourceRoot: join12(graph.sourceDir, "src"),
17259
+ sourceRoot: graph.sourceRoot,
16635
17260
  ...plugin === undefined ? {} : { pluginPath: plugin.path }
16636
17261
  };
16637
17262
  await preprocessText(skill.body, context);
16638
17263
  const sourceOpenAiPath = join12(dirname9(skill.sourcePath), "agents/openai.yaml");
16639
17264
  if (await exists7(sourceOpenAiPath)) {
16640
- await preprocessText(await readFile12(sourceOpenAiPath, "utf8"), {
17265
+ await preprocessText(await readFile13(sourceOpenAiPath, "utf8"), {
16641
17266
  ...context,
16642
17267
  sourcePath: sourceOpenAiPath
16643
17268
  });
16644
17269
  }
16645
- return [...dependencies].sort(compareStrings);
17270
+ return formattedPreprocessDependencies2(graph, dependencies);
16646
17271
  }
16647
17272
  async function rulePreprocessDependencies(graph, rule) {
16648
17273
  const dependencies = new Set;
@@ -16651,14 +17276,14 @@ async function rulePreprocessDependencies(graph, rule) {
16651
17276
  preprocessDependencies: dependencies,
16652
17277
  rootPath: graph.rootPath,
16653
17278
  sourcePath: rule.sourcePath,
16654
- sourceRoot: join12(graph.sourceDir, "src"),
17279
+ sourceRoot: graph.sourceRoot,
16655
17280
  variables: {
16656
17281
  "skillset.output_dir": ".",
16657
17282
  "skillset.repo_root": ".",
16658
17283
  "skillset.source_rule": relativePath(graph, rule.sourcePath)
16659
17284
  }
16660
17285
  });
16661
- return [...dependencies].sort(compareStrings);
17286
+ return formattedPreprocessDependencies2(graph, dependencies);
16662
17287
  }
16663
17288
  async function projectAgentPreprocessDependencies2(graph, agent) {
16664
17289
  const dependencies = new Set;
@@ -16670,7 +17295,7 @@ async function projectAgentPreprocessDependencies2(graph, agent) {
16670
17295
  preprocessDependencies: dependencies,
16671
17296
  rootPath: graph.rootPath,
16672
17297
  sourcePath: agent.sourcePath,
16673
- sourceRoot: join12(graph.sourceDir, "src")
17298
+ sourceRoot: graph.sourceRoot
16674
17299
  });
16675
17300
  };
16676
17301
  await collect(agent.body);
@@ -16678,13 +17303,13 @@ async function projectAgentPreprocessDependencies2(graph, agent) {
16678
17303
  await collect(readString(agent.targets.claude.options, "initialPrompt"));
16679
17304
  await collect(readString(agent.targets.codex.options, "initialPrompt"));
16680
17305
  await collect(readString(agent.targets.codex.options, "developer_instructions"));
16681
- return [...dependencies].sort(compareStrings);
17306
+ return formattedPreprocessDependencies2(graph, dependencies);
16682
17307
  }
16683
17308
  async function islandPreprocessDependencies(graph, island) {
16684
17309
  if (!isTextIslandFile2(island.relativePath))
16685
17310
  return [];
16686
17311
  const dependencies = new Set;
16687
- const source2 = await readFile12(island.sourcePath, "utf8");
17312
+ const source2 = await readFile13(island.sourcePath, "utf8");
16688
17313
  if (island.relativePath.endsWith(".md")) {
16689
17314
  const parsed = parseMarkdown(source2, island.sourcePath);
16690
17315
  await preprocessText(parsed.body, {
@@ -16692,7 +17317,7 @@ async function islandPreprocessDependencies(graph, island) {
16692
17317
  preprocessDependencies: dependencies,
16693
17318
  rootPath: graph.rootPath,
16694
17319
  sourcePath: island.sourcePath,
16695
- sourceRoot: join12(graph.sourceDir, "src")
17320
+ sourceRoot: graph.sourceRoot
16696
17321
  });
16697
17322
  } else {
16698
17323
  await preprocessText(source2, {
@@ -16700,10 +17325,13 @@ async function islandPreprocessDependencies(graph, island) {
16700
17325
  preprocessDependencies: dependencies,
16701
17326
  rootPath: graph.rootPath,
16702
17327
  sourcePath: island.sourcePath,
16703
- sourceRoot: join12(graph.sourceDir, "src")
17328
+ sourceRoot: graph.sourceRoot
16704
17329
  });
16705
17330
  }
16706
- return [...dependencies].sort(compareStrings);
17331
+ return formattedPreprocessDependencies2(graph, dependencies);
17332
+ }
17333
+ function formattedPreprocessDependencies2(graph, dependencies) {
17334
+ return [...dependencies].map((dependency) => formatPreprocessDependency(graph.rootPath, dependency)).sort(compareStrings);
16707
17335
  }
16708
17336
  function isTextIslandFile2(path2) {
16709
17337
  return /\.(json|md|rules|toml|txt|ya?ml)$/.test(path2);
@@ -16778,15 +17406,15 @@ function compareInventories(current, baseline) {
16778
17406
  }
16779
17407
  return changes.sort((left, right) => compareStrings(left.id, right.id));
16780
17408
  }
16781
- async function resolveBaselineInventory(rootPath, options) {
16782
- if (options.since !== undefined) {
16783
- return inventoryFromGitRef(rootPath, options.since, options);
17409
+ async function resolveBaselineInventory(rootPath, baselineOptions, releaseOptions) {
17410
+ if (baselineOptions.since !== undefined) {
17411
+ return inventoryFromGitRef(rootPath, baselineOptions.since, baselineOptions);
16784
17412
  }
16785
- if (options.staged === true) {
16786
- return inventoryFromGitRef(rootPath, "HEAD", options);
17413
+ if (baselineOptions.staged === true) {
17414
+ return inventoryFromGitRef(rootPath, "HEAD", baselineOptions);
16787
17415
  }
16788
- const fallback = await fallbackBaselineInventory(rootPath, options);
16789
- const releaseInventory = await sourceInventoryFromReleaseState(rootPath, options, fallback.inventory);
17416
+ const fallback = await fallbackBaselineInventory(rootPath, baselineOptions);
17417
+ const releaseInventory = await sourceInventoryFromReleaseState(rootPath, releaseOptions, fallback.inventory);
16790
17418
  if (releaseInventory !== undefined)
16791
17419
  return releaseInventory;
16792
17420
  return fallback;
@@ -16801,8 +17429,7 @@ async function fallbackBaselineInventory(rootPath, options) {
16801
17429
  async function inventoryFromGitRef(rootPath, ref, options) {
16802
17430
  const snapshotPath = await snapshotGitRef(rootPath, ref);
16803
17431
  try {
16804
- await normalizeLegacyBaselineSnapshot(snapshotPath, options);
16805
- const inventory = await collectSourceInventory(snapshotPath, options);
17432
+ const inventory = await collectGitSnapshotInventory(snapshotPath, options);
16806
17433
  const resolvedRef = await gitRevParse(rootPath, ref);
16807
17434
  return {
16808
17435
  baseline: { kind: "git-ref", ref, ...resolvedRef === undefined ? {} : { resolvedRef } },
@@ -16812,8 +17439,32 @@ async function inventoryFromGitRef(rootPath, ref, options) {
16812
17439
  await rm2(snapshotPath, { force: true, recursive: true });
16813
17440
  }
16814
17441
  }
17442
+ async function collectGitSnapshotInventory(snapshotPath, options) {
17443
+ try {
17444
+ await normalizeLegacyBaselineSnapshot(snapshotPath, options);
17445
+ return await collectSourceInventory(snapshotPath, options);
17446
+ } catch (error) {
17447
+ if (options.sourceDir === undefined || !canRetryBaselineWithDetectedLayout(error))
17448
+ throw error;
17449
+ const autoOptions = withoutSourceDir(options);
17450
+ await normalizeLegacyBaselineSnapshot(snapshotPath, autoOptions);
17451
+ return collectSourceInventory(snapshotPath, autoOptions);
17452
+ }
17453
+ }
17454
+ function withoutSourceDir(options) {
17455
+ const { sourceDir: _sourceDir, ...rest } = options;
17456
+ return rest;
17457
+ }
17458
+ function canRetryBaselineWithDetectedLayout(error) {
17459
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")
17460
+ return true;
17461
+ const message = error instanceof Error ? error.message : String(error);
17462
+ return message.startsWith("skillset: no source plugins, skills, rules, project agents, or provider source found");
17463
+ }
16815
17464
  async function normalizeLegacyBaselineSnapshot(snapshotPath, options) {
16816
17465
  const sourceDir = options.sourceDir ?? ".skillset";
17466
+ if (sourceDir === ".")
17467
+ return;
16817
17468
  const skillsetPath = join12(snapshotPath, sourceDir);
16818
17469
  if (!await exists7(skillsetPath))
16819
17470
  return;
@@ -16826,7 +17477,7 @@ async function normalizeLegacyBaselineSnapshot(snapshotPath, options) {
16826
17477
  async function moveLegacyBaselinePluginProviderDirs(pluginsPath) {
16827
17478
  if (!await exists7(pluginsPath))
16828
17479
  return;
16829
- const entries = await readdir5(pluginsPath, { withFileTypes: true });
17480
+ const entries = await readdir6(pluginsPath, { withFileTypes: true });
16830
17481
  for (const entry of entries) {
16831
17482
  if (!entry.isDirectory())
16832
17483
  continue;
@@ -16839,7 +17490,7 @@ async function splitLegacyBaselineRootConfig(skillsetPath) {
16839
17490
  const configPath = join12(skillsetPath, ROOT_CONFIG_FILE2);
16840
17491
  if (!await exists7(configPath))
16841
17492
  return;
16842
- const config2 = parseYamlRecord(await readFile12(configPath, "utf8"), configPath);
17493
+ const config2 = parseYamlRecord(await readFile13(configPath, "utf8"), configPath);
16843
17494
  const manifest = sourceManifestFromLegacyRootConfig(config2);
16844
17495
  if (manifest === undefined)
16845
17496
  return;
@@ -16848,8 +17499,8 @@ async function splitLegacyBaselineRootConfig(skillsetPath) {
16848
17499
  throw new Error(`skillset: cannot normalize baseline with both root source metadata and ${manifestPath}`);
16849
17500
  }
16850
17501
  await mkdir4(dirname9(manifestPath), { recursive: true });
16851
- await writeFile4(manifestPath, stringifyYaml(manifest), "utf8");
16852
- await writeFile4(configPath, stringifyLegacyWorkspaceConfig(workspaceConfigFromLegacyRootConfig(config2)), "utf8");
17502
+ await writeFile5(manifestPath, stringifyYaml(manifest), "utf8");
17503
+ await writeFile5(configPath, stringifyLegacyWorkspaceConfig(workspaceConfigFromLegacyRootConfig(config2)), "utf8");
16853
17504
  }
16854
17505
  function sourceManifestFromLegacyRootConfig(record) {
16855
17506
  const manifest = {};
@@ -16903,13 +17554,16 @@ async function sourceInventoryFromReleaseState(rootPath, options, fallback) {
16903
17554
  });
16904
17555
  }
16905
17556
  return {
16906
- baseline: { hashSchema: SOURCE_HASH_SCHEMA, kind: "source-inventory", label: ".skillset/changes/state.json" },
17557
+ baseline: { hashSchema: SOURCE_HASH_SCHEMA, kind: "source-inventory", label: releaseStateRelativePath(options) },
16907
17558
  inventory: {
16908
17559
  hashSchema: SOURCE_HASH_SCHEMA,
16909
17560
  units: [...units.values()].sort((left, right) => compareStrings(left.id, right.id))
16910
17561
  }
16911
17562
  };
16912
17563
  }
17564
+ function releaseStateRelativePath(options) {
17565
+ return join12(options.sourceDir ?? ".skillset", "changes/state.json");
17566
+ }
16913
17567
  function inferredReleaseUnit(id, hash) {
16914
17568
  const kind = kindForSourceUnitId(id);
16915
17569
  return {
@@ -16952,7 +17606,7 @@ async function sourceInventoryFromLock(rootPath, _options) {
16952
17606
  return;
16953
17607
  let parsed;
16954
17608
  try {
16955
- parsed = JSON.parse(await readFile12(lockPath, "utf8"));
17609
+ parsed = JSON.parse(await readFile13(lockPath, "utf8"));
16956
17610
  } catch {
16957
17611
  return;
16958
17612
  }
@@ -17042,7 +17696,7 @@ ${stderr.trim()}`);
17042
17696
  }
17043
17697
  async function regionsForYaml(sourcePath) {
17044
17698
  try {
17045
- return regionsForRecord(parseYamlRecord(await readFile12(sourcePath, "utf8"), sourcePath));
17699
+ return regionsForRecord(parseYamlRecord(await readFile13(sourcePath, "utf8"), sourcePath));
17046
17700
  } catch {
17047
17701
  return [];
17048
17702
  }
@@ -17108,7 +17762,7 @@ async function exists7(path2) {
17108
17762
  }
17109
17763
 
17110
17764
  // apps/skillset/src/change-entries.ts
17111
- var PENDING_DIR = "changes/pending";
17765
+ var PENDING_DIR = "changes";
17112
17766
  var DEFAULT_REASON_MIN_LENGTH = 40;
17113
17767
  var CHANGE_ID_PATTERN = /^[0-9a-f]{12}$/;
17114
17768
  var CHANGE_REF_MIN_LENGTH = 6;
@@ -17118,8 +17772,10 @@ async function changeCheck(rootPath, options = {}) {
17118
17772
  const stagedSnapshot = options.staged === true ? await snapshotGitIndex(rootPath) : undefined;
17119
17773
  try {
17120
17774
  const status = await changeStatus(rootPath, options);
17121
- const entries = await readPendingChangeEntries(stagedSnapshot ?? rootPath, options);
17122
- return await validateChangeCheck(stagedSnapshot ?? rootPath, status, entries, options);
17775
+ const currentRoot = stagedSnapshot ?? rootPath;
17776
+ const storageOptions = await detectWorkspaceOptions(currentRoot, options);
17777
+ const entries = await readPendingChangeEntries(currentRoot, storageOptions);
17778
+ return await validateChangeCheck(currentRoot, status, entries, storageOptions);
17123
17779
  } finally {
17124
17780
  if (stagedSnapshot !== undefined)
17125
17781
  await rm3(stagedSnapshot, { force: true, recursive: true });
@@ -17190,7 +17846,7 @@ async function readPendingChangeEntries(rootPath, options = {}) {
17190
17846
  for (const file of files.sort(compareStrings)) {
17191
17847
  const path2 = join13(sourceDir, PENDING_DIR, file).replaceAll("\\", "/");
17192
17848
  const absolutePath = resolveInside(rootPath, path2);
17193
- const parts = parseMarkdown(await readFile13(absolutePath, "utf8"), absolutePath);
17849
+ const parts = parseMarkdown(await readFile14(absolutePath, "utf8"), absolutePath);
17194
17850
  const id = readString(parts.frontmatter, "id");
17195
17851
  const bump = readBump(parts.frontmatter.bump);
17196
17852
  const ignored = parts.frontmatter.ignored === true;
@@ -17427,8 +18083,11 @@ function readEvidenceHash(record) {
17427
18083
  }
17428
18084
  async function readReasonMinLength(rootPath, options) {
17429
18085
  const sourceDir = options.sourceDir ?? ".skillset";
17430
- const configPath = resolveInside(rootPath, join13(sourceDir, "config.yaml"));
17431
- const config2 = parseYamlRecord(await readFile13(configPath, "utf8"), configPath);
18086
+ const configPaths = sourceDir === "." ? ["skillset.yaml"] : [join13(sourceDir, "config.yaml"), join13(sourceDir, "skillset.yaml")];
18087
+ const resolvedConfigPath = await firstExistingPath(configPaths.map((path2) => resolveInside(rootPath, path2)));
18088
+ if (resolvedConfigPath === undefined)
18089
+ return DEFAULT_REASON_MIN_LENGTH;
18090
+ const config2 = parseYamlRecord(await readFile14(resolvedConfigPath, "utf8"), resolvedConfigPath);
17432
18091
  const changes = config2.changes;
17433
18092
  if (!isJsonRecord(changes))
17434
18093
  return DEFAULT_REASON_MIN_LENGTH;
@@ -17438,6 +18097,13 @@ async function readReasonMinLength(rootPath, options) {
17438
18097
  const minLength = reason.minLength;
17439
18098
  return typeof minLength === "number" && Number.isInteger(minLength) && minLength > 0 ? minLength : DEFAULT_REASON_MIN_LENGTH;
17440
18099
  }
18100
+ async function firstExistingPath(paths) {
18101
+ for (const path2 of paths) {
18102
+ if (await exists8(path2))
18103
+ return path2;
18104
+ }
18105
+ return;
18106
+ }
17441
18107
  function scopeClass(scope) {
17442
18108
  const normalizedScope = sourceUnitSelector(scope);
17443
18109
  return normalizedScope.startsWith("user:") || normalizedScope.startsWith("global:") ? "user" : "repo";
@@ -17458,48 +18124,43 @@ async function exists8(path2) {
17458
18124
  }
17459
18125
 
17460
18126
  // apps/skillset/src/adopt.ts
17461
- import { mkdtemp as mkdtemp3, mkdir as mkdir7, readFile as readFile16, rm as rm5, stat as stat14, writeFile as writeFile7 } from "fs/promises";
18127
+ import { mkdtemp as mkdtemp3, mkdir as mkdir7, readFile as readFile17, rm as rm5, stat as stat13, writeFile as writeFile8 } from "fs/promises";
17462
18128
  import { tmpdir as tmpdir2 } from "os";
17463
- import { basename as basename7, dirname as dirname12, join as join17, relative as relative15, resolve as resolve7 } from "path";
18129
+ import { basename as basename8, dirname as dirname12, join as join16, relative as relative15, resolve as resolve7 } from "path";
17464
18130
 
17465
18131
  // apps/skillset/src/import.ts
17466
- import { mkdir as mkdir5, mkdtemp as mkdtemp2, readdir as readdir6, readFile as readFile14, realpath, rename as rename2, rm as rm4, stat as stat12, writeFile as writeFile5 } from "fs/promises";
18132
+ import { mkdir as mkdir5, mkdtemp as mkdtemp2, readdir as readdir7, readFile as readFile15, realpath, rename as rename2, rm as rm4, stat as stat11, writeFile as writeFile6 } from "fs/promises";
17467
18133
  import { homedir } from "os";
17468
- import { basename as basename5, dirname as dirname10, join as join15, relative as relative13, resolve as resolve5 } from "path";
17469
-
18134
+ import { basename as basename6, dirname as dirname10, join as join14, relative as relative13, resolve as resolve5 } from "path";
17470
18135
  // apps/skillset/src/adoption.ts
17471
- import { stat as stat11 } from "fs/promises";
17472
- import { join as join14 } from "path";
17473
- var ROOT_CONFIG_FILE3 = "config.yaml";
17474
18136
  var NO_SOURCE_MESSAGE = "skillset: no source plugins, skills, rules, project agents, or provider source found";
17475
18137
  async function seedReleaseBaselines(rootPath, options = {}, seedOptions = {}) {
17476
- const sourceDir = options.sourceDir ?? ".skillset";
17477
- const configPath = resolveInside(rootPath, join14(sourceDir, ROOT_CONFIG_FILE3));
17478
- if (!await exists9(configPath)) {
17479
- return {
17480
- conflicts: [],
17481
- entries: [],
17482
- skippedReason: `${sourceDir}/${ROOT_CONFIG_FILE3} not found`,
17483
- write: seedOptions.write === true
17484
- };
17485
- }
17486
18138
  let graph;
17487
18139
  try {
17488
18140
  graph = await loadBuildGraph(rootPath, options);
17489
18141
  } catch (error) {
17490
- const message = error instanceof Error ? error.message : String(error);
17491
- if (message.startsWith(NO_SOURCE_MESSAGE)) {
18142
+ if (isErrno2(error, "ENOENT") && !await workspaceMarkerExists(rootPath, options)) {
17492
18143
  return {
17493
18144
  conflicts: [],
17494
18145
  entries: [],
17495
- skippedReason: "no source units to adopt",
18146
+ skippedReason: "skillset workspace not found",
17496
18147
  write: seedOptions.write === true
17497
18148
  };
17498
18149
  }
17499
- throw error;
17500
- }
17501
- const inventory = await collectSourceInventory(rootPath, options);
17502
- const state = await readReleaseState(rootPath, options);
18150
+ const message = error instanceof Error ? error.message : String(error);
18151
+ if (message.startsWith(NO_SOURCE_MESSAGE)) {
18152
+ return {
18153
+ conflicts: [],
18154
+ entries: [],
18155
+ skippedReason: "no source units to adopt",
18156
+ write: seedOptions.write === true
18157
+ };
18158
+ }
18159
+ throw error;
18160
+ }
18161
+ const resolvedOptions = { ...options, sourceDir: graph.sourceDir };
18162
+ const inventory = await collectSourceInventory(rootPath, resolvedOptions);
18163
+ const state = await readReleaseState(rootPath, resolvedOptions);
17503
18164
  const scopeFilter = seedOptions.scopes === undefined ? undefined : new Set(seedOptions.scopes.map(sourceUnitSelector));
17504
18165
  const entries = [];
17505
18166
  const conflicts = [];
@@ -17551,7 +18212,7 @@ async function seedReleaseBaselines(rootPath, options = {}, seedOptions = {}) {
17551
18212
  }
17552
18213
  let path2;
17553
18214
  if (seedOptions.write === true && entries.some((entry) => entry.status === "create")) {
17554
- path2 = await writeReleaseState(rootPath, { scopes }, options);
18215
+ path2 = await writeReleaseState(rootPath, { scopes }, resolvedOptions);
17555
18216
  }
17556
18217
  return {
17557
18218
  conflicts,
@@ -17592,16 +18253,16 @@ function sourceRootVersion(graph) {
17592
18253
  function sourcePluginVersion(graph, plugin) {
17593
18254
  return readString(plugin.metadata, "version") ?? sourceRootVersion(graph);
17594
18255
  }
17595
- async function exists9(path2) {
17596
- try {
17597
- await stat11(path2);
17598
- return true;
17599
- } catch (error) {
17600
- if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
17601
- return false;
17602
- }
17603
- throw error;
18256
+ async function workspaceMarkerExists(rootPath, options) {
18257
+ if (options.sourceDir === ".")
18258
+ return Bun.file(`${rootPath}/skillset.yaml`).exists();
18259
+ if (options.sourceDir !== undefined) {
18260
+ return await Bun.file(`${rootPath}/${options.sourceDir}/skillset.yaml`).exists() || await Bun.file(`${rootPath}/${options.sourceDir}/config.yaml`).exists();
17604
18261
  }
18262
+ return await Bun.file(`${rootPath}/skillset.yaml`).exists() || await Bun.file(`${rootPath}/.skillset/skillset.yaml`).exists() || await Bun.file(`${rootPath}/.skillset/config.yaml`).exists();
18263
+ }
18264
+ function isErrno2(error, code) {
18265
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
17605
18266
  }
17606
18267
 
17607
18268
  // apps/skillset/src/import.ts
@@ -17665,15 +18326,16 @@ async function importSources(options) {
17665
18326
  }
17666
18327
  async function importSource(options) {
17667
18328
  const sourcePath = resolve5(options.sourcePath);
17668
- const sourceDir = options.sourceDir ?? DEFAULT_SOURCE_DIR2;
18329
+ const sourceDir = await resolveImportSourceDir(options.rootPath, options.sourceDir);
18330
+ const sourceRoot = sourceDir === "." ? "skillset" : join14(sourceDir, SOURCE_ROOT_DIR3);
17669
18331
  const name = await resolveImportName(sourcePath, options);
17670
- const targetPath = resolveInside(options.rootPath, join15(sourceDir, SOURCE_ROOT_DIR3, options.kind === "plugin" ? PLUGINS_DIR2 : SKILLS_DIR2, name));
17671
- if (await exists10(targetPath)) {
18332
+ const targetPath = resolveInside(options.rootPath, join14(sourceRoot, options.kind === "plugin" ? PLUGINS_DIR2 : SKILLS_DIR2, name));
18333
+ if (await exists9(targetPath)) {
17672
18334
  throw new Error(`skillset: import target already exists: ${targetPath}. ` + "Import never overwrites; remove the existing source or import under a different --name.");
17673
18335
  }
17674
18336
  const targetParent = dirname10(targetPath);
17675
18337
  await mkdir5(targetParent, { recursive: true });
17676
- const stagingPath = await mkdtemp2(join15(targetParent, `.${basename5(targetPath)}.tmp-`));
18338
+ const stagingPath = await mkdtemp2(join14(targetParent, `.${basename6(targetPath)}.tmp-`));
17677
18339
  let committed = false;
17678
18340
  try {
17679
18341
  const copiedFiles = await copyImportSource(sourcePath, stagingPath, options.kind, name);
@@ -17682,7 +18344,7 @@ async function importSource(options) {
17682
18344
  }
17683
18345
  const frontmatter = await readImportedFrontmatter(stagingPath, options.kind);
17684
18346
  const classification = classifyFrontmatter(frontmatter);
17685
- if (await exists10(targetPath)) {
18347
+ if (await exists9(targetPath)) {
17686
18348
  throw new Error(`skillset: import target already exists: ${targetPath}. ` + "Import never overwrites; remove the existing source or import under a different --name.");
17687
18349
  }
17688
18350
  await rename2(stagingPath, targetPath);
@@ -17729,6 +18391,23 @@ async function importSource(options) {
17729
18391
  }
17730
18392
  }
17731
18393
  }
18394
+ async function resolveImportSourceDir(rootPath, explicitSourceDir) {
18395
+ if (explicitSourceDir !== undefined)
18396
+ return explicitSourceDir;
18397
+ try {
18398
+ return await detectWorkspaceSourceDir(rootPath);
18399
+ } catch (error) {
18400
+ if (isMissingWorkspace(error))
18401
+ return DEFAULT_SOURCE_DIR2;
18402
+ throw error;
18403
+ }
18404
+ }
18405
+ function isMissingWorkspace(error) {
18406
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")
18407
+ return true;
18408
+ const message = error instanceof Error ? error.message : String(error);
18409
+ return message.includes("skillset workspace not found") || message.includes("no source plugins, skills, rules");
18410
+ }
17732
18411
  async function seedImportedBaselines(rootPath, options) {
17733
18412
  const includeScope = (scope) => {
17734
18413
  if (options.kind === "skill")
@@ -17790,20 +18469,20 @@ function isClaudeToolPolicyField(field) {
17790
18469
  return field === "allowed-tools" || field === "disallowed-tools" || field === "disable-model-invocation";
17791
18470
  }
17792
18471
  function importSourcePath(rootPath, targetPath, kind) {
17793
- const path2 = kind === "skill" ? join15(targetPath, "SKILL.md") : join15(targetPath, "skillset.yaml");
18472
+ const path2 = kind === "skill" ? join14(targetPath, "SKILL.md") : join14(targetPath, "skillset.yaml");
17794
18473
  return relative13(rootPath, path2).replaceAll("\\", "/");
17795
18474
  }
17796
18475
  async function readImportedFrontmatter(targetPath, kind) {
17797
18476
  if (kind === "skill") {
17798
- const skillFile = join15(targetPath, "SKILL.md");
17799
- if (!await exists10(skillFile))
18477
+ const skillFile = join14(targetPath, "SKILL.md");
18478
+ if (!await exists9(skillFile))
17800
18479
  return {};
17801
- return parseMarkdown(await readFile14(skillFile, "utf8"), skillFile).frontmatter;
18480
+ return parseMarkdown(await readFile15(skillFile, "utf8"), skillFile).frontmatter;
17802
18481
  }
17803
- const configPath = join15(targetPath, "skillset.yaml");
17804
- if (!await exists10(configPath))
18482
+ const configPath = join14(targetPath, "skillset.yaml");
18483
+ if (!await exists9(configPath))
17805
18484
  return {};
17806
- return parseYamlRecord(await readFile14(configPath, "utf8"), configPath);
18485
+ return parseYamlRecord(await readFile15(configPath, "utf8"), configPath);
17807
18486
  }
17808
18487
  async function resolveImportName(sourcePath, options) {
17809
18488
  if (options.name !== undefined) {
@@ -17811,22 +18490,22 @@ async function resolveImportName(sourcePath, options) {
17811
18490
  }
17812
18491
  if (options.kind === "skill") {
17813
18492
  const skillPath = await resolveSkillFile(sourcePath);
17814
- const parts = parseMarkdown(await readFile14(skillPath, "utf8"), skillPath);
18493
+ const parts = parseMarkdown(await readFile15(skillPath, "utf8"), skillPath);
17815
18494
  const metadata2 = readSkillsetMetadata(parts.frontmatter, skillPath);
17816
- return validateSlug(readSkillsetName(metadata2, readString(parts.frontmatter, "name") ?? basename5(dirname10(skillPath)), skillPath), `skillset.name in ${skillPath}`);
18495
+ return validateSlug(readSkillsetName(metadata2, readString(parts.frontmatter, "name") ?? basename6(dirname10(skillPath)), skillPath), `skillset.name in ${skillPath}`);
17817
18496
  }
17818
18497
  const configPath = await resolvePluginConfig(sourcePath);
17819
18498
  if (configPath === undefined) {
17820
- return validateSlug(basename5(sourcePath), "plugin directory");
18499
+ return validateSlug(basename6(sourcePath), "plugin directory");
17821
18500
  }
17822
- const config2 = parseYamlRecord(await readFile14(configPath, "utf8"), configPath);
18501
+ const config2 = parseYamlRecord(await readFile15(configPath, "utf8"), configPath);
17823
18502
  const metadata = readSkillsetMetadata(config2, configPath);
17824
- return validateSlug(readSkillsetName(metadata, basename5(sourcePath), configPath), `skillset.name in ${configPath}`);
18503
+ return validateSlug(readSkillsetName(metadata, basename6(sourcePath), configPath), `skillset.name in ${configPath}`);
17825
18504
  }
17826
18505
  async function copyImportSource(sourcePath, targetPath, kind, name) {
17827
- const stats = await stat12(sourcePath);
18506
+ const stats = await stat11(sourcePath);
17828
18507
  if (stats.isFile()) {
17829
- if (kind !== "skill" || basename5(sourcePath) !== "SKILL.md") {
18508
+ if (kind !== "skill" || basename6(sourcePath) !== "SKILL.md") {
17830
18509
  throw new Error("skillset: importing a file is only supported for skill SKILL.md files");
17831
18510
  }
17832
18511
  }
@@ -17834,11 +18513,11 @@ async function copyImportSource(sourcePath, targetPath, kind, name) {
17834
18513
  const copied = [];
17835
18514
  for (const file of await collectFiles6(copyRoot)) {
17836
18515
  const relativePath2 = relativeImportPath(copyRoot, file, kind);
17837
- await mkdir5(dirname10(join15(targetPath, relativePath2)), { recursive: true });
17838
- await writeFile5(join15(targetPath, relativePath2), await readFile14(file));
18516
+ await mkdir5(dirname10(join14(targetPath, relativePath2)), { recursive: true });
18517
+ await writeFile6(join14(targetPath, relativePath2), await readFile15(file));
17839
18518
  copied.push(relativePath2);
17840
18519
  }
17841
- if (kind === "plugin" && !await exists10(join15(targetPath, "skillset.yaml"))) {
18520
+ if (kind === "plugin" && !await exists9(join14(targetPath, "skillset.yaml"))) {
17842
18521
  await writeImportedPluginConfig(targetPath, name);
17843
18522
  copied.push("skillset.yaml");
17844
18523
  }
@@ -17846,21 +18525,21 @@ async function copyImportSource(sourcePath, targetPath, kind, name) {
17846
18525
  }
17847
18526
  async function stampImportedOrigins(targetPath, sourcePath, copiedFiles, kind, sourceOrigin) {
17848
18527
  if (kind === "plugin") {
17849
- await writeYamlSourceOrigin(join15(targetPath, "skillset.yaml"), sourceOrigin(sourcePath));
18528
+ await writeYamlSourceOrigin(join14(targetPath, "skillset.yaml"), sourceOrigin(sourcePath));
17850
18529
  }
17851
18530
  for (const file of copiedFiles) {
17852
- if (basename5(file) !== "SKILL.md")
18531
+ if (basename6(file) !== "SKILL.md")
17853
18532
  continue;
17854
- await writeMarkdownSourceOrigin(join15(targetPath, file), sourceOrigin(sourcePath, file));
18533
+ await writeMarkdownSourceOrigin(join14(targetPath, file), sourceOrigin(sourcePath, file));
17855
18534
  }
17856
18535
  }
17857
18536
  async function writeYamlSourceOrigin(path2, origin) {
17858
- const config2 = parseYamlRecord(await readFile14(path2, "utf8"), path2);
17859
- await writeFile5(path2, stringifyYaml(withSkillsetOrigin(config2, origin)));
18537
+ const config2 = parseYamlRecord(await readFile15(path2, "utf8"), path2);
18538
+ await writeFile6(path2, stringifyYaml(withSkillsetOrigin(config2, origin)));
17860
18539
  }
17861
18540
  async function writeMarkdownSourceOrigin(path2, origin) {
17862
- const parts = parseMarkdown(await readFile14(path2, "utf8"), path2);
17863
- await writeFile5(path2, stringifyMarkdown(withSkillsetOrigin(parts.frontmatter, origin), parts.body));
18541
+ const parts = parseMarkdown(await readFile15(path2, "utf8"), path2);
18542
+ await writeFile6(path2, stringifyMarkdown(withSkillsetOrigin(parts.frontmatter, origin), parts.body));
17864
18543
  }
17865
18544
  function withSkillsetOrigin(record, origin) {
17866
18545
  const existing = isJsonRecord(record.skillset) ? record.skillset : {};
@@ -17887,24 +18566,24 @@ function relativeImportPath(sourceRoot, file, kind) {
17887
18566
  return relativePath2;
17888
18567
  }
17889
18568
  async function resolveSkillFile(sourcePath) {
17890
- const stats = await stat12(sourcePath);
18569
+ const stats = await stat11(sourcePath);
17891
18570
  if (stats.isFile())
17892
18571
  return sourcePath;
17893
- return join15(sourcePath, "SKILL.md");
18572
+ return join14(sourcePath, "SKILL.md");
17894
18573
  }
17895
18574
  async function resolvePluginConfig(sourcePath) {
17896
18575
  for (const file of ["config.yaml", "skillset.yaml"]) {
17897
- const candidate = join15(sourcePath, file);
17898
- if (await exists10(candidate))
18576
+ const candidate = join14(sourcePath, file);
18577
+ if (await exists9(candidate))
17899
18578
  return candidate;
17900
18579
  }
17901
18580
  return;
17902
18581
  }
17903
18582
  async function collectFiles6(root) {
17904
- const entries = await readdir6(root, { withFileTypes: true });
18583
+ const entries = await readdir7(root, { withFileTypes: true });
17905
18584
  const files = [];
17906
18585
  for (const entry of entries.sort((left, right) => compareStrings(left.name, right.name))) {
17907
- const path2 = join15(root, entry.name);
18586
+ const path2 = join14(root, entry.name);
17908
18587
  if (entry.isDirectory()) {
17909
18588
  files.push(...await collectFiles6(path2));
17910
18589
  } else if (entry.isFile() && entry.name !== ".DS_Store") {
@@ -17923,12 +18602,12 @@ function resolveImportSourcePath(options) {
17923
18602
  function defaultProviderSkillRoot(provider) {
17924
18603
  const home = homedir();
17925
18604
  if (provider === "agents")
17926
- return join15(home, ".agents", "skills");
18605
+ return join14(home, ".agents", "skills");
17927
18606
  if (provider === "claude")
17928
- return join15(home, ".claude", "skills");
18607
+ return join14(home, ".claude", "skills");
17929
18608
  if (provider === "codex")
17930
- return join15(home, ".codex", "skills");
17931
- return join15(home, ".skillset");
18609
+ return join14(home, ".codex", "skills");
18610
+ return join14(home, ".skillset");
17932
18611
  }
17933
18612
  async function planImports(sourcePath, requestedKind) {
17934
18613
  if (requestedKind === "skill") {
@@ -17977,46 +18656,46 @@ async function planImports(sourcePath, requestedKind) {
17977
18656
  throw new Error(`skillset: could not infer import kind for ${sourcePath}. ` + "Use --kind skill, --kind skills, --kind plugin, or --kind plugins.");
17978
18657
  }
17979
18658
  async function isSkillSource(sourcePath) {
17980
- if (!await exists10(sourcePath))
18659
+ if (!await exists9(sourcePath))
17981
18660
  return false;
17982
- const stats = await stat12(sourcePath);
18661
+ const stats = await stat11(sourcePath);
17983
18662
  if (stats.isFile())
17984
- return basename5(sourcePath) === "SKILL.md";
18663
+ return basename6(sourcePath) === "SKILL.md";
17985
18664
  if (!stats.isDirectory())
17986
18665
  return false;
17987
- return exists10(join15(sourcePath, "SKILL.md"));
18666
+ return exists9(join14(sourcePath, "SKILL.md"));
17988
18667
  }
17989
18668
  async function isPluginSource(sourcePath) {
17990
- if (!await exists10(sourcePath))
18669
+ if (!await exists9(sourcePath))
17991
18670
  return false;
17992
- const stats = await stat12(sourcePath);
18671
+ const stats = await stat11(sourcePath);
17993
18672
  if (!stats.isDirectory())
17994
18673
  return false;
17995
- return await exists10(join15(sourcePath, "skillset.yaml")) || await exists10(join15(sourcePath, "config.yaml")) || await exists10(join15(sourcePath, ".claude-plugin", "plugin.json")) || await exists10(join15(sourcePath, ".codex-plugin", "plugin.json"));
18674
+ return await exists9(join14(sourcePath, "skillset.yaml")) || await exists9(join14(sourcePath, "config.yaml")) || await exists9(join14(sourcePath, ".claude-plugin", "plugin.json")) || await exists9(join14(sourcePath, ".codex-plugin", "plugin.json"));
17996
18675
  }
17997
18676
  async function skillChildren(sourcePath) {
17998
18677
  return importChildren(sourcePath, "skill", async (path2) => isSkillSource(path2));
17999
18678
  }
18000
18679
  async function pluginChildren(sourcePath) {
18001
- const pluginRoot = await exists10(join15(sourcePath, "plugins")) ? join15(sourcePath, "plugins") : sourcePath;
18680
+ const pluginRoot = await exists9(join14(sourcePath, "plugins")) ? join14(sourcePath, "plugins") : sourcePath;
18002
18681
  return importChildren(pluginRoot, "plugin", async (path2) => isPluginSource(path2));
18003
18682
  }
18004
18683
  async function importChildren(sourcePath, kind, predicate) {
18005
- if (!await exists10(sourcePath))
18684
+ if (!await exists9(sourcePath))
18006
18685
  return [];
18007
- const stats = await stat12(sourcePath);
18686
+ const stats = await stat11(sourcePath);
18008
18687
  if (!stats.isDirectory())
18009
18688
  return [];
18010
- const entries = await readdir6(sourcePath, { withFileTypes: true });
18689
+ const entries = await readdir7(sourcePath, { withFileTypes: true });
18011
18690
  const seen = new Set;
18012
18691
  const items = [];
18013
18692
  for (const entry of entries.sort((left, right) => compareStrings(left.name, right.name))) {
18014
18693
  if (entry.name === ".DS_Store")
18015
18694
  continue;
18016
- const candidate = join15(sourcePath, entry.name);
18017
- if (!await exists10(candidate))
18695
+ const candidate = join14(sourcePath, entry.name);
18696
+ if (!await exists9(candidate))
18018
18697
  continue;
18019
- const candidateStats = await stat12(candidate);
18698
+ const candidateStats = await stat11(candidate);
18020
18699
  if (!candidateStats.isDirectory())
18021
18700
  continue;
18022
18701
  if (!await predicate(candidate))
@@ -18041,16 +18720,16 @@ async function writeImportedPluginConfig(targetPath, name) {
18041
18720
  license: nativeManifest.license,
18042
18721
  keywords: nativeManifest.keywords
18043
18722
  };
18044
- await writeFile5(join15(targetPath, "skillset.yaml"), stringifyYaml({
18723
+ await writeFile6(join14(targetPath, "skillset.yaml"), stringifyYaml({
18045
18724
  skillset: metadata
18046
18725
  }));
18047
18726
  }
18048
18727
  async function readNativePluginManifest(targetPath) {
18049
18728
  for (const file of [".claude-plugin/plugin.json", ".codex-plugin/plugin.json"]) {
18050
- const candidate = join15(targetPath, file);
18051
- if (!await exists10(candidate))
18729
+ const candidate = join14(targetPath, file);
18730
+ if (!await exists9(candidate))
18052
18731
  continue;
18053
- const parsed = JSON.parse(await readFile14(candidate, "utf8"));
18732
+ const parsed = JSON.parse(await readFile15(candidate, "utf8"));
18054
18733
  if (!isJsonRecord(parsed)) {
18055
18734
  throw new Error(`skillset: expected native plugin manifest ${candidate} to contain a JSON object`);
18056
18735
  }
@@ -18058,9 +18737,9 @@ async function readNativePluginManifest(targetPath) {
18058
18737
  }
18059
18738
  return {};
18060
18739
  }
18061
- async function exists10(path2) {
18740
+ async function exists9(path2) {
18062
18741
  try {
18063
- await stat12(path2);
18742
+ await stat11(path2);
18064
18743
  return true;
18065
18744
  } catch (error) {
18066
18745
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
@@ -18070,9 +18749,8 @@ async function exists10(path2) {
18070
18749
  }
18071
18750
  }
18072
18751
  // apps/skillset/src/setup.ts
18073
- import { mkdir as mkdir6, readdir as readdir7, readFile as readFile15, realpath as realpath2, stat as stat13, writeFile as writeFile6 } from "fs/promises";
18074
- import { basename as basename6, dirname as dirname11, join as join16, relative as relative14, resolve as resolve6 } from "path";
18075
-
18752
+ import { mkdir as mkdir6, readdir as readdir8, readFile as readFile16, realpath as realpath2, stat as stat12, writeFile as writeFile7 } from "fs/promises";
18753
+ import { basename as basename7, dirname as dirname11, join as join15, relative as relative14, resolve as resolve6 } from "path";
18076
18754
  // apps/skillset/src/ci.ts
18077
18755
  var EMPTY_DRIFT = { added: [], changed: [], missing: [], removed: [] };
18078
18756
  async function ciSkillset(rootPath, options = {}) {
@@ -18107,6 +18785,7 @@ async function ciSkillset(rootPath, options = {}) {
18107
18785
  }
18108
18786
  const changeErrors = changeIssues.filter((issue) => issue.severity === "error");
18109
18787
  const lintErrors = lintIssues.filter((issue) => issue.severity === "error");
18788
+ const sourceSuggestions = buildError === undefined && hasDrift(drift) ? await sourceSuggestionsForDrift(rootPath, drift, buildOptions) : [];
18110
18789
  let fixedPaths = [];
18111
18790
  if (fix === true && hasDrift(drift) && buildError === undefined && changeError === undefined && changeErrors.length === 0 && lintErrors.length === 0) {
18112
18791
  const staleBefore = [...drift.added, ...drift.changed, ...drift.missing, ...drift.removed];
@@ -18127,6 +18806,7 @@ async function ciSkillset(rootPath, options = {}) {
18127
18806
  fixedPaths,
18128
18807
  lintIssues,
18129
18808
  ok: buildError === undefined && changeError === undefined && lintErrors.length === 0 && changeErrors.length === 0 && !hasDrift(drift),
18809
+ ...sourceSuggestions.length === 0 ? {} : { sourceSuggestions },
18130
18810
  warnings
18131
18811
  };
18132
18812
  }
@@ -18145,7 +18825,7 @@ function renderCiReportMarkdown(report) {
18145
18825
  `;
18146
18826
  }
18147
18827
  if (report.ok) {
18148
- lines.push(report.fixedPaths.length > 0 ? "Generated output was stale and has been rebuilt mechanically. No source changes are needed." : "All checks passed; the lint warnings below are advisory and do not fail CI.", "");
18828
+ lines.push(hasGeneratedChangelogPath(report.fixedPaths) ? "Generated output was stale and has been rebuilt mechanically. Review rebuilt generated changelogs below in case the edit should be recovered through source-side change history." : report.fixedPaths.length > 0 ? "Generated output was stale and has been rebuilt mechanically. No source changes are needed." : "All checks passed; the lint warnings below are advisory and do not fail CI.", "");
18149
18829
  } else {
18150
18830
  lines.push("Skillset CI found problems; the sections below explain each one.", "");
18151
18831
  }
@@ -18153,6 +18833,9 @@ function renderCiReportMarkdown(report) {
18153
18833
  lines.push("### Rebuilt generated output", "");
18154
18834
  for (const path2 of report.fixedPaths)
18155
18835
  lines.push(`- \`${path2}\``);
18836
+ if (hasGeneratedChangelogPath(report.fixedPaths)) {
18837
+ lines.push("", "Generated `CHANGELOG.md` files are managed projections. Edit pending wording with `skillset change reason <@ref>` before release; use `skillset change amend <@ref>` for applied-history wording after release or `skillset release amend <@ref>` for release-event metadata instead of hand-editing generated changelogs.");
18838
+ }
18156
18839
  lines.push("", "These files were regenerated from source the same way `skillset build --yes` would. Commit the rebuilt output if it is not committed for you.", "");
18157
18840
  }
18158
18841
  if (hasDrift(report.drift)) {
@@ -18165,8 +18848,21 @@ function renderCiReportMarkdown(report) {
18165
18848
  lines.push(`- missing: \`${path2}\``);
18166
18849
  for (const path2 of report.drift.removed)
18167
18850
  lines.push(`- removed: \`${path2}\``);
18851
+ if (hasGeneratedChangelogDrift(report.drift)) {
18852
+ lines.push("", "Generated `CHANGELOG.md` files are managed projections. Edit pending wording with `skillset change reason <@ref>` before release; use `skillset change amend <@ref>` for applied-history wording after release or `skillset release amend <@ref>` for release-event metadata instead of hand-editing generated changelogs.");
18853
+ }
18168
18854
  lines.push("", "Run `skillset build --yes`, review the generated diff, and commit it.", "");
18169
18855
  }
18856
+ if (report.sourceSuggestions !== undefined && report.sourceSuggestions.length > 0) {
18857
+ lines.push("### Source suggestions", "");
18858
+ for (const suggestion of report.sourceSuggestions) {
18859
+ const source2 = suggestion.sourcePath === undefined ? "" : ` source \`${suggestion.sourcePath}\``;
18860
+ lines.push(`- ${suggestion.status}: \`${suggestion.generatedPath}\`${source2}: ${suggestion.message}`);
18861
+ for (const step of suggestion.nextSteps)
18862
+ lines.push(` - ${step}`);
18863
+ }
18864
+ lines.push("");
18865
+ }
18170
18866
  if (lintErrors.length > 0) {
18171
18867
  lines.push("### Lint issues", "");
18172
18868
  for (const issue of lintErrors) {
@@ -18202,6 +18898,32 @@ function codeBlock(content) {
18202
18898
  return ["```", content.trimEnd(), "```"].join(`
18203
18899
  `);
18204
18900
  }
18901
+ function hasGeneratedChangelogDrift(diff) {
18902
+ return hasGeneratedChangelogPath([...diff.added, ...diff.changed, ...diff.missing, ...diff.removed]);
18903
+ }
18904
+ function hasGeneratedChangelogPath(paths) {
18905
+ return paths.some((path2) => path2.endsWith("/CHANGELOG.md") || path2 === "CHANGELOG.md");
18906
+ }
18907
+ async function sourceSuggestionsForDrift(rootPath, drift, options) {
18908
+ const paths = [...new Set([...drift.added, ...drift.changed])];
18909
+ const reports = [];
18910
+ for (const path2 of paths) {
18911
+ try {
18912
+ reports.push(await suggestSource(rootPath, path2, options));
18913
+ } catch (error) {
18914
+ reports.push({
18915
+ entries: [],
18916
+ generatedPath: path2,
18917
+ message: errorMessage2(error),
18918
+ nextSteps: ["Run `skillset explain <path>` and update the owning source manually."],
18919
+ status: "refused",
18920
+ wouldWrite: false,
18921
+ wrote: false
18922
+ });
18923
+ }
18924
+ }
18925
+ return reports;
18926
+ }
18205
18927
  function errorMessage2(error) {
18206
18928
  return error instanceof Error ? error.message : String(error);
18207
18929
  }
@@ -18297,7 +19019,9 @@ function renderCiWorkflow() {
18297
19019
  // apps/skillset/src/setup.ts
18298
19020
  var DEFAULT_CREATE_NAME = "my-skillset";
18299
19021
  var DEFAULT_GLOBAL_SOURCE = ".skillset/src";
18300
- var SETUP_SOURCE_DIR = ".skillset/src";
19022
+ var ORDINARY_WORKSPACE_DIR = ".skillset";
19023
+ var ORDINARY_SOURCE_ROOT = ".skillset/src";
19024
+ var DEDICATED_SOURCE_ROOT = "skillset";
18301
19025
  var SETUP_SOURCE_PLACEHOLDERS = [
18302
19026
  "agents",
18303
19027
  "hooks",
@@ -18318,11 +19042,11 @@ async function createSkillset(options = {}) {
18318
19042
  }
18319
19043
  const rootPath = createRootPath(options);
18320
19044
  if (await pathExists(rootPath)) {
18321
- const stats = await stat13(rootPath);
19045
+ const stats = await stat12(rootPath);
18322
19046
  if (!stats.isDirectory()) {
18323
19047
  throw new Error(`skillset: create target exists and is not a directory: ${rootPath}`);
18324
19048
  }
18325
- const entries = await readdir7(rootPath);
19049
+ const entries = await readdir8(rootPath);
18326
19050
  if (entries.length > 0) {
18327
19051
  throw new Error(`skillset: create target must be empty: ${rootPath}`);
18328
19052
  }
@@ -18334,25 +19058,22 @@ function defaultGlobalSourcePath(homeDir = process.env.HOME ?? "~") {
18334
19058
  }
18335
19059
  async function applySetupPlan(kind, rootPath, options) {
18336
19060
  const name = options.name === undefined ? defaultSetupName(kind, rootPath) : validateSlug(options.name, "skillset setup name");
18337
- const alreadyAdopted = await pathExists(join16(rootPath, ".skillset/config.yaml"));
19061
+ const layout = await resolveSetupLayout(kind, rootPath, options);
19062
+ const workspaceManifestPath = await setupWorkspaceManifestPath(kind, rootPath, layout);
19063
+ const alreadyAdopted = await setupWorkspaceExists(rootPath, layout);
18338
19064
  const targets = normalizeTargets(options.targets);
18339
- const plannedFiles = setupFiles({ ...options, kind, name, targets });
19065
+ const plannedFiles = setupFiles({ ...options, kind, layout, name, targets, workspaceManifestPath });
18340
19066
  const git = await setupGit(kind, rootPath, options);
18341
19067
  const files = [];
18342
19068
  for (const file of plannedFiles) {
18343
- const absolutePath = join16(rootPath, file.path);
19069
+ const absolutePath = join15(rootPath, file.path);
18344
19070
  const existing = await readExistingFile(absolutePath);
18345
19071
  if (existing !== undefined && existing !== file.content) {
18346
- if (kind === "init" && file.path === ".skillset/config.yaml") {
19072
+ if (kind === "init" && file.path === workspaceManifestPath) {
18347
19073
  await validateExistingRootConfig(absolutePath);
18348
19074
  files.push({ path: file.path, status: "exists" });
18349
19075
  continue;
18350
19076
  }
18351
- if (kind === "init" && file.path === `${SETUP_SOURCE_DIR}/skillset.yaml`) {
18352
- await validateExistingSourceManifest(absolutePath);
18353
- files.push({ path: file.path, status: "exists" });
18354
- continue;
18355
- }
18356
19077
  if (file.path === CI_WORKFLOW_PATH) {
18357
19078
  files.push({ path: file.path, status: "exists" });
18358
19079
  continue;
@@ -18363,12 +19084,12 @@ async function applySetupPlan(kind, rootPath, options) {
18363
19084
  }
18364
19085
  if (options.write === true) {
18365
19086
  for (const file of plannedFiles) {
18366
- const absolutePath = join16(rootPath, file.path);
19087
+ const absolutePath = join15(rootPath, file.path);
18367
19088
  const existing = await readExistingFile(absolutePath);
18368
19089
  if (existing !== undefined)
18369
19090
  continue;
18370
19091
  await mkdir6(dirname11(absolutePath), { recursive: true });
18371
- await writeFile6(absolutePath, file.content);
19092
+ await writeFile7(absolutePath, file.content);
18372
19093
  }
18373
19094
  if (git?.status === "create")
18374
19095
  await initializeGit(rootPath);
@@ -18383,11 +19104,42 @@ async function applySetupPlan(kind, rootPath, options) {
18383
19104
  importCandidates,
18384
19105
  kind,
18385
19106
  rootPath,
18386
- sourceDir: SETUP_SOURCE_DIR,
19107
+ sourceDir: layout === "dedicated" ? "." : ORDINARY_WORKSPACE_DIR,
18387
19108
  surveySkips,
18388
19109
  write: options.write === true
18389
19110
  };
18390
19111
  }
19112
+ async function resolveSetupLayout(kind, rootPath, options) {
19113
+ if (options.global === true)
19114
+ return "dedicated";
19115
+ if (kind === "create")
19116
+ return "dedicated";
19117
+ const dedicated = await hasDedicatedWorkspaceMarker(rootPath);
19118
+ const ordinary = await hasOrdinaryWorkspaceMarker(rootPath);
19119
+ if (dedicated && ordinary) {
19120
+ throw new Error("skillset: ambiguous setup workspace; found both dedicated root skillset.yaml/skillset and ordinary .skillset workspace");
19121
+ }
19122
+ return dedicated ? "dedicated" : "ordinary";
19123
+ }
19124
+ async function hasDedicatedWorkspaceMarker(rootPath) {
19125
+ return await pathExists(join15(rootPath, "skillset.yaml")) || await pathExists(join15(rootPath, DEDICATED_SOURCE_ROOT));
19126
+ }
19127
+ async function hasOrdinaryWorkspaceMarker(rootPath) {
19128
+ return await pathExists(join15(rootPath, ".skillset/skillset.yaml")) || await pathExists(join15(rootPath, ".skillset/config.yaml")) || await pathExists(join15(rootPath, ORDINARY_SOURCE_ROOT));
19129
+ }
19130
+ async function setupWorkspaceExists(rootPath, layout) {
19131
+ if (layout === "dedicated")
19132
+ return await pathExists(join15(rootPath, "skillset.yaml"));
19133
+ return await pathExists(join15(rootPath, ".skillset/skillset.yaml")) || await pathExists(join15(rootPath, ".skillset/config.yaml"));
19134
+ }
19135
+ async function setupWorkspaceManifestPath(kind, rootPath, layout) {
19136
+ if (layout === "dedicated")
19137
+ return "skillset.yaml";
19138
+ if (kind === "init" && !await pathExists(join15(rootPath, ".skillset/skillset.yaml")) && await pathExists(join15(rootPath, ".skillset/config.yaml"))) {
19139
+ return ".skillset/config.yaml";
19140
+ }
19141
+ return ".skillset/skillset.yaml";
19142
+ }
18391
19143
  async function initRootPath(options) {
18392
19144
  const cwd = resolve6(options.cwd ?? process.cwd());
18393
19145
  if (options.rootPath !== undefined)
@@ -18413,12 +19165,12 @@ async function gitRoot(cwd) {
18413
19165
  return path2.length === 0 ? undefined : path2;
18414
19166
  }
18415
19167
  async function validateExistingRootConfig(path2) {
18416
- const content = await readFile15(path2, "utf8");
18417
- validateWorkspaceConfigDocument(parseYamlRecord(content, path2), path2);
18418
- }
18419
- async function validateExistingSourceManifest(path2) {
18420
- const content = await readFile15(path2, "utf8");
18421
- validateRootSourceManifestDocument(parseYamlRecord(content, path2), path2);
19168
+ const content = await readFile16(path2, "utf8");
19169
+ const parsed = parseYamlRecord(content, path2);
19170
+ if (path2.endsWith("/config.yaml"))
19171
+ validateWorkspaceConfigDocument(parsed, path2);
19172
+ else
19173
+ validateConfigDocument(parsed, path2, { allowCompile: true });
18422
19174
  }
18423
19175
  var GENERATED_INSTRUCTIONS_MARKER = "<!-- Generated by skillset";
18424
19176
  var ROOT_INSTRUCTION_FILES = ["AGENTS.md", "CLAUDE.md"];
@@ -18430,7 +19182,7 @@ async function detectImportCandidates(rootPath, alreadyAdopted) {
18430
19182
  await maybeCandidate(candidates, rootPath, ".agents/skills", "skills");
18431
19183
  await maybeCandidate(candidates, rootPath, "plugins-claude/plugins", "plugins");
18432
19184
  await maybeCandidate(candidates, rootPath, "plugins-codex/plugins", "plugins");
18433
- if (await pathExists(join16(rootPath, ".claude-plugin/plugin.json")) || await pathExists(join16(rootPath, ".codex-plugin/plugin.json"))) {
19185
+ if (await pathExists(join15(rootPath, ".claude-plugin/plugin.json")) || await pathExists(join15(rootPath, ".codex-plugin/plugin.json"))) {
18434
19186
  candidates.push({ kind: "plugin", path: "." });
18435
19187
  }
18436
19188
  for (const path2 of [...await marketplacePluginSources(rootPath), ...await nestedPluginSources(rootPath)]) {
@@ -18440,7 +19192,7 @@ async function detectImportCandidates(rootPath, alreadyAdopted) {
18440
19192
  }
18441
19193
  if (!alreadyAdopted) {
18442
19194
  for (const name of ROOT_INSTRUCTION_FILES) {
18443
- if (await isImportableInstructionFile(join16(rootPath, name))) {
19195
+ if (await isImportableInstructionFile(join15(rootPath, name))) {
18444
19196
  candidates.push({ kind: "instructions", path: name });
18445
19197
  }
18446
19198
  }
@@ -18448,20 +19200,20 @@ async function detectImportCandidates(rootPath, alreadyAdopted) {
18448
19200
  return candidates.sort((left, right) => compareCandidate(left, right));
18449
19201
  }
18450
19202
  async function nestedPluginSources(rootPath) {
18451
- const pluginsPath = join16(rootPath, "plugins");
19203
+ const pluginsPath = join15(rootPath, "plugins");
18452
19204
  if (!await pathExists(pluginsPath))
18453
19205
  return [];
18454
- if (!(await stat13(pluginsPath)).isDirectory())
19206
+ if (!(await stat12(pluginsPath)).isDirectory())
18455
19207
  return [];
18456
19208
  const realRoot = await realpath2(rootPath);
18457
19209
  const sources = [];
18458
- for (const entry of (await readdir7(pluginsPath)).sort()) {
18459
- const absolutePath = join16(pluginsPath, entry);
19210
+ for (const entry of (await readdir8(pluginsPath)).sort()) {
19211
+ const absolutePath = join15(pluginsPath, entry);
18460
19212
  if (!await pathExists(absolutePath))
18461
19213
  continue;
18462
- if (!(await stat13(absolutePath)).isDirectory())
19214
+ if (!(await stat12(absolutePath)).isDirectory())
18463
19215
  continue;
18464
- const hasManifest = await pathExists(join16(absolutePath, ".claude-plugin/plugin.json")) || await pathExists(join16(absolutePath, ".codex-plugin/plugin.json"));
19216
+ const hasManifest = await pathExists(join15(absolutePath, ".claude-plugin/plugin.json")) || await pathExists(join15(absolutePath, ".codex-plugin/plugin.json"));
18465
19217
  if (!hasManifest)
18466
19218
  continue;
18467
19219
  const realSource = await realpath2(absolutePath);
@@ -18479,36 +19231,36 @@ async function nestedPluginSources(rootPath) {
18479
19231
  async function isImportableInstructionFile(path2) {
18480
19232
  if (!await pathExists(path2))
18481
19233
  return false;
18482
- if (!(await stat13(path2)).isFile())
19234
+ if (!(await stat12(path2)).isFile())
18483
19235
  return false;
18484
- const content = await readFile15(path2, "utf8");
19236
+ const content = await readFile16(path2, "utf8");
18485
19237
  return !content.startsWith(GENERATED_INSTRUCTIONS_MARKER);
18486
19238
  }
18487
19239
  async function detectSurveySkips(rootPath) {
18488
19240
  const skips = [];
18489
- await maybeSkip(skips, rootPath, ".claude/commands", "commands", "project-level commands have no portable source home yet; adopt will lower them to target-native islands in the transform milestone", surveySkipOutcome({
19241
+ await maybeSkip(skips, rootPath, ".claude/commands", "commands", "project-level commands have no portable source home yet; adopt will represent them as provider source in the transform milestone", surveySkipOutcome({
18490
19242
  featureId: "target-native-islands",
18491
19243
  path: ".claude/commands",
18492
- reason: "project-level commands have no portable source home yet; adopt will lower them to target-native islands in the transform milestone",
19244
+ reason: "project-level commands have no portable source home yet; adopt will represent them as provider source in the transform milestone",
18493
19245
  relativeTargetPath: "commands",
18494
19246
  target: "claude"
18495
19247
  }));
18496
- await maybeSkip(skips, rootPath, ".claude/agents", "agents", "project-level agents are not importable yet; adopt will lower them to .skillset/src/agents/ in the transform milestone", surveySkipOutcome({
19248
+ await maybeSkip(skips, rootPath, ".claude/agents", "agents", "project-level agents are not importable yet; adopt will represent them in the active workspace source root in the transform milestone", surveySkipOutcome({
18497
19249
  featureId: "project-agents",
18498
19250
  path: ".claude/agents",
18499
- reason: "project-level agents are not importable yet; adopt will lower them to .skillset/src/agents/ in the transform milestone",
19251
+ reason: "project-level agents are not importable yet; adopt will represent them in the active workspace source root in the transform milestone",
18500
19252
  relativeTargetPath: "agents",
18501
19253
  target: "claude"
18502
19254
  }));
18503
- await maybeSkip(skips, rootPath, ".claude/rules", "rules", "rules are not importable yet; adopt will lower them to .skillset/src/rules/ in the transform milestone", surveySkipOutcome({
19255
+ await maybeSkip(skips, rootPath, ".claude/rules", "rules", "rules are not importable yet; adopt will represent them in the active workspace source root in the transform milestone", surveySkipOutcome({
18504
19256
  featureId: "project-instructions",
18505
19257
  path: ".claude/rules",
18506
- reason: "rules are not importable yet; adopt will lower them to .skillset/src/rules/ in the transform milestone",
19258
+ reason: "rules are not importable yet; adopt will represent them in the active workspace source root in the transform milestone",
18507
19259
  relativeTargetPath: "rules",
18508
19260
  target: "claude"
18509
19261
  }));
18510
19262
  if (await hasNonSkillCodexContent(rootPath)) {
18511
- const reason = "Codex content outside .codex/skills has no portable lowering yet; adopt will lower it to target-native islands in the transform milestone";
19263
+ const reason = "Codex content outside .codex/skills has no portable source representation yet; adopt will represent it as provider source in the transform milestone";
18512
19264
  skips.push({
18513
19265
  renderResult: surveySkipOutcome({
18514
19266
  featureId: "target-native-islands",
@@ -18534,14 +19286,14 @@ async function detectSurveySkips(rootPath) {
18534
19286
  return skips.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
18535
19287
  }
18536
19288
  async function maybeSkip(skips, rootPath, path2, surface, reason, renderResult) {
18537
- const absolutePath = join16(rootPath, path2);
19289
+ const absolutePath = join15(rootPath, path2);
18538
19290
  if (!await pathExists(absolutePath))
18539
19291
  return;
18540
- if (!(await stat13(absolutePath)).isDirectory())
19292
+ if (!(await stat12(absolutePath)).isDirectory())
18541
19293
  return;
18542
19294
  if (await isManagedCandidate(absolutePath))
18543
19295
  return;
18544
- const entries = await readdir7(absolutePath);
19296
+ const entries = await readdir8(absolutePath);
18545
19297
  if (entries.filter((entry) => entry !== ".DS_Store").length === 0)
18546
19298
  return;
18547
19299
  skips.push({ ...renderResult === undefined ? {} : { renderResult }, path: path2, reason, surface });
@@ -18580,39 +19332,39 @@ function foreignManifestRuntime(path2) {
18580
19332
  return path2.match(/^\.([a-z][a-z0-9]*)-plugin$/)?.[1] ?? path2;
18581
19333
  }
18582
19334
  async function hasNonSkillCodexContent(rootPath) {
18583
- const codexPath = join16(rootPath, ".codex");
19335
+ const codexPath = join15(rootPath, ".codex");
18584
19336
  if (!await pathExists(codexPath))
18585
19337
  return false;
18586
- if (!(await stat13(codexPath)).isDirectory())
19338
+ if (!(await stat12(codexPath)).isDirectory())
18587
19339
  return false;
18588
19340
  if (await isManagedCandidate(codexPath))
18589
19341
  return false;
18590
- const entries = await readdir7(codexPath);
19342
+ const entries = await readdir8(codexPath);
18591
19343
  return entries.some((entry) => entry !== "skills" && entry !== ".DS_Store");
18592
19344
  }
18593
19345
  async function foreignPluginManifests(rootPath) {
18594
19346
  const paths = [];
18595
- for (const entry of (await readdir7(rootPath)).sort()) {
19347
+ for (const entry of (await readdir8(rootPath)).sort()) {
18596
19348
  if (NATIVE_PLUGIN_MANIFEST_DIRS.has(entry))
18597
19349
  continue;
18598
19350
  if (!/^\.[a-z][a-z0-9]*-plugin$/.test(entry))
18599
19351
  continue;
18600
- const absolutePath = join16(rootPath, entry);
19352
+ const absolutePath = join15(rootPath, entry);
18601
19353
  if (!await pathExists(absolutePath))
18602
19354
  continue;
18603
- if (!(await stat13(absolutePath)).isDirectory())
19355
+ if (!(await stat12(absolutePath)).isDirectory())
18604
19356
  continue;
18605
19357
  paths.push(entry);
18606
19358
  }
18607
19359
  return paths;
18608
19360
  }
18609
19361
  async function marketplacePluginSources(rootPath) {
18610
- const manifestPath = join16(rootPath, ".claude-plugin/marketplace.json");
19362
+ const manifestPath = join15(rootPath, ".claude-plugin/marketplace.json");
18611
19363
  if (!await pathExists(manifestPath))
18612
19364
  return [];
18613
19365
  let parsed;
18614
19366
  try {
18615
- parsed = JSON.parse(await readFile15(manifestPath, "utf8"));
19367
+ parsed = JSON.parse(await readFile16(manifestPath, "utf8"));
18616
19368
  } catch {
18617
19369
  return [];
18618
19370
  }
@@ -18634,7 +19386,7 @@ async function marketplacePluginSources(rootPath) {
18634
19386
  continue;
18635
19387
  if (!await pathExists(absolutePath))
18636
19388
  continue;
18637
- if (!(await stat13(absolutePath)).isDirectory())
19389
+ if (!(await stat12(absolutePath)).isDirectory())
18638
19390
  continue;
18639
19391
  const realSource = await realpath2(absolutePath);
18640
19392
  if (realSource !== realRoot && !realSource.startsWith(`${realRoot}/`))
@@ -18649,44 +19401,66 @@ async function marketplacePluginSources(rootPath) {
18649
19401
  return sources;
18650
19402
  }
18651
19403
  async function maybeCandidate(candidates, rootPath, path2, kind) {
18652
- const absolutePath = join16(rootPath, path2);
19404
+ const absolutePath = join15(rootPath, path2);
18653
19405
  if (!await pathExists(absolutePath))
18654
19406
  return;
18655
- const stats = await stat13(absolutePath);
19407
+ const stats = await stat12(absolutePath);
18656
19408
  if (!stats.isDirectory())
18657
19409
  return;
18658
19410
  if (await isManagedCandidate(absolutePath))
18659
19411
  return;
18660
- const entries = await readdir7(absolutePath);
19412
+ const entries = await readdir8(absolutePath);
18661
19413
  if (entries.filter((entry) => entry !== ".DS_Store").length === 0)
18662
19414
  return;
18663
19415
  candidates.push({ kind, path: relative14(rootPath, absolutePath).replaceAll("\\", "/") });
18664
19416
  }
18665
19417
  async function isManagedCandidate(path2) {
18666
- return await pathExists(join16(path2, ".skillset.lock")) || await pathExists(join16(dirname11(path2), ".skillset.lock"));
19418
+ return await pathExists(join15(path2, ".skillset.lock")) || await pathExists(join15(dirname11(path2), ".skillset.lock"));
18667
19419
  }
18668
19420
  function compareCandidate(left, right) {
18669
19421
  return `${left.kind}:${left.path}` < `${right.kind}:${right.path}` ? -1 : `${left.kind}:${left.path}` > `${right.kind}:${right.path}` ? 1 : 0;
18670
19422
  }
18671
19423
  function setupFiles(options) {
19424
+ const sourceRoot = options.layout === "dedicated" ? DEDICATED_SOURCE_ROOT : ORDINARY_SOURCE_ROOT;
19425
+ const changesRoot = options.layout === "dedicated" ? "changes" : ".skillset/changes";
18672
19426
  const files = [
18673
19427
  {
18674
- path: ".skillset/config.yaml",
18675
- content: rootConfig(options.targets)
18676
- },
18677
- {
18678
- path: `${SETUP_SOURCE_DIR}/skillset.yaml`,
18679
- content: rootSourceManifest(options.name)
19428
+ path: options.workspaceManifestPath,
19429
+ content: workspaceManifest(options.name, options.targets)
18680
19430
  },
18681
19431
  {
18682
- path: `${SETUP_SOURCE_DIR}/.gitkeep`,
19432
+ path: `${sourceRoot}/.gitkeep`,
18683
19433
  content: ""
18684
19434
  },
18685
19435
  ...SETUP_SOURCE_PLACEHOLDERS.map((directory) => ({
18686
- path: `${SETUP_SOURCE_DIR}/${directory}/.gitkeep`,
19436
+ path: `${sourceRoot}/${directory}/.gitkeep`,
18687
19437
  content: ""
18688
- }))
19438
+ })),
19439
+ {
19440
+ path: `${changesRoot}/.gitkeep`,
19441
+ content: ""
19442
+ }
18689
19443
  ];
19444
+ if (options.layout === "ordinary") {
19445
+ files.push({
19446
+ path: ".skillset/.gitignore",
19447
+ content: `build/
19448
+ `
19449
+ }, {
19450
+ path: ".skillset/build/.gitkeep",
19451
+ content: ""
19452
+ });
19453
+ }
19454
+ if (options.layout === "dedicated" && options.kind === "create" && options.global !== true) {
19455
+ files.push({
19456
+ path: ".gitignore",
19457
+ content: `.skillset/
19458
+ `
19459
+ }, {
19460
+ path: "skillset.lock",
19461
+ content: emptyWorkspaceLock()
19462
+ });
19463
+ }
18690
19464
  if (options.kind === "create" && options.global !== true) {
18691
19465
  files.unshift({
18692
19466
  path: "README.md",
@@ -18707,7 +19481,7 @@ async function setupGit(kind, rootPath, options) {
18707
19481
  return;
18708
19482
  return {
18709
19483
  path: ".git",
18710
- status: await pathExists(join16(rootPath, ".git")) ? "exists" : "create"
19484
+ status: await pathExists(join15(rootPath, ".git")) ? "exists" : "create"
18711
19485
  };
18712
19486
  }
18713
19487
  async function initializeGit(rootPath) {
@@ -18740,10 +19514,20 @@ function rootConfig(targets) {
18740
19514
  ].join(`
18741
19515
  `);
18742
19516
  }
18743
- function rootSourceManifest(name) {
19517
+ function workspaceManifest(name, targets) {
18744
19518
  return [
18745
19519
  "skillset:",
18746
19520
  ` name: ${name}`,
19521
+ rootConfig(targets).trimEnd(),
19522
+ ""
19523
+ ].join(`
19524
+ `);
19525
+ }
19526
+ function emptyWorkspaceLock() {
19527
+ return [
19528
+ "{",
19529
+ ' "items": []',
19530
+ "}",
18747
19531
  ""
18748
19532
  ].join(`
18749
19533
  `);
@@ -18752,7 +19536,7 @@ function createReadme(name, targets) {
18752
19536
  return [
18753
19537
  `# ${name}`,
18754
19538
  "",
18755
- "This repository is a Skillset source repo. Edit files under `.skillset/src/`, then run Skillset commands to preview or write generated Claude and Codex outputs.",
19539
+ "This repository is a dedicated Skillset source repo. Edit files under `skillset/`, then run Skillset commands to preview or write generated Claude and Codex outputs.",
18756
19540
  "",
18757
19541
  "## Quick Start",
18758
19542
  "",
@@ -18765,11 +19549,12 @@ function createReadme(name, targets) {
18765
19549
  "",
18766
19550
  "## Layout",
18767
19551
  "",
18768
- "- `.skillset/config.yaml` selects compile targets and destination settings.",
18769
- "- `.skillset/src/skillset.yaml` names the source loadout and carries root source metadata.",
18770
- "- `.skillset/src/` is the adaptive project source area for rules, agents, hooks, skills, plugins, shared files, and provider source.",
18771
- "- `.skillset/src/plugins/` holds plugin source when this repo authors marketplace plugins.",
18772
- "- `.skillset/src/skills/` holds standalone skill source when this repo authors repo-local or user skill roots.",
19552
+ "- `skillset.yaml` names the source loadout and selects compile targets and destination settings.",
19553
+ "- `skillset/` is the adaptive project source area for rules, agents, hooks, skills, plugins, shared files, and provider source.",
19554
+ "- `skillset/plugins/` holds plugin source when this repo authors marketplace plugins.",
19555
+ "- `skillset/skills/` holds standalone skill source when this repo authors repo-local or user skill roots.",
19556
+ "- `changes/` stores pending and applied Skillset change history.",
19557
+ "- `.skillset/` is ignored operational output for previews, test runs, and build scratch data.",
18773
19558
  "",
18774
19559
  `Default compile targets: ${targets.join(", ")}.`,
18775
19560
  ""
@@ -18784,8 +19569,9 @@ function createAgentsGuide(name) {
18784
19569
  "",
18785
19570
  "## Working Rules",
18786
19571
  "",
18787
- "- Treat `.skillset/src/` as editable source.",
18788
- "- Treat `.skillset/config.yaml` as workspace/build configuration.",
19572
+ "- Treat `skillset/` as editable source.",
19573
+ "- Treat `skillset.yaml` as workspace/build configuration and root source metadata.",
19574
+ "- Treat `changes/` as Skillset-managed change and release state.",
18789
19575
  "- Treat generated target directories as outputs; do not hand-edit them as source truth.",
18790
19576
  "- Run `skillset build --dry-run` before writing generated outputs.",
18791
19577
  "- Run `skillset check` before committing source changes.",
@@ -18802,10 +19588,10 @@ function createRootPath(options) {
18802
19588
  return resolve6(options.cwd ?? process.cwd(), DEFAULT_CREATE_NAME);
18803
19589
  }
18804
19590
  function defaultSetupName(kind, rootPath) {
18805
- if (kind === "create" && basename6(rootPath) === "src" && basename6(join16(rootPath, "..")) === ".skillset") {
19591
+ if (kind === "create" && basename7(rootPath) === "src" && basename7(join15(rootPath, "..")) === ".skillset") {
18806
19592
  return DEFAULT_CREATE_NAME;
18807
19593
  }
18808
- const slug = slugifySetupName(basename6(rootPath));
19594
+ const slug = slugifySetupName(basename7(rootPath));
18809
19595
  return slug.length === 0 ? DEFAULT_CREATE_NAME : slug;
18810
19596
  }
18811
19597
  function slugifySetupName(value) {
@@ -18825,28 +19611,28 @@ function normalizeTargets(targets) {
18825
19611
  }
18826
19612
  async function readExistingFile(path2) {
18827
19613
  try {
18828
- const stats = await stat13(path2);
19614
+ const stats = await stat12(path2);
18829
19615
  if (!stats.isFile()) {
18830
19616
  throw new Error(`skillset: refusing to overwrite non-file setup path ${path2}`);
18831
19617
  }
18832
- return await readFile15(path2, "utf8");
19618
+ return await readFile16(path2, "utf8");
18833
19619
  } catch (error) {
18834
- if (isErrno2(error, "ENOENT"))
19620
+ if (isErrno3(error, "ENOENT"))
18835
19621
  return;
18836
19622
  throw error;
18837
19623
  }
18838
19624
  }
18839
19625
  async function pathExists(path2) {
18840
19626
  try {
18841
- await stat13(path2);
19627
+ await stat12(path2);
18842
19628
  return true;
18843
19629
  } catch (error) {
18844
- if (isErrno2(error, "ENOENT"))
19630
+ if (isErrno3(error, "ENOENT"))
18845
19631
  return false;
18846
19632
  throw error;
18847
19633
  }
18848
19634
  }
18849
- function isErrno2(error, code) {
19635
+ function isErrno3(error, code) {
18850
19636
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
18851
19637
  }
18852
19638
 
@@ -18866,7 +19652,7 @@ async function adoptResolvedRoot(acquisition, options = {}) {
18866
19652
  const { targets, write, ...buildOptions } = options;
18867
19653
  const writeMode = write === true;
18868
19654
  const resolvedRoot = acquisition.rootPath;
18869
- const alreadyAdopted = await exists11(join17(resolvedRoot, ".skillset/config.yaml"));
19655
+ const alreadyAdopted = await adoptedWorkspaceExists(resolvedRoot);
18870
19656
  const init = await initSkillset({
18871
19657
  cwd: resolvedRoot,
18872
19658
  useGitRoot: false,
@@ -18943,22 +19729,28 @@ async function adoptResolvedRoot(acquisition, options = {}) {
18943
19729
  transformPreviews,
18944
19730
  write: true
18945
19731
  };
18946
- const reportDir = join17(init.rootPath, ADOPT_REPORT_DIR);
19732
+ const reportDir = join16(init.rootPath, ADOPT_REPORT_DIR);
18947
19733
  await mkdir7(reportDir, { recursive: true });
18948
- await writeFile7(join17(reportDir, "report.md"), renderAdoptReportMarkdown(report, { rootPath: init.rootPath }));
18949
- await writeFile7(join17(reportDir, "report.json"), `${JSON.stringify(report, null, 2)}
19734
+ await writeFile8(join16(reportDir, "report.md"), renderAdoptReportMarkdown(report, { rootPath: init.rootPath }));
19735
+ await writeFile8(join16(reportDir, "report.json"), `${JSON.stringify(report, null, 2)}
18950
19736
  `);
18951
19737
  return report;
18952
19738
  }
19739
+ async function adoptedWorkspaceExists(rootPath) {
19740
+ const currentOrdinary = await exists10(join16(rootPath, ".skillset/skillset.yaml"));
19741
+ const legacyOrdinary = await exists10(join16(rootPath, ".skillset/config.yaml"));
19742
+ const dedicated = await exists10(join16(rootPath, "skillset.yaml")) && await exists10(join16(rootPath, "skillset"));
19743
+ return currentOrdinary || legacyOrdinary || dedicated;
19744
+ }
18953
19745
  function surveySkipRenderResults(skips) {
18954
19746
  return skips.flatMap((skip) => skip.renderResult === undefined ? [] : [skip.renderResult]);
18955
19747
  }
18956
19748
  async function acquireAdoptSource(source2, cwd) {
18957
19749
  const localPath = resolve7(cwd, source2);
18958
- if (await exists11(localPath)) {
19750
+ if (await exists10(localPath)) {
18959
19751
  return { input: source2, kind: "path", rootPath: localPath };
18960
19752
  }
18961
- const clonePath = await mkdtemp3(join17(tmpdir2(), "skillset-adopt-remote-"));
19753
+ const clonePath = await mkdtemp3(join16(tmpdir2(), "skillset-adopt-remote-"));
18962
19754
  try {
18963
19755
  await runGit(["clone", "--depth", "1", source2, clonePath], cwd);
18964
19756
  const ref = (await runGit(["-C", clonePath, "rev-parse", "HEAD"], cwd)).trim();
@@ -18992,7 +19784,7 @@ async function importCandidate(rootPath, acquisition, candidate, cutover, previe
18992
19784
  if (candidate.kind === "instructions") {
18993
19785
  try {
18994
19786
  const destination = await importInstructionFile(rootPath, candidate.path);
18995
- await writeMarkdownSourceOrigin2(join17(rootPath, destination), sourceOriginFor(acquisition, candidate.path));
19787
+ await writeMarkdownSourceOrigin2(join16(rootPath, destination), sourceOriginFor(acquisition, candidate.path));
18996
19788
  cutover.push(candidate.path);
18997
19789
  previewSources.push(destination);
18998
19790
  return { candidate, destination, detail: destination, renderResults: [], ok: true, units: [] };
@@ -19005,7 +19797,7 @@ async function importCandidate(rootPath, acquisition, candidate, cutover, previe
19005
19797
  kind: candidate.kind,
19006
19798
  rootPath,
19007
19799
  sourceOrigin: (sourcePath, copiedFile) => sourceOriginFor(acquisition, relativeOriginPath(rootPath, sourcePath, copiedFile)),
19008
- sourcePath: join17(rootPath, candidate.path)
19800
+ sourcePath: join16(rootPath, candidate.path)
19009
19801
  });
19010
19802
  const units = batch.imports.map((report) => {
19011
19803
  const sourcePath = relative15(rootPath, report.sourcePath).replaceAll("\\", "/");
@@ -19017,9 +19809,9 @@ async function importCandidate(rootPath, acquisition, candidate, cutover, previe
19017
19809
  });
19018
19810
  for (const report of batch.imports) {
19019
19811
  for (const file of report.copiedFiles) {
19020
- if (basename7(file) !== "SKILL.md")
19812
+ if (basename8(file) !== "SKILL.md")
19021
19813
  continue;
19022
- previewSources.push(relative15(rootPath, join17(report.targetPath, file)).replaceAll("\\", "/"));
19814
+ previewSources.push(relative15(rootPath, join16(report.targetPath, file)).replaceAll("\\", "/"));
19023
19815
  }
19024
19816
  }
19025
19817
  return {
@@ -19035,26 +19827,26 @@ async function importCandidate(rootPath, acquisition, candidate, cutover, previe
19035
19827
  }
19036
19828
  async function importInstructionFile(rootPath, sourceName) {
19037
19829
  const destinationRelative = `${INSTRUCTIONS_DIR}/${sourceName.toLowerCase()}`;
19038
- const destination = join17(rootPath, destinationRelative);
19039
- if (await exists11(destination)) {
19830
+ const destination = join16(rootPath, destinationRelative);
19831
+ if (await exists10(destination)) {
19040
19832
  throw new Error(`skillset: instructions import target already exists: ${destinationRelative}. ` + "Adopt never overwrites; remove the existing file or merge by hand.");
19041
19833
  }
19042
- const content = await readFile16(join17(rootPath, sourceName));
19834
+ const content = await readFile17(join16(rootPath, sourceName));
19043
19835
  await mkdir7(dirname12(destination), { recursive: true });
19044
- await writeFile7(destination, content);
19836
+ await writeFile8(destination, content);
19045
19837
  return destinationRelative;
19046
19838
  }
19047
19839
  function sourceOriginFor(acquisition, path2) {
19048
19840
  return acquisition.kind === "git" ? { path: path2, ref: acquisition.ref, repo: acquisition.repo } : { path: path2 };
19049
19841
  }
19050
19842
  function relativeOriginPath(rootPath, sourcePath, copiedFile) {
19051
- const sourceFile = copiedFile === undefined ? sourcePath : basename7(sourcePath) === copiedFile ? sourcePath : join17(sourcePath, copiedFile);
19843
+ const sourceFile = copiedFile === undefined ? sourcePath : basename8(sourcePath) === copiedFile ? sourcePath : join16(sourcePath, copiedFile);
19052
19844
  const path2 = relative15(rootPath, sourceFile).replaceAll("\\", "/");
19053
19845
  return path2.length === 0 ? "." : path2;
19054
19846
  }
19055
19847
  async function writeMarkdownSourceOrigin2(path2, origin) {
19056
- const parts = parseMarkdown(await readFile16(path2, "utf8"), path2);
19057
- await writeFile7(path2, stringifyMarkdown(withSkillsetOrigin2(parts.frontmatter, origin), parts.body));
19848
+ const parts = parseMarkdown(await readFile17(path2, "utf8"), path2);
19849
+ await writeFile8(path2, stringifyMarkdown(withSkillsetOrigin2(parts.frontmatter, origin), parts.body));
19058
19850
  }
19059
19851
  function withSkillsetOrigin2(record, origin) {
19060
19852
  const existing = isJsonRecord(record.skillset) ? record.skillset : {};
@@ -19076,13 +19868,13 @@ function sourceOriginRecord3(origin) {
19076
19868
  async function buildTransformPreviews(rootPath, paths) {
19077
19869
  const previews = [];
19078
19870
  for (const path2 of paths) {
19079
- const raw = await readFile16(join17(rootPath, path2), "utf8");
19080
- const body = basename7(path2) === "SKILL.md" ? markdownBody(raw, path2) : raw;
19871
+ const raw = await readFile17(join16(rootPath, path2), "utf8");
19872
+ const body = basename8(path2) === "SKILL.md" ? markdownBody(raw, path2) : raw;
19081
19873
  const matches = recognizeTransforms(body, "claude");
19082
19874
  if (matches.length === 0)
19083
19875
  continue;
19084
19876
  const transformable = matches.some((match) => match.lowering !== "none");
19085
- const dialectDeclared = transformable ? await declareClaudeDialect(join17(rootPath, path2)) : false;
19877
+ const dialectDeclared = transformable ? await declareClaudeDialect(join16(rootPath, path2)) : false;
19086
19878
  previews.push({
19087
19879
  dialectDeclared,
19088
19880
  matches: matches.map((match) => {
@@ -19101,7 +19893,7 @@ async function buildTransformPreviews(rootPath, paths) {
19101
19893
  return previews;
19102
19894
  }
19103
19895
  async function declareClaudeDialect(path2) {
19104
- const raw = await readFile16(path2, "utf8");
19896
+ const raw = await readFile17(path2, "utf8");
19105
19897
  try {
19106
19898
  if (parseMarkdown(raw, path2).frontmatter.dialect !== undefined)
19107
19899
  return true;
@@ -19114,7 +19906,7 @@ dialect: claude
19114
19906
  ---
19115
19907
 
19116
19908
  ${raw}`;
19117
- await writeFile7(path2, updated);
19909
+ await writeFile8(path2, updated);
19118
19910
  return true;
19119
19911
  }
19120
19912
  function markdownBody(raw, label) {
@@ -19134,7 +19926,7 @@ function renderAdoptReportMarkdown(report, opts) {
19134
19926
  lines.push(`- root: \`${opts.rootPath}\``);
19135
19927
  lines.push(`- result: ${report.ok ? "pass" : "fail"}`);
19136
19928
  if (report.alreadyAdopted) {
19137
- lines.push("- note: the repo already had `.skillset/config.yaml`; adoption proceeded against the existing source tree");
19929
+ lines.push("- note: the repo already had a Skillset workspace marker; adoption proceeded against the existing source tree");
19138
19930
  }
19139
19931
  lines.push(`- imports: ${succeeded.length} succeeded, ${failed.length} failed`, `- lint: ${lintErrors.length} error(s), ${lintWarnings.length} warning(s)`, report.buildError === undefined ? `- build (isolated): ${report.builtFiles} generated files` : "- build (isolated): failed", `- render results: ${report.renderResults.length}`, "");
19140
19932
  lines.push("## Acquisition", "");
@@ -19262,7 +20054,7 @@ function summarizeRenderResults(outcomes) {
19262
20054
  function aggregatePreviewMatches(matches) {
19263
20055
  const aggregated = new Map;
19264
20056
  for (const match of matches) {
19265
- const key = `${match.intent}\x00${match.text}\x00${match.codexForm ?? ""}`;
20057
+ const key = JSON.stringify([match.intent, match.text, match.codexForm ?? ""]);
19266
20058
  const existing = aggregated.get(key);
19267
20059
  aggregated.set(key, existing === undefined ? { ...match, count: 1 } : { ...existing, count: existing.count + 1 });
19268
20060
  }
@@ -19277,9 +20069,9 @@ function inlineCode(text) {
19277
20069
  function errorMessage3(error) {
19278
20070
  return error instanceof Error ? error.message : String(error);
19279
20071
  }
19280
- async function exists11(path2) {
20072
+ async function exists10(path2) {
19281
20073
  try {
19282
- await stat14(path2);
20074
+ await stat13(path2);
19283
20075
  return true;
19284
20076
  } catch (error) {
19285
20077
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
@@ -19291,10 +20083,11 @@ async function exists11(path2) {
19291
20083
 
19292
20084
  // apps/skillset/src/change-workflow.ts
19293
20085
  import { createHash as createHash6, randomBytes } from "crypto";
19294
- import { mkdir as mkdir8, readFile as readFile17, stat as stat15, writeFile as writeFile8 } from "fs/promises";
19295
- import { isAbsolute as isAbsolute2, join as join18, resolve as resolve8 } from "path";
19296
- var PENDING_DIR2 = "changes/pending";
20086
+ import { appendFile, mkdir as mkdir8, readFile as readFile18, stat as stat14, writeFile as writeFile9 } from "fs/promises";
20087
+ import { dirname as dirname13, isAbsolute as isAbsolute2, join as join17, resolve as resolve8 } from "path";
20088
+ var PENDING_DIR2 = "changes";
19297
20089
  var HISTORY_FILE2 = "changes/history.jsonl";
20090
+ var AMENDMENTS_FILE2 = "changes/amendments.jsonl";
19298
20091
  var MIN_REF_LENGTH = 6;
19299
20092
  async function addChangeEntry(rootPath, options) {
19300
20093
  if (options.scopes.length === 0)
@@ -19302,11 +20095,11 @@ async function addChangeEntry(rootPath, options) {
19302
20095
  if (options.bump === undefined)
19303
20096
  throw new Error("skillset: change add requires --bump major, minor, patch, or none");
19304
20097
  const scopes = [...new Set(options.scopes.map(sourceUnitSelector))].sort(compareStrings);
19305
- const reason = await resolveReason(rootPath, options.reason);
19306
- const statusOptions = sourceStatusOptions(options);
20098
+ const reason = await resolveChangeReason(rootPath, options.reason);
20099
+ const statusOptions = await detectWorkspaceOptions(rootPath, sourceStatusOptions(options));
19307
20100
  const status = await changeStatus(rootPath, statusOptions);
19308
20101
  const existing = await readAllChangeEntries(rootPath, statusOptions);
19309
- const id = await generateChangeId(rootPath, { ...options, scopes }, existing.map((entry2) => entry2.id));
20102
+ const id = await generateChangeId(rootPath, { ...options, scopes, ...statusOptions.sourceDir === undefined ? {} : { sourceDir: statusOptions.sourceDir } }, existing.map((entry2) => entry2.id));
19310
20103
  const sourceHashes = new Map;
19311
20104
  for (const scope of scopes) {
19312
20105
  const hash = sourceHashForScope(scope, status.sourceUnits, status.sourceChanges);
@@ -19314,8 +20107,8 @@ async function addChangeEntry(rootPath, options) {
19314
20107
  throw new Error(`skillset: unknown change scope ${scope}`);
19315
20108
  sourceHashes.set(scope, [hash]);
19316
20109
  }
19317
- const sourceDir = options.sourceDir ?? ".skillset";
19318
- const relativePath2 = join18(sourceDir, PENDING_DIR2, `${id}.md`).replaceAll("\\", "/");
20110
+ const sourceDir = statusOptions.sourceDir ?? ".skillset";
20111
+ const relativePath2 = join17(sourceDir, PENDING_DIR2, `${id}.md`).replaceAll("\\", "/");
19319
20112
  const absolutePath = resolveInside(rootPath, relativePath2);
19320
20113
  const group = options.group === undefined ? undefined : parseGroupArgument(options.group);
19321
20114
  const entryFrontmatter = pendingFrontmatter({
@@ -19325,8 +20118,8 @@ async function addChangeEntry(rootPath, options) {
19325
20118
  scopes,
19326
20119
  sourceHashes
19327
20120
  });
19328
- await mkdir8(resolveInside(rootPath, join18(sourceDir, PENDING_DIR2)), { recursive: true });
19329
- await writeFile8(absolutePath, stringifyMarkdown(entryFrontmatter, reason), "utf8");
20121
+ await mkdir8(resolveInside(rootPath, join17(sourceDir, PENDING_DIR2)), { recursive: true });
20122
+ await writeFile9(absolutePath, stringifyMarkdown(entryFrontmatter, reason), "utf8");
19330
20123
  const [entry] = await readPendingChangeEntries(rootPath, statusOptions).then((entries) => entries.filter((item) => item.id === id));
19331
20124
  if (entry === undefined)
19332
20125
  throw new Error(`skillset: failed to read created change entry ${id}`);
@@ -19334,33 +20127,67 @@ async function addChangeEntry(rootPath, options) {
19334
20127
  return { entry: pendingView(entry, refs) };
19335
20128
  }
19336
20129
  async function updateChangeReason(rootPath, options) {
19337
- const pendingEntries = await readPendingChangeEntries(rootPath, options);
20130
+ const storageOptions = await detectWorkspaceOptions(rootPath, options);
20131
+ const pendingEntries = await readPendingChangeEntries(rootPath, storageOptions);
19338
20132
  const entry = resolvePendingChangeRef(pendingEntries, options.ref);
19339
- const newReason = await resolveReason(rootPath, options.reason);
20133
+ const newReason = await resolveChangeReason(rootPath, options.reason);
19340
20134
  const absolutePath = resolveInside(rootPath, entry.path);
19341
- const parts = parseMarkdown(await readFile17(absolutePath, "utf8"), absolutePath);
20135
+ const parts = parseMarkdown(await readFile18(absolutePath, "utf8"), absolutePath);
19342
20136
  const body = options.append ? `${parts.body.trimEnd()}
19343
20137
 
19344
20138
  ${newReason}` : newReason;
19345
- await writeFile8(absolutePath, stringifyMarkdown(parts.frontmatter, body), "utf8");
19346
- const updated = resolvePendingChangeRef(await readPendingChangeEntries(rootPath, options), entry.id ?? options.ref);
19347
- const refs = refIndex([updated], await readHistoryEntries(rootPath, options));
20139
+ await writeFile9(absolutePath, stringifyMarkdown(parts.frontmatter, body), "utf8");
20140
+ const updated = resolvePendingChangeRef(await readPendingChangeEntries(rootPath, storageOptions), entry.id ?? options.ref);
20141
+ const refs = refIndex([updated], await readHistoryEntries(rootPath, storageOptions));
19348
20142
  return { entry: pendingView(updated, refs) };
19349
20143
  }
20144
+ async function amendAppliedChange(rootPath, options) {
20145
+ const storageOptions = await detectWorkspaceOptions(rootPath, options);
20146
+ const pendingEntries = await readPendingChangeEntries(rootPath, storageOptions);
20147
+ const historyEntries = await readHistoryEntries(rootPath, storageOptions);
20148
+ const refs = refIndex(pendingEntries, historyEntries);
20149
+ assertCombinedRefUnambiguous(options.ref, pendingEntries, historyEntries, refs);
20150
+ const pending = tryResolvePending(pendingEntries, options.ref);
20151
+ if (pending !== undefined) {
20152
+ const pendingRef = pending.id === undefined ? options.ref : refs.get(pending.id) ?? `@${pending.id}`;
20153
+ throw new Error(`skillset: ${pendingRef} is pending; use skillset change reason before release`);
20154
+ }
20155
+ const entry = resolveHistoryRef(historyEntries, options.ref);
20156
+ const reason = await resolveChangeReason(rootPath, options.reason);
20157
+ const now = new Date().toISOString();
20158
+ const sourceDir = storageOptions.sourceDir ?? ".skillset";
20159
+ const relativePath2 = join17(sourceDir, AMENDMENTS_FILE2).replaceAll("\\", "/");
20160
+ const absolutePath = resolveInside(rootPath, relativePath2);
20161
+ await mkdir8(dirname13(absolutePath), { recursive: true });
20162
+ await appendFile(absolutePath, `${JSON.stringify({
20163
+ amendedAt: now,
20164
+ id: entry.id,
20165
+ previousReason: entry.reason,
20166
+ reason,
20167
+ source: entry.path
20168
+ })}
20169
+ `, "utf8");
20170
+ const updatedHistory = await readHistoryEntries(rootPath, storageOptions);
20171
+ const updatedRefs = refIndex(pendingEntries, updatedHistory);
20172
+ return { entry: historyView(resolveHistoryRef(updatedHistory, entry.id), updatedRefs), path: relativePath2 };
20173
+ }
19350
20174
  async function listChangeEntries(rootPath, options = {}) {
19351
- const pendingEntries = await readPendingChangeEntries(rootPath, options);
19352
- const historyEntries = await readHistoryEntries(rootPath, options);
20175
+ const storageOptions = await detectWorkspaceOptions(rootPath, options);
20176
+ const pendingEntries = await readPendingChangeEntries(rootPath, storageOptions);
20177
+ const historyEntries = await readHistoryEntries(rootPath, storageOptions);
19353
20178
  const refs = refIndex(pendingEntries, historyEntries);
19354
20179
  const entries = pendingEntries.filter((entry) => options.group === undefined || groupMatches(entry.group, options.group)).map((entry) => pendingView(entry, refs));
19355
20180
  return { entries };
19356
20181
  }
19357
20182
  async function showChangeEntry(rootPath, options) {
19358
- const resolved = await resolveAnyChangeRef(rootPath, options.ref, options);
20183
+ const storageOptions = await detectWorkspaceOptions(rootPath, options);
20184
+ const resolved = await resolveAnyChangeRef(rootPath, options.ref, storageOptions);
19359
20185
  return { entry: resolved };
19360
20186
  }
19361
20187
  async function readChangeHistory(rootPath, options = {}) {
19362
- const pendingEntries = await readPendingChangeEntries(rootPath, options);
19363
- const historyEntries = await readHistoryEntries(rootPath, options);
20188
+ const storageOptions = await detectWorkspaceOptions(rootPath, options);
20189
+ const pendingEntries = await readPendingChangeEntries(rootPath, storageOptions);
20190
+ const historyEntries = await readHistoryEntries(rootPath, storageOptions);
19364
20191
  const refs = refIndex(pendingEntries, historyEntries);
19365
20192
  if (options.ref === undefined)
19366
20193
  return { entries: historyEntries.map((entry) => historyView(entry, refs)) };
@@ -19420,19 +20247,19 @@ async function generateChangeId(rootPath, options, existingIds) {
19420
20247
  const id = hash.digest("hex").slice(0, 12);
19421
20248
  if (existing.has(id))
19422
20249
  continue;
19423
- if (await exists12(resolveInside(rootPath, join18(sourceDir, PENDING_DIR2, `${id}.md`))))
20250
+ if (await exists11(resolveInside(rootPath, join17(sourceDir, PENDING_DIR2, `${id}.md`))))
19424
20251
  continue;
19425
20252
  return id;
19426
20253
  }
19427
20254
  throw new Error("skillset: failed to generate a unique change id");
19428
20255
  }
19429
- async function resolveReason(rootPath, input) {
20256
+ async function resolveChangeReason(rootPath, input) {
19430
20257
  let reason;
19431
20258
  if (input.kind === "inline")
19432
20259
  reason = input.value;
19433
20260
  if (input.kind === "file") {
19434
20261
  const reasonPath = isAbsolute2(input.path) ? input.path : resolve8(rootPath, input.path);
19435
- reason = await readFile17(reasonPath, "utf8");
20262
+ reason = await readFile18(reasonPath, "utf8");
19436
20263
  }
19437
20264
  if (input.kind === "stdin" || input.kind === "auto") {
19438
20265
  if (input.kind === "auto" && process.stdin.isTTY === true) {
@@ -19502,12 +20329,12 @@ function historyView(entry, refs) {
19502
20329
  }
19503
20330
  async function readHistoryEntries(rootPath, options = {}) {
19504
20331
  const sourceDir = options.sourceDir ?? ".skillset";
19505
- const path2 = join18(sourceDir, HISTORY_FILE2).replaceAll("\\", "/");
20332
+ const path2 = join17(sourceDir, HISTORY_FILE2).replaceAll("\\", "/");
19506
20333
  const absolutePath = resolveInside(rootPath, path2);
19507
- if (!await exists12(absolutePath))
20334
+ if (!await exists11(absolutePath))
19508
20335
  return [];
19509
20336
  const entries = [];
19510
- const lines = (await readFile17(absolutePath, "utf8")).split(`
20337
+ const lines = (await readFile18(absolutePath, "utf8")).split(`
19511
20338
  `);
19512
20339
  for (const [index, line] of lines.entries()) {
19513
20340
  if (line.trim().length === 0)
@@ -19537,7 +20364,44 @@ async function readHistoryEntries(rootPath, options = {}) {
19537
20364
  sourceHashes: readHistoryEvidence2(parsed.evidence, scopes)
19538
20365
  });
19539
20366
  }
19540
- return entries.sort((left, right) => compareStrings(left.id, right.id));
20367
+ const amended = await applyHistoryAmendments2(rootPath, sourceDir, entries);
20368
+ return [...amended].sort((left, right) => compareStrings(left.id, right.id));
20369
+ }
20370
+ async function applyHistoryAmendments2(rootPath, sourceDir, entries) {
20371
+ const amendments = await readHistoryAmendments2(rootPath, sourceDir);
20372
+ if (amendments.size === 0)
20373
+ return entries;
20374
+ return entries.map((entry) => {
20375
+ const amendment = amendments.get(entry.id);
20376
+ return amendment === undefined ? entry : { ...entry, reason: amendment.reason };
20377
+ });
20378
+ }
20379
+ async function readHistoryAmendments2(rootPath, sourceDir) {
20380
+ const path2 = join17(sourceDir, AMENDMENTS_FILE2).replaceAll("\\", "/");
20381
+ const absolutePath = resolveInside(rootPath, path2);
20382
+ if (!await exists11(absolutePath))
20383
+ return new Map;
20384
+ const amendments = new Map;
20385
+ const lines = (await readFile18(absolutePath, "utf8")).split(`
20386
+ `);
20387
+ for (const [index, line] of lines.entries()) {
20388
+ if (line.trim().length === 0)
20389
+ continue;
20390
+ let parsed;
20391
+ try {
20392
+ parsed = JSON.parse(line);
20393
+ } catch {
20394
+ throw new Error(`skillset: invalid JSON in ${path2}:${index + 1}`);
20395
+ }
20396
+ if (!isJsonRecord(parsed))
20397
+ throw new Error(`skillset: expected ${path2}:${index + 1} to contain a JSON object`);
20398
+ const id = readString(parsed, "id");
20399
+ const reason = readString(parsed, "reason");
20400
+ if (id === undefined || reason === undefined)
20401
+ continue;
20402
+ amendments.set(id, { reason });
20403
+ }
20404
+ return amendments;
19541
20405
  }
19542
20406
  async function readAllChangeEntries(rootPath, options) {
19543
20407
  const pending = await readPendingChangeEntries(rootPath, options);
@@ -19704,9 +20568,9 @@ function historicalSourceUnitSelector2(raw) {
19704
20568
  }
19705
20569
  return sourceUnitSelector(raw);
19706
20570
  }
19707
- async function exists12(path2) {
20571
+ async function exists11(path2) {
19708
20572
  try {
19709
- await stat15(path2);
20573
+ await stat14(path2);
19710
20574
  return true;
19711
20575
  } catch (error) {
19712
20576
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
@@ -19715,6 +20579,7 @@ async function exists12(path2) {
19715
20579
  throw error;
19716
20580
  }
19717
20581
  }
20582
+
19718
20583
  // apps/skillset/src/cli-renderers.ts
19719
20584
  function printDiagnostics(diagnostics) {
19720
20585
  for (const diagnostic of diagnostics) {
@@ -19739,11 +20604,31 @@ function printDiffPlan(diff, reason) {
19739
20604
  for (const path2 of diff.removed)
19740
20605
  console.log(` - ${path2}`);
19741
20606
  console.log(`skillset: planned ${diff.added.length} added, ${diff.changed.length} changed, ${diff.missing.length} missing, ${diff.removed.length} removed (${reason})`);
20607
+ printGeneratedChangelogDriftHint(diff);
20608
+ }
20609
+ function hasGeneratedChangelogDrift2(diff) {
20610
+ return hasGeneratedChangelogPath2([...diff.added, ...diff.changed, ...diff.missing, ...diff.removed]);
20611
+ }
20612
+ function hasGeneratedChangelogPath2(paths) {
20613
+ return paths.some((path2) => path2.endsWith("/CHANGELOG.md") || path2 === "CHANGELOG.md");
20614
+ }
20615
+ function printGeneratedChangelogDriftHint(diff) {
20616
+ if (!hasGeneratedChangelogDrift2(diff))
20617
+ return;
20618
+ printGeneratedChangelogPathHint([...diff.added, ...diff.changed, ...diff.missing, ...diff.removed]);
20619
+ }
20620
+ function printGeneratedChangelogPathHint(paths) {
20621
+ if (!hasGeneratedChangelogPath2(paths))
20622
+ return;
20623
+ console.log(generatedChangelogHint());
20624
+ }
20625
+ function generatedChangelogHint() {
20626
+ return "skillset: generated CHANGELOG.md files are managed projections; edit pending wording with `skillset change reason <@ref>` before release, `skillset change amend <@ref>` for applied-history wording after release, or `skillset release amend <@ref>` for release-event metadata.";
19742
20627
  }
19743
20628
 
19744
20629
  // apps/skillset/src/runtime-hooks/commands.ts
19745
- import { readFile as readFile18, stat as stat16 } from "fs/promises";
19746
- import { join as join19 } from "path";
20630
+ import { readFile as readFile19, stat as stat15 } from "fs/promises";
20631
+ import { join as join18 } from "path";
19747
20632
  async function resolveSkillsetCommand(rootPath = process.cwd(), env = process.env) {
19748
20633
  const override = env.SKILLSET_HOOK_COMMAND?.trim();
19749
20634
  if (override !== undefined && override.length > 0)
@@ -19797,10 +20682,10 @@ async function commandExists(command, cwd, env) {
19797
20682
  return result.exitCode === 0;
19798
20683
  }
19799
20684
  async function isLocalSkillsetCheckout(rootPath) {
19800
- if (!await exists13(join19(rootPath, "apps", "skillset", "src", "cli.ts")))
20685
+ if (!await exists12(join18(rootPath, "apps", "skillset", "src", "cli.ts")))
19801
20686
  return false;
19802
20687
  try {
19803
- const packageJson = JSON.parse(await readFile18(join19(rootPath, "package.json"), "utf8"));
20688
+ const packageJson = JSON.parse(await readFile19(join18(rootPath, "package.json"), "utf8"));
19804
20689
  return packageJson.name === "skillset-workspace";
19805
20690
  } catch (error) {
19806
20691
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
@@ -19819,9 +20704,9 @@ async function capture(argv, options) {
19819
20704
  const exitCode = await proc.exited;
19820
20705
  return { exitCode };
19821
20706
  }
19822
- async function exists13(path2) {
20707
+ async function exists12(path2) {
19823
20708
  try {
19824
- await stat16(path2);
20709
+ await stat15(path2);
19825
20710
  return true;
19826
20711
  } catch (error) {
19827
20712
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
@@ -20077,13 +20962,21 @@ function renderAgentRuntimeSnippet(target) {
20077
20962
  }
20078
20963
  // apps/skillset/src/runtime-hooks/source-gate.ts
20079
20964
  var HOOK_RELEVANT_SOURCE_PATHS = [
20965
+ "skillset.yaml",
20966
+ "skillset/rules",
20967
+ "skillset/skills",
20968
+ "skillset/plugins",
20969
+ "skillset/shared",
20970
+ "skillset",
20971
+ "changes",
20972
+ ".skillset/skillset.yaml",
20080
20973
  ".skillset/config.yaml",
20081
20974
  ".skillset/src/rules",
20082
20975
  ".skillset/src/skills",
20083
20976
  ".skillset/src/plugins",
20084
20977
  ".skillset/src/shared",
20085
20978
  ".skillset/src",
20086
- ".skillset/changes/pending"
20979
+ ".skillset/changes"
20087
20980
  ];
20088
20981
  async function readHookSourceGate(rootPath = process.cwd()) {
20089
20982
  const result = await capture2(["git", "status", "--porcelain=v1", "--untracked-files=all", "--", ...HOOK_RELEVANT_SOURCE_PATHS], {
@@ -20168,12 +21061,14 @@ function result(args) {
20168
21061
  };
20169
21062
  }
20170
21063
  // apps/skillset/src/release.ts
20171
- import { appendFile, mkdir as mkdir9, readFile as readFile19, rm as rm6, stat as stat17, writeFile as writeFile9 } from "fs/promises";
21064
+ import { appendFile as appendFile2, mkdir as mkdir9, readFile as readFile20, rm as rm6, stat as stat16, writeFile as writeFile10 } from "fs/promises";
20172
21065
  import { createHash as createHash7 } from "crypto";
20173
- import { dirname as dirname13, join as join20 } from "path";
21066
+ import { dirname as dirname14, join as join19 } from "path";
20174
21067
  var HISTORY_FILE3 = "changes/history.jsonl";
20175
21068
  var RELEASES_FILE = "changes/releases.jsonl";
21069
+ var RELEASE_AMENDMENTS_FILE = "changes/release-amendments.jsonl";
20176
21070
  var STATE_FILE2 = "changes/state.json";
21071
+ var MIN_REF_LENGTH2 = 6;
20177
21072
  var BUMP_WEIGHT = {
20178
21073
  none: 0,
20179
21074
  patch: 1,
@@ -20181,17 +21076,18 @@ var BUMP_WEIGHT = {
20181
21076
  major: 3
20182
21077
  };
20183
21078
  async function planRelease(rootPath, options = {}) {
20184
- const pending = await readPendingChangeEntries(rootPath, options);
21079
+ const releaseOptions = await detectWorkspaceOptions(rootPath, options);
21080
+ const graph = await loadBuildGraph(rootPath, releaseOptions);
21081
+ const pending = await readPendingChangeEntries(rootPath, releaseOptions);
20185
21082
  if (pending.length === 0)
20186
21083
  return { baselineScopes: [], entries: [], ignoredEntries: [], scopes: [] };
20187
- const check = await changeCheck(rootPath, options);
21084
+ const check = await changeCheck(rootPath, releaseOptions);
20188
21085
  const errors = check.issues.filter((issue) => issue.severity === "error");
20189
21086
  if (errors.length > 0) {
20190
21087
  throw new Error(`skillset: release plan requires valid pending change entries
20191
21088
  ` + errors.map((issue) => `${issue.path ?? "change"}: ${issue.code}: ${issue.message}`).join(`
20192
21089
  `));
20193
21090
  }
20194
- const graph = await loadBuildGraph(rootPath, options);
20195
21091
  const sourceUnits = new Map(check.status.sourceUnits.map((unit) => [unit.id, unit]));
20196
21092
  const sourceChanges = new Map(check.status.sourceChanges.map((change) => [change.id, change]));
20197
21093
  const entries = check.entries.flatMap((entry) => releaseEntryPlan(entry));
@@ -20208,27 +21104,28 @@ async function planRelease(rootPath, options = {}) {
20208
21104
  };
20209
21105
  }
20210
21106
  async function applyRelease(rootPath, options = {}) {
20211
- const plan = await planRelease(rootPath, options);
21107
+ const releaseOptions = await detectWorkspaceOptions(rootPath, options);
21108
+ const plan = await planRelease(rootPath, releaseOptions);
20212
21109
  if (plan.entries.length === 0) {
20213
21110
  return { files: [], plan, renderedFiles: 0 };
20214
21111
  }
20215
- const sourceDir = options.sourceDir ?? ".skillset";
21112
+ const sourceDir = releaseOptions.sourceDir ?? ".skillset";
20216
21113
  const now = new Date().toISOString();
20217
21114
  const files = new Set;
20218
- const pending = (await changeCheck(rootPath, options)).entries;
21115
+ const pending = (await changeCheck(rootPath, releaseOptions)).entries;
20219
21116
  const snapshots = await snapshotReleaseFiles(rootPath, sourceDir, pending, plan.baselineScopes.length > 0);
20220
21117
  let renderedFiles = 0;
20221
21118
  try {
20222
21119
  await appendHistory(rootPath, sourceDir, pending, now, files);
20223
21120
  if (plan.baselineScopes.length > 0) {
20224
- const state = await readReleaseState(rootPath, options);
20225
- const statePath = await writeReleaseState(rootPath, nextReleaseState(state, plan.baselineScopes, now), options);
21121
+ const state = await readReleaseState(rootPath, releaseOptions);
21122
+ const statePath = await writeReleaseState(rootPath, nextReleaseState(state, plan.baselineScopes, now), releaseOptions);
20226
21123
  files.add(statePath);
20227
21124
  }
20228
21125
  if (plan.scopes.length > 0) {
20229
21126
  await appendReleaseRecord(rootPath, sourceDir, plan, now, files);
20230
21127
  }
20231
- const rendered = await buildSkillset(rootPath, options);
21128
+ const rendered = await buildSkillset(rootPath, releaseOptions);
20232
21129
  renderedFiles = rendered.length;
20233
21130
  for (const file of rendered)
20234
21131
  files.add(file.path);
@@ -20242,6 +21139,31 @@ async function applyRelease(rootPath, options = {}) {
20242
21139
  }
20243
21140
  return { files: [...files].sort(compareStrings), plan, renderedFiles };
20244
21141
  }
21142
+ async function amendReleaseRecord(rootPath, options) {
21143
+ const storageOptions = await detectWorkspaceOptions(rootPath, options);
21144
+ const records = await readReleaseRecords(rootPath, storageOptions);
21145
+ const amendments = await readReleaseAmendments(rootPath, storageOptions);
21146
+ const release = releaseView(resolveReleaseRef(records, options.ref), releaseRefIndex(records), amendments);
21147
+ const notes = await resolveChangeReason(rootPath, options.reason);
21148
+ const now = new Date().toISOString();
21149
+ const sourceDir = storageOptions.sourceDir ?? ".skillset";
21150
+ const amendmentPath = join19(sourceDir, RELEASE_AMENDMENTS_FILE).replaceAll("\\", "/");
21151
+ const absolutePath = resolveInside(rootPath, amendmentPath);
21152
+ await mkdir9(dirname14(absolutePath), { recursive: true });
21153
+ await appendFile2(absolutePath, `${JSON.stringify({
21154
+ amendedAt: now,
21155
+ id: release.id,
21156
+ notes,
21157
+ ...release.notes === undefined ? {} : { previousNotes: release.notes },
21158
+ source: release.path
21159
+ })}
21160
+ `, "utf8");
21161
+ const updatedAmendments = await readReleaseAmendments(rootPath, storageOptions);
21162
+ return {
21163
+ amendmentPath,
21164
+ release: releaseView(resolveReleaseRef(records, release.id), releaseRefIndex(records), updatedAmendments)
21165
+ };
21166
+ }
20245
21167
  function releaseEntryPlan(entry) {
20246
21168
  if (entry.id === undefined || entry.bump === undefined)
20247
21169
  return [];
@@ -20372,9 +21294,9 @@ function nextReleaseState(state, scopesToWrite, updatedAt) {
20372
21294
  async function appendHistory(rootPath, sourceDir, entries, appliedAt, files) {
20373
21295
  if (entries.length === 0)
20374
21296
  return;
20375
- const relativePath2 = join20(sourceDir, HISTORY_FILE3).replaceAll("\\", "/");
21297
+ const relativePath2 = join19(sourceDir, HISTORY_FILE3).replaceAll("\\", "/");
20376
21298
  const absolutePath = resolveInside(rootPath, relativePath2);
20377
- await mkdir9(dirname13(absolutePath), { recursive: true });
21299
+ await mkdir9(dirname14(absolutePath), { recursive: true });
20378
21300
  const lines = entries.flatMap((entry) => entry.id === undefined || entry.bump === undefined ? [] : [
20379
21301
  JSON.stringify({
20380
21302
  appliedAt,
@@ -20389,7 +21311,7 @@ async function appendHistory(rootPath, sourceDir, entries, appliedAt, files) {
20389
21311
  ]);
20390
21312
  if (lines.length === 0)
20391
21313
  return;
20392
- await appendFile(absolutePath, `${lines.join(`
21314
+ await appendFile2(absolutePath, `${lines.join(`
20393
21315
  `)}
20394
21316
  `, "utf8");
20395
21317
  files.add(relativePath2);
@@ -20397,10 +21319,10 @@ async function appendHistory(rootPath, sourceDir, entries, appliedAt, files) {
20397
21319
  async function appendReleaseRecord(rootPath, sourceDir, plan, appliedAt, files) {
20398
21320
  if (plan.scopes.length === 0)
20399
21321
  return;
20400
- const relativePath2 = join20(sourceDir, RELEASES_FILE).replaceAll("\\", "/");
21322
+ const relativePath2 = join19(sourceDir, RELEASES_FILE).replaceAll("\\", "/");
20401
21323
  const absolutePath = resolveInside(rootPath, relativePath2);
20402
- await mkdir9(dirname13(absolutePath), { recursive: true });
20403
- await appendFile(absolutePath, `${JSON.stringify({
21324
+ await mkdir9(dirname14(absolutePath), { recursive: true });
21325
+ await appendFile2(absolutePath, `${JSON.stringify({
20404
21326
  appliedAt,
20405
21327
  baseline: { hashSchema: SOURCE_HASH_SCHEMA, kind: "source-hashes" },
20406
21328
  entries: plan.entries.filter((entry) => !entry.ignored).map((entry) => entry.id).sort(compareStrings),
@@ -20419,18 +21341,18 @@ async function appendReleaseRecord(rootPath, sourceDir, plan, appliedAt, files)
20419
21341
  }
20420
21342
  async function snapshotReleaseFiles(rootPath, sourceDir, entries, includeReleaseState) {
20421
21343
  const paths = new Set([
20422
- join20(sourceDir, HISTORY_FILE3).replaceAll("\\", "/"),
21344
+ join19(sourceDir, HISTORY_FILE3).replaceAll("\\", "/"),
20423
21345
  ...entries.map((entry) => entry.path)
20424
21346
  ]);
20425
21347
  if (includeReleaseState) {
20426
- paths.add(join20(sourceDir, STATE_FILE2).replaceAll("\\", "/"));
20427
- paths.add(join20(sourceDir, RELEASES_FILE).replaceAll("\\", "/"));
21348
+ paths.add(join19(sourceDir, STATE_FILE2).replaceAll("\\", "/"));
21349
+ paths.add(join19(sourceDir, RELEASES_FILE).replaceAll("\\", "/"));
20428
21350
  }
20429
21351
  const snapshots = [];
20430
21352
  for (const path2 of [...paths].sort(compareStrings)) {
20431
21353
  const absolutePath = resolveInside(rootPath, path2);
20432
21354
  snapshots.push({
20433
- ...await exists14(absolutePath) ? { content: await readFile19(absolutePath) } : {},
21355
+ ...await exists13(absolutePath) ? { content: await readFile20(absolutePath) } : {},
20434
21356
  path: path2
20435
21357
  });
20436
21358
  }
@@ -20443,13 +21365,13 @@ async function restoreSnapshots(rootPath, snapshots) {
20443
21365
  await rm6(absolutePath, { force: true });
20444
21366
  continue;
20445
21367
  }
20446
- await mkdir9(dirname13(absolutePath), { recursive: true });
20447
- await writeFile9(absolutePath, snapshot.content);
21368
+ await mkdir9(dirname14(absolutePath), { recursive: true });
21369
+ await writeFile10(absolutePath, snapshot.content);
20448
21370
  }
20449
21371
  }
20450
- async function exists14(path2) {
21372
+ async function exists13(path2) {
20451
21373
  try {
20452
- await stat17(path2);
21374
+ await stat16(path2);
20453
21375
  return true;
20454
21376
  } catch (error) {
20455
21377
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
@@ -20487,17 +21409,146 @@ function releaseIdFor(scopes) {
20487
21409
  }
20488
21410
  return hash.digest("hex").slice(0, 12);
20489
21411
  }
21412
+ async function readReleaseRecords(rootPath, options = {}) {
21413
+ const sourceDir = options.sourceDir ?? ".skillset";
21414
+ const path2 = join19(sourceDir, RELEASES_FILE).replaceAll("\\", "/");
21415
+ const absolutePath = resolveInside(rootPath, path2);
21416
+ if (!await exists13(absolutePath))
21417
+ return [];
21418
+ const records = [];
21419
+ const lines = (await readFile20(absolutePath, "utf8")).split(`
21420
+ `);
21421
+ for (const [index, line] of lines.entries()) {
21422
+ if (line.trim().length === 0)
21423
+ continue;
21424
+ const parsed = parseJsonLine(line, `${path2}:${index + 1}`);
21425
+ const id = readStringField(parsed, "id", `${path2}:${index + 1}`);
21426
+ const entries = readStringArrayField(parsed, "entries", `${path2}:${index + 1}`);
21427
+ const scopes = readReleaseScopeArray(parsed, `${path2}:${index + 1}`);
21428
+ records.push({
21429
+ ...typeof parsed.appliedAt === "string" ? { appliedAt: parsed.appliedAt } : {},
21430
+ entries,
21431
+ id,
21432
+ path: path2,
21433
+ scopes
21434
+ });
21435
+ }
21436
+ return records;
21437
+ }
21438
+ async function readReleaseAmendments(rootPath, options = {}) {
21439
+ const sourceDir = options.sourceDir ?? ".skillset";
21440
+ const path2 = join19(sourceDir, RELEASE_AMENDMENTS_FILE).replaceAll("\\", "/");
21441
+ const absolutePath = resolveInside(rootPath, path2);
21442
+ if (!await exists13(absolutePath))
21443
+ return new Map;
21444
+ const amendments = new Map;
21445
+ const lines = (await readFile20(absolutePath, "utf8")).split(`
21446
+ `);
21447
+ for (const [index, line] of lines.entries()) {
21448
+ if (line.trim().length === 0)
21449
+ continue;
21450
+ const parsed = parseJsonLine(line, `${path2}:${index + 1}`);
21451
+ const id = readStringField(parsed, "id", `${path2}:${index + 1}`);
21452
+ const notes = readStringField(parsed, "notes", `${path2}:${index + 1}`);
21453
+ amendments.set(id, {
21454
+ ...typeof parsed.amendedAt === "string" ? { amendedAt: parsed.amendedAt } : {},
21455
+ id,
21456
+ notes
21457
+ });
21458
+ }
21459
+ return amendments;
21460
+ }
21461
+ function resolveReleaseRef(records, ref) {
21462
+ const normalized = ref.startsWith("@") ? ref.slice(1) : ref;
21463
+ if (normalized.length < MIN_REF_LENGTH2) {
21464
+ throw new Error(`skillset: release ref ${ref} must include at least ${MIN_REF_LENGTH2} characters`);
21465
+ }
21466
+ const matches = records.filter((record2) => record2.id === normalized || record2.id.startsWith(normalized));
21467
+ if (matches.length === 0)
21468
+ throw new Error(`skillset: unknown release ref ${ref}`);
21469
+ if (matches.length > 1) {
21470
+ throw new Error(`skillset: ambiguous release ref ${ref}; matches ${matches.map((record2) => `@${record2.id.slice(0, MIN_REF_LENGTH2)}`).join(", ")}`);
21471
+ }
21472
+ const [record] = matches;
21473
+ if (record === undefined)
21474
+ throw new Error(`skillset: unknown release ref ${ref}`);
21475
+ return record;
21476
+ }
21477
+ function releaseRefIndex(records) {
21478
+ const refs = new Map;
21479
+ for (const record of records)
21480
+ refs.set(record.id, `@${record.id.slice(0, MIN_REF_LENGTH2)}`);
21481
+ return refs;
21482
+ }
21483
+ function releaseView(record, refs, amendments) {
21484
+ const notes = amendments.get(record.id)?.notes;
21485
+ return {
21486
+ ...record.appliedAt === undefined ? {} : { appliedAt: record.appliedAt },
21487
+ entries: record.entries,
21488
+ id: record.id,
21489
+ ...notes === undefined ? {} : { notes },
21490
+ path: record.path,
21491
+ ref: refs.get(record.id) ?? `@${record.id}`,
21492
+ scopes: record.scopes
21493
+ };
21494
+ }
21495
+ function readReleaseScopeArray(record, location) {
21496
+ if (!Array.isArray(record.scopes))
21497
+ throw new Error(`skillset: release record ${location} scopes must be an array`);
21498
+ return record.scopes.map((item, index) => {
21499
+ const scopeLocation = `${location}.scopes[${index}]`;
21500
+ if (!isRecord3(item))
21501
+ throw new Error(`skillset: release record ${scopeLocation} must be an object`);
21502
+ const scope = readStringField(item, "scope", scopeLocation);
21503
+ return {
21504
+ ...isChangeBump(item.bump) ? { bump: item.bump } : {},
21505
+ entries: Array.isArray(item.entries) ? item.entries.filter((entry) => typeof entry === "string") : [],
21506
+ ...typeof item.nextVersion === "string" ? { nextVersion: item.nextVersion } : {},
21507
+ ...typeof item.previousVersion === "string" ? { previousVersion: item.previousVersion } : {},
21508
+ scope,
21509
+ ...typeof item.sourceHash === "string" ? { sourceHash: item.sourceHash } : {}
21510
+ };
21511
+ });
21512
+ }
21513
+ function readStringArrayField(record, field, location) {
21514
+ const value = record[field];
21515
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
21516
+ throw new Error(`skillset: release record ${location} ${field} must be an array of strings`);
21517
+ }
21518
+ return value.map((item) => String(item));
21519
+ }
21520
+ function readStringField(record, field, location) {
21521
+ const value = record[field];
21522
+ if (typeof value !== "string" || value.length === 0) {
21523
+ throw new Error(`skillset: release record ${location} ${field} must be a string`);
21524
+ }
21525
+ return value;
21526
+ }
21527
+ function parseJsonLine(line, location) {
21528
+ try {
21529
+ const parsed = JSON.parse(line);
21530
+ if (isRecord3(parsed))
21531
+ return parsed;
21532
+ } catch {}
21533
+ throw new Error(`skillset: invalid release JSON in ${location}`);
21534
+ }
21535
+ function isRecord3(value) {
21536
+ return typeof value === "object" && value !== null && !Array.isArray(value);
21537
+ }
21538
+ function isChangeBump(value) {
21539
+ return value === "major" || value === "minor" || value === "patch" || value === "none";
21540
+ }
20490
21541
  // apps/skillset/src/test-runner.ts
20491
- import { cp, mkdir as mkdir10, mkdtemp as mkdtemp4, readFile as readFile20, rm as rm7, stat as stat18, writeFile as writeFile10 } from "fs/promises";
21542
+ import { cp, mkdir as mkdir10, mkdtemp as mkdtemp4, readFile as readFile21, rm as rm7, stat as stat17, writeFile as writeFile11 } from "fs/promises";
20492
21543
  import { createHash as createHash8, randomBytes as randomBytes2 } from "crypto";
20493
- import { dirname as dirname14, join as join21, relative as relative16, resolve as resolve9 } from "path";
21544
+ import { dirname as dirname15, join as join20, relative as relative16, resolve as resolve9 } from "path";
20494
21545
  import { tmpdir as tmpdir3 } from "os";
20495
- var DEFAULT_SOURCE_DIR3 = ".skillset";
20496
21546
  var TEST_BUILD_DIR = "build/tests";
20497
21547
  var TEST_SCHEMA = 1;
20498
21548
  async function runSkillsetTest(rootPath, name, options = {}) {
20499
- const sourceDir = options.sourceDir ?? DEFAULT_SOURCE_DIR3;
20500
- const declaration = await readTestDeclaration(rootPath, sourceDir, name);
21549
+ const graph = await loadBuildGraph(rootPath, options);
21550
+ const sourceDir = graph.sourceDir;
21551
+ const declaration = await readTestDeclaration(graph.rootConfigPath, name);
20501
21552
  if (declaration.source !== `repo:${sourceDir}`) {
20502
21553
  throw new Error(`skillset: test ${declaration.name} source ${declaration.source} is not supported yet; use repo:${sourceDir}`);
20503
21554
  }
@@ -20508,23 +21559,23 @@ async function runSkillsetTest(rootPath, name, options = {}) {
20508
21559
  targetFilter: declaration.targets
20509
21560
  };
20510
21561
  const runId = makeRunId(declaration.name);
20511
- const buildRoot = resolveInside(rootPath, join21(sourceDir, TEST_BUILD_DIR));
20512
- const runsRoot = join21(buildRoot, "runs");
20513
- const runPath = join21(runsRoot, runId);
20514
- const workspacePath = join21(runPath, "workspace");
20515
- const stagingRoot = await mkdtemp4(join21(tmpdir3(), "skillset-test-"));
20516
- const stagingWorkspacePath = join21(stagingRoot, "workspace");
20517
- const stagingSourcePath = join21(stagingWorkspacePath, sourceDir);
21562
+ const buildRoot = resolveInside(rootPath, join20(sourceDir, TEST_BUILD_DIR));
21563
+ const runsRoot = join20(buildRoot, "runs");
21564
+ const runPath = join20(runsRoot, runId);
21565
+ const workspacePath = join20(runPath, "workspace");
21566
+ const stagingRoot = await mkdtemp4(join20(tmpdir3(), "skillset-test-"));
21567
+ const stagingWorkspacePath = join20(stagingRoot, "workspace");
21568
+ const stagingSourcePath = join20(stagingWorkspacePath, sourceDir);
20518
21569
  const sourcePath = resolveInside(rootPath, sourceDir);
20519
21570
  const workspaceLockPath = resolveInside(rootPath, ".skillset.lock");
20520
- const ignoredSourceBuildPath = resolveInside(rootPath, join21(sourceDir, "build"));
21571
+ const ignoredSourceBuildPath = resolveInside(rootPath, join20(sourceDir, "build"));
20521
21572
  try {
20522
21573
  await mkdir10(stagingWorkspacePath, { recursive: true });
20523
21574
  await cp(sourcePath, stagingSourcePath, {
20524
21575
  filter: (path2) => !isSameOrInside2(ignoredSourceBuildPath, path2),
20525
21576
  recursive: true
20526
21577
  });
20527
- await copyIfExists(workspaceLockPath, join21(stagingWorkspacePath, ".skillset.lock"));
21578
+ await copyIfExists(workspaceLockPath, join20(stagingWorkspacePath, ".skillset.lock"));
20528
21579
  await copyWorkspaceManagedFiles(rootPath, stagingWorkspacePath, workspaceLockPath, sourceDir);
20529
21580
  const assertions = [];
20530
21581
  let generatedFiles = 0;
@@ -20548,9 +21599,9 @@ async function runSkillsetTest(rootPath, name, options = {}) {
20548
21599
  await cp(stagingWorkspacePath, workspacePath, { recursive: true });
20549
21600
  const activationPath = await writeActivationProbes(runPath, declaration);
20550
21601
  const ok = assertions.every((assertion) => assertion.ok);
20551
- const reportPath = join21(runPath, "report.json");
20552
- const reportMarkdownPath = join21(runPath, "report.md");
20553
- const latestPath = join21(buildRoot, "latest");
21602
+ const reportPath = join20(runPath, "report.json");
21603
+ const reportMarkdownPath = join20(runPath, "report.md");
21604
+ const latestPath = join20(buildRoot, "latest");
20554
21605
  const activationReport = activationPath === undefined ? {} : {
20555
21606
  activation: {
20556
21607
  path: relative16(rootPath, activationPath),
@@ -20569,8 +21620,8 @@ async function runSkillsetTest(rootPath, name, options = {}) {
20569
21620
  ...activationReport,
20570
21621
  workspacePath: relative16(rootPath, workspacePath)
20571
21622
  };
20572
- await writeFile10(reportPath, renderValidatedJson(report, relative16(rootPath, reportPath)), "utf8");
20573
- await writeFile10(reportMarkdownPath, renderMarkdownReport(report), "utf8");
21623
+ await writeFile11(reportPath, renderValidatedJson(report, relative16(rootPath, reportPath)), "utf8");
21624
+ await writeFile11(reportMarkdownPath, renderMarkdownReport(report), "utf8");
20574
21625
  await refreshLatest(buildRoot, runPath, latestPath, report, rootPath);
20575
21626
  return {
20576
21627
  assertions,
@@ -20592,9 +21643,8 @@ async function runSkillsetTest(rootPath, name, options = {}) {
20592
21643
  await rm7(stagingRoot, { force: true, recursive: true });
20593
21644
  }
20594
21645
  }
20595
- async function readTestDeclaration(rootPath, sourceDir, requestedName) {
20596
- const configPath = resolveInside(rootPath, join21(sourceDir, "config.yaml"));
20597
- const config2 = parseYamlRecord(await readFile20(configPath, "utf8"), configPath);
21646
+ async function readTestDeclaration(configPath, requestedName) {
21647
+ const config2 = parseYamlRecord(await readFile21(configPath, "utf8"), configPath);
20598
21648
  const defaultTargets2 = readEffectiveTargets(config2, configPath);
20599
21649
  const tests = config2.tests;
20600
21650
  if (!isJsonRecord(tests)) {
@@ -20761,18 +21811,18 @@ function assertionRecord(assertion) {
20761
21811
  async function runAssertion(workspacePath, assertion, options) {
20762
21812
  if (assertion.kind === "exists") {
20763
21813
  const targetPath = resolveInside(workspacePath, assertion.path);
20764
- const exists15 = await pathExists2(targetPath);
21814
+ const exists14 = await pathExists2(targetPath);
20765
21815
  return {
20766
21816
  kind: "exists",
20767
- ok: exists15,
21817
+ ok: exists14,
20768
21818
  path: assertion.path,
20769
- ...exists15 ? {} : { detail: "path does not exist" }
21819
+ ...exists14 ? {} : { detail: "path does not exist" }
20770
21820
  };
20771
21821
  }
20772
21822
  if (assertion.kind === "contains") {
20773
21823
  const targetPath = resolveInside(workspacePath, assertion.path);
20774
21824
  try {
20775
- const content = await readFile20(targetPath, "utf8");
21825
+ const content = await readFile21(targetPath, "utf8");
20776
21826
  const ok = content.includes(assertion.text);
20777
21827
  return {
20778
21828
  kind: "contains",
@@ -20816,11 +21866,11 @@ function activationExpectationCandidatePaths(graph, target, expect) {
20816
21866
  }
20817
21867
  if (expect.kind === "agent") {
20818
21868
  const projectRoot = targetProjectRoot4(graph, target);
20819
- return graph.projectAgents.filter((agent) => agent.name === expect.name || agent.outputName === expect.name).map((agent) => join21(projectRoot, "agents", `${agent.outputName}.${target === "claude" ? "md" : "toml"}`));
21869
+ return graph.projectAgents.filter((agent) => agent.name === expect.name || agent.outputName === expect.name).map((agent) => join20(projectRoot, "agents", `${agent.outputName}.${target === "claude" ? "md" : "toml"}`));
20820
21870
  }
20821
21871
  return [
20822
- ...graph.standaloneSkills.filter((skill) => skill.id === expect.name).map((skill) => join21(graph.root.outputs.skills[target], dirname14(skill.relativePath), "SKILL.md")),
20823
- ...graph.plugins.flatMap((plugin) => plugin.skills.filter((skill) => skill.id === expect.name).map((skill) => join21(graph.root.outputs.plugins[target], "plugins", plugin.id, dirname14(skill.relativePath), "SKILL.md")))
21872
+ ...graph.standaloneSkills.filter((skill) => skill.id === expect.name).map((skill) => join20(graph.root.outputs.skills[target], dirname15(skill.relativePath), "SKILL.md")),
21873
+ ...graph.plugins.flatMap((plugin) => plugin.skills.filter((skill) => skill.id === expect.name).map((skill) => join20(graph.root.outputs.plugins[target], "plugins", plugin.id, dirname15(skill.relativePath), "SKILL.md")))
20824
21874
  ];
20825
21875
  }
20826
21876
  function targetProjectRoot4(graph, target) {
@@ -20829,22 +21879,22 @@ function targetProjectRoot4(graph, target) {
20829
21879
  async function writeActivationProbes(runPath, declaration) {
20830
21880
  if (declaration.activationProbes.length === 0)
20831
21881
  return;
20832
- const activationRoot = join21(runPath, "activation");
21882
+ const activationRoot = join20(runPath, "activation");
20833
21883
  for (const target of declaration.targets) {
20834
21884
  const probes = declaration.activationProbes.filter((probe) => probe.targets.includes(target));
20835
21885
  if (probes.length === 0)
20836
21886
  continue;
20837
- const targetRoot = join21(activationRoot, target);
21887
+ const targetRoot = join20(activationRoot, target);
20838
21888
  await mkdir10(targetRoot, { recursive: true });
20839
21889
  const records = probes.map((probe) => activationProbeRecord(probe, target));
20840
- await writeFile10(join21(targetRoot, "probes.json"), renderValidatedJson({
21890
+ await writeFile11(join20(targetRoot, "probes.json"), renderValidatedJson({
20841
21891
  probes: records,
20842
21892
  schemaVersion: TEST_SCHEMA,
20843
21893
  target
20844
21894
  }, `activation ${target} probes`), "utf8");
20845
21895
  for (const record of records) {
20846
21896
  const name = typeof record.name === "string" ? record.name : "probe";
20847
- await writeFile10(join21(targetRoot, `${name}.md`), renderActivationProbeMarkdown(record), "utf8");
21897
+ await writeFile11(join20(targetRoot, `${name}.md`), renderActivationProbeMarkdown(record), "utf8");
20848
21898
  }
20849
21899
  }
20850
21900
  return activationRoot;
@@ -20901,14 +21951,14 @@ async function refreshLatest(buildRoot, runPath, latestPath, report, rootPath) {
20901
21951
  const latest = {
20902
21952
  name: report.name,
20903
21953
  ok: report.ok,
20904
- reportPath: relative16(rootPath, join21(latestPath, "report.json")),
21954
+ reportPath: relative16(rootPath, join20(latestPath, "report.json")),
20905
21955
  runId: report.runId,
20906
21956
  runPath: relative16(rootPath, runPath),
20907
21957
  schemaVersion: TEST_SCHEMA,
20908
21958
  source: report.source,
20909
- workspacePath: relative16(rootPath, join21(latestPath, "workspace"))
21959
+ workspacePath: relative16(rootPath, join20(latestPath, "workspace"))
20910
21960
  };
20911
- await writeFile10(join21(buildRoot, "latest.json"), renderValidatedJson(latest, relative16(rootPath, join21(buildRoot, "latest.json"))), "utf8");
21961
+ await writeFile11(join20(buildRoot, "latest.json"), renderValidatedJson(latest, relative16(rootPath, join20(buildRoot, "latest.json"))), "utf8");
20912
21962
  }
20913
21963
  function renderMarkdownReport(report) {
20914
21964
  const assertions = Array.isArray(report.assertions) ? report.assertions : [];
@@ -20945,7 +21995,7 @@ function activationProbeCount(report) {
20945
21995
  }
20946
21996
  async function pathExists2(path2) {
20947
21997
  try {
20948
- await stat18(path2);
21998
+ await stat17(path2);
20949
21999
  return true;
20950
22000
  } catch {
20951
22001
  return false;
@@ -20954,7 +22004,7 @@ async function pathExists2(path2) {
20954
22004
  async function copyIfExists(sourcePath, targetPath) {
20955
22005
  if (!await pathExists2(sourcePath))
20956
22006
  return;
20957
- await mkdir10(dirname14(targetPath), { recursive: true });
22007
+ await mkdir10(dirname15(targetPath), { recursive: true });
20958
22008
  await cp(sourcePath, targetPath);
20959
22009
  }
20960
22010
  async function copyWorkspaceManagedFiles(rootPath, stagingWorkspacePath, workspaceLockPath, sourceDir) {
@@ -20962,13 +22012,13 @@ async function copyWorkspaceManagedFiles(rootPath, stagingWorkspacePath, workspa
20962
22012
  return;
20963
22013
  let lock;
20964
22014
  try {
20965
- lock = JSON.parse(await readFile20(workspaceLockPath, "utf8"));
22015
+ lock = JSON.parse(await readFile21(workspaceLockPath, "utf8"));
20966
22016
  } catch {
20967
22017
  return;
20968
22018
  }
20969
22019
  if (!isJsonRecord(lock) || !Array.isArray(lock.items))
20970
22020
  return;
20971
- const ignoredSourceBuildPath = resolveInside(rootPath, join21(sourceDir, "build"));
22021
+ const ignoredSourceBuildPath = resolveInside(rootPath, join20(sourceDir, "build"));
20972
22022
  for (const item of lock.items) {
20973
22023
  if (!isJsonRecord(item) || !Array.isArray(item.files))
20974
22024
  continue;
@@ -20978,7 +22028,7 @@ async function copyWorkspaceManagedFiles(rootPath, stagingWorkspacePath, workspa
20978
22028
  const sourcePath = resolveInside(rootPath, file);
20979
22029
  if (isSameOrInside2(ignoredSourceBuildPath, sourcePath))
20980
22030
  continue;
20981
- await copyIfExists(sourcePath, join21(stagingWorkspacePath, file));
22031
+ await copyIfExists(sourcePath, join20(stagingWorkspacePath, file));
20982
22032
  }
20983
22033
  }
20984
22034
  }
@@ -21007,12 +22057,15 @@ var USAGE = [
21007
22057
  " skillset change <status|check> --staged [--root <path>] [--source <dir>] [--dist <dir>]",
21008
22058
  " skillset change add --scope <source-unit> --bump <bump> [--group <group>] [--reason <text>|--reason-file <path>|--reason -] [--since <ref>] [--root <path>] [--source <dir>]",
21009
22059
  " skillset change reason <@ref> [--append] [--reason <text>|--reason-file <path>|--reason -] [--root <path>] [--source <dir>]",
22060
+ " skillset change amend <@ref> [--reason <text>|--reason-file <path>|--reason -] [--root <path>] [--source <dir>]",
21010
22061
  " skillset change <show|history> [@ref] [--root <path>] [--source <dir>]",
21011
22062
  " skillset change list [--group <group>] [--root <path>] [--source <dir>]",
21012
22063
  " skillset release audit [--root <path>] [--source <dir>] [--dist <dir>]",
21013
22064
  " skillset release plan [--root <path>] [--source <dir>] [--dist <dir>]",
21014
22065
  " skillset release apply [--yes|--dry-run] [--root <path>] [--source <dir>] [--dist <dir>]",
22066
+ " skillset release amend <@ref> [--reason <text>|--reason-file <path>|--reason -] [--root <path>]",
21015
22067
  " skillset restore <backup-id> [--yes|--dry-run] [--root <path>]",
22068
+ " skillset suggest-source <generated-path> [--write --yes] [--root <path>] [--source <dir>] [--dist <dir>]",
21016
22069
  " skillset distribute plan [name] [--root <path>] [--source <dir>] [--dist <dir>]",
21017
22070
  " skillset features [feature-id] [--json]",
21018
22071
  " skillset test [name] [--root <path>] [--source <dir>]",
@@ -21027,7 +22080,7 @@ var USAGE = [
21027
22080
  " skillset import <claude|codex|agents> [--root <path>] [--source <dir>]"
21028
22081
  ].join(`
21029
22082
  `);
21030
- async function runCli(rawArgs = process.argv.slice(2), invokedName = basename8(process.argv[1] ?? "")) {
22083
+ async function runCli(rawArgs = process.argv.slice(2), invokedName = basename9(process.argv[1] ?? "")) {
21031
22084
  const args = invokedName === "create-skillset" ? ["create", ...rawArgs] : rawArgs;
21032
22085
  if (args.some((arg) => arg === "--help" || arg === "-h")) {
21033
22086
  console.log(USAGE);
@@ -21065,9 +22118,12 @@ async function runCli(rawArgs = process.argv.slice(2), invokedName = basename8(p
21065
22118
  rootPath,
21066
22119
  rootExplicit,
21067
22120
  releaseSubcommand,
22121
+ releaseReason,
22122
+ releaseRef,
21068
22123
  setupGlobal,
21069
22124
  setupIncludes,
21070
22125
  setupTargets,
22126
+ sourceSuggestionWrite,
21071
22127
  testName,
21072
22128
  yes
21073
22129
  } = parseArgs(args);
@@ -21100,8 +22156,8 @@ async function runCli(rawArgs = process.argv.slice(2), invokedName = basename8(p
21100
22156
  });
21101
22157
  if (ciReportPath !== undefined) {
21102
22158
  const reportPath = resolve10(ciReportPath);
21103
- await mkdir11(dirname15(reportPath), { recursive: true });
21104
- await writeFile11(reportPath, renderCiReportMarkdown(report));
22159
+ await mkdir11(dirname16(reportPath), { recursive: true });
22160
+ await writeFile12(reportPath, renderCiReportMarkdown(report));
21105
22161
  }
21106
22162
  printCiReport(report);
21107
22163
  if (!report.ok)
@@ -21146,6 +22202,18 @@ async function runCli(rawArgs = process.argv.slice(2), invokedName = basename8(p
21146
22202
  })).entry);
21147
22203
  return;
21148
22204
  }
22205
+ if (changeSubcommand === "amend") {
22206
+ if (changeRef === undefined)
22207
+ throw new Error("skillset: change amend requires @ref");
22208
+ const report = await amendAppliedChange(rootPath, {
22209
+ ...changeOptions,
22210
+ reason: changeReason ?? { kind: "auto" },
22211
+ ref: changeRef
22212
+ });
22213
+ printChangeEntry("amended", report.entry);
22214
+ console.log(` amendment: ${report.path}`);
22215
+ return;
22216
+ }
21149
22217
  if (changeSubcommand === "show") {
21150
22218
  if (changeRef === undefined)
21151
22219
  throw new Error("skillset: change show requires @ref");
@@ -21194,7 +22262,17 @@ async function runCli(rawArgs = process.argv.slice(2), invokedName = basename8(p
21194
22262
  printReleaseApply(result3.plan, result3.files, result3.renderedFiles);
21195
22263
  return;
21196
22264
  }
21197
- throw new Error("skillset: expected release subcommand apply, audit, or plan");
22265
+ if (releaseSubcommand === "amend") {
22266
+ if (releaseRef === undefined)
22267
+ throw new Error("skillset: release amend requires @ref");
22268
+ printReleaseAmend(await amendReleaseRecord(rootPath, {
22269
+ ...options,
22270
+ reason: releaseReason ?? { kind: "auto" },
22271
+ ref: releaseRef
22272
+ }));
22273
+ return;
22274
+ }
22275
+ throw new Error("skillset: expected release subcommand amend, apply, audit, or plan");
21198
22276
  }
21199
22277
  if (command === "restore") {
21200
22278
  if (importPath === undefined)
@@ -21335,6 +22413,7 @@ async function runCli(rawArgs = process.argv.slice(2), invokedName = basename8(p
21335
22413
  for (const path2 of diff.removed)
21336
22414
  console.log(` - ${path2}`);
21337
22415
  console.log(`skillset: ${diff.added.length} added, ${diff.changed.length} changed, ${diff.missing.length} missing, ${diff.removed.length} removed (run skillset build --yes to apply)`);
22416
+ printGeneratedChangelogDriftHint(diff);
21338
22417
  return;
21339
22418
  }
21340
22419
  if (command === "list") {
@@ -21423,6 +22502,19 @@ async function runCli(rawArgs = process.argv.slice(2), invokedName = basename8(p
21423
22502
  process.exitCode = 1;
21424
22503
  return;
21425
22504
  }
22505
+ if (command === "suggest-source") {
22506
+ if (importPath === undefined) {
22507
+ throw new Error("skillset: expected a generated path to suggest source");
22508
+ }
22509
+ const report = await suggestSource(rootPath, importPath, {
22510
+ ...options,
22511
+ write: sourceSuggestionWrite && yes
22512
+ });
22513
+ printSourceSuggestion(report);
22514
+ if (report.status === "refused")
22515
+ process.exitCode = 1;
22516
+ return;
22517
+ }
21426
22518
  if (command === "doctor") {
21427
22519
  const report = await doctorSkillset(rootPath, options);
21428
22520
  if (jsonOutput) {
@@ -21542,6 +22634,24 @@ function printChangeCheck(report) {
21542
22634
  console.log(`skillset: change check found ${errors} error${errors === 1 ? "" : "s"} and ${warnings} warning${warnings === 1 ? "" : "s"}`);
21543
22635
  process.exitCode = 1;
21544
22636
  }
22637
+ function printSourceSuggestion(report) {
22638
+ console.log(`skillset: source suggestion ${report.status} ${report.generatedPath}`);
22639
+ if (report.sourcePath !== undefined)
22640
+ console.log(` source: ${report.sourcePath}`);
22641
+ if (report.lockPath !== undefined)
22642
+ console.log(` lock: ${report.lockPath}`);
22643
+ console.log(` message: ${report.message}`);
22644
+ if (report.wouldWrite)
22645
+ console.log(` write: ${report.wrote ? "applied" : "preview"}`);
22646
+ for (const entry of report.entries) {
22647
+ console.log(` owner: [${entry.target}] ${entry.kind ?? "generated"} ${entry.sourcePath} -> ${entry.outputPath}`);
22648
+ }
22649
+ if (report.nextSteps.length > 0) {
22650
+ console.log(" next:");
22651
+ for (const step of report.nextSteps)
22652
+ console.log(` ${step}`);
22653
+ }
22654
+ }
21545
22655
  function printChangeStatus(report) {
21546
22656
  const baseline = report.baseline.kind === "git-ref" ? `git ref ${report.baseline.ref}${report.baseline.resolvedRef === undefined ? "" : ` (${report.baseline.resolvedRef.slice(0, 12)})`}` : `${report.baseline.label} (${report.baseline.hashSchema})`;
21547
22657
  console.log(`skillset: source hash schema ${report.hashSchema}`);
@@ -21570,6 +22680,7 @@ function printChangeStatus(report) {
21570
22680
  for (const path2 of drift.removed)
21571
22681
  console.log(` generated - ${path2}`);
21572
22682
  console.log(`skillset: generated-output drift ${drift.added.length} added, ${drift.changed.length} changed, ${drift.missing.length} missing, ${drift.removed.length} removed`);
22683
+ printGeneratedChangelogDriftHint(drift);
21573
22684
  }
21574
22685
  function printCiReport(report) {
21575
22686
  for (const issue of report.lintIssues) {
@@ -21584,6 +22695,7 @@ function printCiReport(report) {
21584
22695
  }
21585
22696
  for (const path2 of report.fixedPaths)
21586
22697
  console.log(` fixed ${path2}`);
22698
+ printGeneratedChangelogPathHint(report.fixedPaths);
21587
22699
  const drift = report.drift;
21588
22700
  for (const path2 of drift.added)
21589
22701
  console.log(` generated + ${path2}`);
@@ -21593,6 +22705,13 @@ function printCiReport(report) {
21593
22705
  console.log(` generated ! ${path2}`);
21594
22706
  for (const path2 of drift.removed)
21595
22707
  console.log(` generated - ${path2}`);
22708
+ printGeneratedChangelogDriftHint(drift);
22709
+ for (const suggestion of report.sourceSuggestions ?? []) {
22710
+ console.log(` source suggestion ${suggestion.status}: ${suggestion.generatedPath}`);
22711
+ if (suggestion.sourcePath !== undefined)
22712
+ console.log(` source: ${suggestion.sourcePath}`);
22713
+ console.log(` ${suggestion.message}`);
22714
+ }
21596
22715
  if (report.buildError !== undefined) {
21597
22716
  console.log(` build error: ${report.buildError}`);
21598
22717
  }
@@ -21658,6 +22777,26 @@ function printReleaseApply(plan, files, renderedFiles) {
21658
22777
  for (const file of files)
21659
22778
  console.log(` ${file}`);
21660
22779
  }
22780
+ function printReleaseAmend(report) {
22781
+ console.log(`skillset: amended release ${report.release.ref} ${report.release.path}`);
22782
+ console.log(` id: ${report.release.id}`);
22783
+ console.log(` amendment: ${report.amendmentPath}`);
22784
+ if (report.release.appliedAt !== undefined)
22785
+ console.log(` applied: ${report.release.appliedAt}`);
22786
+ if (report.release.entries.length > 0)
22787
+ console.log(` entries: ${report.release.entries.join(",")}`);
22788
+ for (const scope of report.release.scopes) {
22789
+ const version = scope.previousVersion === undefined || scope.nextVersion === undefined ? "" : ` ${scope.previousVersion} -> ${scope.nextVersion}`;
22790
+ const bump = scope.bump === undefined ? "" : ` (${scope.bump})`;
22791
+ console.log(` scope: ${sourceUnitDisplay(scope.scope)}${version}${bump}`);
22792
+ }
22793
+ if (report.release.notes !== undefined) {
22794
+ console.log(" notes:");
22795
+ for (const line of report.release.notes.split(`
22796
+ `))
22797
+ console.log(` ${line}`);
22798
+ }
22799
+ }
21661
22800
  function printDistributionPlan(report) {
21662
22801
  if (report.plans.length === 0) {
21663
22802
  console.log("skillset: no distributions configured");
@@ -21725,7 +22864,7 @@ function printAdoptReport(report, reason) {
21725
22864
  console.log(` source: git ${report.acquisition.repo} @ ${report.acquisition.ref}`);
21726
22865
  }
21727
22866
  if (report.alreadyAdopted) {
21728
- console.log(" note: repo already has .skillset/config.yaml; adopting against existing source");
22867
+ console.log(" note: repo already has a Skillset workspace marker; adopting against existing source");
21729
22868
  }
21730
22869
  for (const file of report.setupFiles) {
21731
22870
  console.log(` ${file.status === "create" ? "+" : "="} ${file.path}`);
@@ -21845,14 +22984,16 @@ function printRenderResult(outcome) {
21845
22984
  }
21846
22985
  function parseArgs(args) {
21847
22986
  const command = args[0];
21848
- if (command !== "adopt" && command !== "build" && command !== "change" && command !== "check" && command !== "ci" && command !== "create" && command !== "diff" && command !== "distribute" && command !== "doctor" && command !== "explain" && command !== "features" && command !== "hooks" && command !== "import" && command !== "init" && command !== "lint" && command !== "list" && command !== "release" && command !== "restore" && command !== "test") {
21849
- throw new Error(`skillset: expected command adopt, build, change, check, ci, create, diff, distribute, doctor, explain, features, hooks, import, init, lint, list, release, restore, or test
22987
+ if (command !== "adopt" && command !== "build" && command !== "change" && command !== "check" && command !== "ci" && command !== "create" && command !== "diff" && command !== "distribute" && command !== "doctor" && command !== "explain" && command !== "features" && command !== "hooks" && command !== "import" && command !== "init" && command !== "lint" && command !== "list" && command !== "release" && command !== "restore" && command !== "suggest-source" && command !== "test") {
22988
+ throw new Error(`skillset: expected command adopt, build, change, check, ci, create, diff, distribute, doctor, explain, features, hooks, import, init, lint, list, release, restore, suggest-source, or test
21850
22989
  ` + USAGE);
21851
22990
  }
21852
22991
  let changeSubcommand;
21853
22992
  let distributionName;
21854
22993
  let distributionSubcommand;
21855
22994
  let releaseSubcommand;
22995
+ let releaseReason;
22996
+ let releaseRef;
21856
22997
  let changeAppend = false;
21857
22998
  let changeBump;
21858
22999
  let changeGroup;
@@ -21886,18 +23027,19 @@ function parseArgs(args) {
21886
23027
  let setupGlobal = false;
21887
23028
  let setupIncludes;
21888
23029
  let setupTargets;
23030
+ let sourceSuggestionWrite = false;
21889
23031
  let testName;
21890
23032
  let yes = false;
21891
23033
  let index = 1;
21892
23034
  if (command === "change") {
21893
23035
  const subcommand = args[index];
21894
23036
  if (!isChangeSubcommand(subcommand)) {
21895
- throw new Error("skillset: expected change subcommand add, check, history, list, reason, show, or status");
23037
+ throw new Error("skillset: expected change subcommand add, amend, check, history, list, reason, show, or status");
21896
23038
  }
21897
23039
  changeSubcommand = subcommand;
21898
23040
  index += 1;
21899
23041
  const rawRef = args[index];
21900
- if ((subcommand === "check" || subcommand === "history" || subcommand === "reason" || subcommand === "show") && rawRef !== undefined && !rawRef.startsWith("--")) {
23042
+ if ((subcommand === "amend" || subcommand === "check" || subcommand === "history" || subcommand === "reason" || subcommand === "show") && rawRef !== undefined && !rawRef.startsWith("--")) {
21901
23043
  changeRef = rawRef;
21902
23044
  index += 1;
21903
23045
  }
@@ -21905,10 +23047,15 @@ function parseArgs(args) {
21905
23047
  if (command === "release") {
21906
23048
  const subcommand = args[index];
21907
23049
  if (!isReleaseSubcommand(subcommand)) {
21908
- throw new Error("skillset: expected release subcommand apply, audit, or plan");
23050
+ throw new Error("skillset: expected release subcommand amend, apply, audit, or plan");
21909
23051
  }
21910
23052
  releaseSubcommand = subcommand;
21911
23053
  index += 1;
23054
+ const rawRef = args[index];
23055
+ if (subcommand === "amend" && rawRef !== undefined && !rawRef.startsWith("--")) {
23056
+ releaseRef = rawRef;
23057
+ index += 1;
23058
+ }
21912
23059
  }
21913
23060
  if (command === "distribute") {
21914
23061
  const subcommand = args[index];
@@ -21962,10 +23109,10 @@ function parseArgs(args) {
21962
23109
  index += 1;
21963
23110
  }
21964
23111
  }
21965
- if (command === "explain") {
23112
+ if (command === "explain" || command === "suggest-source") {
21966
23113
  const rawPath = args[index];
21967
23114
  if (rawPath === undefined || rawPath.startsWith("--")) {
21968
- throw new Error("skillset: expected a path to explain");
23115
+ throw new Error(`skillset: expected a path to ${command}`);
21969
23116
  }
21970
23117
  importPath = rawPath;
21971
23118
  index += 1;
@@ -21999,10 +23146,10 @@ function parseArgs(args) {
21999
23146
  const equalsIndex = arg.indexOf("=");
22000
23147
  const flag = equalsIndex === -1 ? arg : arg.slice(0, equalsIndex);
22001
23148
  const inlineValue = equalsIndex === -1 ? undefined : arg.slice(equalsIndex + 1);
22002
- if (flag !== "--root" && flag !== "--source" && flag !== "--dist" && flag !== "--name" && flag !== "--kind" && flag !== "--from" && flag !== "--append" && flag !== "--bump" && flag !== "--group" && flag !== "--reason" && flag !== "--reason-file" && flag !== "--ref" && flag !== "--since" && flag !== "--staged" && flag !== "--yes" && flag !== "--dry-run" && flag !== "--updated" && flag !== "--all" && flag !== "--isolated" && flag !== "--scope" && flag !== "--global" && flag !== "--targets" && flag !== "--include" && flag !== "--fix" && flag !== "--report" && flag !== "--json" && flag !== "--runner" && flag !== "--target" && flag !== "--agent-runtime" && flag !== "--pre-commit" && flag !== "--pre-push") {
23149
+ if (flag !== "--root" && flag !== "--source" && flag !== "--dist" && flag !== "--name" && flag !== "--kind" && flag !== "--from" && flag !== "--append" && flag !== "--bump" && flag !== "--group" && flag !== "--reason" && flag !== "--reason-file" && flag !== "--ref" && flag !== "--since" && flag !== "--staged" && flag !== "--yes" && flag !== "--dry-run" && flag !== "--updated" && flag !== "--all" && flag !== "--isolated" && flag !== "--scope" && flag !== "--global" && flag !== "--targets" && flag !== "--include" && flag !== "--fix" && flag !== "--report" && flag !== "--json" && flag !== "--runner" && flag !== "--target" && flag !== "--agent-runtime" && flag !== "--pre-commit" && flag !== "--pre-push" && flag !== "--write") {
22003
23150
  throw new Error(`skillset: unknown option ${arg}`);
22004
23151
  }
22005
- if (flag === "--yes" || flag === "--dry-run" || flag === "--updated" || flag === "--all" || flag === "--isolated" || flag === "--append" || flag === "--staged" || flag === "--global" || flag === "--fix" || flag === "--json" || flag === "--agent-runtime" || flag === "--pre-commit" || flag === "--pre-push") {
23152
+ if (flag === "--yes" || flag === "--dry-run" || flag === "--updated" || flag === "--all" || flag === "--isolated" || flag === "--append" || flag === "--staged" || flag === "--global" || flag === "--fix" || flag === "--json" || flag === "--agent-runtime" || flag === "--pre-commit" || flag === "--pre-push" || flag === "--write") {
22006
23153
  if (inlineValue !== undefined)
22007
23154
  throw new Error(`skillset: ${flag} does not take a value`);
22008
23155
  if (flag === "--yes")
@@ -22031,6 +23178,8 @@ function parseArgs(args) {
22031
23178
  hookPreCommit = true;
22032
23179
  if (flag === "--pre-push")
22033
23180
  hookPrePush = true;
23181
+ if (flag === "--write")
23182
+ sourceSuggestionWrite = true;
22034
23183
  continue;
22035
23184
  }
22036
23185
  const value = inlineValue ?? args[index + 1];
@@ -22047,8 +23196,12 @@ function parseArgs(args) {
22047
23196
  sourceDir = value;
22048
23197
  if (flag === "--dist")
22049
23198
  distDir = value;
22050
- if (flag === "--ref")
22051
- changeRef = value;
23199
+ if (flag === "--ref") {
23200
+ if (command === "release" && releaseSubcommand === "amend")
23201
+ releaseRef = value;
23202
+ else
23203
+ changeRef = value;
23204
+ }
22052
23205
  if (flag === "--since")
22053
23206
  changeSince = value;
22054
23207
  if (flag === "--scope") {
@@ -22064,10 +23217,20 @@ function parseArgs(args) {
22064
23217
  }
22065
23218
  if (flag === "--group")
22066
23219
  changeGroup = value;
22067
- if (flag === "--reason")
22068
- changeReason = setChangeReason(changeReason, value === "-" ? { kind: "stdin" } : { kind: "inline", value });
22069
- if (flag === "--reason-file")
22070
- changeReason = setChangeReason(changeReason, { kind: "file", path: value });
23220
+ if (flag === "--reason") {
23221
+ const reason = value === "-" ? { kind: "stdin" } : { kind: "inline", value };
23222
+ if (command === "release" && releaseSubcommand === "amend")
23223
+ releaseReason = setChangeReason(releaseReason, reason);
23224
+ else
23225
+ changeReason = setChangeReason(changeReason, reason);
23226
+ }
23227
+ if (flag === "--reason-file") {
23228
+ const reason = { kind: "file", path: value };
23229
+ if (command === "release" && releaseSubcommand === "amend")
23230
+ releaseReason = setChangeReason(releaseReason, reason);
23231
+ else
23232
+ changeReason = setChangeReason(changeReason, reason);
23233
+ }
22071
23234
  if (flag === "--bump")
22072
23235
  changeBump = readChangeBump(value);
22073
23236
  if (flag === "--report")
@@ -22159,6 +23322,10 @@ function parseArgs(args) {
22159
23322
  if (command === "release" && releaseSubcommand !== "apply" && (dryRun || yes)) {
22160
23323
  throw new Error("skillset: --yes and --dry-run are only supported with release apply");
22161
23324
  }
23325
+ validateReleaseFlags(command, releaseSubcommand, {
23326
+ ...releaseReason === undefined ? {} : { reason: releaseReason },
23327
+ ...releaseRef === undefined ? {} : { ref: releaseRef }
23328
+ });
22162
23329
  validateTestFlags(command, {
22163
23330
  ...buildMode === undefined ? {} : { buildMode },
22164
23331
  ...distDir === undefined ? {} : { distDir },
@@ -22173,6 +23340,14 @@ function parseArgs(args) {
22173
23340
  ...scopes === undefined ? {} : { scopes },
22174
23341
  ...sourceDir === undefined ? {} : { sourceDir }
22175
23342
  });
23343
+ validateSuggestSourceFlags(command, {
23344
+ ...buildMode === undefined ? {} : { buildMode },
23345
+ ...changeSince === undefined ? {} : { changeSince },
23346
+ dryRun,
23347
+ ...scopes === undefined ? {} : { scopes },
23348
+ write: sourceSuggestionWrite,
23349
+ yes
23350
+ });
22176
23351
  const options = {
22177
23352
  ...buildMode === undefined ? {} : { buildMode },
22178
23353
  ...scopes === undefined ? {} : { scopes },
@@ -22210,20 +23385,23 @@ function parseArgs(args) {
22210
23385
  jsonOutput,
22211
23386
  options,
22212
23387
  ...releaseSubcommand === undefined ? {} : { releaseSubcommand },
23388
+ ...releaseReason === undefined ? {} : { releaseReason },
23389
+ ...releaseRef === undefined ? {} : { releaseRef },
22213
23390
  rootExplicit,
22214
23391
  rootPath: resolve10(rootPath),
22215
23392
  setupGlobal,
22216
23393
  ...setupIncludes === undefined ? {} : { setupIncludes },
22217
23394
  ...setupTargets === undefined ? {} : { setupTargets },
23395
+ sourceSuggestionWrite,
22218
23396
  ...testName === undefined ? {} : { testName },
22219
23397
  yes
22220
23398
  };
22221
23399
  }
22222
23400
  function isReleaseSubcommand(value) {
22223
- return value === "apply" || value === "audit" || value === "plan";
23401
+ return value === "amend" || value === "apply" || value === "audit" || value === "plan";
22224
23402
  }
22225
23403
  function isChangeSubcommand(value) {
22226
- return value === "add" || value === "check" || value === "history" || value === "list" || value === "reason" || value === "show" || value === "status";
23404
+ return value === "add" || value === "amend" || value === "check" || value === "history" || value === "list" || value === "reason" || value === "show" || value === "status";
22227
23405
  }
22228
23406
  function readChangeScopes(value) {
22229
23407
  const scopes = value.split(",").map((scope) => scope.trim()).filter((scope) => scope.length > 0);
@@ -22252,8 +23430,8 @@ function validateChangeFlags(command, subcommand, change) {
22252
23430
  append: subcommand === "reason",
22253
23431
  bump: subcommand === "add",
22254
23432
  group: subcommand === "add" || subcommand === "list",
22255
- reason: subcommand === "add" || subcommand === "reason",
22256
- ref: subcommand === "check" || subcommand === "history" || subcommand === "reason" || subcommand === "show",
23433
+ reason: subcommand === "add" || subcommand === "amend" || subcommand === "reason",
23434
+ ref: subcommand === "amend" || subcommand === "check" || subcommand === "history" || subcommand === "reason" || subcommand === "show",
22257
23435
  scopes: subcommand === "add",
22258
23436
  staged: subcommand === "check" || subcommand === "status"
22259
23437
  };
@@ -22264,14 +23442,28 @@ function validateChangeFlags(command, subcommand, change) {
22264
23442
  if (change.group !== undefined && !allowed.group)
22265
23443
  throw new Error("skillset: --group is only supported with change add or change list");
22266
23444
  if (change.reason !== undefined && !allowed.reason)
22267
- throw new Error("skillset: --reason and --reason-file are only supported with change add or change reason");
23445
+ throw new Error("skillset: --reason and --reason-file are only supported with change add, change amend, or change reason");
22268
23446
  if (change.ref !== undefined && !allowed.ref)
22269
- throw new Error("skillset: --ref is only supported with change check, change history, change reason, or change show");
23447
+ throw new Error("skillset: --ref is only supported with change amend, change check, change history, change reason, or change show");
22270
23448
  if (change.scopes !== undefined && !allowed.scopes)
22271
23449
  throw new Error("skillset: source-unit --scope is only supported with change add");
22272
23450
  if (change.staged && !allowed.staged)
22273
23451
  throw new Error("skillset: --staged is only supported with change status or change check");
22274
23452
  }
23453
+ function validateReleaseFlags(command, subcommand, release) {
23454
+ const hasReleaseFlag = release.reason !== undefined || release.ref !== undefined;
23455
+ if (hasReleaseFlag && command !== "release") {
23456
+ throw new Error("skillset: release options are only supported with release commands");
23457
+ }
23458
+ if (command !== "release")
23459
+ return;
23460
+ if (release.reason !== undefined && subcommand !== "amend") {
23461
+ throw new Error("skillset: --reason and --reason-file are only supported with release amend");
23462
+ }
23463
+ if (release.ref !== undefined && subcommand !== "amend") {
23464
+ throw new Error("skillset: --ref is only supported with release amend");
23465
+ }
23466
+ }
22275
23467
  function validateHookFlags(command, hooks) {
22276
23468
  const hasHookPrintFlag = hooks.agentRuntime || hooks.preCommit || hooks.prePush || hooks.runner !== undefined || hooks.target !== undefined;
22277
23469
  if (hasHookPrintFlag && (command !== "hooks" || hooks.subcommand !== "print")) {
@@ -22312,6 +23504,25 @@ function validateRestoreFlags(command, restore) {
22312
23504
  throw new Error("skillset: restore only supports --root, --yes, and --dry-run");
22313
23505
  }
22314
23506
  }
23507
+ function validateSuggestSourceFlags(command, suggestion) {
23508
+ if (suggestion.write && command !== "suggest-source") {
23509
+ throw new Error("skillset: --write is only supported with suggest-source");
23510
+ }
23511
+ if (command !== "suggest-source")
23512
+ return;
23513
+ if (suggestion.buildMode !== undefined)
23514
+ throw new Error("skillset: --updated and --all are not supported with suggest-source");
23515
+ if (suggestion.changeSince !== undefined)
23516
+ throw new Error("skillset: --since is not supported with suggest-source");
23517
+ if (suggestion.dryRun)
23518
+ throw new Error("skillset: --dry-run is redundant for suggest-source preview mode");
23519
+ if (suggestion.scopes !== undefined)
23520
+ throw new Error("skillset: --scope is not supported with suggest-source");
23521
+ if (suggestion.yes && !suggestion.write)
23522
+ throw new Error("skillset: --yes is only supported with suggest-source --write");
23523
+ if (suggestion.write && !suggestion.yes)
23524
+ throw new Error("skillset: suggest-source --write requires --yes");
23525
+ }
22315
23526
  function setBuildMode(current, next) {
22316
23527
  if (current !== undefined && current !== next) {
22317
23528
  throw new Error(`skillset: conflicting build mode flags --${current} and --${next}`);