cherry-works 0.7.4 → 0.8.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/README.md +47 -3
- package/dist/cw.js +1 -1
- package/dist/main.js +326 -125
- package/dist/portal/main.js +2 -2
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -21898,7 +21898,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
21898
21898
|
// package.json
|
|
21899
21899
|
var package_default = {
|
|
21900
21900
|
name: "cherry-works",
|
|
21901
|
-
version: "0.
|
|
21901
|
+
version: "0.8.0",
|
|
21902
21902
|
description: "The charter engine: resolves a charter and compiles it into what a coding agent reads.",
|
|
21903
21903
|
keywords: [
|
|
21904
21904
|
"claude-code",
|
|
@@ -57394,6 +57394,15 @@ var ServedToolsSchema = dto(
|
|
|
57394
57394
|
})
|
|
57395
57395
|
);
|
|
57396
57396
|
var ToolAnswerSchema = dto("ToolAnswer", external_exports.record(external_exports.string(), external_exports.unknown()));
|
|
57397
|
+
var VendorPrimitiveSchema = dto(
|
|
57398
|
+
"VendorPrimitive",
|
|
57399
|
+
external_exports.object({
|
|
57400
|
+
kind: external_exports.string(),
|
|
57401
|
+
id: external_exports.string(),
|
|
57402
|
+
description: external_exports.string(),
|
|
57403
|
+
leftOutHeaders: StringsSchema
|
|
57404
|
+
})
|
|
57405
|
+
);
|
|
57397
57406
|
var WorkspaceSettingsSchema = dto(
|
|
57398
57407
|
"WorkspaceSettings",
|
|
57399
57408
|
external_exports.object({
|
|
@@ -57474,6 +57483,7 @@ var DataDTOs = byType(
|
|
|
57474
57483
|
TestSuiteSchema,
|
|
57475
57484
|
TestSuitesSchema,
|
|
57476
57485
|
ToolAnswerSchema,
|
|
57486
|
+
VendorPrimitiveSchema,
|
|
57477
57487
|
WorkspaceSettingsSchema
|
|
57478
57488
|
);
|
|
57479
57489
|
|
|
@@ -57523,7 +57533,20 @@ var SensorRunOutcomeSchema = dto(
|
|
|
57523
57533
|
problem: external_exports.string()
|
|
57524
57534
|
})
|
|
57525
57535
|
);
|
|
57526
|
-
var
|
|
57536
|
+
var VendorAddOutcomeSchema = dto(
|
|
57537
|
+
"VendorAddOutcome",
|
|
57538
|
+
external_exports.object({
|
|
57539
|
+
folder: external_exports.string(),
|
|
57540
|
+
primitives: external_exports.array(DataDTOs.VendorPrimitive).readonly()
|
|
57541
|
+
})
|
|
57542
|
+
);
|
|
57543
|
+
var OutcomeDTOs = byType(
|
|
57544
|
+
DoctorOutcomeSchema,
|
|
57545
|
+
ExplanationOutcomeSchema,
|
|
57546
|
+
SensorRunOutcomeSchema,
|
|
57547
|
+
SessionReviewOutcomeSchema,
|
|
57548
|
+
VendorAddOutcomeSchema
|
|
57549
|
+
);
|
|
57527
57550
|
|
|
57528
57551
|
// src/hexagon/domain/models/helper.ts
|
|
57529
57552
|
function formatFrontmatterValue(value) {
|
|
@@ -58086,6 +58109,13 @@ function sessionReviewOutcomeDTO(sessionAnalysis, sessionAnalysisSummary, sessio
|
|
|
58086
58109
|
function sensorRunOutcomeDTO(signalAnswer) {
|
|
58087
58110
|
return { type: "SensorRunOutcome", data: { exitCode: signalAnswer.exitCode, answer: signalAnswer.stdout, problem: signalAnswer.stderr } };
|
|
58088
58111
|
}
|
|
58112
|
+
function vendorPrimitiveDTO(vendorPrimitive) {
|
|
58113
|
+
const { kind, id, description, leftOutHeaders } = vendorPrimitive;
|
|
58114
|
+
return { type: "VendorPrimitive", data: { kind, id, description, leftOutHeaders } };
|
|
58115
|
+
}
|
|
58116
|
+
function vendorAddOutcomeDTO(vendorInstall) {
|
|
58117
|
+
return { type: "VendorAddOutcome", data: { folder: vendorInstall.folder, primitives: vendorInstall.vendorPrimitives.map(vendorPrimitiveDTO) } };
|
|
58118
|
+
}
|
|
58089
58119
|
|
|
58090
58120
|
// src/driver/portal/routes.ts
|
|
58091
58121
|
var json3 = (schema, description) => ({
|
|
@@ -58388,8 +58418,8 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
|
|
|
58388
58418
|
)
|
|
58389
58419
|
},
|
|
58390
58420
|
responses: {
|
|
58391
|
-
204: { description: "The source is installed,
|
|
58392
|
-
422: json3(DataDTOs.Fault, "No repository,
|
|
58421
|
+
204: { description: "The source is installed \u2014 a charter whole, or every skill of a skills repository \u2014 left unstaged" },
|
|
58422
|
+
422: json3(DataDTOs.Fault, "No repository, a source holding neither a charter nor a skill, or what git refused")
|
|
58393
58423
|
}
|
|
58394
58424
|
}),
|
|
58395
58425
|
async (c) => {
|
|
@@ -58702,23 +58732,31 @@ function lines2(one) {
|
|
|
58702
58732
|
var OPTIONS16 = {
|
|
58703
58733
|
source: {
|
|
58704
58734
|
type: "string",
|
|
58705
|
-
describe: "What git
|
|
58735
|
+
describe: "What git fetches the charter or the skills from: an address over any transport, or a path on disk"
|
|
58706
58736
|
},
|
|
58707
58737
|
ref: {
|
|
58708
58738
|
type: "string",
|
|
58709
58739
|
describe: "The tag, branch or commit to pin to; the source's own default where none is named"
|
|
58740
|
+
},
|
|
58741
|
+
skill: {
|
|
58742
|
+
type: "string",
|
|
58743
|
+
isArray: true,
|
|
58744
|
+
describe: "A skill to take from a skills repository, by its name; typed once per skill, and every skill it holds where none is named. Refused for a charter"
|
|
58710
58745
|
}
|
|
58711
58746
|
};
|
|
58712
58747
|
var VendorAddCommand = class {
|
|
58713
58748
|
name = "vendor add <source>";
|
|
58714
|
-
summary = "Install a charter from a git source, or bring an installed one up to date";
|
|
58749
|
+
summary = "Install a charter or skills from a git source, or bring an installed one up to date";
|
|
58715
58750
|
options = OPTIONS16;
|
|
58716
|
-
async run({ charterVendoringApp: charterVendoringApp2 }, { source, ref }) {
|
|
58717
|
-
const folder = await charterVendoringApp2.add(source ?? "", ref);
|
|
58751
|
+
async run({ charterVendoringApp: charterVendoringApp2 }, { source, ref, skill }) {
|
|
58752
|
+
const { folder, primitives } = (await charterVendoringApp2.add(source ?? "", ref, skill)).data;
|
|
58718
58753
|
return {
|
|
58719
58754
|
code: EXIT_OK,
|
|
58720
58755
|
result: [
|
|
58721
58756
|
`Installed ${source} under ${folder}${ref === void 0 ? "" : `, pinned to ${ref}`}.`,
|
|
58757
|
+
...primitives.map(
|
|
58758
|
+
({ data: { kind, id, leftOutHeaders } }) => ` ${kind} ${id}${leftOutHeaders.length === 0 ? "" : `, leaving out ${leftOutHeaders.join(", ")}: the charter has no field for them`}`
|
|
58759
|
+
),
|
|
58722
58760
|
'Run "cw build" to compile what it adds to this charter.',
|
|
58723
58761
|
""
|
|
58724
58762
|
].join("\n")
|
|
@@ -59119,7 +59157,16 @@ var PlaybookHeadersSchema = CommonHeadersSchema.extend({
|
|
|
59119
59157
|
triggers: GoodArraySchema,
|
|
59120
59158
|
/** Whether it is kept out of the commands its host offers the user, being a
|
|
59121
59159
|
* step of something else; the agent still opens it (CORE-FR-173). */
|
|
59122
|
-
"disable-user-invocation": FlagSchema.optional()
|
|
59160
|
+
"disable-user-invocation": FlagSchema.optional(),
|
|
59161
|
+
/** Whether the agent is kept from opening it by itself, its description
|
|
59162
|
+
* matching what is asked; the user still runs it as a command (CORE-FR-181). */
|
|
59163
|
+
"disable-model-invocation": FlagSchema.optional()
|
|
59164
|
+
}).superRefine((headers, context) => {
|
|
59165
|
+
if (headers["disable-user-invocation"] === true && headers["disable-model-invocation"] === true)
|
|
59166
|
+
context.addIssue({
|
|
59167
|
+
code: "custom",
|
|
59168
|
+
message: `This playbook declares both "disable-user-invocation: true" and "disable-model-invocation: true", so neither the user nor the agent could open it; drop one.`
|
|
59169
|
+
});
|
|
59123
59170
|
});
|
|
59124
59171
|
var PlaybookPrimitive = class _PlaybookPrimitive extends BasePrimitive {
|
|
59125
59172
|
static kind = "playbook";
|
|
@@ -59191,7 +59238,16 @@ var SkillHeadersSchema = CommonHeadersSchema.extend({
|
|
|
59191
59238
|
triggers: GoodArraySchema,
|
|
59192
59239
|
/** Whether it is kept out of the commands its host offers the user, being a
|
|
59193
59240
|
* step of something else; the agent still opens it (CORE-FR-173). */
|
|
59194
|
-
"disable-user-invocation": FlagSchema.optional()
|
|
59241
|
+
"disable-user-invocation": FlagSchema.optional(),
|
|
59242
|
+
/** Whether the agent is kept from opening it by itself, its description
|
|
59243
|
+
* matching what is asked; the user still runs it as a command (CORE-FR-181). */
|
|
59244
|
+
"disable-model-invocation": FlagSchema.optional()
|
|
59245
|
+
}).superRefine((headers, context) => {
|
|
59246
|
+
if (headers["disable-user-invocation"] === true && headers["disable-model-invocation"] === true)
|
|
59247
|
+
context.addIssue({
|
|
59248
|
+
code: "custom",
|
|
59249
|
+
message: `This skill declares both "disable-user-invocation: true" and "disable-model-invocation: true", so neither the user nor the agent could open it; drop one.`
|
|
59250
|
+
});
|
|
59195
59251
|
});
|
|
59196
59252
|
var SkillPrimitive = class _SkillPrimitive extends BasePrimitive {
|
|
59197
59253
|
static kind = "skill";
|
|
@@ -59989,6 +60045,220 @@ agent stops as it would have.`,
|
|
|
59989
60045
|
// src/hexagon/domain/models/charter/builtin/index.ts
|
|
59990
60046
|
var BUILTIN_PRIMITIVES = [new CwAuthorSkill(), new CwSessionTokensCounterScript(), new CwSessionTokensSensor(), new CwSessionCostSkill()];
|
|
59991
60047
|
|
|
60048
|
+
// src/hexagon/domain/models/vendor/VendorPrimitive.ts
|
|
60049
|
+
var SKILL_FILE = "SKILL.md";
|
|
60050
|
+
var SKILLS_FOLDER = "skills";
|
|
60051
|
+
var SKILLS_FOLDER_DEPTH = 3;
|
|
60052
|
+
var DEPENDENCIES_FOLDER = "node_modules";
|
|
60053
|
+
var CONVERTED_HEADERS = ["name", "description", "disable-model-invocation", "user-invocable"];
|
|
60054
|
+
var VendorPrimitive = class {
|
|
60055
|
+
constructor(kind, id, description, sourceFolder, convertedSkill, leftOutHeaders = []) {
|
|
60056
|
+
this.kind = kind;
|
|
60057
|
+
this.id = id;
|
|
60058
|
+
this.description = description;
|
|
60059
|
+
this.sourceFolder = sourceFolder;
|
|
60060
|
+
this.convertedSkill = convertedSkill;
|
|
60061
|
+
this.leftOutHeaders = leftOutHeaders;
|
|
60062
|
+
}
|
|
60063
|
+
kind;
|
|
60064
|
+
id;
|
|
60065
|
+
description;
|
|
60066
|
+
sourceFolder;
|
|
60067
|
+
convertedSkill;
|
|
60068
|
+
leftOutHeaders;
|
|
60069
|
+
};
|
|
60070
|
+
function frontmatterOf(markdown, parser2) {
|
|
60071
|
+
const lines3 = markdown.replace(/^/, "").split(/\r?\n/);
|
|
60072
|
+
const frontmatterEnd = lines3[0]?.trim() === "---" ? lines3.findIndex((line, index) => index > 0 && line.trim() === "---") : -1;
|
|
60073
|
+
if (frontmatterEnd === -1) return void 0;
|
|
60074
|
+
return {
|
|
60075
|
+
headers: parser2.parse(lines3.slice(1, frontmatterEnd).join("\n")),
|
|
60076
|
+
body: lines3.slice(frontmatterEnd + 1).join("\n").replace(/^(\s*\n)+/, "").trimEnd()
|
|
60077
|
+
};
|
|
60078
|
+
}
|
|
60079
|
+
function skillFilesOf(sourcePaths) {
|
|
60080
|
+
const skillFolders = sourcePaths.filter((sourcePath) => sourcePath.startsWith(`${SKILLS_FOLDER}/`) && sourcePath.endsWith(`/${SKILL_FILE}`)).map((skillFile) => skillFile.slice(0, -`/${SKILL_FILE}`.length)).filter((skillFolder) => {
|
|
60081
|
+
const folderNames = skillFolder.split("/");
|
|
60082
|
+
return folderNames.length - 1 <= SKILLS_FOLDER_DEPTH && !folderNames.includes(DEPENDENCIES_FOLDER);
|
|
60083
|
+
});
|
|
60084
|
+
return skillFolders.filter((skillFolder) => !skillFolders.some((outerFolder) => skillFolder.startsWith(`${outerFolder}/`))).map((skillFolder) => `${skillFolder}/${SKILL_FILE}`);
|
|
60085
|
+
}
|
|
60086
|
+
function charterVendorPrimitiveOf(indexMd, indexPath, charterFolder, parser2) {
|
|
60087
|
+
const sourceFolder = indexPath.slice(0, indexPath.lastIndexOf("/"));
|
|
60088
|
+
const [kind = "", ...idFolders] = sourceFolder.slice(charterFolder.length + 1).split("/");
|
|
60089
|
+
let description = "";
|
|
60090
|
+
try {
|
|
60091
|
+
const declaredDescription = frontmatterOf(indexMd, parser2)?.headers.description;
|
|
60092
|
+
if (typeof declaredDescription === "string") description = declaredDescription;
|
|
60093
|
+
} catch {
|
|
60094
|
+
}
|
|
60095
|
+
return new VendorPrimitive(kind, idFolders.join("/"), description, sourceFolder);
|
|
60096
|
+
}
|
|
60097
|
+
function skillVendorPrimitiveOf(skillMd, skillFile, parser2) {
|
|
60098
|
+
const skillFrontmatter = frontmatterOf(skillMd, parser2);
|
|
60099
|
+
if (skillFrontmatter === void 0)
|
|
60100
|
+
throw new VendorFault(`${skillFile} opens with no "---" frontmatter block.`, `Take a skill whose ${SKILL_FILE} declares its name and description.`);
|
|
60101
|
+
const { headers: skillHeaders, body } = skillFrontmatter;
|
|
60102
|
+
try {
|
|
60103
|
+
const skillPrimitive = SkillPrimitive.of(
|
|
60104
|
+
{
|
|
60105
|
+
id: skillHeaders.name,
|
|
60106
|
+
description: skillHeaders.description,
|
|
60107
|
+
triggers: [skillHeaders.name],
|
|
60108
|
+
...skillHeaders["disable-model-invocation"] === true ? { "disable-model-invocation": true } : {},
|
|
60109
|
+
...skillHeaders["user-invocable"] === false ? { "disable-user-invocation": true } : {}
|
|
60110
|
+
},
|
|
60111
|
+
body
|
|
60112
|
+
);
|
|
60113
|
+
return new VendorPrimitive(
|
|
60114
|
+
SkillPrimitive.kind,
|
|
60115
|
+
skillPrimitive.headers.id,
|
|
60116
|
+
skillPrimitive.headers.description,
|
|
60117
|
+
skillFile.slice(0, -SKILL_FILE.length).replace(/\/$/, ""),
|
|
60118
|
+
skillPrimitive,
|
|
60119
|
+
Object.keys(skillHeaders).filter((header) => !CONVERTED_HEADERS.includes(header))
|
|
60120
|
+
);
|
|
60121
|
+
} catch (raised) {
|
|
60122
|
+
const faultMessages = raised instanceof AggregateError ? raised.errors.map((fault) => fault.message) : [`${raised}`];
|
|
60123
|
+
throw new VendorFault(
|
|
60124
|
+
`${skillFile} is no skill the charter can hold: ${faultMessages.join(" ")}`,
|
|
60125
|
+
`Take a skill whose ${SKILL_FILE} declares a "name" of lowercase words joined by "-", and a "description".`
|
|
60126
|
+
);
|
|
60127
|
+
}
|
|
60128
|
+
}
|
|
60129
|
+
|
|
60130
|
+
// src/hexagon/domain/models/vendor/VendorSource.ts
|
|
60131
|
+
var VendorSource = class {
|
|
60132
|
+
constructor(source, name) {
|
|
60133
|
+
this.source = source;
|
|
60134
|
+
this.name = name;
|
|
60135
|
+
}
|
|
60136
|
+
source;
|
|
60137
|
+
name;
|
|
60138
|
+
};
|
|
60139
|
+
function vendorSourceOf(source) {
|
|
60140
|
+
const addressPath = source.replace(/\.git$/, "").replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]*/i, "").replace(/^[^/\\:@]+@[^/\\:]+:/, "");
|
|
60141
|
+
const folderNames = addressPath.split(/[/\\]/).filter(Boolean).slice(-2);
|
|
60142
|
+
if (folderNames.length < 2 || folderNames.some((folderName) => folderName === "." || folderName === ".."))
|
|
60143
|
+
throw new VendorFault(`Invalid path "${source}".`, "Use correct git path, naming its owner and its repository.");
|
|
60144
|
+
return new VendorSource(source, folderNames.join("/"));
|
|
60145
|
+
}
|
|
60146
|
+
function installedVendorFolderOf(name) {
|
|
60147
|
+
const folderNames = name.split(/[/\\]/).filter(Boolean);
|
|
60148
|
+
if (folderNames.length === 0 || folderNames.length > 2 || folderNames.some((folderName) => folderName === "." || folderName === ".."))
|
|
60149
|
+
throw new VendorFault(`Invalid path "${name}".`, "Name the folder as cw vendor list names it under .cw/vendor/.");
|
|
60150
|
+
return folderNames.join("/");
|
|
60151
|
+
}
|
|
60152
|
+
|
|
60153
|
+
// src/hexagon/service/vendorRepo.ts
|
|
60154
|
+
var VENDOR_README = "README.md";
|
|
60155
|
+
async function loadVendorFolders(repo, fileReaders) {
|
|
60156
|
+
const installedFolders = await fileReaders.listFolders(vendorFolderIn(repo));
|
|
60157
|
+
const vendorFolders = await Promise.all(
|
|
60158
|
+
installedFolders.map(async (folder) => {
|
|
60159
|
+
const ownerName = decodeURIComponent(folder.href.split("/").at(-2) ?? "");
|
|
60160
|
+
const childFolders = await fileReaders.listFolders(folder);
|
|
60161
|
+
const childFolderNames = childFolders.map((childFolder) => decodeURIComponent(childFolder.href.split("/").at(-2) ?? ""));
|
|
60162
|
+
const isSource = childFolderNames.some((childFolderName) => KINDS.includes(childFolderName)) || (await fileReaders.listFiles(folder)).some(({ file: file2 }) => file2.href.endsWith(`/${VENDOR_README}`));
|
|
60163
|
+
if (isSource) return [{ folder, vendorName: ownerName }];
|
|
60164
|
+
return childFolders.map((childFolder, index) => ({ folder: childFolder, vendorName: `${ownerName}/${childFolderNames[index]}` }));
|
|
60165
|
+
})
|
|
60166
|
+
);
|
|
60167
|
+
return vendorFolders.flat().sort((leftFolder, rightFolder) => leftFolder.vendorName.localeCompare(rightFolder.vendorName));
|
|
60168
|
+
}
|
|
60169
|
+
async function loadVendorNames(repo, fileReaders) {
|
|
60170
|
+
return (await loadVendorFolders(repo, fileReaders)).map(({ vendorName }) => `${VENDOR_DIRECTORY}/${vendorName}`);
|
|
60171
|
+
}
|
|
60172
|
+
async function ensureVCSReady(repo, vcs2, because) {
|
|
60173
|
+
if (!await vcs2.isInstalled(repo))
|
|
60174
|
+
throw new DomainFault(
|
|
60175
|
+
"This folder is not inside a git repository, and a vendor lives in one.",
|
|
60176
|
+
'Run "git init" here, or run this again where your repository is.'
|
|
60177
|
+
);
|
|
60178
|
+
if (because !== void 0 && !await vcs2.isClean(repo))
|
|
60179
|
+
throw new DomainFault(
|
|
60180
|
+
`This repository has work in hand, and ${because}.`,
|
|
60181
|
+
"Commit or stash what you are holding, then run this again."
|
|
60182
|
+
);
|
|
60183
|
+
}
|
|
60184
|
+
async function loadFetchedVendor(repo, fileReaders, vcs2, yamlParser2, source, fetchedCommit) {
|
|
60185
|
+
const vendorName = vendorSourceOf(source).name;
|
|
60186
|
+
const folder = `${VENDOR_DIRECTORY}/${vendorName}`;
|
|
60187
|
+
const sourcePaths = await vcs2.listFilesAt(repo, fetchedCommit);
|
|
60188
|
+
const folderUrl = new URL(`${folder}/`, repo);
|
|
60189
|
+
const installedFolderNames = (await fileReaders.listFolders(folderUrl)).map((installedFolder) => decodeURIComponent(installedFolder.href.split("/").at(-2) ?? ""));
|
|
60190
|
+
const holdsCharter = installedFolderNames.some((installedFolderName) => installedFolderName !== SkillPrimitive.kind) || (await fileReaders.listFiles(folderUrl)).some(({ file: file2 }) => file2.href.endsWith(`/${VENDOR_README}`));
|
|
60191
|
+
const holdsVendorSkills = !holdsCharter && installedFolderNames.includes(SkillPrimitive.kind);
|
|
60192
|
+
const reinstallFix = `Run "cw vendor remove ${vendorName}", then install it again.`;
|
|
60193
|
+
if (sourcePaths.some((sourcePath) => sourcePath.startsWith(`${CHARTER_DIRECTORY}/`))) {
|
|
60194
|
+
if (sourcePaths.some((sourcePath) => sourcePath.startsWith(`${VENDOR_DIRECTORY}/`)))
|
|
60195
|
+
throw new VendorFault(`${source} holds "${VENDOR_DIRECTORY}", and vendoring is one level deep.`, `Remove "${VENDOR_DIRECTORY}" from the source, or install a version without it.`);
|
|
60196
|
+
if (holdsVendorSkills) throw new VendorFault(`${folder} holds skills taken from ${source}, which holds a charter now.`, reinstallFix);
|
|
60197
|
+
const charterIndexPaths = sourcePaths.filter((sourcePath) => sourcePath.startsWith(`${CHARTER_DIRECTORY}/`) && sourcePath.endsWith(`/${SkillPrimitive.index}`));
|
|
60198
|
+
const vendorPrimitives2 = await Promise.all(
|
|
60199
|
+
charterIndexPaths.map(async (indexPath) => charterVendorPrimitiveOf(await vcs2.readFileAt(repo, fetchedCommit, indexPath), indexPath, CHARTER_DIRECTORY, yamlParser2))
|
|
60200
|
+
);
|
|
60201
|
+
return { folder, isCharter: true, vendorPrimitives: vendorPrimitives2 };
|
|
60202
|
+
}
|
|
60203
|
+
const skillFiles = skillFilesOf(sourcePaths);
|
|
60204
|
+
if (skillFiles.length === 0)
|
|
60205
|
+
throw new VendorFault(
|
|
60206
|
+
`${source} has no "${CHARTER_DIRECTORY}" and no ${SKILL_FILE} under "${SKILLS_FOLDER}/".`,
|
|
60207
|
+
`Check that the source holds "${CHARTER_DIRECTORY}", or skills under "${SKILLS_FOLDER}/" each in a folder holding its ${SKILL_FILE}, at the version asked for.`
|
|
60208
|
+
);
|
|
60209
|
+
if (holdsCharter) throw new VendorFault(`${folder} holds a charter installed from ${source}, which holds skills alone now.`, reinstallFix);
|
|
60210
|
+
const vendorPrimitives = await Promise.all(
|
|
60211
|
+
skillFiles.map(async (skillFile) => skillVendorPrimitiveOf(await vcs2.readFileAt(repo, fetchedCommit, skillFile), skillFile, yamlParser2))
|
|
60212
|
+
);
|
|
60213
|
+
const sharedIdPrimitives = vendorPrimitives.filter((vendorPrimitive) => vendorPrimitives.some((otherPrimitive) => otherPrimitive !== vendorPrimitive && otherPrimitive.id === vendorPrimitive.id));
|
|
60214
|
+
if (sharedIdPrimitives.length > 0)
|
|
60215
|
+
throw new VendorFault(
|
|
60216
|
+
`${sharedIdPrimitives.map(({ sourceFolder }) => `${sourceFolder}/${SKILL_FILE}`).join(", ")} declare one name, and a name is one skill.`,
|
|
60217
|
+
"Take the skills from a version of the source whose names are its own."
|
|
60218
|
+
);
|
|
60219
|
+
return { folder, isCharter: false, vendorPrimitives: [...vendorPrimitives].sort((leftPrimitive, rightPrimitive) => leftPrimitive.id.localeCompare(rightPrimitive.id)) };
|
|
60220
|
+
}
|
|
60221
|
+
async function addVendor(repo, fileReaders, fileWriter2, vcs2, yamlParser2, source, version2, skillIds = []) {
|
|
60222
|
+
await ensureVCSReady(repo, vcs2);
|
|
60223
|
+
vendorSourceOf(source);
|
|
60224
|
+
const fetchedCommit = await vcs2.fetchCommit(source, repo, version2);
|
|
60225
|
+
const { folder, isCharter, vendorPrimitives } = await loadFetchedVendor(repo, fileReaders, vcs2, yamlParser2, source, fetchedCommit);
|
|
60226
|
+
if (isCharter) {
|
|
60227
|
+
if (skillIds.length > 0)
|
|
60228
|
+
throw new VendorFault(`${source} holds a charter, and --skill is for skills repositories (compatible with npx skills), not a charter: a charter is installed whole.`, "Run this again without --skill.");
|
|
60229
|
+
await vcs2.checkoutFolderAt(repo, fetchedCommit, CHARTER_DIRECTORY, folder);
|
|
60230
|
+
if ((await vcs2.listFilesAt(repo, fetchedCommit)).includes(VENDOR_README))
|
|
60231
|
+
await fileWriter2.write(new URL(`${folder}/${VENDOR_README}`, repo), await vcs2.readFileAt(repo, fetchedCommit, VENDOR_README));
|
|
60232
|
+
return { folder, vendorPrimitives };
|
|
60233
|
+
}
|
|
60234
|
+
const heldSkillIds = vendorPrimitives.map(({ id }) => id);
|
|
60235
|
+
const unheldSkillIds = skillIds.filter((skillId) => !heldSkillIds.includes(skillId));
|
|
60236
|
+
if (unheldSkillIds.length > 0) throw new VendorFault(`${source} holds no skill ${unheldSkillIds.join(", ")}.`, `Take one it holds: ${heldSkillIds.join(", ")}.`);
|
|
60237
|
+
const chosenPrimitives = skillIds.length === 0 ? vendorPrimitives : vendorPrimitives.filter(({ id }) => skillIds.includes(id));
|
|
60238
|
+
for (const { id, sourceFolder, convertedSkill } of chosenPrimitives) {
|
|
60239
|
+
const skillFolder = `${folder}/${SkillPrimitive.kind}/${id}`;
|
|
60240
|
+
await vcs2.checkoutFolderAt(repo, fetchedCommit, sourceFolder, skillFolder);
|
|
60241
|
+
await fileWriter2.write(new URL(`${skillFolder}/${SkillPrimitive.index}`, repo), convertedSkill.toMarkdown());
|
|
60242
|
+
await fileWriter2.delete(new URL(`${skillFolder}/${SKILL_FILE}`, repo));
|
|
60243
|
+
}
|
|
60244
|
+
return { folder, vendorPrimitives: chosenPrimitives };
|
|
60245
|
+
}
|
|
60246
|
+
async function removeVendor(repo, vcs2, name) {
|
|
60247
|
+
await ensureVCSReady(repo, vcs2, "removing a vendor commits that it is gone");
|
|
60248
|
+
const folder = `${VENDOR_DIRECTORY}/${installedVendorFolderOf(name)}`;
|
|
60249
|
+
await vcs2.removeSubFolder(repo, folder);
|
|
60250
|
+
return folder;
|
|
60251
|
+
}
|
|
60252
|
+
async function driftedVendors(repo, vcs2) {
|
|
60253
|
+
const changed = await vcs2.changedUnder(repo, VENDOR_DIRECTORY);
|
|
60254
|
+
const vendorNames = changed.map((path) => {
|
|
60255
|
+
const [ownerName = "", childName = ""] = path.slice(`${VENDOR_DIRECTORY}/`.length).split("/");
|
|
60256
|
+
const isSource = childName === VENDOR_README || KINDS.includes(childName);
|
|
60257
|
+
return isSource ? ownerName : `${ownerName}/${childName}`;
|
|
60258
|
+
});
|
|
60259
|
+
return [...new Set(vendorNames.filter((vendorName) => vendorName !== "" && !vendorName.endsWith("/")))].sort();
|
|
60260
|
+
}
|
|
60261
|
+
|
|
59992
60262
|
// src/hexagon/service/charterRepo.ts
|
|
59993
60263
|
async function loadCharterRoot(repo, fileReaders, yamlParser2) {
|
|
59994
60264
|
const repoHref = repo.href.endsWith("/") ? repo.href : `${repo.href}/`;
|
|
@@ -60020,12 +60290,9 @@ async function loadCharterRoot(repo, fileReaders, yamlParser2) {
|
|
|
60020
60290
|
];
|
|
60021
60291
|
};
|
|
60022
60292
|
const authoredFiles = await read(charterFolderIn(repo));
|
|
60023
|
-
const
|
|
60293
|
+
const vendorFolders = await loadVendorFolders(repo, fileReaders);
|
|
60024
60294
|
const vendorFiles = await Promise.all(
|
|
60025
|
-
|
|
60026
|
-
const vendor = decodeURIComponent(folder.href.split("/").at(-2) ?? "");
|
|
60027
|
-
return (await read(folder)).map((one) => ({ ...one, vendor }));
|
|
60028
|
-
})
|
|
60295
|
+
vendorFolders.map(async ({ folder, vendorName }) => (await read(folder)).map((vendoredFile) => ({ ...vendoredFile, vendor: vendorName })))
|
|
60029
60296
|
);
|
|
60030
60297
|
const builtinFiles = BUILTIN_PRIMITIVES.map((primitive) => ({
|
|
60031
60298
|
path: primitive.file,
|
|
@@ -60184,54 +60451,6 @@ async function ensureTestSuiteNamed(repo, fileReaders, name) {
|
|
|
60184
60451
|
throw new TestSuiteFault(`${TEST_DIRECTORY}/ holds no test file called "${name}".`, 'Run "cw suite add" to write a new one.');
|
|
60185
60452
|
}
|
|
60186
60453
|
|
|
60187
|
-
// src/hexagon/domain/models/vendor/VendorSource.ts
|
|
60188
|
-
var VendorSource = class {
|
|
60189
|
-
constructor(source, name) {
|
|
60190
|
-
this.source = source;
|
|
60191
|
-
this.name = name;
|
|
60192
|
-
}
|
|
60193
|
-
source;
|
|
60194
|
-
name;
|
|
60195
|
-
};
|
|
60196
|
-
function vendorSourceOf(source) {
|
|
60197
|
-
const name = source.replace(/\.git$/, "").split(/[/:\\]/).filter(Boolean).at(-1) ?? "";
|
|
60198
|
-
if (name === "." || name === ".." || name === "") throw new VendorFault(`Invalid path "${source}".`, "Use correct git path.");
|
|
60199
|
-
return new VendorSource(source, name);
|
|
60200
|
-
}
|
|
60201
|
-
|
|
60202
|
-
// src/hexagon/service/vendorRepo.ts
|
|
60203
|
-
async function loadVendorNames(repo, fileReaders) {
|
|
60204
|
-
return (await fileReaders.listFolders(vendorFolderIn(repo))).map((folder) => `${VENDOR_DIRECTORY}/${decodeURIComponent(folder.href.split("/").at(-2) ?? "")}`).sort();
|
|
60205
|
-
}
|
|
60206
|
-
async function ensureVCSReady(repo, vcs2, because) {
|
|
60207
|
-
if (!await vcs2.isInstalled(repo))
|
|
60208
|
-
throw new DomainFault(
|
|
60209
|
-
"This folder is not inside a git repository, and a vendor lives in one.",
|
|
60210
|
-
'Run "git init" here, or run this again where your repository is.'
|
|
60211
|
-
);
|
|
60212
|
-
if (because !== void 0 && !await vcs2.isClean(repo))
|
|
60213
|
-
throw new DomainFault(
|
|
60214
|
-
`This repository has work in hand, and ${because}.`,
|
|
60215
|
-
"Commit or stash what you are holding, then run this again."
|
|
60216
|
-
);
|
|
60217
|
-
}
|
|
60218
|
-
async function addVendor(repo, vcs2, source, version2) {
|
|
60219
|
-
await ensureVCSReady(repo, vcs2);
|
|
60220
|
-
const folder = `${VENDOR_DIRECTORY}/${vendorSourceOf(source).name}`;
|
|
60221
|
-
return vcs2.subtreeAdd(source, [CHARTER_DIRECTORY, "README.md"], repo, folder, version2, VENDOR_DIRECTORY);
|
|
60222
|
-
}
|
|
60223
|
-
async function removeVendor(repo, vcs2, name) {
|
|
60224
|
-
await ensureVCSReady(repo, vcs2, "removing a vendor commits that it is gone");
|
|
60225
|
-
const folder = `${VENDOR_DIRECTORY}/${vendorSourceOf(name).name}`;
|
|
60226
|
-
await vcs2.removeSubFolder(repo, folder);
|
|
60227
|
-
return folder;
|
|
60228
|
-
}
|
|
60229
|
-
async function driftedVendors(repo, vcs2) {
|
|
60230
|
-
const changed = await vcs2.changedUnder(repo, VENDOR_DIRECTORY);
|
|
60231
|
-
const names = changed.map((path) => path.slice(`${VENDOR_DIRECTORY}/`.length).split("/")[0]).filter((name) => name !== void 0 && name !== "");
|
|
60232
|
-
return [...new Set(names)].sort();
|
|
60233
|
-
}
|
|
60234
|
-
|
|
60235
60454
|
// src/hexagon/domain/services/testService.ts
|
|
60236
60455
|
var TestCaseReport = class {
|
|
60237
60456
|
constructor(suiteName, situation, unmet) {
|
|
@@ -60814,12 +61033,17 @@ function claudeDocumentComponentOf(sc, compiledFile, agentTools) {
|
|
|
60814
61033
|
// for them: what decides that the body is worth opening is the description,
|
|
60815
61034
|
// because that line is all this host reads before deciding (FR-013), and
|
|
60816
61035
|
// composing it is the primitive's. One that is a step of another is kept
|
|
60817
|
-
// out of the user's commands (CORE-FR-174)
|
|
61036
|
+
// out of the user's commands (CORE-FR-174), and one the user alone starts is
|
|
61037
|
+
// kept out of the agent's own reach (CORE-FR-182).
|
|
60818
61038
|
case SkillPrimitive.kind:
|
|
60819
61039
|
case PlaybookPrimitive.kind:
|
|
60820
61040
|
return ClaudeSkill.of(
|
|
60821
61041
|
name,
|
|
60822
|
-
{
|
|
61042
|
+
{
|
|
61043
|
+
description: sc.description(),
|
|
61044
|
+
...sc.headers["disable-user-invocation"] === true ? { "user-invocable": false } : {},
|
|
61045
|
+
...sc.headers["disable-model-invocation"] === true ? { "disable-model-invocation": true } : {}
|
|
61046
|
+
},
|
|
60823
61047
|
pointerTo(`../${compiledFromClaudeFolder}`)
|
|
60824
61048
|
);
|
|
60825
61049
|
default:
|
|
@@ -61663,13 +61887,17 @@ var CharterVendoring = class {
|
|
|
61663
61887
|
#repoPath;
|
|
61664
61888
|
#fileReader;
|
|
61665
61889
|
#vcs;
|
|
61666
|
-
|
|
61890
|
+
#fileWriter;
|
|
61891
|
+
#yamlParser;
|
|
61892
|
+
constructor(repoPath2, fileReader2, vcs2, fileWriter2, yamlParser2) {
|
|
61667
61893
|
this.#repoPath = repoPath2;
|
|
61668
61894
|
this.#fileReader = fileReader2;
|
|
61669
61895
|
this.#vcs = vcs2;
|
|
61896
|
+
this.#fileWriter = fileWriter2;
|
|
61897
|
+
this.#yamlParser = yamlParser2;
|
|
61670
61898
|
}
|
|
61671
|
-
async add(source, version2) {
|
|
61672
|
-
return addVendor(this.#repoPath, this.#vcs, source, version2);
|
|
61899
|
+
async add(source, version2, skillIds = []) {
|
|
61900
|
+
return vendorAddOutcomeDTO(await addVendor(this.#repoPath, this.#fileReader, this.#fileWriter, this.#vcs, this.#yamlParser, source, version2, skillIds));
|
|
61673
61901
|
}
|
|
61674
61902
|
async remove(name) {
|
|
61675
61903
|
return removeVendor(this.#repoPath, this.#vcs, name);
|
|
@@ -62390,7 +62618,7 @@ import { execFile } from "child_process";
|
|
|
62390
62618
|
import { rm as rm2 } from "fs/promises";
|
|
62391
62619
|
import { promisify } from "util";
|
|
62392
62620
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
62393
|
-
import {
|
|
62621
|
+
import { join as join3 } from "path";
|
|
62394
62622
|
var run = promisify(execFile);
|
|
62395
62623
|
var Git = class {
|
|
62396
62624
|
async isInstalled(folder) {
|
|
@@ -62433,74 +62661,47 @@ var Git = class {
|
|
|
62433
62661
|
return [];
|
|
62434
62662
|
}
|
|
62435
62663
|
}
|
|
62436
|
-
/**
|
|
62437
|
-
*
|
|
62438
|
-
|
|
62439
|
-
* given — a folder's files at that folder's root, a file under its own name:
|
|
62440
|
-
* the repository's own index is never touched, so nothing is staged, nothing
|
|
62441
|
-
* is committed, and whatever is in hand stays as it was. A folder already
|
|
62442
|
-
* there is replaced, so installing a source and bringing it up to date are
|
|
62443
|
-
* one call (FR-023, FR-030, FR-042).
|
|
62444
|
-
*
|
|
62445
|
-
* Which transport it is, whether the credentials are there, whether the
|
|
62446
|
-
* version exists: git answers all three, and what it says is what the user
|
|
62447
|
-
* reads (FR-031). `HEAD` where no version is named, which is what the source
|
|
62448
|
-
* calls its own default.
|
|
62449
|
-
*
|
|
62450
|
-
* `git subtree` takes a source whole and cannot take one folder of it, so
|
|
62451
|
-
* each path is read straight off what was fetched. Whether the first is
|
|
62452
|
-
* there, and whether the refused one is, is asked of git before anything is
|
|
62453
|
-
* touched. What lands has no history to walk (FR-028).
|
|
62454
|
-
*/
|
|
62455
|
-
async subtreeAdd(source, includePaths, repo, intoSubFolder, version2, refusedIfSourceHolds) {
|
|
62664
|
+
/** `git fetch` of one version, and `FETCH_HEAD` read at once, before
|
|
62665
|
+
* anything else can move it. */
|
|
62666
|
+
async fetchCommit(source, repo, version2) {
|
|
62456
62667
|
const cwd = fileURLToPath3(repo);
|
|
62457
|
-
const ref = version2 ?? "HEAD";
|
|
62458
62668
|
try {
|
|
62459
|
-
await run("git", ["fetch", "--quiet", "--no-tags", "--", source,
|
|
62669
|
+
await run("git", ["fetch", "--quiet", "--no-tags", "--", source, version2 ?? "HEAD"], { cwd });
|
|
62460
62670
|
} catch (raised) {
|
|
62461
62671
|
throw new AdapterFault(
|
|
62462
|
-
`${raised}`,
|
|
62672
|
+
`${source} could not be fetched: ${raised}`,
|
|
62463
62673
|
version2 === void 0 ? "Check the address, and that you can reach it from here." : `Check the address, and that "${version2}" is a tag or branch it has.`
|
|
62464
62674
|
);
|
|
62465
62675
|
}
|
|
62466
|
-
|
|
62467
|
-
|
|
62468
|
-
|
|
62469
|
-
|
|
62470
|
-
|
|
62471
|
-
const
|
|
62472
|
-
|
|
62473
|
-
|
|
62474
|
-
|
|
62475
|
-
|
|
62476
|
-
|
|
62477
|
-
|
|
62478
|
-
|
|
62479
|
-
|
|
62480
|
-
|
|
62481
|
-
|
|
62676
|
+
return (await run("git", ["rev-parse", "FETCH_HEAD"], { cwd })).stdout.trim();
|
|
62677
|
+
}
|
|
62678
|
+
/** `git ls-tree -r`, the paths split on the NUL `-z` ends each with, so a
|
|
62679
|
+
* path holding a newline or a quote is read as it is. */
|
|
62680
|
+
async listFilesAt(repo, commit) {
|
|
62681
|
+
const { stdout } = await run("git", ["ls-tree", "-r", "-z", "--name-only", commit], { cwd: fileURLToPath3(repo), maxBuffer: 64 * 1024 * 1024 });
|
|
62682
|
+
return stdout.split("\0").filter((path) => path !== "");
|
|
62683
|
+
}
|
|
62684
|
+
/** `git show <commit>:<path>`. */
|
|
62685
|
+
async readFileAt(repo, commit, path) {
|
|
62686
|
+
return (await run("git", ["show", `${commit}:${path}`], { cwd: fileURLToPath3(repo), maxBuffer: 64 * 1024 * 1024 })).stdout;
|
|
62687
|
+
}
|
|
62688
|
+
/** `git read-tree --prefix` into an index of its own, then `checkout-index`
|
|
62689
|
+
* over the folder emptied first: the repository's own
|
|
62690
|
+
* index, and what is staged in it, is never read from or written to. */
|
|
62691
|
+
async checkoutFolderAt(repo, commit, fromFolder, intoSubFolder) {
|
|
62692
|
+
const cwd = fileURLToPath3(repo);
|
|
62693
|
+
const checkoutIndexPath = (await run("git", ["rev-parse", "--path-format=absolute", "--git-path", "cw-checkout-index"], { cwd })).stdout.trim();
|
|
62694
|
+
const checkoutIndexEnv = { ...process.env, GIT_INDEX_FILE: checkoutIndexPath };
|
|
62482
62695
|
try {
|
|
62483
|
-
await rm2(
|
|
62484
|
-
|
|
62485
|
-
const objectType = await objectTypeAt(includePath);
|
|
62486
|
-
if (objectType === "tree")
|
|
62487
|
-
await run("git", ["read-tree", `--prefix=${intoSubFolder}/`, `${fetchedCommit}:${includePath}`], { cwd, env: vendorIndexEnv });
|
|
62488
|
-
if (objectType === "blob") {
|
|
62489
|
-
const [fileMode = "100644", , fileHash = ""] = (await run("git", ["ls-tree", fetchedCommit, "--", includePath], { cwd })).stdout.split(/\s+/);
|
|
62490
|
-
await run("git", ["update-index", "--add", "--cacheinfo", `${fileMode},${fileHash},${intoSubFolder}/${basename2(includePath)}`], {
|
|
62491
|
-
cwd,
|
|
62492
|
-
env: vendorIndexEnv
|
|
62493
|
-
});
|
|
62494
|
-
}
|
|
62495
|
-
}
|
|
62696
|
+
await rm2(checkoutIndexPath, { force: true });
|
|
62697
|
+
await run("git", ["read-tree", `--prefix=${intoSubFolder}/`, fromFolder === "" ? commit : `${commit}:${fromFolder}`], { cwd, env: checkoutIndexEnv });
|
|
62496
62698
|
await rm2(join3(cwd, intoSubFolder), { recursive: true, force: true });
|
|
62497
|
-
await run("git", ["checkout-index", "--all", "--force"], { cwd, env:
|
|
62498
|
-
return intoSubFolder;
|
|
62699
|
+
await run("git", ["checkout-index", "--all", "--force"], { cwd, env: checkoutIndexEnv });
|
|
62499
62700
|
} catch (raised) {
|
|
62500
62701
|
await run("git", ["restore", "--source=HEAD", "--worktree", "--", intoSubFolder], { cwd }).catch(() => void 0);
|
|
62501
62702
|
throw new AdapterFault(`${raised}`, `Check that "${intoSubFolder}" can be written, and run this again.`);
|
|
62502
62703
|
} finally {
|
|
62503
|
-
await rm2(
|
|
62704
|
+
await rm2(checkoutIndexPath, { force: true });
|
|
62504
62705
|
}
|
|
62505
62706
|
}
|
|
62506
62707
|
/** `git rm -r` and a commit of its own: the folder is gone from the working
|
|
@@ -65594,7 +65795,7 @@ var charterAuthoringApp = new CharterAuthoring(
|
|
|
65594
65795
|
secrets,
|
|
65595
65796
|
authorizing
|
|
65596
65797
|
);
|
|
65597
|
-
var charterVendoringApp = new CharterVendoring(repoPath, fileReader, vcs);
|
|
65798
|
+
var charterVendoringApp = new CharterVendoring(repoPath, fileReader, vcs, fileWriter, yamlParser);
|
|
65598
65799
|
var testAuthoringApp = new TestAuthoring(repoPath, fileReader, fileWriter);
|
|
65599
65800
|
var mcpConnectingApp = new McpConnecting(repoPath, fileReader, secrets, authorizing, yamlParser, mcpServers);
|
|
65600
65801
|
var homePath = pathToFileURL2(`${homedir()}/`);
|