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.
- package/dist/cli.js +1765 -554
- package/dist/create.js +1765 -554
- 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
|
|
7028
|
-
import { basename as
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
|
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
|
|
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
|
|
10187
|
-
import { readdir as
|
|
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 {
|
|
11343
|
-
import {
|
|
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
|
-
|
|
11358
|
+
const escapedTokens = [];
|
|
11359
|
+
let expanded = escapeTripleBraceTokens(normalizeText(content), escapedTokens);
|
|
11349
11360
|
expanded = await expandPartials(expanded, context);
|
|
11350
|
-
|
|
11351
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11382
|
-
|
|
11383
|
-
|
|
11384
|
-
|
|
11385
|
-
|
|
11386
|
-
|
|
11387
|
-
|
|
11388
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
12719
|
+
sourceRoot: graph.sourceRoot,
|
|
12388
12720
|
...plugin === undefined ? {} : { pluginPath: plugin.path }
|
|
12389
12721
|
}), sourceOpenAiPath) : {};
|
|
12390
12722
|
const merged = mergeRecords(source2, generated);
|
|
12391
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
13533
|
+
return readFileSync2(path);
|
|
13162
13534
|
}
|
|
13163
13535
|
|
|
13164
13536
|
// packages/core/src/resolver.ts
|
|
13165
|
-
import { readdir as
|
|
13166
|
-
import { basename as
|
|
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
|
|
13272
|
-
const sourcePath
|
|
13273
|
-
const
|
|
13274
|
-
const
|
|
13275
|
-
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13285
|
-
|
|
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,
|
|
13290
|
-
const filteredTargets = applyTargetFilter(rootTargets, options.targetFilter,
|
|
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:
|
|
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(
|
|
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 ${
|
|
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
|
-
|
|
13326
|
-
|
|
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
|
-
[
|
|
13370
|
-
[
|
|
13371
|
-
|
|
13372
|
-
[
|
|
13373
|
-
[
|
|
13374
|
-
[
|
|
13375
|
-
[
|
|
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,
|
|
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
|
|
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(
|
|
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(
|
|
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,
|
|
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 .
|
|
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
|
|
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,
|
|
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
|
|
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") ??
|
|
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:
|
|
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,
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
|
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,
|
|
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") ??
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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 =
|
|
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,
|
|
14496
|
+
function validateOutputRoots(rootPath, protectedRoots, outputRoots) {
|
|
13986
14497
|
const seen = new Map;
|
|
13987
14498
|
for (const outputRoot of outputRoots) {
|
|
13988
|
-
const absoluteOutputRoot =
|
|
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
|
|
14507
|
+
function validateOutputRootNotInsideProtectedRoots(rootPath, protectedRoots, outputRoot) {
|
|
13997
14508
|
const absoluteOutputRoot = resolveInside(rootPath, outputRoot.path);
|
|
13998
|
-
|
|
13999
|
-
|
|
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
|
|
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
|
|
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:
|
|
15044
|
-
files: [
|
|
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.
|
|
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
|
|
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 `${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
16307
|
-
const
|
|
16308
|
-
const
|
|
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 =
|
|
16363
|
-
const manifestPath =
|
|
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
|
-
|
|
16371
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
17246
|
+
hash.update(dependency);
|
|
16622
17247
|
hash.update("\x00");
|
|
16623
|
-
hash.update(
|
|
17248
|
+
hash.update(readPreprocessDependencySync(graph.rootPath, dependency));
|
|
16624
17249
|
hash.update("\x00");
|
|
16625
17250
|
}
|
|
16626
17251
|
}
|
|
16627
|
-
async function
|
|
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:
|
|
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
|
|
17265
|
+
await preprocessText(await readFile13(sourceOpenAiPath, "utf8"), {
|
|
16641
17266
|
...context,
|
|
16642
17267
|
sourcePath: sourceOpenAiPath
|
|
16643
17268
|
});
|
|
16644
17269
|
}
|
|
16645
|
-
return
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
17328
|
+
sourceRoot: graph.sourceRoot
|
|
16704
17329
|
});
|
|
16705
17330
|
}
|
|
16706
|
-
return
|
|
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,
|
|
16782
|
-
if (
|
|
16783
|
-
return inventoryFromGitRef(rootPath,
|
|
17409
|
+
async function resolveBaselineInventory(rootPath, baselineOptions, releaseOptions) {
|
|
17410
|
+
if (baselineOptions.since !== undefined) {
|
|
17411
|
+
return inventoryFromGitRef(rootPath, baselineOptions.since, baselineOptions);
|
|
16784
17412
|
}
|
|
16785
|
-
if (
|
|
16786
|
-
return inventoryFromGitRef(rootPath, "HEAD",
|
|
17413
|
+
if (baselineOptions.staged === true) {
|
|
17414
|
+
return inventoryFromGitRef(rootPath, "HEAD", baselineOptions);
|
|
16787
17415
|
}
|
|
16788
|
-
const fallback = await fallbackBaselineInventory(rootPath,
|
|
16789
|
-
const releaseInventory = await sourceInventoryFromReleaseState(rootPath,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
16852
|
-
await
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
17122
|
-
|
|
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
|
|
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
|
|
17431
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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: "
|
|
18146
|
+
skippedReason: "skillset workspace not found",
|
|
17496
18147
|
write: seedOptions.write === true
|
|
17497
18148
|
};
|
|
17498
18149
|
}
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
|
|
17502
|
-
|
|
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 },
|
|
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
|
|
17596
|
-
|
|
17597
|
-
|
|
17598
|
-
|
|
17599
|
-
|
|
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
|
|
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,
|
|
17671
|
-
if (await
|
|
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(
|
|
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
|
|
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" ?
|
|
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 =
|
|
17799
|
-
if (!await
|
|
18477
|
+
const skillFile = join14(targetPath, "SKILL.md");
|
|
18478
|
+
if (!await exists9(skillFile))
|
|
17800
18479
|
return {};
|
|
17801
|
-
return parseMarkdown(await
|
|
18480
|
+
return parseMarkdown(await readFile15(skillFile, "utf8"), skillFile).frontmatter;
|
|
17802
18481
|
}
|
|
17803
|
-
const configPath =
|
|
17804
|
-
if (!await
|
|
18482
|
+
const configPath = join14(targetPath, "skillset.yaml");
|
|
18483
|
+
if (!await exists9(configPath))
|
|
17805
18484
|
return {};
|
|
17806
|
-
return parseYamlRecord(await
|
|
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
|
|
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") ??
|
|
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(
|
|
18499
|
+
return validateSlug(basename6(sourcePath), "plugin directory");
|
|
17821
18500
|
}
|
|
17822
|
-
const config2 = parseYamlRecord(await
|
|
18501
|
+
const config2 = parseYamlRecord(await readFile15(configPath, "utf8"), configPath);
|
|
17823
18502
|
const metadata = readSkillsetMetadata(config2, configPath);
|
|
17824
|
-
return validateSlug(readSkillsetName(metadata,
|
|
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
|
|
18506
|
+
const stats = await stat11(sourcePath);
|
|
17828
18507
|
if (stats.isFile()) {
|
|
17829
|
-
if (kind !== "skill" ||
|
|
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(
|
|
17838
|
-
await
|
|
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
|
|
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(
|
|
18528
|
+
await writeYamlSourceOrigin(join14(targetPath, "skillset.yaml"), sourceOrigin(sourcePath));
|
|
17850
18529
|
}
|
|
17851
18530
|
for (const file of copiedFiles) {
|
|
17852
|
-
if (
|
|
18531
|
+
if (basename6(file) !== "SKILL.md")
|
|
17853
18532
|
continue;
|
|
17854
|
-
await writeMarkdownSourceOrigin(
|
|
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
|
|
17859
|
-
await
|
|
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
|
|
17863
|
-
await
|
|
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
|
|
18569
|
+
const stats = await stat11(sourcePath);
|
|
17891
18570
|
if (stats.isFile())
|
|
17892
18571
|
return sourcePath;
|
|
17893
|
-
return
|
|
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 =
|
|
17898
|
-
if (await
|
|
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
|
|
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 =
|
|
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
|
|
18605
|
+
return join14(home, ".agents", "skills");
|
|
17927
18606
|
if (provider === "claude")
|
|
17928
|
-
return
|
|
18607
|
+
return join14(home, ".claude", "skills");
|
|
17929
18608
|
if (provider === "codex")
|
|
17930
|
-
return
|
|
17931
|
-
return
|
|
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
|
|
18659
|
+
if (!await exists9(sourcePath))
|
|
17981
18660
|
return false;
|
|
17982
|
-
const stats = await
|
|
18661
|
+
const stats = await stat11(sourcePath);
|
|
17983
18662
|
if (stats.isFile())
|
|
17984
|
-
return
|
|
18663
|
+
return basename6(sourcePath) === "SKILL.md";
|
|
17985
18664
|
if (!stats.isDirectory())
|
|
17986
18665
|
return false;
|
|
17987
|
-
return
|
|
18666
|
+
return exists9(join14(sourcePath, "SKILL.md"));
|
|
17988
18667
|
}
|
|
17989
18668
|
async function isPluginSource(sourcePath) {
|
|
17990
|
-
if (!await
|
|
18669
|
+
if (!await exists9(sourcePath))
|
|
17991
18670
|
return false;
|
|
17992
|
-
const stats = await
|
|
18671
|
+
const stats = await stat11(sourcePath);
|
|
17993
18672
|
if (!stats.isDirectory())
|
|
17994
18673
|
return false;
|
|
17995
|
-
return await
|
|
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
|
|
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
|
|
18684
|
+
if (!await exists9(sourcePath))
|
|
18006
18685
|
return [];
|
|
18007
|
-
const stats = await
|
|
18686
|
+
const stats = await stat11(sourcePath);
|
|
18008
18687
|
if (!stats.isDirectory())
|
|
18009
18688
|
return [];
|
|
18010
|
-
const entries = await
|
|
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 =
|
|
18017
|
-
if (!await
|
|
18695
|
+
const candidate = join14(sourcePath, entry.name);
|
|
18696
|
+
if (!await exists9(candidate))
|
|
18018
18697
|
continue;
|
|
18019
|
-
const candidateStats = await
|
|
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
|
|
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 =
|
|
18051
|
-
if (!await
|
|
18729
|
+
const candidate = join14(targetPath, file);
|
|
18730
|
+
if (!await exists9(candidate))
|
|
18052
18731
|
continue;
|
|
18053
|
-
const parsed = JSON.parse(await
|
|
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
|
|
18740
|
+
async function exists9(path2) {
|
|
18062
18741
|
try {
|
|
18063
|
-
await
|
|
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
|
|
18074
|
-
import { basename as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 ===
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
18417
|
-
|
|
18418
|
-
|
|
18419
|
-
|
|
18420
|
-
|
|
18421
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
19203
|
+
const pluginsPath = join15(rootPath, "plugins");
|
|
18452
19204
|
if (!await pathExists(pluginsPath))
|
|
18453
19205
|
return [];
|
|
18454
|
-
if (!(await
|
|
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
|
|
18459
|
-
const absolutePath =
|
|
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
|
|
19214
|
+
if (!(await stat12(absolutePath)).isDirectory())
|
|
18463
19215
|
continue;
|
|
18464
|
-
const hasManifest = await pathExists(
|
|
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
|
|
19234
|
+
if (!(await stat12(path2)).isFile())
|
|
18483
19235
|
return false;
|
|
18484
|
-
const content = await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
19289
|
+
const absolutePath = join15(rootPath, path2);
|
|
18538
19290
|
if (!await pathExists(absolutePath))
|
|
18539
19291
|
return;
|
|
18540
|
-
if (!(await
|
|
19292
|
+
if (!(await stat12(absolutePath)).isDirectory())
|
|
18541
19293
|
return;
|
|
18542
19294
|
if (await isManagedCandidate(absolutePath))
|
|
18543
19295
|
return;
|
|
18544
|
-
const entries = await
|
|
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 =
|
|
19335
|
+
const codexPath = join15(rootPath, ".codex");
|
|
18584
19336
|
if (!await pathExists(codexPath))
|
|
18585
19337
|
return false;
|
|
18586
|
-
if (!(await
|
|
19338
|
+
if (!(await stat12(codexPath)).isDirectory())
|
|
18587
19339
|
return false;
|
|
18588
19340
|
if (await isManagedCandidate(codexPath))
|
|
18589
19341
|
return false;
|
|
18590
|
-
const entries = await
|
|
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
|
|
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 =
|
|
19352
|
+
const absolutePath = join15(rootPath, entry);
|
|
18601
19353
|
if (!await pathExists(absolutePath))
|
|
18602
19354
|
continue;
|
|
18603
|
-
if (!(await
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
19404
|
+
const absolutePath = join15(rootPath, path2);
|
|
18653
19405
|
if (!await pathExists(absolutePath))
|
|
18654
19406
|
return;
|
|
18655
|
-
const stats = await
|
|
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
|
|
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(
|
|
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:
|
|
18675
|
-
content:
|
|
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: `${
|
|
19432
|
+
path: `${sourceRoot}/.gitkeep`,
|
|
18683
19433
|
content: ""
|
|
18684
19434
|
},
|
|
18685
19435
|
...SETUP_SOURCE_PLACEHOLDERS.map((directory) => ({
|
|
18686
|
-
path: `${
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
"-
|
|
18769
|
-
"-
|
|
18770
|
-
"-
|
|
18771
|
-
"-
|
|
18772
|
-
"-
|
|
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
|
|
18788
|
-
"- Treat
|
|
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" &&
|
|
19591
|
+
if (kind === "create" && basename7(rootPath) === "src" && basename7(join15(rootPath, "..")) === ".skillset") {
|
|
18806
19592
|
return DEFAULT_CREATE_NAME;
|
|
18807
19593
|
}
|
|
18808
|
-
const slug = slugifySetupName(
|
|
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
|
|
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
|
|
19618
|
+
return await readFile16(path2, "utf8");
|
|
18833
19619
|
} catch (error) {
|
|
18834
|
-
if (
|
|
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
|
|
19627
|
+
await stat12(path2);
|
|
18842
19628
|
return true;
|
|
18843
19629
|
} catch (error) {
|
|
18844
|
-
if (
|
|
19630
|
+
if (isErrno3(error, "ENOENT"))
|
|
18845
19631
|
return false;
|
|
18846
19632
|
throw error;
|
|
18847
19633
|
}
|
|
18848
19634
|
}
|
|
18849
|
-
function
|
|
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
|
|
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 =
|
|
19732
|
+
const reportDir = join16(init.rootPath, ADOPT_REPORT_DIR);
|
|
18947
19733
|
await mkdir7(reportDir, { recursive: true });
|
|
18948
|
-
await
|
|
18949
|
-
await
|
|
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
|
|
19750
|
+
if (await exists10(localPath)) {
|
|
18959
19751
|
return { input: source2, kind: "path", rootPath: localPath };
|
|
18960
19752
|
}
|
|
18961
|
-
const clonePath = await mkdtemp3(
|
|
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(
|
|
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:
|
|
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 (
|
|
19812
|
+
if (basename8(file) !== "SKILL.md")
|
|
19021
19813
|
continue;
|
|
19022
|
-
previewSources.push(relative15(rootPath,
|
|
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 =
|
|
19039
|
-
if (await
|
|
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
|
|
19834
|
+
const content = await readFile17(join16(rootPath, sourceName));
|
|
19043
19835
|
await mkdir7(dirname12(destination), { recursive: true });
|
|
19044
|
-
await
|
|
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 :
|
|
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
|
|
19057
|
-
await
|
|
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
|
|
19080
|
-
const body =
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
20072
|
+
async function exists10(path2) {
|
|
19281
20073
|
try {
|
|
19282
|
-
await
|
|
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
|
|
19295
|
-
import { isAbsolute as isAbsolute2, join as
|
|
19296
|
-
var PENDING_DIR2 = "changes
|
|
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
|
|
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 =
|
|
19318
|
-
const relativePath2 =
|
|
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,
|
|
19329
|
-
await
|
|
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
|
|
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
|
|
20133
|
+
const newReason = await resolveChangeReason(rootPath, options.reason);
|
|
19340
20134
|
const absolutePath = resolveInside(rootPath, entry.path);
|
|
19341
|
-
const parts = parseMarkdown(await
|
|
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
|
|
19346
|
-
const updated = resolvePendingChangeRef(await readPendingChangeEntries(rootPath,
|
|
19347
|
-
const refs = refIndex([updated], await readHistoryEntries(rootPath,
|
|
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
|
|
19352
|
-
const
|
|
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
|
|
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
|
|
19363
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
20332
|
+
const path2 = join17(sourceDir, HISTORY_FILE2).replaceAll("\\", "/");
|
|
19506
20333
|
const absolutePath = resolveInside(rootPath, path2);
|
|
19507
|
-
if (!await
|
|
20334
|
+
if (!await exists11(absolutePath))
|
|
19508
20335
|
return [];
|
|
19509
20336
|
const entries = [];
|
|
19510
|
-
const lines = (await
|
|
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
|
-
|
|
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
|
|
20571
|
+
async function exists11(path2) {
|
|
19708
20572
|
try {
|
|
19709
|
-
await
|
|
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
|
|
19746
|
-
import { join as
|
|
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
|
|
20685
|
+
if (!await exists12(join18(rootPath, "apps", "skillset", "src", "cli.ts")))
|
|
19801
20686
|
return false;
|
|
19802
20687
|
try {
|
|
19803
|
-
const packageJson = JSON.parse(await
|
|
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
|
|
20707
|
+
async function exists12(path2) {
|
|
19823
20708
|
try {
|
|
19824
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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 =
|
|
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,
|
|
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,
|
|
20225
|
-
const statePath = await writeReleaseState(rootPath, nextReleaseState(state, plan.baselineScopes, now),
|
|
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,
|
|
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 =
|
|
21297
|
+
const relativePath2 = join19(sourceDir, HISTORY_FILE3).replaceAll("\\", "/");
|
|
20376
21298
|
const absolutePath = resolveInside(rootPath, relativePath2);
|
|
20377
|
-
await mkdir9(
|
|
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
|
|
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 =
|
|
21322
|
+
const relativePath2 = join19(sourceDir, RELEASES_FILE).replaceAll("\\", "/");
|
|
20401
21323
|
const absolutePath = resolveInside(rootPath, relativePath2);
|
|
20402
|
-
await mkdir9(
|
|
20403
|
-
await
|
|
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
|
-
|
|
21344
|
+
join19(sourceDir, HISTORY_FILE3).replaceAll("\\", "/"),
|
|
20423
21345
|
...entries.map((entry) => entry.path)
|
|
20424
21346
|
]);
|
|
20425
21347
|
if (includeReleaseState) {
|
|
20426
|
-
paths.add(
|
|
20427
|
-
paths.add(
|
|
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
|
|
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(
|
|
20447
|
-
await
|
|
21368
|
+
await mkdir9(dirname14(absolutePath), { recursive: true });
|
|
21369
|
+
await writeFile10(absolutePath, snapshot.content);
|
|
20448
21370
|
}
|
|
20449
21371
|
}
|
|
20450
|
-
async function
|
|
21372
|
+
async function exists13(path2) {
|
|
20451
21373
|
try {
|
|
20452
|
-
await
|
|
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
|
|
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
|
|
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
|
|
20500
|
-
const
|
|
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,
|
|
20512
|
-
const runsRoot =
|
|
20513
|
-
const runPath =
|
|
20514
|
-
const workspacePath =
|
|
20515
|
-
const stagingRoot = await mkdtemp4(
|
|
20516
|
-
const stagingWorkspacePath =
|
|
20517
|
-
const stagingSourcePath =
|
|
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,
|
|
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,
|
|
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 =
|
|
20552
|
-
const reportMarkdownPath =
|
|
20553
|
-
const latestPath =
|
|
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
|
|
20573
|
-
await
|
|
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(
|
|
20596
|
-
const
|
|
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
|
|
21814
|
+
const exists14 = await pathExists2(targetPath);
|
|
20765
21815
|
return {
|
|
20766
21816
|
kind: "exists",
|
|
20767
|
-
ok:
|
|
21817
|
+
ok: exists14,
|
|
20768
21818
|
path: assertion.path,
|
|
20769
|
-
...
|
|
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
|
|
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) =>
|
|
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) =>
|
|
20823
|
-
...graph.plugins.flatMap((plugin) => plugin.skills.filter((skill) => skill.id === expect.name).map((skill) =>
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
21959
|
+
workspacePath: relative16(rootPath, join20(latestPath, "workspace"))
|
|
20910
21960
|
};
|
|
20911
|
-
await
|
|
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
|
|
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(
|
|
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
|
|
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,
|
|
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,
|
|
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 =
|
|
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(
|
|
21104
|
-
await
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
22069
|
-
|
|
22070
|
-
|
|
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}`);
|