@bensandee/tooling 0.91.0 → 0.92.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.
Files changed (2) hide show
  1. package/dist/bin.mjs +28 -27
  2. package/package.json +1 -1
package/dist/bin.mjs CHANGED
@@ -175,7 +175,7 @@ function loadEnvFileForBin(rawArgs, bin, io = processIo) {
175
175
  //#region src/env-file-preload.ts
176
176
  const envFileLoad = loadEnvFileForBin(process.argv.slice(2), {
177
177
  name: "bst",
178
- version: "0.91.0"
178
+ version: "0.92.0"
179
179
  });
180
180
  //#endregion
181
181
  //#region ../cli-kit/src/logger.ts
@@ -5433,7 +5433,7 @@ function buildManagedDevDeps(ctx) {
5433
5433
  const devDeps = { ...rootDevDeps(ctx.config) };
5434
5434
  if (!isMonorepo) Object.assign(devDeps, perPackageDevDeps(ctx.config));
5435
5435
  if (ctx.config.stack !== "none" || ctx.config.ci === "forgejo") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.15.0";
5436
- if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.91.0";
5436
+ if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.92.0";
5437
5437
  if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.17.0";
5438
5438
  if (wantsAgentCli(ctx)) devDeps[AGENT_CLI_PACKAGE] = "0.1.2";
5439
5439
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
@@ -9362,7 +9362,7 @@ const PLANNING_GUIDE_BODY = [
9362
9362
  "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/`.",
9363
9363
  "",
9364
9364
  "- 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.",
9365
- "- 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.",
9365
+ "- Label each task `Task <n>:` at the start of its heading or checkbox, numbered from 1, so `bst plan:brief` can find it. To split a task after the plan is written, give the parts one lowercase letter (`Task 18a:`, `Task 18b:`) rather than renumbering, so later tasks and every cross-reference keep their numbers. 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.",
9366
9366
  `- Give each task what makes it unambiguous and nothing more: a test's name and its assertions with the spec's exact values and why that test fails before the change, a code change's exact signature and file, a command with the output that means it passed. Write out a body only for an algorithm the signature and tests do not determine. Work that uses another task's result names that task's interfaces, never repeats its code. A task with no test to write says \`${TEST_EVIDENCE_PREFIXES.noTest} <reason>\` in the plan, so the executor inherits the call instead of making it cold.`,
9367
9367
  "- Keep tasks small. Each one runs its own test cycle and ends in a result that can be checked on its own: write the failing test, watch it fail, make it pass, commit. A run cut off partway then loses one task, not the plan. A large plan is fine; a large task is not.",
9368
9368
  "- A plan that builds on earlier work opens with a task that checks the prerequisite exists, and records a `**Blocked:**` note if it does not, instead of rebuilding it.",
@@ -9999,7 +9999,7 @@ function reportFailures(failed, command) {
9999
9999
  function generateMigratePrompt(results, config, detected) {
10000
10000
  const sections = [];
10001
10001
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
10002
- sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.91.0 repo:sync\` on ${timestamp}_\n`);
10002
+ sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.92.0 repo:sync\` on ${timestamp}_\n`);
10003
10003
  const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
10004
10004
  if (changed.length > 0) {
10005
10005
  const items = changed.map((r) => {
@@ -17555,7 +17555,7 @@ function countDeferralNotes(content) {
17555
17555
  //#region src/plan-workspace/task-label.ts
17556
17556
  const HEADING = /^(#{1,6})[ \t]+(.*)$/;
17557
17557
  const BOX = /^([ \t]*)[-*] \[[ xX]\][ \t]*(.*)$/;
17558
- const LABEL = /^Task[ \t]+([0-9]+)(?![0-9])/;
17558
+ const LABEL = /^Task[ \t]+([0-9]+)([a-z]?)(?![0-9A-Za-z])/;
17559
17559
  const FENCE_OPEN = /^[ \t]*(`{3,}|~{3,})/;
17560
17560
  const FENCE_CLOSE = /^[ \t]*(`{3,}|~{3,})[ \t]*$/;
17561
17561
  function closesFence(text, fenceChar, fenceLength) {
@@ -17588,10 +17588,11 @@ function fencedLines(lines) {
17588
17588
  }
17589
17589
  return fenced;
17590
17590
  }
17591
- function taskLabelNumber(text) {
17592
- const digits = LABEL.exec(text.replace(/^(\*\*|__)/, ""))?.[1];
17591
+ function taskLabelId(text) {
17592
+ const match = LABEL.exec(text.replace(/^(\*\*|__)/, ""));
17593
+ const digits = match?.[1];
17593
17594
  if (digits === void 0 || String(Number(digits)) !== digits) return null;
17594
- return Number(digits);
17595
+ return `${digits}${match?.[2] ?? ""}`;
17595
17596
  }
17596
17597
  //#endregion
17597
17598
  //#region src/plan-workspace/plan-boxes.ts
@@ -17735,16 +17736,16 @@ function parsePlanBoxes(planText) {
17735
17736
  //#endregion
17736
17737
  //#region src/plan-workspace/status.ts
17737
17738
  const RETIRED_SEGMENT = /(^|\/)retired\//;
17738
- function enclosingTaskNumber(planText, line) {
17739
+ function enclosingTaskLabel(planText, line) {
17739
17740
  const lines = planText.split("\n");
17740
17741
  const fenced = fencedLines(lines);
17741
17742
  let label = null;
17742
17743
  for (let index = 0; index < lines.length; index++) {
17743
17744
  const text = lines[index] ?? "";
17744
- if (index === line) return taskLabelNumber(BOX.exec(text)?.[2] ?? "") ?? label;
17745
+ if (index === line) return taskLabelId(BOX.exec(text)?.[2] ?? "") ?? label;
17745
17746
  if (fenced[index] === true) continue;
17746
17747
  const title = BOX.exec(text)?.[2] ?? HEADING.exec(text)?.[2];
17747
- const n = title === void 0 ? null : taskLabelNumber(title);
17748
+ const n = title === void 0 ? null : taskLabelId(title);
17748
17749
  if (n !== null) label = n;
17749
17750
  }
17750
17751
  return null;
@@ -17766,7 +17767,7 @@ function planStatus(planText, planPath) {
17766
17767
  outstanding: open.length,
17767
17768
  declined: boxes.filter((box) => box.declined).length,
17768
17769
  next: nextBox === void 0 || nextBox.title === "" ? null : nextBox.title,
17769
- nextTask: nextBox === void 0 ? null : enclosingTaskNumber(planText, nextBox.line),
17770
+ nextTask: nextBox === void 0 ? null : enclosingTaskLabel(planText, nextBox.line),
17770
17771
  onlyRetireLeft: open.length > 0 && open.every((box) => box.retireTask),
17771
17772
  notes
17772
17773
  };
@@ -26996,10 +26997,10 @@ function writeReviewPackage(planPath, base, head, cwd, out) {
26996
26997
  }
26997
26998
  //#endregion
26998
26999
  //#region src/plan-workspace/task-brief.ts
26999
- function isTaskLabel(text, n) {
27000
- return taskLabelNumber(text) === n;
27000
+ function isTaskLabel(text, label) {
27001
+ return taskLabelId(text) === label;
27001
27002
  }
27002
- function extractTask(planText, n) {
27003
+ function extractTask(planText, label) {
27003
27004
  const lines = planText.split("\n");
27004
27005
  const fenced = fencedLines(lines);
27005
27006
  let start = -1;
@@ -27011,13 +27012,13 @@ function extractTask(planText, n) {
27011
27012
  const heading = HEADING.exec(line);
27012
27013
  const box = BOX.exec(line);
27013
27014
  if (shape === null) {
27014
- if (heading !== null && isTaskLabel(heading[2] ?? "", n)) {
27015
+ if (heading !== null && isTaskLabel(heading[2] ?? "", label)) {
27015
27016
  start = index;
27016
27017
  shape = {
27017
27018
  kind: "heading",
27018
27019
  level: (heading[1] ?? "").length
27019
27020
  };
27020
- } else if (box !== null && isTaskLabel(box[2] ?? "", n)) {
27021
+ } else if (box !== null && isTaskLabel(box[2] ?? "", label)) {
27021
27022
  start = index;
27022
27023
  shape = {
27023
27024
  kind: "box",
@@ -27036,16 +27037,16 @@ function extractTask(planText, n) {
27036
27037
  if (start < 0) return null;
27037
27038
  return `${lines.slice(start, end).join("\n").trimEnd()}\n`;
27038
27039
  }
27039
- function writeTaskBrief(planPath, n, cwd, out) {
27040
+ function writeTaskBrief(planPath, label, cwd, out) {
27040
27041
  let planText;
27041
27042
  try {
27042
27043
  planText = readFileSync(path.resolve(cwd, planPath), "utf-8");
27043
27044
  } catch {
27044
27045
  throw new FatalError(`no such plan file: ${planPath}`);
27045
27046
  }
27046
- const task = extractTask(planText, n);
27047
- if (task === null) throw new FatalError(`task ${String(n)} not found in ${planPath} (no heading or checkbox starting "Task ${String(n)}")`);
27048
- const target = out === void 0 ? path.join(resolvePlanWorkspace(planPath, cwd), `task-${String(n)}-brief.md`) : resolveOutPath(out, cwd);
27047
+ const task = extractTask(planText, label);
27048
+ if (task === null) throw new FatalError(`task ${label} not found in ${planPath} (no heading or checkbox starting "Task ${label}")`);
27049
+ const target = out === void 0 ? path.join(resolvePlanWorkspace(planPath, cwd), `task-${label}-brief.md`) : resolveOutPath(out, cwd);
27049
27050
  writeFileSync(target, task);
27050
27051
  return {
27051
27052
  path: target,
@@ -27095,7 +27096,7 @@ const planBriefCommand = defineCommand({
27095
27096
  "task-number": {
27096
27097
  type: "positional",
27097
27098
  required: true,
27098
- description: "The task's number, as its heading or checkbox names it"
27099
+ description: "The task's label, as its heading or checkbox names it: a number, or a number and one lowercase letter (18a)"
27099
27100
  },
27100
27101
  out: {
27101
27102
  type: "string",
@@ -27109,8 +27110,8 @@ const planBriefCommand = defineCommand({
27109
27110
  run({ args }) {
27110
27111
  const plan = nonEmpty(args.plan, "plan", "plan:brief");
27111
27112
  const raw = args["task-number"];
27112
- if (!/^[1-9][0-9]*$/.test(raw)) throw new FatalError(`plan:brief: <task-number> must be a positive integer, got: ${raw}`);
27113
- const result = writeTaskBrief(plan, Number(raw), process.cwd(), args.out);
27113
+ if (!/^[1-9][0-9]*[a-z]?$/.test(raw)) throw new FatalError(`plan:brief: <task-number> must be a positive integer, optionally followed by one lowercase letter (18, 18a), got: ${raw}`);
27114
+ const result = writeTaskBrief(plan, raw, process.cwd(), args.out);
27114
27115
  const out = createCliOutput(args.json === true);
27115
27116
  out.json({
27116
27117
  ok: true,
@@ -27157,7 +27158,7 @@ const planReviewPackageCommand = defineCommand({
27157
27158
  });
27158
27159
  function renderStatus(report) {
27159
27160
  const next = report.next ?? "(none)";
27160
- const task = report.nextTask === null || taskLabelNumber(next) === report.nextTask ? "" : `Task ${String(report.nextTask)} -- `;
27161
+ const task = report.nextTask === null || taskLabelId(next) === report.nextTask ? "" : `Task ${report.nextTask} -- `;
27161
27162
  const lines = [`${report.plan}${report.retired ? " (retired)" : ""}: ${String(report.checked)} done, ${String(report.outstanding)} outstanding, ${String(report.declined)} declined`, `next: ${task}${next}${report.onlyRetireLeft ? " -- only the retire task is left" : ""}`];
27162
27163
  for (const note of report.notes) lines.push(` [${note.kind}] ${note.task}: ${note.text}`);
27163
27164
  return lines.join("\n");
@@ -27171,7 +27172,7 @@ const planStatusCommand = defineCommand({
27171
27172
  plan: PLAN_ARG,
27172
27173
  json: {
27173
27174
  type: "boolean",
27174
- description: "Emit one JSON document: plan, retired, checked, outstanding, declined, next (or null), nextTask (or null), onlyRetireLeft, notes ([{task, kind (progress | blocked | declined), text}]). next is null when there is none; nextTask is null when no task label encloses it."
27175
+ description: "Emit one JSON document: plan, retired, checked, outstanding, declined, next (or null), nextTask (a task label string such as \"3\" or \"18a\", or null), onlyRetireLeft, notes ([{task, kind (progress | blocked | declined), text}]). next is null when there is none; nextTask is null when no task label encloses it."
27175
27176
  }
27176
27177
  },
27177
27178
  run({ args }) {
@@ -27344,7 +27345,7 @@ async function runCli(config) {
27344
27345
  //#region src/cli-main.ts
27345
27346
  setLogLevel(resolveLogLevel({}));
27346
27347
  setForgejoLogger(log$1);
27347
- const displayVersion = "0.91.0";
27348
+ const displayVersion = "0.92.0";
27348
27349
  const bstSubCommands = {
27349
27350
  "repo:sync": syncCommand,
27350
27351
  "checks:run": runChecksCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.91.0",
3
+ "version": "0.92.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "license": "MIT",
6
6
  "repository": {