@theholocron/cli 5.0.0-alpha.166 → 5.0.0-alpha.168

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.mjs CHANGED
@@ -5217,6 +5217,50 @@ const STALE_LABELS = [
5217
5217
  //#region src/templates/dco.yml
5218
5218
  var dco_default = "allowRemediationCommits:\n individual: true\n";
5219
5219
  //#endregion
5220
+ //#region src/commands/workflow-source-marker/workflow-source-marker.ts
5221
+ /** `# Source package: <path>` line at the head of a generated thin-caller workflow — see {@link resolvePkgRelPath}. */
5222
+ const SOURCE_PACKAGE_MARKER = /^# Source package: (.+)$/m;
5223
+ /** Strip control characters (including ANSI escapes) before a value reaches a printed error message. */
5224
+ function sanitizeForDisplay(value) {
5225
+ return value.replace(/[\x00-\x1f\x7f]/g, "");
5226
+ }
5227
+ /**
5228
+ * Repo-root-relative path of the PACKAGE a workflow-writing command run is
5229
+ * scoped to — the directory holding the discovered `holocron.config.*`
5230
+ * (`configFilepath`), not necessarily `--cwd` itself. `loadConfig()` walks
5231
+ * up to find the config from any subdirectory ("every command is usable
5232
+ * from any subdirectory of a repo, not only its exact root"), so a `--cwd`
5233
+ * one level too deep would otherwise stamp a different marker than a later
5234
+ * run from the package's own root and misfire the collision check this
5235
+ * feeds (holocron#1026).
5236
+ *
5237
+ * "." for a root invocation, "apps/sentinel" for a subpackage. Normalized
5238
+ * to forward slashes so the same package produces the same marker on
5239
+ * Windows and POSIX. Throws if the result contains a line break — a path
5240
+ * containing one could otherwise break out of the `# Source package: `
5241
+ * comment line and inject arbitrary workflow YAML when the generated
5242
+ * content is later written. Not reachable through this CLI's own flows
5243
+ * (no code path writes a config file whose directory name contains a line
5244
+ * break), but cheap to reject outright rather than trust the filesystem.
5245
+ */
5246
+ function resolvePkgRelPath(gitRoot, configFilepath) {
5247
+ let root;
5248
+ try {
5249
+ root = realpathSync(gitRoot);
5250
+ } catch {
5251
+ root = gitRoot;
5252
+ }
5253
+ let configDir;
5254
+ try {
5255
+ configDir = realpathSync(dirname(configFilepath));
5256
+ } catch {
5257
+ configDir = dirname(configFilepath);
5258
+ }
5259
+ const pkgRelPath = (relative(root, configDir) || ".").split(sep).join("/");
5260
+ if (/[\r\n]/.test(pkgRelPath)) throw new Error(`refusing to use a package path containing a line break: ${JSON.stringify(pkgRelPath)}`);
5261
+ return pkgRelPath;
5262
+ }
5263
+ //#endregion
5220
5264
  //#region src/commands/setup/agent-prompts-data.ts
5221
5265
  /**
5222
5266
  * Canonical AI engineering workflow role prompts.
@@ -5877,14 +5921,35 @@ async function installSkills({ agent, skills, repoRoot }) {
5877
5921
  try {
5878
5922
  skillsRoot = dirname(require.resolve("@theholocron/skills/package.json"));
5879
5923
  } catch {
5880
- if (spawnSync("pnpm", [
5924
+ const atWorkspaceRoot = await isWorkspaceRoot(repoRoot);
5925
+ let result = spawnSync("pnpm", [
5881
5926
  "add",
5882
5927
  "-D",
5928
+ ...atWorkspaceRoot ? ["-w"] : [],
5883
5929
  "@theholocron/skills"
5884
5930
  ], {
5885
5931
  cwd: repoRoot,
5886
- stdio: "inherit"
5887
- }).status !== 0) throw new Error("failed to auto-install @theholocron/skills");
5932
+ stdio: [
5933
+ "inherit",
5934
+ "inherit",
5935
+ "pipe"
5936
+ ],
5937
+ encoding: "utf8"
5938
+ });
5939
+ if (result.status !== 0 && !atWorkspaceRoot && result.stderr?.includes("ERR_PNPM_ADDING_TO_ROOT")) {
5940
+ process.stderr.write(result.stderr);
5941
+ result = spawnSync("pnpm", [
5942
+ "add",
5943
+ "-D",
5944
+ "-w",
5945
+ "@theholocron/skills"
5946
+ ], {
5947
+ cwd: repoRoot,
5948
+ stdio: "inherit",
5949
+ encoding: "utf8"
5950
+ });
5951
+ } else if (result.status !== 0 && result.stderr) process.stderr.write(result.stderr);
5952
+ if (result.status !== 0) throw new Error("failed to auto-install @theholocron/skills");
5888
5953
  try {
5889
5954
  skillsRoot = dirname(require.resolve("@theholocron/skills/package.json"));
5890
5955
  } catch {
@@ -6040,6 +6105,10 @@ async function runSetup(input) {
6040
6105
  const steps = [];
6041
6106
  const repo = config.repo;
6042
6107
  const effectivePreset = repo?.protection;
6108
+ const setupPkgRelPath = resolvePkgRelPath(findRootSync(input.context.repoRoot, ".git", {
6109
+ fallback: "start",
6110
+ realpath: true
6111
+ }), input.loaded.filepath);
6043
6112
  const hooksEnabled = input.hooks ?? (typeof config.hooks === "boolean" ? config.hooks : typeof config.hooks === "object" ? config.hooks.prePush !== false : effectivePreset === "strict");
6044
6113
  print(style.header(`Holocron setup — ${config.name}${dryRun ? " (dry-run)" : ""}`));
6045
6114
  print(style.dim(` config: ${input.loaded.filepath}`));
@@ -6119,7 +6188,7 @@ async function runSetup(input) {
6119
6188
  if (content === void 0) continue;
6120
6189
  const step = content.includes("workflows/preview.yml@main") ? `write workflow ${name} (with preview)` : `write workflow ${name}`;
6121
6190
  steps.push(await runStep("source", step, dryRun, async () => {
6122
- await source.writeWorkflowFile(filename, `${thinCallerHeader()}${content}`);
6191
+ await source.writeWorkflowFile(filename, `${thinCallerHeader()}# Source package: ${setupPkgRelPath}\n\n${content}`);
6123
6192
  }));
6124
6193
  print(formatStep(steps[steps.length - 1]));
6125
6194
  }
@@ -7222,6 +7291,7 @@ async function runSync(input) {
7222
7291
  } catch {
7223
7292
  resolvedRepoRoot = input.context.repoRoot;
7224
7293
  }
7294
+ const pkgRelPath = resolvePkgRelPath(gitRoot, input.loaded.filepath);
7225
7295
  logger.info({
7226
7296
  config: config.name,
7227
7297
  steps: requestedSteps ?? "all",
@@ -7499,7 +7569,8 @@ async function runSync(input) {
7499
7569
  if (content === void 0) continue;
7500
7570
  const step = `sync workflow ${name}${content.includes("workflows/preview.yml@main") ? " (with preview)" : ""}`;
7501
7571
  steps.push(await runSyncStep("local", step, dryRun, async () => {
7502
- await writeWorkflowFile(gitRoot, filename, `${workflowHeader()}${content}`);
7572
+ await assertNoPackageCollision(gitRoot, filename, name, pkgRelPath);
7573
+ await writeWorkflowFile(gitRoot, filename, `${workflowHeader()}# Source package: ${pkgRelPath}\n\n${content}`);
7503
7574
  }));
7504
7575
  print(formatSyncStep(steps[steps.length - 1]));
7505
7576
  }
@@ -7762,6 +7833,32 @@ async function writeWorkflowFile(repoRoot, filename, content) {
7762
7833
  await writeFile(tmpPath, content, "utf8");
7763
7834
  await rename(tmpPath, join(dir, filename));
7764
7835
  }
7836
+ /**
7837
+ * Two different monorepo packages opting into the same generic task name
7838
+ * (e.g. both `apps/sentinel` and some future `apps/other` configuring
7839
+ * `delivery.previewDeploy`) would otherwise overwrite each other's
7840
+ * `.github/workflows/<task>.yml` on whichever package's `holocron repo sync`
7841
+ * ran last — only one package's behavior would ever actually run in CI,
7842
+ * with no signal that the other's was silently dropped (holocron#1026).
7843
+ *
7844
+ * A file with no marker at all predates this check (or was written before
7845
+ * #1026) and is never flagged — only a marker that's present AND disagrees
7846
+ * with the current run's own package is a real collision. Any read error
7847
+ * other than "file doesn't exist" propagates rather than being treated as
7848
+ * "nothing to collide with" — the file may genuinely be unreadable, not
7849
+ * missing.
7850
+ */
7851
+ async function assertNoPackageCollision(repoRoot, filename, taskName, pkgRelPath) {
7852
+ let existing;
7853
+ try {
7854
+ existing = await readFile(join(repoRoot, ".github", "workflows", filename), "utf8");
7855
+ } catch (err) {
7856
+ if (err.code === "ENOENT") return;
7857
+ throw err;
7858
+ }
7859
+ const previousPkg = SOURCE_PACKAGE_MARKER.exec(existing)?.[1];
7860
+ if (previousPkg !== void 0 && previousPkg !== pkgRelPath) throw new Error(`task "${taskName}" would overwrite .github/workflows/${filename}, already generated for package "${sanitizeForDisplay(previousPkg)}" — the same task name can't be used by two different packages (holocron#1026). If "${sanitizeForDisplay(previousPkg)}" moved or was renamed to this path, delete .github/workflows/${filename} first and re-run sync; otherwise rename one or give it a different thin-caller filename.`);
7861
+ }
7765
7862
  //#endregion
7766
7863
  //#region src/observability/resolve-telemetry/resolve-telemetry.ts
7767
7864
  /**