@bensandee/tooling 0.89.1 → 0.90.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/bin.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { accessSync, appendFileSync, closeSync, constants, copyFileSync, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
3
- import * as path$1 from "node:path";
4
- import path from "node:path";
3
+ import path, * as path$1 from "node:path";
5
4
  import { parseArgs, parseEnv } from "node:util";
6
5
  import { FatalError, TransientError, UnexpectedError, describeError, describeError as describeError$1, redact, redact as redact$1 } from "@bensandee/common";
7
6
  import { defineCommand, renderUsage, runCommand, runMain } from "citty";
@@ -176,7 +175,7 @@ function loadEnvFileForBin(rawArgs, bin, io = processIo) {
176
175
  //#region src/env-file-preload.ts
177
176
  const envFileLoad = loadEnvFileForBin(process.argv.slice(2), {
178
177
  name: "bst",
179
- version: "0.89.1"
178
+ version: "0.90.0"
180
179
  });
181
180
  //#endregion
182
181
  //#region ../cli-kit/src/logger.ts
@@ -828,7 +827,7 @@ const DockerImageOverrideSchema = z.strictObject({
828
827
  buildArgs: z.record(z.string(), z.string()).optional().meta({ description: "Build args passed to `docker build` for this package. Values support ${VAR} and ${VAR:-default} expansion against process.env." }),
829
828
  imageName: z.string().min(1).optional().meta({ description: "Image name used for docker:build tags, docker:publish refs, and docker:check cross-referencing. Falls back to the derived name: the package.json name for the root package (npm scope stripped), or {root-name}-{package-name} for a monorepo package. A value that already carries a registry host (e.g. 'ghcr.io/me/app') is used as written by docker:publish, with no registryNamespace prefix added, and must name the registry this run logs in to -- one naming any other host refuses before login." }),
830
829
  version: z.string().optional().meta({ description: "Version used for docker:publish tags; overrides the version resolved from the enclosing package." }),
831
- publish: z.boolean().optional().meta({ description: "The publish decision either way: true keeps an image published -- including one its compose service is declared only in the check overlay, which a later release stops publishing by default -- and false excludes it while publishDocker stays true. Unset means the discovery default, which publishes everything except a check-only image once that rule is enforced." })
830
+ publish: z.boolean().optional().meta({ description: "The publish decision either way: true keeps an image published -- including one whose compose service is declared only in the check overlay, which is not published by default -- and false excludes it while publishDocker stays true. Unset means the discovery default, which publishes everything except a check-only image." })
832
831
  });
833
832
  const DockerMapSchema = z.record(z.string(), DockerImageOverrideSchema);
834
833
  const TolerantDockerMapSchema = z.record(z.string(), tolerantBlock(DockerImageOverrideSchema));
@@ -1701,6 +1700,7 @@ const LintSweepConfigSchema = z.strictObject({
1701
1700
  });
1702
1701
  const AgentEgressConfigSchema = z.strictObject({ browseHosts: z.array(z.string().min(1)).optional().meta({ description: "Host patterns this repo's agent containers may fetch from, on top of the hosts every run needs: an exact host (`fastdl.mongodb.org`) or a wildcard over its leftmost label (`*.example.com`). No ports. These join the browse lane, which serves GET and HEAD only." }) });
1703
1702
  const AgentConfigSchema = z.strictObject({
1703
+ enabled: z.boolean().optional().meta({ description: "The repo's explicit consent to the agent (default false). Only `true`, read from the repo's **default branch**, lets the agent work the repo: claim units, run sweeps, recover orphans, merge, act on a chat proposal or start a new spec. A missing key, `false`, a missing `.tooling.json` or a read that fails all mean not enabled, and the answer is re-read within about a minute. Every other `agent.*` key below has no effect without this one; `repo:sync` warns when the block sets one of them and this key is not `true`. Where this is `true`, `repo:sync` also installs `@bensandee/agent-cli`. Provisioning the agent's labels is not consent, and is never gated by this key." }),
1704
1704
  autonomy: z.boolean().optional().meta({ description: "Opt into interior autonomy (default false). When a phase's latest rationale reports readiness \"ready\" with an empty openQuestions[] and the review phase is clean, the agent applies the existing plan -> implement -> review -> park-at-mergeable advance itself instead of parking for a human. Never applies to the design/spec gate or the final merge from the mergeable terminal -- those always wait on a human. Also arms the trunk-red detector: when this repo's default branch combined status settles red, the agent files a tracking issue naming the failing checks and labels it agent/spec, starting the fix through the ordinary spec/plan/implement/review pipeline. It also closes that issue, with a comment, if the default branch recovers on its own before a human acts on it. Also arms the world-changed retrigger sweep: a pull request labelled agent-flags/autopilot that is parked at a -stuck or -failed terminal is re-queued when the fact its park recorded stops being true (the default branch went green, or moved past a conflicted base)." }),
1705
1705
  preRunRebase: z.boolean().optional().meta({ description: "Merge the current default branch into the work branch before the implement (execute) phase's container starts (default true). A clean merge or an already-up-to-date branch runs the container as is; a conflicting merge is aborted, recorded on the run's status comment, and the run proceeds on the un-synced branch -- it never fails the run. Two consecutive conflicting attempts hand the unit to agent/repair before any container runs; a timeout or a failed merge never counts toward that pair. Set false to skip the attempt entirely for this repo: no fetch, no merge, no abort, and the branch is worked exactly as cloned." }),
1706
1706
  backlog: z.boolean().optional().meta({ description: "Whether the agent may self-trigger backlog runs consuming this repo's .agent/findings/ ledger (default: on wherever the ledger exists, which only happens in a repo whose own review loop wrote it). A backlog run opens a pull request that fixes or refutes the pending findings; false disarms it, leaving the ledger to accumulate for a human. A run starts only once at least three findings are pending or one is older than seven days." }),
@@ -3375,7 +3375,7 @@ function localTarget(call, entry) {
3375
3375
  }
3376
3376
  //#endregion
3377
3377
  //#region src/release/docker-check-only-phase.ts
3378
- const DOCKER_CHECK_ONLY_PHASE = "warn";
3378
+ const DOCKER_CHECK_ONLY_PHASE = "enforce";
3379
3379
  //#endregion
3380
3380
  //#region src/release/docker.ts
3381
3381
  function defaultPublish(checkOnlyIn, phase) {
@@ -3816,7 +3816,7 @@ function runDockerBuild(executor, config) {
3816
3816
  log$1.info(`Built ${pkg.imageName}:latest`);
3817
3817
  return { packages: [pkg] };
3818
3818
  }
3819
- const packages = detectDockerPackages(executor, config.cwd, repoName, config.checkOnlyPhase ?? "warn");
3819
+ const packages = detectDockerPackages(executor, config.cwd, repoName, config.checkOnlyPhase ?? "enforce");
3820
3820
  if (packages.length === 0) {
3821
3821
  log$1.info("No packages with docker config found");
3822
3822
  return { packages: [] };
@@ -3976,7 +3976,7 @@ function reportCheckOnlyImages(packages, phase) {
3976
3976
  }
3977
3977
  }
3978
3978
  function runDockerPublish(executor, config) {
3979
- const checkOnlyPhase = config.checkOnlyPhase ?? "warn";
3979
+ const checkOnlyPhase = config.checkOnlyPhase ?? "enforce";
3980
3980
  const { packages } = runDockerBuild(executor, {
3981
3981
  cwd: config.cwd,
3982
3982
  packageDir: void 0,
@@ -4439,8 +4439,8 @@ function buildWorkflowYaml(options) {
4439
4439
  }
4440
4440
  //#endregion
4441
4441
  //#region src/generators/mise-pin.ts
4442
- const MISE_VERSION = "2026.10.3";
4443
- const MISE_LINUX_X64_SHA256 = "8d48bc510b7d844fad0bc7156c0855c2a1e7230c51e498a7f6b63b021f8b57c5";
4442
+ const MISE_VERSION = "2026.10.8";
4443
+ const MISE_LINUX_X64_SHA256 = "c72477ae6c3a8e7d817a322f43da70e4643e75ee5c21265f1c19c894273b7428";
4444
4444
  const SETUP_MISE_ACTION_URL = "https://code.orangebikelabs.com/bensandee/common/actions/setup-mise";
4445
4445
  const SETUP_MISE_ACTION_TAG = "setup-mise@latest";
4446
4446
  const SETUP_MISE_ACTION_SHA = "ebe622ef0d24c7efec4a5f6093ddb18500900e24";
@@ -4858,6 +4858,39 @@ function diagnoseCheckScriptForwardingAt(targetDir) {
4858
4858
  return diagnoseCheckScriptForwarding(scripts);
4859
4859
  }
4860
4860
  //#endregion
4861
+ //#region src/generators/agent-cli-dep.ts
4862
+ const AGENT_CLI_PACKAGE = "@bensandee/agent-cli";
4863
+ const AgentEnabledSliceSchema = z.looseObject({ agent: z.looseObject({ enabled: z.boolean().optional() }).optional() });
4864
+ function agentEnabled(ctx) {
4865
+ return parseToolingConfigSlice(ctx.read(".tooling.json"), AgentEnabledSliceSchema, { block: "agent" })?.agent?.enabled === true;
4866
+ }
4867
+ function wantsAgentCli(ctx) {
4868
+ if (!agentEnabled(ctx)) return false;
4869
+ if (isWorkspacePackage(ctx, "@bensandee/agent-cli")) return false;
4870
+ return !readUnmanagedDevDepNames(ctx).has(AGENT_CLI_PACKAGE);
4871
+ }
4872
+ function isWorkspacePackage(ctx, packageName) {
4873
+ if (ctx.config.structure !== "monorepo") return false;
4874
+ const packagesDir = path.join(ctx.targetDir, "packages");
4875
+ if (!existsSync(packagesDir)) return false;
4876
+ try {
4877
+ for (const entry of readdirSync(packagesDir, { withFileTypes: true })) {
4878
+ if (!entry.isDirectory()) continue;
4879
+ const pkgJsonPath = path.join(packagesDir, entry.name, "package.json");
4880
+ if (!existsSync(pkgJsonPath)) continue;
4881
+ try {
4882
+ if (parsePackageJson(readFileSync(pkgJsonPath, "utf-8"))?.name === packageName) return true;
4883
+ } catch (_error) {}
4884
+ }
4885
+ } catch (_error) {}
4886
+ return false;
4887
+ }
4888
+ const UnmanagedSliceSchema = z.object({ unmanagedDevDependencies: UnmanagedDevDependenciesSchema.optional() });
4889
+ function readUnmanagedDevDepNames(ctx) {
4890
+ const slice = parseToolingConfigSlice(ctx.read(".tooling.json"), UnmanagedSliceSchema, { block: "unmanagedDevDependencies" });
4891
+ return new Set(slice?.unmanagedDevDependencies ?? []);
4892
+ }
4893
+ //#endregion
4861
4894
  //#region src/generators/package-json.ts
4862
4895
  const NODE_ENGINES_FLOOR = "24.13.0";
4863
4896
  const ENGINES_NODE = `>=${NODE_ENGINES_FLOOR}`;
@@ -4966,18 +4999,18 @@ function perPackageDevDeps(config) {
4966
4999
  "@types/node": "24.19.1",
4967
5000
  "@vitest/coverage-v8": "5.0.3",
4968
5001
  "commit-and-tag-version": "13.2.1",
4969
- "knip": "6.39.0",
4970
- "lefthook": "2.1.16",
4971
- "modern-web-guidance": "0.0.191",
4972
- "oxfmt": "0.71.0",
4973
- "oxlint": "1.86.0",
5002
+ "knip": "6.41.0",
5003
+ "lefthook": "2.2.1",
5004
+ "modern-web-guidance": "0.0.193",
5005
+ "oxfmt": "0.72.0",
5006
+ "oxlint": "1.87.0",
4974
5007
  "oxlint-tsgolint": "7.0.2003",
4975
5008
  "prettier": "3.9.9",
4976
- "release-it": "21.1.0",
5009
+ "release-it": "21.1.1",
4977
5010
  "tsdown": "0.23.0",
4978
5011
  "typescript": "6.0.3",
4979
5012
  "vitest": "5.0.3",
4980
- "pnpm": "11.28.3"
5013
+ "pnpm": "11.28.5"
4981
5014
  }["typescript"];
4982
5015
  deps["@types/node"] = {
4983
5016
  "@changesets/cli": "3.0.3",
@@ -4985,18 +5018,18 @@ function perPackageDevDeps(config) {
4985
5018
  "@types/node": "24.19.1",
4986
5019
  "@vitest/coverage-v8": "5.0.3",
4987
5020
  "commit-and-tag-version": "13.2.1",
4988
- "knip": "6.39.0",
4989
- "lefthook": "2.1.16",
4990
- "modern-web-guidance": "0.0.191",
4991
- "oxfmt": "0.71.0",
4992
- "oxlint": "1.86.0",
5021
+ "knip": "6.41.0",
5022
+ "lefthook": "2.2.1",
5023
+ "modern-web-guidance": "0.0.193",
5024
+ "oxfmt": "0.72.0",
5025
+ "oxlint": "1.87.0",
4993
5026
  "oxlint-tsgolint": "7.0.2003",
4994
5027
  "prettier": "3.9.9",
4995
- "release-it": "21.1.0",
5028
+ "release-it": "21.1.1",
4996
5029
  "tsdown": "0.23.0",
4997
5030
  "typescript": "6.0.3",
4998
5031
  "vitest": "5.0.3",
4999
- "pnpm": "11.28.3"
5032
+ "pnpm": "11.28.5"
5000
5033
  }["@types/node"];
5001
5034
  }
5002
5035
  if (config.builder === "tsdown") deps["tsdown"] = {
@@ -5005,18 +5038,18 @@ function perPackageDevDeps(config) {
5005
5038
  "@types/node": "24.19.1",
5006
5039
  "@vitest/coverage-v8": "5.0.3",
5007
5040
  "commit-and-tag-version": "13.2.1",
5008
- "knip": "6.39.0",
5009
- "lefthook": "2.1.16",
5010
- "modern-web-guidance": "0.0.191",
5011
- "oxfmt": "0.71.0",
5012
- "oxlint": "1.86.0",
5041
+ "knip": "6.41.0",
5042
+ "lefthook": "2.2.1",
5043
+ "modern-web-guidance": "0.0.193",
5044
+ "oxfmt": "0.72.0",
5045
+ "oxlint": "1.87.0",
5013
5046
  "oxlint-tsgolint": "7.0.2003",
5014
5047
  "prettier": "3.9.9",
5015
- "release-it": "21.1.0",
5048
+ "release-it": "21.1.1",
5016
5049
  "tsdown": "0.23.0",
5017
5050
  "typescript": "6.0.3",
5018
5051
  "vitest": "5.0.3",
5019
- "pnpm": "11.28.3"
5052
+ "pnpm": "11.28.5"
5020
5053
  }["tsdown"];
5021
5054
  return deps;
5022
5055
  }
@@ -5028,18 +5061,18 @@ function vitestDevDeps() {
5028
5061
  "@types/node": "24.19.1",
5029
5062
  "@vitest/coverage-v8": "5.0.3",
5030
5063
  "commit-and-tag-version": "13.2.1",
5031
- "knip": "6.39.0",
5032
- "lefthook": "2.1.16",
5033
- "modern-web-guidance": "0.0.191",
5034
- "oxfmt": "0.71.0",
5035
- "oxlint": "1.86.0",
5064
+ "knip": "6.41.0",
5065
+ "lefthook": "2.2.1",
5066
+ "modern-web-guidance": "0.0.193",
5067
+ "oxfmt": "0.72.0",
5068
+ "oxlint": "1.87.0",
5036
5069
  "oxlint-tsgolint": "7.0.2003",
5037
5070
  "prettier": "3.9.9",
5038
- "release-it": "21.1.0",
5071
+ "release-it": "21.1.1",
5039
5072
  "tsdown": "0.23.0",
5040
5073
  "typescript": "6.0.3",
5041
5074
  "vitest": "5.0.3",
5042
- "pnpm": "11.28.3"
5075
+ "pnpm": "11.28.5"
5043
5076
  }["vitest"],
5044
5077
  "@vitest/coverage-v8": {
5045
5078
  "@changesets/cli": "3.0.3",
@@ -5047,18 +5080,18 @@ function vitestDevDeps() {
5047
5080
  "@types/node": "24.19.1",
5048
5081
  "@vitest/coverage-v8": "5.0.3",
5049
5082
  "commit-and-tag-version": "13.2.1",
5050
- "knip": "6.39.0",
5051
- "lefthook": "2.1.16",
5052
- "modern-web-guidance": "0.0.191",
5053
- "oxfmt": "0.71.0",
5054
- "oxlint": "1.86.0",
5083
+ "knip": "6.41.0",
5084
+ "lefthook": "2.2.1",
5085
+ "modern-web-guidance": "0.0.193",
5086
+ "oxfmt": "0.72.0",
5087
+ "oxlint": "1.87.0",
5055
5088
  "oxlint-tsgolint": "7.0.2003",
5056
5089
  "prettier": "3.9.9",
5057
- "release-it": "21.1.0",
5090
+ "release-it": "21.1.1",
5058
5091
  "tsdown": "0.23.0",
5059
5092
  "typescript": "6.0.3",
5060
5093
  "vitest": "5.0.3",
5061
- "pnpm": "11.28.3"
5094
+ "pnpm": "11.28.5"
5062
5095
  }["@vitest/coverage-v8"]
5063
5096
  };
5064
5097
  }
@@ -5069,18 +5102,18 @@ function rootDevDeps(config) {
5069
5102
  "@types/node": "24.19.1",
5070
5103
  "@vitest/coverage-v8": "5.0.3",
5071
5104
  "commit-and-tag-version": "13.2.1",
5072
- "knip": "6.39.0",
5073
- "lefthook": "2.1.16",
5074
- "modern-web-guidance": "0.0.191",
5075
- "oxfmt": "0.71.0",
5076
- "oxlint": "1.86.0",
5105
+ "knip": "6.41.0",
5106
+ "lefthook": "2.2.1",
5107
+ "modern-web-guidance": "0.0.193",
5108
+ "oxfmt": "0.72.0",
5109
+ "oxlint": "1.87.0",
5077
5110
  "oxlint-tsgolint": "7.0.2003",
5078
5111
  "prettier": "3.9.9",
5079
- "release-it": "21.1.0",
5112
+ "release-it": "21.1.1",
5080
5113
  "tsdown": "0.23.0",
5081
5114
  "typescript": "6.0.3",
5082
5115
  "vitest": "5.0.3",
5083
- "pnpm": "11.28.3"
5116
+ "pnpm": "11.28.5"
5084
5117
  }["lefthook"] };
5085
5118
  if (config.stack !== "none") {
5086
5119
  deps["knip"] = {
@@ -5089,18 +5122,18 @@ function rootDevDeps(config) {
5089
5122
  "@types/node": "24.19.1",
5090
5123
  "@vitest/coverage-v8": "5.0.3",
5091
5124
  "commit-and-tag-version": "13.2.1",
5092
- "knip": "6.39.0",
5093
- "lefthook": "2.1.16",
5094
- "modern-web-guidance": "0.0.191",
5095
- "oxfmt": "0.71.0",
5096
- "oxlint": "1.86.0",
5125
+ "knip": "6.41.0",
5126
+ "lefthook": "2.2.1",
5127
+ "modern-web-guidance": "0.0.193",
5128
+ "oxfmt": "0.72.0",
5129
+ "oxlint": "1.87.0",
5097
5130
  "oxlint-tsgolint": "7.0.2003",
5098
5131
  "prettier": "3.9.9",
5099
- "release-it": "21.1.0",
5132
+ "release-it": "21.1.1",
5100
5133
  "tsdown": "0.23.0",
5101
5134
  "typescript": "6.0.3",
5102
5135
  "vitest": "5.0.3",
5103
- "pnpm": "11.28.3"
5136
+ "pnpm": "11.28.5"
5104
5137
  }["knip"];
5105
5138
  deps["oxlint"] = {
5106
5139
  "@changesets/cli": "3.0.3",
@@ -5108,18 +5141,18 @@ function rootDevDeps(config) {
5108
5141
  "@types/node": "24.19.1",
5109
5142
  "@vitest/coverage-v8": "5.0.3",
5110
5143
  "commit-and-tag-version": "13.2.1",
5111
- "knip": "6.39.0",
5112
- "lefthook": "2.1.16",
5113
- "modern-web-guidance": "0.0.191",
5114
- "oxfmt": "0.71.0",
5115
- "oxlint": "1.86.0",
5144
+ "knip": "6.41.0",
5145
+ "lefthook": "2.2.1",
5146
+ "modern-web-guidance": "0.0.193",
5147
+ "oxfmt": "0.72.0",
5148
+ "oxlint": "1.87.0",
5116
5149
  "oxlint-tsgolint": "7.0.2003",
5117
5150
  "prettier": "3.9.9",
5118
- "release-it": "21.1.0",
5151
+ "release-it": "21.1.1",
5119
5152
  "tsdown": "0.23.0",
5120
5153
  "typescript": "6.0.3",
5121
5154
  "vitest": "5.0.3",
5122
- "pnpm": "11.28.3"
5155
+ "pnpm": "11.28.5"
5123
5156
  }["oxlint"];
5124
5157
  Object.assign(deps, vitestDevDeps());
5125
5158
  }
@@ -5129,37 +5162,21 @@ function rootDevDeps(config) {
5129
5162
  "@types/node": "24.19.1",
5130
5163
  "@vitest/coverage-v8": "5.0.3",
5131
5164
  "commit-and-tag-version": "13.2.1",
5132
- "knip": "6.39.0",
5133
- "lefthook": "2.1.16",
5134
- "modern-web-guidance": "0.0.191",
5135
- "oxfmt": "0.71.0",
5136
- "oxlint": "1.86.0",
5165
+ "knip": "6.41.0",
5166
+ "lefthook": "2.2.1",
5167
+ "modern-web-guidance": "0.0.193",
5168
+ "oxfmt": "0.72.0",
5169
+ "oxlint": "1.87.0",
5137
5170
  "oxlint-tsgolint": "7.0.2003",
5138
5171
  "prettier": "3.9.9",
5139
- "release-it": "21.1.0",
5172
+ "release-it": "21.1.1",
5140
5173
  "tsdown": "0.23.0",
5141
5174
  "typescript": "6.0.3",
5142
5175
  "vitest": "5.0.3",
5143
- "pnpm": "11.28.3"
5176
+ "pnpm": "11.28.5"
5144
5177
  }["oxlint-tsgolint"];
5145
5178
  return deps;
5146
5179
  }
5147
- function isWorkspacePackage(ctx, packageName) {
5148
- if (ctx.config.structure !== "monorepo") return false;
5149
- const packagesDir = path.join(ctx.targetDir, "packages");
5150
- if (!existsSync(packagesDir)) return false;
5151
- try {
5152
- for (const entry of readdirSync(packagesDir, { withFileTypes: true })) {
5153
- if (!entry.isDirectory()) continue;
5154
- const pkgJsonPath = path.join(packagesDir, entry.name, "package.json");
5155
- if (!existsSync(pkgJsonPath)) continue;
5156
- try {
5157
- if (parsePackageJson(readFileSync(pkgJsonPath, "utf-8"))?.name === packageName) return true;
5158
- } catch (_error) {}
5159
- }
5160
- } catch (_error) {}
5161
- return false;
5162
- }
5163
5180
  function addReleaseDeps(deps, config) {
5164
5181
  switch (config.releaseStrategy) {
5165
5182
  case "release-it":
@@ -5169,18 +5186,18 @@ function addReleaseDeps(deps, config) {
5169
5186
  "@types/node": "24.19.1",
5170
5187
  "@vitest/coverage-v8": "5.0.3",
5171
5188
  "commit-and-tag-version": "13.2.1",
5172
- "knip": "6.39.0",
5173
- "lefthook": "2.1.16",
5174
- "modern-web-guidance": "0.0.191",
5175
- "oxfmt": "0.71.0",
5176
- "oxlint": "1.86.0",
5189
+ "knip": "6.41.0",
5190
+ "lefthook": "2.2.1",
5191
+ "modern-web-guidance": "0.0.193",
5192
+ "oxfmt": "0.72.0",
5193
+ "oxlint": "1.87.0",
5177
5194
  "oxlint-tsgolint": "7.0.2003",
5178
5195
  "prettier": "3.9.9",
5179
- "release-it": "21.1.0",
5196
+ "release-it": "21.1.1",
5180
5197
  "tsdown": "0.23.0",
5181
5198
  "typescript": "6.0.3",
5182
5199
  "vitest": "5.0.3",
5183
- "pnpm": "11.28.3"
5200
+ "pnpm": "11.28.5"
5184
5201
  }["release-it"];
5185
5202
  if (config.structure === "monorepo") deps["@release-it/bumper"] = {
5186
5203
  "@changesets/cli": "3.0.3",
@@ -5188,18 +5205,18 @@ function addReleaseDeps(deps, config) {
5188
5205
  "@types/node": "24.19.1",
5189
5206
  "@vitest/coverage-v8": "5.0.3",
5190
5207
  "commit-and-tag-version": "13.2.1",
5191
- "knip": "6.39.0",
5192
- "lefthook": "2.1.16",
5193
- "modern-web-guidance": "0.0.191",
5194
- "oxfmt": "0.71.0",
5195
- "oxlint": "1.86.0",
5208
+ "knip": "6.41.0",
5209
+ "lefthook": "2.2.1",
5210
+ "modern-web-guidance": "0.0.193",
5211
+ "oxfmt": "0.72.0",
5212
+ "oxlint": "1.87.0",
5196
5213
  "oxlint-tsgolint": "7.0.2003",
5197
5214
  "prettier": "3.9.9",
5198
- "release-it": "21.1.0",
5215
+ "release-it": "21.1.1",
5199
5216
  "tsdown": "0.23.0",
5200
5217
  "typescript": "6.0.3",
5201
5218
  "vitest": "5.0.3",
5202
- "pnpm": "11.28.3"
5219
+ "pnpm": "11.28.5"
5203
5220
  }["@release-it/bumper"];
5204
5221
  break;
5205
5222
  case "simple":
@@ -5209,18 +5226,18 @@ function addReleaseDeps(deps, config) {
5209
5226
  "@types/node": "24.19.1",
5210
5227
  "@vitest/coverage-v8": "5.0.3",
5211
5228
  "commit-and-tag-version": "13.2.1",
5212
- "knip": "6.39.0",
5213
- "lefthook": "2.1.16",
5214
- "modern-web-guidance": "0.0.191",
5215
- "oxfmt": "0.71.0",
5216
- "oxlint": "1.86.0",
5229
+ "knip": "6.41.0",
5230
+ "lefthook": "2.2.1",
5231
+ "modern-web-guidance": "0.0.193",
5232
+ "oxfmt": "0.72.0",
5233
+ "oxlint": "1.87.0",
5217
5234
  "oxlint-tsgolint": "7.0.2003",
5218
5235
  "prettier": "3.9.9",
5219
- "release-it": "21.1.0",
5236
+ "release-it": "21.1.1",
5220
5237
  "tsdown": "0.23.0",
5221
5238
  "typescript": "6.0.3",
5222
5239
  "vitest": "5.0.3",
5223
- "pnpm": "11.28.3"
5240
+ "pnpm": "11.28.5"
5224
5241
  }["commit-and-tag-version"];
5225
5242
  break;
5226
5243
  case "changesets": deps["@changesets/cli"] = {
@@ -5229,18 +5246,18 @@ function addReleaseDeps(deps, config) {
5229
5246
  "@types/node": "24.19.1",
5230
5247
  "@vitest/coverage-v8": "5.0.3",
5231
5248
  "commit-and-tag-version": "13.2.1",
5232
- "knip": "6.39.0",
5233
- "lefthook": "2.1.16",
5234
- "modern-web-guidance": "0.0.191",
5235
- "oxfmt": "0.71.0",
5236
- "oxlint": "1.86.0",
5249
+ "knip": "6.41.0",
5250
+ "lefthook": "2.2.1",
5251
+ "modern-web-guidance": "0.0.193",
5252
+ "oxfmt": "0.72.0",
5253
+ "oxlint": "1.87.0",
5237
5254
  "oxlint-tsgolint": "7.0.2003",
5238
5255
  "prettier": "3.9.9",
5239
- "release-it": "21.1.0",
5256
+ "release-it": "21.1.1",
5240
5257
  "tsdown": "0.23.0",
5241
5258
  "typescript": "6.0.3",
5242
5259
  "vitest": "5.0.3",
5243
- "pnpm": "11.28.3"
5260
+ "pnpm": "11.28.5"
5244
5261
  }["@changesets/cli"];
5245
5262
  }
5246
5263
  }
@@ -5307,10 +5324,6 @@ function reclaimManagedDevDeps(ctx, pkg, managedDevDeps, unmanaged, changes) {
5307
5324
  }
5308
5325
  pkg.devDependencies = existingDevDeps;
5309
5326
  }
5310
- function readUnmanagedDevDeps(ctx) {
5311
- const slice = parseToolingConfigSlice(ctx.read(".tooling.json"), z.object({ unmanagedDevDependencies: UnmanagedDevDependenciesSchema.optional() }), { block: "unmanagedDevDependencies" });
5312
- return new Set(slice?.unmanagedDevDependencies ?? []);
5313
- }
5314
5327
  function isAsciiCheckEnabled(ctx) {
5315
5328
  return parseToolingConfigSlice(ctx.read(".tooling.json"), z.object({ ascii: AsciiConfigSchema.optional() }), { block: "ascii" })?.ascii?.enabled === true;
5316
5329
  }
@@ -5345,18 +5358,18 @@ function applyMetadataDefaults(pkg, ctx, changes) {
5345
5358
  "@types/node": "24.19.1",
5346
5359
  "@vitest/coverage-v8": "5.0.3",
5347
5360
  "commit-and-tag-version": "13.2.1",
5348
- "knip": "6.39.0",
5349
- "lefthook": "2.1.16",
5350
- "modern-web-guidance": "0.0.191",
5351
- "oxfmt": "0.71.0",
5352
- "oxlint": "1.86.0",
5361
+ "knip": "6.41.0",
5362
+ "lefthook": "2.2.1",
5363
+ "modern-web-guidance": "0.0.193",
5364
+ "oxfmt": "0.72.0",
5365
+ "oxlint": "1.87.0",
5353
5366
  "oxlint-tsgolint": "7.0.2003",
5354
5367
  "prettier": "3.9.9",
5355
- "release-it": "21.1.0",
5368
+ "release-it": "21.1.1",
5356
5369
  "tsdown": "0.23.0",
5357
5370
  "typescript": "6.0.3",
5358
5371
  "vitest": "5.0.3",
5359
- "pnpm": "11.28.3"
5372
+ "pnpm": "11.28.5"
5360
5373
  }["pnpm"];
5361
5374
  const desiredPm = pinnedPnpm ? `pnpm@${pinnedPnpm}` : void 0;
5362
5375
  if (desiredPm) {
@@ -5395,26 +5408,27 @@ function buildManagedDevDeps(ctx) {
5395
5408
  const devDeps = { ...rootDevDeps(ctx.config) };
5396
5409
  if (!isMonorepo) Object.assign(devDeps, perPackageDevDeps(ctx.config));
5397
5410
  if (ctx.config.stack !== "none" || ctx.config.ci === "forgejo") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.15.0";
5398
- if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.89.1";
5411
+ if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.90.0";
5399
5412
  if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.17.0";
5413
+ if (wantsAgentCli(ctx)) devDeps[AGENT_CLI_PACKAGE] = "0.1.1";
5400
5414
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
5401
5415
  "@changesets/cli": "3.0.3",
5402
5416
  "@release-it/bumper": "8.0.1",
5403
5417
  "@types/node": "24.19.1",
5404
5418
  "@vitest/coverage-v8": "5.0.3",
5405
5419
  "commit-and-tag-version": "13.2.1",
5406
- "knip": "6.39.0",
5407
- "lefthook": "2.1.16",
5408
- "modern-web-guidance": "0.0.191",
5409
- "oxfmt": "0.71.0",
5410
- "oxlint": "1.86.0",
5420
+ "knip": "6.41.0",
5421
+ "lefthook": "2.2.1",
5422
+ "modern-web-guidance": "0.0.193",
5423
+ "oxfmt": "0.72.0",
5424
+ "oxlint": "1.87.0",
5411
5425
  "oxlint-tsgolint": "7.0.2003",
5412
5426
  "prettier": "3.9.9",
5413
- "release-it": "21.1.0",
5427
+ "release-it": "21.1.1",
5414
5428
  "tsdown": "0.23.0",
5415
5429
  "typescript": "6.0.3",
5416
5430
  "vitest": "5.0.3",
5417
- "pnpm": "11.28.3"
5431
+ "pnpm": "11.28.5"
5418
5432
  }["oxfmt"];
5419
5433
  if (ctx.config.formatter === "prettier") devDeps["prettier"] = {
5420
5434
  "@changesets/cli": "3.0.3",
@@ -5422,18 +5436,18 @@ function buildManagedDevDeps(ctx) {
5422
5436
  "@types/node": "24.19.1",
5423
5437
  "@vitest/coverage-v8": "5.0.3",
5424
5438
  "commit-and-tag-version": "13.2.1",
5425
- "knip": "6.39.0",
5426
- "lefthook": "2.1.16",
5427
- "modern-web-guidance": "0.0.191",
5428
- "oxfmt": "0.71.0",
5429
- "oxlint": "1.86.0",
5439
+ "knip": "6.41.0",
5440
+ "lefthook": "2.2.1",
5441
+ "modern-web-guidance": "0.0.193",
5442
+ "oxfmt": "0.72.0",
5443
+ "oxlint": "1.87.0",
5430
5444
  "oxlint-tsgolint": "7.0.2003",
5431
5445
  "prettier": "3.9.9",
5432
- "release-it": "21.1.0",
5446
+ "release-it": "21.1.1",
5433
5447
  "tsdown": "0.23.0",
5434
5448
  "typescript": "6.0.3",
5435
5449
  "vitest": "5.0.3",
5436
- "pnpm": "11.28.3"
5450
+ "pnpm": "11.28.5"
5437
5451
  }["prettier"];
5438
5452
  addReleaseDeps(devDeps, ctx.config);
5439
5453
  return devDeps;
@@ -5442,7 +5456,7 @@ async function generatePackageJson(ctx) {
5442
5456
  const filePath = "package.json";
5443
5457
  const existing = ctx.read(filePath);
5444
5458
  const allScripts = buildManagedScripts(ctx);
5445
- const unmanaged = readUnmanagedDevDeps(ctx);
5459
+ const unmanaged = readUnmanagedDevDepNames(ctx);
5446
5460
  const devDeps = buildManagedDevDeps(ctx);
5447
5461
  if (existing) {
5448
5462
  const pkg = parsePackageJson(existing);
@@ -5492,18 +5506,18 @@ async function generatePackageJson(ctx) {
5492
5506
  "@types/node": "24.19.1",
5493
5507
  "@vitest/coverage-v8": "5.0.3",
5494
5508
  "commit-and-tag-version": "13.2.1",
5495
- "knip": "6.39.0",
5496
- "lefthook": "2.1.16",
5497
- "modern-web-guidance": "0.0.191",
5498
- "oxfmt": "0.71.0",
5499
- "oxlint": "1.86.0",
5509
+ "knip": "6.41.0",
5510
+ "lefthook": "2.2.1",
5511
+ "modern-web-guidance": "0.0.193",
5512
+ "oxfmt": "0.72.0",
5513
+ "oxlint": "1.87.0",
5500
5514
  "oxlint-tsgolint": "7.0.2003",
5501
5515
  "prettier": "3.9.9",
5502
- "release-it": "21.1.0",
5516
+ "release-it": "21.1.1",
5503
5517
  "tsdown": "0.23.0",
5504
5518
  "typescript": "6.0.3",
5505
5519
  "vitest": "5.0.3",
5506
- "pnpm": "11.28.3"
5520
+ "pnpm": "11.28.5"
5507
5521
  }["pnpm"]}`
5508
5522
  };
5509
5523
  ctx.write(filePath, JSON.stringify(pkg, null, 2) + "\n");
@@ -7828,19 +7842,25 @@ function foreignOwnedBinaries(ctx) {
7828
7842
  function ignoreBinariesField(binaries) {
7829
7843
  return binaries.length > 0 ? `\n ignoreBinaries: ${JSON.stringify(binaries)},` : "";
7830
7844
  }
7831
- function buildKnipConfigSingle(ignoreBinaries) {
7845
+ function ignorableDependencies(ctx) {
7846
+ return wantsAgentCli(ctx) ? [AGENT_CLI_PACKAGE] : [];
7847
+ }
7848
+ function ignoreDependenciesField(dependencies) {
7849
+ return dependencies.length > 0 ? `\n ignoreDependencies: ${JSON.stringify(dependencies)},` : "";
7850
+ }
7851
+ function buildKnipConfigSingle(ignoreBinaries, ignoreDependencies) {
7832
7852
  return `import type { KnipConfig } from "knip";
7833
7853
 
7834
7854
  export default {
7835
7855
  entry: ["src/index.ts"],
7836
- project: ["src/**/*.ts"],${ignoreBinariesField(ignoreBinaries)}
7856
+ project: ["src/**/*.ts"],${ignoreBinariesField(ignoreBinaries)}${ignoreDependenciesField(ignoreDependencies)}
7837
7857
  } satisfies KnipConfig;
7838
7858
  `;
7839
7859
  }
7840
- function buildKnipConfigMonorepo(ignoreBinaries) {
7860
+ function buildKnipConfigMonorepo(ignoreBinaries, ignoreDependencies) {
7841
7861
  return `import type { KnipConfig } from "knip";
7842
7862
 
7843
- export default {${ignoreBinariesField(ignoreBinaries)}
7863
+ export default {${ignoreBinariesField(ignoreBinaries)}${ignoreDependenciesField(ignoreDependencies)}
7844
7864
  workspaces: {
7845
7865
  ".": {
7846
7866
  entry: [],
@@ -7901,16 +7921,17 @@ function hookIsInProjectScope(content, path) {
7901
7921
  if (patterns === void 0) return true;
7902
7922
  return patterns.some((pattern) => picomatch(pattern, { dot: true })(HOOK_FILE));
7903
7923
  }
7904
- function ensureIgnorePattern(content, path, ignoreBinaries) {
7924
+ function ensureIgnorePattern(content, path, ignoreBinaries, ignoreDependencies) {
7905
7925
  const needsHooksIgnore = hookIsInProjectScope(content, path);
7906
- if (isJsonConfig(path)) return ensureIgnoreInJson(content, ignoreBinaries, needsHooksIgnore);
7907
- return ensureIgnoreInTs(content, ignoreBinaries, needsHooksIgnore);
7926
+ if (isJsonConfig(path)) return ensureIgnoreInJson(content, ignoreBinaries, ignoreDependencies, needsHooksIgnore);
7927
+ return ensureIgnoreInTs(content, ignoreBinaries, ignoreDependencies, needsHooksIgnore);
7908
7928
  }
7909
7929
  const KnipJsonSchema = z.object({
7910
7930
  ignore: z.array(z.string()).optional(),
7911
- ignoreBinaries: z.array(z.string()).optional()
7931
+ ignoreBinaries: z.array(z.string()).optional(),
7932
+ ignoreDependencies: z.array(z.string()).optional()
7912
7933
  }).passthrough();
7913
- function ensureIgnoreInJson(content, ignoreBinaries, needsHooksIgnore) {
7934
+ function ensureIgnoreInJson(content, ignoreBinaries, ignoreDependencies, needsHooksIgnore) {
7914
7935
  let parsedJson;
7915
7936
  try {
7916
7937
  parsedJson = JSON.parse(content);
@@ -7931,6 +7952,12 @@ function ensureIgnoreInJson(content, ignoreBinaries, needsHooksIgnore) {
7931
7952
  parsed.ignoreBinaries = [...parsed.ignoreBinaries ?? [], ...missingBinaries];
7932
7953
  changed = true;
7933
7954
  }
7955
+ const existingDependencies = new Set(parsed.ignoreDependencies ?? []);
7956
+ const missingDependencies = ignoreDependencies.filter((dependency) => !existingDependencies.has(dependency));
7957
+ if (missingDependencies.length > 0) {
7958
+ parsed.ignoreDependencies = [...parsed.ignoreDependencies ?? [], ...missingDependencies];
7959
+ changed = true;
7960
+ }
7934
7961
  if (!changed) return void 0;
7935
7962
  return JSON.stringify(parsed, null, 2) + "\n";
7936
7963
  }
@@ -7940,7 +7967,30 @@ function missingIgnoreBinariesInTs(content, binaries) {
7940
7967
  const arrayContent = match[1] ?? "";
7941
7968
  return binaries.filter((binary) => !arrayContent.includes(`"${binary}"`));
7942
7969
  }
7943
- function ensureIgnoreInTs(content, ignoreBinaries, needsHooksIgnore) {
7970
+ function missingIgnoreDependenciesInTs(content, dependencies) {
7971
+ const match = /ignoreDependencies:\s*\[([^\]]*)\]/.exec(content);
7972
+ if (!match) return dependencies;
7973
+ const arrayContent = match[1] ?? "";
7974
+ return dependencies.filter((dependency) => !arrayContent.includes(`"${dependency}"`));
7975
+ }
7976
+ function insertIntoTsArray(content, field, entries) {
7977
+ const quoted = entries.map((entry) => `"${entry}"`).join(", ");
7978
+ const arrayPattern = new RegExp("(" + field + ":\\s*\\[)");
7979
+ if (arrayPattern.test(content)) return {
7980
+ content: content.replace(arrayPattern, `$1${quoted}, `),
7981
+ changed: true
7982
+ };
7983
+ const exportPattern = /(export default\s*\{)\n/;
7984
+ if (exportPattern.test(content)) return {
7985
+ content: content.replace(exportPattern, `$1\n ${field}: [${quoted}],\n`),
7986
+ changed: true
7987
+ };
7988
+ return {
7989
+ content,
7990
+ changed: false
7991
+ };
7992
+ }
7993
+ function ensureIgnoreInTs(content, ignoreBinaries, ignoreDependencies, needsHooksIgnore) {
7944
7994
  let result = content;
7945
7995
  let changed = false;
7946
7996
  if (needsHooksIgnore && !result.includes(".claude/hooks")) {
@@ -7958,18 +8008,15 @@ function ensureIgnoreInTs(content, ignoreBinaries, needsHooksIgnore) {
7958
8008
  }
7959
8009
  const missingBinaries = missingIgnoreBinariesInTs(result, ignoreBinaries);
7960
8010
  if (missingBinaries.length > 0) {
7961
- const quoted = missingBinaries.map((binary) => `"${binary}"`).join(", ");
7962
- const binariesArrayPattern = /(ignoreBinaries:\s*\[)/;
7963
- if (binariesArrayPattern.test(result)) {
7964
- result = result.replace(binariesArrayPattern, `$1${quoted}, `);
7965
- changed = true;
7966
- } else {
7967
- const exportPattern = /(export default\s*\{)\n/;
7968
- if (exportPattern.test(result)) {
7969
- result = result.replace(exportPattern, `$1\n ignoreBinaries: [${quoted}],\n`);
7970
- changed = true;
7971
- }
7972
- }
8011
+ const inserted = insertIntoTsArray(result, "ignoreBinaries", missingBinaries);
8012
+ result = inserted.content;
8013
+ changed = changed || inserted.changed;
8014
+ }
8015
+ const missingDependencies = missingIgnoreDependenciesInTs(result, ignoreDependencies);
8016
+ if (missingDependencies.length > 0) {
8017
+ const inserted = insertIntoTsArray(result, "ignoreDependencies", missingDependencies);
8018
+ result = inserted.content;
8019
+ changed = changed || inserted.changed;
7973
8020
  }
7974
8021
  return changed ? result : void 0;
7975
8022
  }
@@ -7985,6 +8032,7 @@ async function generateKnip(ctx) {
7985
8032
  };
7986
8033
  const isMonorepo = ctx.config.structure === "monorepo";
7987
8034
  const ignoreBinaries = foreignOwnedBinaries(ctx);
8035
+ const ignoreDependencies = ignorableDependencies(ctx);
7988
8036
  const buildConfig = isMonorepo ? buildKnipConfigMonorepo : buildKnipConfigSingle;
7989
8037
  const existingPath = KNIP_CONFIG_PATHS.find((p) => ctx.exists(p));
7990
8038
  if (existingPath) {
@@ -7995,7 +8043,7 @@ async function generateKnip(ctx) {
7995
8043
  description: "Could not read existing knip config"
7996
8044
  };
7997
8045
  if (content.trim() === "") {
7998
- const blank = isJsonConfig(existingPath) ? ensureIgnorePattern("{}", existingPath, ignoreBinaries) ?? "{}\n" : formattedTemplate(ctx, existingPath, buildConfig(ignoreBinaries));
8046
+ const blank = isJsonConfig(existingPath) ? ensureIgnorePattern("{}", existingPath, ignoreBinaries, ignoreDependencies) ?? "{}\n" : formattedTemplate(ctx, existingPath, buildConfig(ignoreBinaries, ignoreDependencies));
7999
8047
  ctx.write(existingPath, blank);
8000
8048
  return {
8001
8049
  filePath: existingPath,
@@ -8003,13 +8051,13 @@ async function generateKnip(ctx) {
8003
8051
  description: createdDescription(existingPath)
8004
8052
  };
8005
8053
  }
8006
- const updated = ensureIgnorePattern(content, existingPath, ignoreBinaries);
8054
+ const updated = ensureIgnorePattern(content, existingPath, ignoreBinaries, ignoreDependencies);
8007
8055
  if (updated) {
8008
8056
  ctx.write(existingPath, updated);
8009
8057
  return {
8010
8058
  filePath: existingPath,
8011
8059
  action: "updated",
8012
- description: "Added \".claude/hooks/**\" and/or foreign-owned ignoreBinaries"
8060
+ description: "Added \".claude/hooks/**\", foreign-owned ignoreBinaries, and/or ignored dependencies"
8013
8061
  };
8014
8062
  }
8015
8063
  return {
@@ -8018,7 +8066,7 @@ async function generateKnip(ctx) {
8018
8066
  description: existingPath === filePath ? "Already configured" : `Existing config found at ${existingPath}`
8019
8067
  };
8020
8068
  }
8021
- ctx.write(filePath, formattedTemplate(ctx, filePath, buildConfig(ignoreBinaries)));
8069
+ ctx.write(filePath, formattedTemplate(ctx, filePath, buildConfig(ignoreBinaries, ignoreDependencies)));
8022
8070
  return {
8023
8071
  filePath,
8024
8072
  action: "created",
@@ -9136,7 +9184,7 @@ const COMMENTS_GUIDE_BODY = [
9136
9184
  "",
9137
9185
  "- Default to none. A comment earns its place by carrying what the code cannot -- a rejected alternative, the bug that forced this shape, an invariant enforced elsewhere -- and earns it one decision at a time, in a sentence or two each: what was rejected, and what it cost. Five decisions is five sentences, not five paragraphs. Recoverable from the code? Leave it unwritten.",
9138
9186
  "- Present tense, about what is. No `now`, `previously`, `changed to`: diff narration is stale on the next edit, and the commit message carries it. No running ledgers or changelogs: when adjusting a threshold, constant, or baseline, state the active invariant and prune past deltas.",
9139
- "- Never cite a lifecycle-managed doc, PR, or issue from code: no PR or issue numbers (`PR #123`, `closes #456`), no path under `docs/superpowers/` or `docs/design/`, none carrying a `retired/` or `completed/` segment, no `docs/plan-*.md`, and no bare decision or task ID (`D3`, `T2`). They move, retire, and renumber, nothing checks the reference, and the reader has no way to tell a dead one from a live one. State the constraint itself, or cite a maintained reference doc instead. A `docs/` path that is a glob or an elided example rather than a reference (`docs/*.md`) is vocabulary, not a citation, and is fine.",
9187
+ "- Never cite a lifecycle-managed doc, PR, or issue from code: no PR or issue numbers (`PR #123`, `closes #456`), no path under `docs/specs/`, `docs/plans/`, `docs/superpowers/` or `docs/design/`, none carrying a `retired/` or `completed/` segment, no `docs/plan-*.md`, and no bare decision or task ID (`D3`, `T2`). They move, retire, and renumber, nothing checks the reference, and the reader has no way to tell a dead one from a live one. State the constraint itself, or cite a maintained reference doc instead. A `docs/` path that is a glob or an elided example rather than a reference (`docs/*.md`) is vocabulary, not a citation, and is fine.",
9140
9188
  "- Code that exists only to accept an older shape -- a file, field, flag, environment variable, label or comment format -- carries `// @compat <id> until <expiry>: <reason>` on the line above it. The expiry is a date (`YYYY-MM-DD`) when the old shape lives with consumers who upgrade on their own schedule; a version of the code's own package (`v1.4.0`) when its consumer upgrades with each release; or `forge-clean` for old data on the forge, which has neither. Remove the code and every test that carries the same id together.",
9141
9189
  "- **The code is the fact.** When a comment disagrees with it, fix it in one line or delete it -- never grow one to argue the code is right."
9142
9190
  ].join("\n");
@@ -9198,7 +9246,7 @@ const PLAN_LIFECYCLE_GUIDE_BODY = [
9198
9246
  "",
9199
9247
  "A plan's **directory** is the source of truth for whether it shipped -- not its `Status:` line, not its checkboxes. Read the source it names.",
9200
9248
  "",
9201
- "`.superpowers/` (no `docs/` prefix) is scratch state, not a plan's home -- a plan doc always lives under `docs/superpowers/plans/`, even one drafted while running a plan through subagents.",
9249
+ "`.superpowers/` (no `docs/` prefix) is scratch state, not a plan's home -- a plan doc lives under `docs/plans/` (older ones under `docs/superpowers/plans/`), even one drafted while running a plan through subagents.",
9202
9250
  "",
9203
9251
  "Retiring the plan is the last task of the plan, done in the PR that delivers the work: \"in the tree\" means committed on this branch, not merged to a default branch. Run `bst docs:retire <plan.md> --status <verb> --shipped \"<text>\"` -- it moves the doc, relinks what the move breaks, de-links paths pointing outside `docs/`, and writes the dated terminal `Status:` line, the historical banner, and the \"Shipped:\" paragraph. You supply the judgement: the honest verb -- `completed` / `superseded` / `abandoned` / `rejected` -- and a truthful `--shipped` naming the delivering PR; never fabricate it for uncommitted code or claim a merge that has not happened. A plan that lands one phase of a larger spec retires the plan and leaves the spec open.",
9204
9252
  "",
@@ -9215,11 +9263,11 @@ const PLANNING_GUIDE_BODY = [
9215
9263
  "",
9216
9264
  "## The spec",
9217
9265
  "",
9218
- "Say why and what, not how: the problem, who it is for, the constraints, what done looks like, and the alternatives you rejected, each with its reason. Work with several independent parts gets one spec per part. Save it as `docs/superpowers/specs/<YYYY-MM-DD>-<topic>-design.md`.",
9266
+ "Say why and what, not how: the problem, who it is for, the constraints, what done looks like, and the alternatives you rejected, each with its reason. Work with several independent parts gets one spec per part. Save it as `docs/specs/<YYYY-MM-DD>-<topic>-design.md`.",
9219
9267
  "",
9220
9268
  "## The plan",
9221
9269
  "",
9222
- "Write for an executor who has not seen this repo or the spec. They write idiomatic code once they know the exact interface and the exact test, and make a reasonable choice wherever the plan leaves one open; what they cannot know is what you decided. A plan records those decisions -- it is not a transcript of the code. Save it under `docs/superpowers/plans/`.",
9270
+ "Write for an executor who has not seen this repo or the spec. They write idiomatic code once they know the exact interface and the exact test, and make a reasonable choice wherever the plan leaves one open; what they cannot know is what you decided. A plan records those decisions -- it is not a transcript of the code. Save it under `docs/plans/`.",
9223
9271
  "",
9224
9272
  "- Check the plan's premises against the tree before building on them. \"Today X is broken\" is a claim: read the code that handles X, not just the line that throws.",
9225
9273
  "- Label each task `Task <n>:` at the start of its heading or checkbox, numbered from 1, so `bst plan:brief` can find it. A checkbox's first sentence is the task's name on the control panel: end it with a period inside one short line, and put the files, commands, and checks in the sentences after it.",
@@ -9288,18 +9336,18 @@ function webUiGuideBody() {
9288
9336
  "@types/node": "24.19.1",
9289
9337
  "@vitest/coverage-v8": "5.0.3",
9290
9338
  "commit-and-tag-version": "13.2.1",
9291
- "knip": "6.39.0",
9292
- "lefthook": "2.1.16",
9293
- "modern-web-guidance": "0.0.191",
9294
- "oxfmt": "0.71.0",
9295
- "oxlint": "1.86.0",
9339
+ "knip": "6.41.0",
9340
+ "lefthook": "2.2.1",
9341
+ "modern-web-guidance": "0.0.193",
9342
+ "oxfmt": "0.72.0",
9343
+ "oxlint": "1.87.0",
9296
9344
  "oxlint-tsgolint": "7.0.2003",
9297
9345
  "prettier": "3.9.9",
9298
- "release-it": "21.1.0",
9346
+ "release-it": "21.1.1",
9299
9347
  "tsdown": "0.23.0",
9300
9348
  "typescript": "6.0.3",
9301
9349
  "vitest": "5.0.3",
9302
- "pnpm": "11.28.3"
9350
+ "pnpm": "11.28.5"
9303
9351
  }["modern-web-guidance"]}`;
9304
9352
  return [
9305
9353
  "# Web UI",
@@ -9859,7 +9907,7 @@ function reportFailures(failed, command) {
9859
9907
  function generateMigratePrompt(results, config, detected) {
9860
9908
  const sections = [];
9861
9909
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
9862
- sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.89.1 repo:sync\` on ${timestamp}_\n`);
9910
+ sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.90.0 repo:sync\` on ${timestamp}_\n`);
9863
9911
  const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
9864
9912
  if (changed.length > 0) {
9865
9913
  const items = changed.map((r) => {
@@ -10880,7 +10928,7 @@ function originHeadBranch(git, cwd) {
10880
10928
  return name === "" ? void 0 : name;
10881
10929
  }
10882
10930
  function unconfirmedDefaultWarning(branch, namedByTree) {
10883
- return `health: could not confirm the default branch, so the reference was read against origin/${branch}, while .tooling.json#defaultBranch names ${namedByTree}. Record it in this clone with \`git remote set-head origin ${namedByTree}\`, or run in GitHub or Forgejo Actions, where the event payload names it.`;
10931
+ return `health: could not confirm the default branch, so the reference was read against origin/${branch}, while .tooling.json#defaultBranch names ${namedByTree}. If ${namedByTree} is the repository's default branch, record it in this clone with \`git remote set-head origin --auto\`, or run in GitHub or Forgejo Actions, where the event payload names it.`;
10884
10932
  }
10885
10933
  function gitRunner(executor) {
10886
10934
  return (args, cwd) => {
@@ -11962,10 +12010,10 @@ function runComposeLintGate(targetDir, config, memo) {
11962
12010
  for (const warning of findDockerRebuildRisks(targetDir, config, memo)) log$1.warn(warning);
11963
12011
  return issues.length;
11964
12012
  }
11965
- function runCheckOnlyImageGate(config, targetDir, memo) {
12013
+ function runCheckOnlyImageGate(config, targetDir, memo, phase) {
11966
12014
  if (!config.publishDocker) return [];
11967
12015
  const images = [];
11968
- for (const risk of findCheckOnlyPublishRisks(targetDir, void 0, memo)) {
12016
+ for (const risk of findCheckOnlyPublishRisks(targetDir, phase, memo)) {
11969
12017
  log$1.warn(`Image "${risk.imageName}" is declared only in ${risk.declaredIn}, the check overlay, so a later tooling release stops publishing it with docker:publish by default. Keep it: set publish to true for "${risk.imageName}" under .tooling.json#docker. Settle it: set publish to false there, or move the service into a base compose file.`);
11970
12018
  images.push(risk.imageName);
11971
12019
  }
@@ -11979,6 +12027,18 @@ function runWorkflowAptGate(targetDir) {
11979
12027
  }
11980
12028
  return steps;
11981
12029
  }
12030
+ function findInertAgentKeys(saved) {
12031
+ const agent = saved?.agent;
12032
+ if (!agent) return [];
12033
+ if (agent.enabled === true) return [];
12034
+ return Object.keys(agent).filter((key) => key !== "enabled").toSorted();
12035
+ }
12036
+ function runInertAgentSettingsGate(saved) {
12037
+ const keys = findInertAgentKeys(saved);
12038
+ if (keys.length === 0) return [];
12039
+ log$1.warn(`.tooling.json sets ${keys.map((key) => `agent.${key}`).join(", ")}, but agent.enabled is not true, so the agent ignores them. Set "agent.enabled": true, or remove those keys.`);
12040
+ return keys;
12041
+ }
11982
12042
  function runPublishConfigGate(config, targetDir) {
11983
12043
  if (!config.publishNpm) return 0;
11984
12044
  const issues = lintPublishConfig(targetDir, config.structure);
@@ -12204,7 +12264,7 @@ const syncCommand = defineCommand({
12204
12264
  },
12205
12265
  json: {
12206
12266
  type: "boolean",
12207
- description: "Emit one JSON document: status (up-to-date | drift | workflow-drift | failed), actionable, failed, toolchain, checkOnlyImages, aptSteps, warnCount, exitCode. --check only: the write mode prompts and inherits child streams."
12267
+ description: "Emit one JSON document: status (up-to-date | drift | workflow-drift | failed), actionable, failed, toolchain, checkOnlyImages, aptSteps, inertAgentKeys, warnCount, exitCode. --check only: the write mode prompts and inherits child streams."
12208
12268
  }
12209
12269
  },
12210
12270
  async run({ args }) {
@@ -12252,6 +12312,7 @@ async function runUpdate(targetDir, flags = {}) {
12252
12312
  }
12253
12313
  warnStaleChecksAdd(saved);
12254
12314
  warnCoveredChecksAdd(saved, targetDir);
12315
+ runInertAgentSettingsGate(saved);
12255
12316
  if (runComposeLintGate(targetDir, saved, dockerDiscovery) > 0) {
12256
12317
  process.exitCode = 1;
12257
12318
  return [];
@@ -12277,12 +12338,17 @@ function gateFailure(toolchainWarned = false) {
12277
12338
  toolchain: { warned: toolchainWarned },
12278
12339
  checkOnlyImages: [],
12279
12340
  aptSteps: [],
12341
+ inertAgentKeys: [],
12280
12342
  warnCount: 0,
12281
12343
  exitCode: 1
12282
12344
  };
12283
12345
  }
12284
- function upToDateReport(toolchain, checkOnlyImages, aptSteps) {
12285
- const warnCount = (toolchain.warned ? 1 : 0) + checkOnlyImages.length + aptSteps.length;
12346
+ function advisoryWarnCount(advisories) {
12347
+ return (advisories.toolchainWarned ? 1 : 0) + advisories.checkOnlyImages.length + advisories.aptSteps.length + (advisories.inertAgentKeys.length > 0 ? 1 : 0);
12348
+ }
12349
+ function upToDateReport(advisories) {
12350
+ const toolchain = { warned: advisories.toolchainWarned };
12351
+ const warnCount = advisoryWarnCount(advisories);
12286
12352
  if (warnCount > 0) {
12287
12353
  if (!isJsonMode()) rawLog$1(formatWarnMarker(warnCount));
12288
12354
  } else log$1.success("Repository is up to date.");
@@ -12291,8 +12357,9 @@ function upToDateReport(toolchain, checkOnlyImages, aptSteps) {
12291
12357
  actionable: [],
12292
12358
  failed: [],
12293
12359
  toolchain,
12294
- checkOnlyImages,
12295
- aptSteps,
12360
+ checkOnlyImages: advisories.checkOnlyImages,
12361
+ aptSteps: advisories.aptSteps,
12362
+ inertAgentKeys: advisories.inertAgentKeys,
12296
12363
  warnCount,
12297
12364
  exitCode: 0
12298
12365
  };
@@ -12315,17 +12382,21 @@ async function runCheckReport(targetDir) {
12315
12382
  ...gateFailure(),
12316
12383
  toolchain: { warned: toolchain.warned }
12317
12384
  };
12318
- const checkOnlyImages = runCheckOnlyImageGate(config, targetDir, dockerDiscovery);
12319
- const aptSteps = runWorkflowAptGate(targetDir);
12385
+ const advisories = {
12386
+ toolchainWarned: toolchain.warned,
12387
+ checkOnlyImages: runCheckOnlyImageGate(config, targetDir, dockerDiscovery),
12388
+ aptSteps: runWorkflowAptGate(targetDir),
12389
+ inertAgentKeys: runInertAgentSettingsGate(saved)
12390
+ };
12320
12391
  const { ctx, pendingWrites } = createDryRunContext(config, dockerDiscovery);
12321
12392
  logDetectionSummary(ctx);
12322
12393
  const results = await runGenerators(ctx);
12323
12394
  const failed = failedResults(results);
12324
12395
  reportFailures(failed, "repo:sync");
12325
12396
  const actionable = actionableResults(results, pendingWrites, targetDir);
12326
- const advisoryWarnCount = (toolchain.warned ? 1 : 0) + checkOnlyImages.length + aptSteps.length;
12327
- const toolchainState = { warned: toolchain.warned };
12328
- if (actionable.length === 0 && failed.length === 0) return upToDateReport(toolchainState, checkOnlyImages, aptSteps);
12397
+ const advisoryCount = advisoryWarnCount(advisories);
12398
+ const toolchainState = { warned: advisories.toolchainWarned };
12399
+ if (actionable.length === 0 && failed.length === 0) return upToDateReport(advisories);
12329
12400
  if (actionable.length === 0) return {
12330
12401
  status: "failed",
12331
12402
  actionable: [],
@@ -12333,21 +12404,23 @@ async function runCheckReport(targetDir) {
12333
12404
  toolchain: toolchainState,
12334
12405
  checkOnlyImages: [],
12335
12406
  aptSteps: [],
12407
+ inertAgentKeys: [],
12336
12408
  warnCount: 0,
12337
12409
  exitCode: 1
12338
12410
  };
12339
12411
  log$1.warn(`${actionable.length} file(s) would be changed by repo:sync`);
12340
12412
  for (const r of actionable) logActionableChange(r, targetDir, pendingWrites);
12341
12413
  if (failed.length === 0 && actionable.every((r) => isWorkflowDirPath(r.filePath))) {
12342
- const warnCount = actionable.length + advisoryWarnCount;
12414
+ const warnCount = actionable.length + advisoryCount;
12343
12415
  if (!isJsonMode()) rawLog$1(formatWarnMarker(warnCount));
12344
12416
  return {
12345
12417
  status: "workflow-drift",
12346
12418
  actionable,
12347
12419
  failed: [],
12348
12420
  toolchain: toolchainState,
12349
- checkOnlyImages,
12350
- aptSteps,
12421
+ checkOnlyImages: advisories.checkOnlyImages,
12422
+ aptSteps: advisories.aptSteps,
12423
+ inertAgentKeys: advisories.inertAgentKeys,
12351
12424
  warnCount,
12352
12425
  exitCode: 0
12353
12426
  };
@@ -12359,6 +12432,7 @@ async function runCheckReport(targetDir) {
12359
12432
  toolchain: toolchainState,
12360
12433
  checkOnlyImages: [],
12361
12434
  aptSteps: [],
12435
+ inertAgentKeys: [],
12362
12436
  warnCount: 0,
12363
12437
  exitCode: 1
12364
12438
  };
@@ -15251,7 +15325,7 @@ function retireDestination(src, verb) {
15251
15325
  if (dir === DOCS_ROOT && LEGACY_ACTIVE_PATTERN.test(base)) return `${DOCS_ROOT}/${RETIRED_DIR}/${verb}-${base.slice(5)}`;
15252
15326
  return `${dir}/${RETIRED_DIR}/${base}`;
15253
15327
  }
15254
- function discoverPairs(exec, config) {
15328
+ function candidatePairs(exec, config) {
15255
15329
  const candidates = [];
15256
15330
  for (const active of [...conventionalActiveDirs(exec), ...config.extraDirs]) candidates.push({
15257
15331
  active,
@@ -15266,8 +15340,16 @@ function discoverPairs(exec, config) {
15266
15340
  activePattern: LEGACY_ACTIVE_PATTERN,
15267
15341
  enforced: isEnforced(DOCS_ROOT, config.enforced)
15268
15342
  });
15343
+ return candidates;
15344
+ }
15345
+ function matchableActiveDirs(exec, config) {
15269
15346
  const seen = /* @__PURE__ */ new Set();
15270
- return candidates.filter((pair) => {
15347
+ for (const pair of candidatePairs(exec, config)) if (exec.readDir(pair.active) !== void 0) seen.add(pair.active);
15348
+ return [...seen].toSorted();
15349
+ }
15350
+ function discoverPairs(exec, config) {
15351
+ const seen = /* @__PURE__ */ new Set();
15352
+ return candidatePairs(exec, config).filter((pair) => {
15271
15353
  if (seen.has(pair.active)) return false;
15272
15354
  if (!(listDocs(exec, pair.active, pair.activePattern).length > 0 || pair.retiredDirs.some((dir) => listDocs(exec, dir).length > 0))) return false;
15273
15355
  seen.add(pair.active);
@@ -20115,7 +20197,7 @@ function composePs(executor, config) {
20115
20197
  //#region src/docker-check/health.ts
20116
20198
  const DOCKER_RUN_START_FAILURE_EXIT_CODE = 125;
20117
20199
  const PROBE_TIMEOUT_SECONDS = "5";
20118
- const PROBE_IMAGE = "curlimages/curl:8.21.0@sha256:7c12af72ceb38b7432ab85e1a265cff6ae58e06f95539d539b654f2cfa64bb13";
20200
+ const PROBE_IMAGE = "curlimages/curl:8.22.0@sha256:58adaa4e8dca9c988bae2aba4ab3434a0bb2da16bbe3f92dec39ec7785166777";
20119
20201
  function getContainerHealth(containers, serviceName) {
20120
20202
  return containers.find((c) => c.Service === serviceName)?.Health ?? "unknown";
20121
20203
  }
@@ -24093,7 +24175,7 @@ function collectViolations(exec, config, opts) {
24093
24175
  retiredCount += result.retired;
24094
24176
  }
24095
24177
  violations.push(...collectSpecRetirementViolations(exec, pairs, config, opts));
24096
- const activeDirs = pairs.map((pair) => pair.active).toSorted();
24178
+ const activeDirs = matchableActiveDirs(exec, config);
24097
24179
  const unmatchedEnforced = Array.isArray(config.enforced) ? config.enforced.filter((entry) => !activeDirs.includes(entry)) : [];
24098
24180
  return {
24099
24181
  violations,
@@ -24236,7 +24318,7 @@ const docsCheckCommand = defineCommand({
24236
24318
  },
24237
24319
  json: {
24238
24320
  type: "boolean",
24239
- description: "Emit one JSON document: violations ([{file, rule, message, fix, severity (fatal | advisory)}]), activeCount, retiredCount, pairCount, pass, activeDirs, unmatchedEnforced. When docs are disabled in .tooling.json the document is only enabled (false)."
24321
+ description: "Emit one JSON document: violations ([{file, rule, message, fix, severity (fatal | advisory)}]), activeCount, retiredCount, pairCount, pass, activeDirs, unmatchedEnforced. activeDirs lists every existing doc directory an enforced entry can match, holding docs or not; unmatchedEnforced the .tooling.json#docs.enforced entries that match none of them. When docs are disabled in .tooling.json the document is only enabled (false)."
24240
24322
  },
24241
24323
  verbose: {
24242
24324
  type: "boolean",
@@ -25871,6 +25953,9 @@ var EmptyCommentError = class extends FatalError {
25871
25953
  super("The comment resolved to empty text.");
25872
25954
  }
25873
25955
  };
25956
+ function rejectEmptyComment(comment) {
25957
+ if (comment !== void 0 && comment.trim() === "") throw new EmptyCommentError();
25958
+ }
25874
25959
  var PullRequestStateError = class extends FatalError {
25875
25960
  number;
25876
25961
  action;
@@ -25880,20 +25965,35 @@ var PullRequestStateError = class extends FatalError {
25880
25965
  this.action = action;
25881
25966
  }
25882
25967
  };
25968
+ var NotAPullRequestError = class extends FatalError {
25969
+ number;
25970
+ constructor(number) {
25971
+ super(`#${String(number)} is an issue, not a pull request.`);
25972
+ this.number = number;
25973
+ }
25974
+ };
25975
+ var StateNotTakenError = class extends FatalError {
25976
+ number;
25977
+ expected;
25978
+ actual;
25979
+ constructor(number, expected, actual) {
25980
+ super(`The forge did not change #${String(number)}: it is still ${actual}, not ${expected}.`);
25981
+ this.number = number;
25982
+ this.expected = expected;
25983
+ this.actual = actual;
25984
+ }
25985
+ };
25883
25986
  const TARGET_STATE = {
25884
25987
  close: "closed",
25885
25988
  reopen: "open"
25886
25989
  };
25887
- async function setIssueState(resolve, request) {
25888
- const ref = parseIssueRef(request.ref);
25889
- const number = requireIssueNumber(ref);
25890
- if (request.comment !== void 0 && request.comment.trim() === "") throw new EmptyCommentError();
25891
- const commentGiven = request.comment !== void 0;
25892
- const { api } = resolve(resolveIssueTarget(ref.target, request.repo));
25990
+ async function changeState(api, number, request, subject) {
25893
25991
  const before = await api.get(number);
25894
- if (before.isPullRequest) throw new PullRequestStateError(number, request.action);
25992
+ if (subject === "issue" && before.isPullRequest) throw new PullRequestStateError(number, request.action);
25993
+ if (subject === "pull-request" && !before.isPullRequest) throw new NotAPullRequestError(number);
25994
+ const commentGiven = request.comment !== void 0;
25895
25995
  const targetState = TARGET_STATE[request.action];
25896
- const buildResult = (url, current, changed, posted) => ({
25996
+ const buildResult = (url, current, changed, posted, merged) => ({
25897
25997
  number,
25898
25998
  url,
25899
25999
  action: request.action,
@@ -25902,20 +26002,32 @@ async function setIssueState(resolve, request) {
25902
26002
  current
25903
26003
  },
25904
26004
  changed,
25905
- ...commentGiven ? { comment: { posted } } : {}
26005
+ ...commentGiven ? { comment: { posted } } : {},
26006
+ ...merged === void 0 ? {} : { merged }
25906
26007
  });
25907
- if (before.state === targetState) return buildResult(before.url, before.state, false, false);
26008
+ if (before.state === targetState) {
26009
+ const merged = subject === "pull-request" ? (await api.pullRequest(number)).merged : void 0;
26010
+ return buildResult(before.url, before.state, false, false, merged);
26011
+ }
25908
26012
  if (request.dryRun) return buildResult(before.url, targetState, true, true);
25909
26013
  if (commentGiven) await api.addComment(number, request.comment ?? "");
25910
26014
  let after;
25911
26015
  try {
25912
26016
  after = await api.setState(number, targetState);
26017
+ if (after.state !== targetState) throw new StateNotTakenError(number, targetState, after.state);
25913
26018
  } catch (error) {
25914
26019
  if (commentGiven && error instanceof Error) error.message = `The comment on #${String(number)} was already posted; a retry will duplicate it. ` + error.message;
25915
26020
  throw error;
25916
26021
  }
25917
26022
  return buildResult(after.url, after.state, true, true);
25918
26023
  }
26024
+ async function setIssueState(resolve, request) {
26025
+ const ref = parseIssueRef(request.ref);
26026
+ const number = requireIssueNumber(ref);
26027
+ rejectEmptyComment(request.comment);
26028
+ const { api } = resolve(resolveIssueTarget(ref.target, request.repo));
26029
+ return changeState(api, number, request, "issue");
26030
+ }
25919
26031
  const PAST_TENSE = {
25920
26032
  close: "Closed",
25921
26033
  reopen: "Reopened"
@@ -26808,7 +26920,7 @@ async function runCli(config) {
26808
26920
  //#region src/cli-main.ts
26809
26921
  setLogLevel(resolveLogLevel({}));
26810
26922
  setForgejoLogger(log$1);
26811
- const displayVersion = "0.89.1";
26923
+ const displayVersion = "0.90.0";
26812
26924
  const bstSubCommands = {
26813
26925
  "repo:sync": syncCommand,
26814
26926
  "checks:run": runChecksCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.89.1",
3
+ "version": "0.90.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -52,10 +52,10 @@
52
52
  "@types/node": "24.19.1",
53
53
  "@types/picomatch": "4.0.3",
54
54
  "commit-and-tag-version": "13.2.1",
55
- "modern-web-guidance": "0.0.191",
56
- "oxfmt": "0.71.0",
55
+ "modern-web-guidance": "0.0.193",
56
+ "oxfmt": "0.72.0",
57
57
  "prettier": "3.9.9",
58
- "release-it": "21.1.0",
58
+ "release-it": "21.1.1",
59
59
  "tsdown": "0.23.0"
60
60
  },
61
61
  "scripts": {
@@ -94,7 +94,7 @@
94
94
  "type": "string"
95
95
  },
96
96
  "publish": {
97
- "description": "The publish decision either way: true keeps an image published -- including one its compose service is declared only in the check overlay, which a later release stops publishing by default -- and false excludes it while publishDocker stays true. Unset means the discovery default, which publishes everything except a check-only image once that rule is enforced.",
97
+ "description": "The publish decision either way: true keeps an image published -- including one whose compose service is declared only in the check overlay, which is not published by default -- and false excludes it while publishDocker stays true. Unset means the discovery default, which publishes everything except a check-only image.",
98
98
  "type": "boolean"
99
99
  }
100
100
  },
@@ -600,6 +600,10 @@
600
600
  "description": "agent:start behaviour overrides. Opt-in: see agent.autonomy for the interior-autonomy gate.",
601
601
  "type": "object",
602
602
  "properties": {
603
+ "enabled": {
604
+ "description": "The repo's explicit consent to the agent (default false). Only `true`, read from the repo's **default branch**, lets the agent work the repo: claim units, run sweeps, recover orphans, merge, act on a chat proposal or start a new spec. A missing key, `false`, a missing `.tooling.json` or a read that fails all mean not enabled, and the answer is re-read within about a minute. Every other `agent.*` key below has no effect without this one; `repo:sync` warns when the block sets one of them and this key is not `true`. Where this is `true`, `repo:sync` also installs `@bensandee/agent-cli`. Provisioning the agent's labels is not consent, and is never gated by this key.",
605
+ "type": "boolean"
606
+ },
603
607
  "autonomy": {
604
608
  "description": "Opt into interior autonomy (default false). When a phase's latest rationale reports readiness \"ready\" with an empty openQuestions[] and the review phase is clean, the agent applies the existing plan -> implement -> review -> park-at-mergeable advance itself instead of parking for a human. Never applies to the design/spec gate or the final merge from the mergeable terminal -- those always wait on a human. Also arms the trunk-red detector: when this repo's default branch combined status settles red, the agent files a tracking issue naming the failing checks and labels it agent/spec, starting the fix through the ordinary spec/plan/implement/review pipeline. It also closes that issue, with a comment, if the default branch recovers on its own before a human acts on it. Also arms the world-changed retrigger sweep: a pull request labelled agent-flags/autopilot that is parked at a -stuck or -failed terminal is re-queued when the fact its park recorded stops being true (the default branch went green, or moved past a conflicted base).",
605
609
  "type": "boolean"