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/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.7.4",
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 OutcomeDTOs = byType(DoctorOutcomeSchema, ExplanationOutcomeSchema, SensorRunOutcomeSchema, SessionReviewOutcomeSchema);
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, and committed" },
58392
- 422: json3(DataDTOs.Fault, "No repository, work in hand, or what git refused")
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 clones the charter from: an address over any transport, or a path on disk"
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 vendors = await fileReaders.listFolders(vendorFolderIn(repo));
60293
+ const vendorFolders = await loadVendorFolders(repo, fileReaders);
60024
60294
  const vendorFiles = await Promise.all(
60025
- vendors.map(async (folder) => {
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
- { description: sc.description(), ...sc.headers["disable-user-invocation"] === true ? { "user-invocable": false } : {} },
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
- constructor(repoPath2, fileReader2, vcs2) {
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 { basename as basename2, join as join3 } from "path";
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
- * `git fetch`, handed the source exactly as it was written, then each path
62438
- * asked for read into an index of its own and checked out into the folder
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, ref], { cwd });
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
- const fetchedCommit = (await run("git", ["rev-parse", "FETCH_HEAD"], { cwd })).stdout.trim();
62467
- const objectTypeAt = (path) => run("git", ["cat-file", "-t", `${fetchedCommit}:${path}`], { cwd }).then(
62468
- ({ stdout }) => stdout.trim(),
62469
- () => void 0
62470
- );
62471
- const atVersion = version2 === void 0 ? "" : ` at ${version2}`;
62472
- const [requiredPath = ""] = includePaths;
62473
- if (await objectTypeAt(requiredPath) === void 0)
62474
- throw new AdapterFault(`${source} has no "${requiredPath}"${atVersion}.`, `Check that the source holds "${requiredPath}" at the version asked for.`);
62475
- if (refusedIfSourceHolds !== void 0 && await objectTypeAt(refusedIfSourceHolds) === "tree")
62476
- throw new AdapterFault(
62477
- `${source} holds "${refusedIfSourceHolds}"${atVersion}, which this copy refuses.`,
62478
- `Remove "${refusedIfSourceHolds}" from the source, or copy a version without it.`
62479
- );
62480
- const vendorIndexPath = (await run("git", ["rev-parse", "--path-format=absolute", "--git-path", "cw-vendor-index"], { cwd })).stdout.trim();
62481
- const vendorIndexEnv = { ...process.env, GIT_INDEX_FILE: vendorIndexPath };
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(vendorIndexPath, { force: true });
62484
- for (const includePath of includePaths) {
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: vendorIndexEnv });
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(vendorIndexPath, { force: true });
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()}/`);