@bensandee/tooling 0.91.0 → 0.92.1

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 +31 -49
  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.1"
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.1";
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"] = {
@@ -9305,6 +9305,8 @@ const GIT_GUIDE_BODY = [
9305
9305
  "",
9306
9306
  "If the bst agent drives this repo, claim an issue or pull request before you work it by hand, so the agent does not start a run beside you: run `bst work:start <n>`. It prints the same orientation `bst work:status` does and then puts `agent-flags/hand-authored` on the object. It claims only where the repo's default branch sets `agent.enabled: true`, and does nothing anywhere else. **Exit 1 means do not start**: a run is live on the pair, or the agent holds it -- the output names which. Exit 0 means the pair is yours, whether the command wrote the flag or found it there already.",
9307
9307
  "",
9308
+ "The flag records who is working an object now, not who wrote it. Do not claim an issue or pull request you only file or open for later, even one a person or a coding session wrote by hand: leave it unclaimed so the agent can take it if asked. Claim it when you are about to work it.",
9309
+ "",
9308
9310
  "A pull request you open yourself that closes no claimed issue needs its own `bst work:start <n>` once it exists -- the flag on either half of a pair holds both, but there is no pair until something closes something.",
9309
9311
  "",
9310
9312
  "Hand the work back with the control panel's **Release to the agent**, or by removing `agent-flags/hand-authored` from both halves. Removing it does not queue anything: the agent picks the object up on its next cycle only if a label or a comment asks it to.",
@@ -9362,7 +9364,7 @@ const PLANNING_GUIDE_BODY = [
9362
9364
  "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
9365
  "",
9364
9366
  "- 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.",
9367
+ "- 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
9368
  `- 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
9369
  "- 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
9370
  "- 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 +10001,7 @@ function reportFailures(failed, command) {
9999
10001
  function generateMigratePrompt(results, config, detected) {
10000
10002
  const sections = [];
10001
10003
  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`);
10004
+ sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.92.1 repo:sync\` on ${timestamp}_\n`);
10003
10005
  const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
10004
10006
  if (changed.length > 0) {
10005
10007
  const items = changed.map((r) => {
@@ -17555,7 +17557,7 @@ function countDeferralNotes(content) {
17555
17557
  //#region src/plan-workspace/task-label.ts
17556
17558
  const HEADING = /^(#{1,6})[ \t]+(.*)$/;
17557
17559
  const BOX = /^([ \t]*)[-*] \[[ xX]\][ \t]*(.*)$/;
17558
- const LABEL = /^Task[ \t]+([0-9]+)(?![0-9])/;
17560
+ const LABEL = /^Task[ \t]+([0-9]+)([a-z]?)(?![0-9A-Za-z])/;
17559
17561
  const FENCE_OPEN = /^[ \t]*(`{3,}|~{3,})/;
17560
17562
  const FENCE_CLOSE = /^[ \t]*(`{3,}|~{3,})[ \t]*$/;
17561
17563
  function closesFence(text, fenceChar, fenceLength) {
@@ -17588,10 +17590,11 @@ function fencedLines(lines) {
17588
17590
  }
17589
17591
  return fenced;
17590
17592
  }
17591
- function taskLabelNumber(text) {
17592
- const digits = LABEL.exec(text.replace(/^(\*\*|__)/, ""))?.[1];
17593
+ function taskLabelId(text) {
17594
+ const match = LABEL.exec(text.replace(/^(\*\*|__)/, ""));
17595
+ const digits = match?.[1];
17593
17596
  if (digits === void 0 || String(Number(digits)) !== digits) return null;
17594
- return Number(digits);
17597
+ return `${digits}${match?.[2] ?? ""}`;
17595
17598
  }
17596
17599
  //#endregion
17597
17600
  //#region src/plan-workspace/plan-boxes.ts
@@ -17635,27 +17638,6 @@ function firstSentence$1(paragraph) {
17635
17638
  }
17636
17639
  return paragraph;
17637
17640
  }
17638
- const LABEL_REGEX = /^[A-Za-z]+\s+\d+[a-z]?:\s+/;
17639
- function firstClause(title) {
17640
- const label = LABEL_REGEX.exec(title)?.[0].length ?? 0;
17641
- let inCode = false;
17642
- for (let index = label; index < title.length; index++) {
17643
- const char = title[index];
17644
- if (char === "`") inCode = !inCode;
17645
- if (inCode || char !== ";" && char !== ":") continue;
17646
- if (index + 1 < title.length && !/\s/.test(title[index + 1] ?? "")) continue;
17647
- return title.slice(0, index).trimEnd();
17648
- }
17649
- return title;
17650
- }
17651
- function shortTitle(title) {
17652
- if (title.length <= 100) return title;
17653
- const clause = firstClause(title);
17654
- if (clause.length <= 100) return clause;
17655
- const room = clause.slice(0, 97);
17656
- const space = room.lastIndexOf(" ");
17657
- return `${(space > 0 ? room.slice(0, space) : room).trimEnd()}...`;
17658
- }
17659
17641
  function stripStrong(text) {
17660
17642
  return text.split(/(`[^`]*`)/).map((part) => part.startsWith("`") ? part : part.replaceAll("**", "")).join("").trim();
17661
17643
  }
@@ -17688,7 +17670,7 @@ function parsePlanBoxes(planText) {
17688
17670
  break;
17689
17671
  }
17690
17672
  }
17691
- const title = shortTitle(stripStrong(unwrapEmphasis(firstSentence$1(boxParagraph(lines, fenced, box.index, blockEnd, box.rawTitle)))));
17673
+ const title = stripStrong(unwrapEmphasis(firstSentence$1(boxParagraph(lines, fenced, box.index, blockEnd, box.rawTitle))));
17692
17674
  const retireTask = RETIRE_TASK_REGEX.test(title);
17693
17675
  if (box.checked) return {
17694
17676
  line: box.index,
@@ -17735,16 +17717,16 @@ function parsePlanBoxes(planText) {
17735
17717
  //#endregion
17736
17718
  //#region src/plan-workspace/status.ts
17737
17719
  const RETIRED_SEGMENT = /(^|\/)retired\//;
17738
- function enclosingTaskNumber(planText, line) {
17720
+ function enclosingTaskLabel(planText, line) {
17739
17721
  const lines = planText.split("\n");
17740
17722
  const fenced = fencedLines(lines);
17741
17723
  let label = null;
17742
17724
  for (let index = 0; index < lines.length; index++) {
17743
17725
  const text = lines[index] ?? "";
17744
- if (index === line) return taskLabelNumber(BOX.exec(text)?.[2] ?? "") ?? label;
17726
+ if (index === line) return taskLabelId(BOX.exec(text)?.[2] ?? "") ?? label;
17745
17727
  if (fenced[index] === true) continue;
17746
17728
  const title = BOX.exec(text)?.[2] ?? HEADING.exec(text)?.[2];
17747
- const n = title === void 0 ? null : taskLabelNumber(title);
17729
+ const n = title === void 0 ? null : taskLabelId(title);
17748
17730
  if (n !== null) label = n;
17749
17731
  }
17750
17732
  return null;
@@ -17766,7 +17748,7 @@ function planStatus(planText, planPath) {
17766
17748
  outstanding: open.length,
17767
17749
  declined: boxes.filter((box) => box.declined).length,
17768
17750
  next: nextBox === void 0 || nextBox.title === "" ? null : nextBox.title,
17769
- nextTask: nextBox === void 0 ? null : enclosingTaskNumber(planText, nextBox.line),
17751
+ nextTask: nextBox === void 0 ? null : enclosingTaskLabel(planText, nextBox.line),
17770
17752
  onlyRetireLeft: open.length > 0 && open.every((box) => box.retireTask),
17771
17753
  notes
17772
17754
  };
@@ -26996,10 +26978,10 @@ function writeReviewPackage(planPath, base, head, cwd, out) {
26996
26978
  }
26997
26979
  //#endregion
26998
26980
  //#region src/plan-workspace/task-brief.ts
26999
- function isTaskLabel(text, n) {
27000
- return taskLabelNumber(text) === n;
26981
+ function isTaskLabel(text, label) {
26982
+ return taskLabelId(text) === label;
27001
26983
  }
27002
- function extractTask(planText, n) {
26984
+ function extractTask(planText, label) {
27003
26985
  const lines = planText.split("\n");
27004
26986
  const fenced = fencedLines(lines);
27005
26987
  let start = -1;
@@ -27011,13 +26993,13 @@ function extractTask(planText, n) {
27011
26993
  const heading = HEADING.exec(line);
27012
26994
  const box = BOX.exec(line);
27013
26995
  if (shape === null) {
27014
- if (heading !== null && isTaskLabel(heading[2] ?? "", n)) {
26996
+ if (heading !== null && isTaskLabel(heading[2] ?? "", label)) {
27015
26997
  start = index;
27016
26998
  shape = {
27017
26999
  kind: "heading",
27018
27000
  level: (heading[1] ?? "").length
27019
27001
  };
27020
- } else if (box !== null && isTaskLabel(box[2] ?? "", n)) {
27002
+ } else if (box !== null && isTaskLabel(box[2] ?? "", label)) {
27021
27003
  start = index;
27022
27004
  shape = {
27023
27005
  kind: "box",
@@ -27036,16 +27018,16 @@ function extractTask(planText, n) {
27036
27018
  if (start < 0) return null;
27037
27019
  return `${lines.slice(start, end).join("\n").trimEnd()}\n`;
27038
27020
  }
27039
- function writeTaskBrief(planPath, n, cwd, out) {
27021
+ function writeTaskBrief(planPath, label, cwd, out) {
27040
27022
  let planText;
27041
27023
  try {
27042
27024
  planText = readFileSync(path.resolve(cwd, planPath), "utf-8");
27043
27025
  } catch {
27044
27026
  throw new FatalError(`no such plan file: ${planPath}`);
27045
27027
  }
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);
27028
+ const task = extractTask(planText, label);
27029
+ if (task === null) throw new FatalError(`task ${label} not found in ${planPath} (no heading or checkbox starting "Task ${label}")`);
27030
+ const target = out === void 0 ? path.join(resolvePlanWorkspace(planPath, cwd), `task-${label}-brief.md`) : resolveOutPath(out, cwd);
27049
27031
  writeFileSync(target, task);
27050
27032
  return {
27051
27033
  path: target,
@@ -27095,7 +27077,7 @@ const planBriefCommand = defineCommand({
27095
27077
  "task-number": {
27096
27078
  type: "positional",
27097
27079
  required: true,
27098
- description: "The task's number, as its heading or checkbox names it"
27080
+ description: "The task's label, as its heading or checkbox names it: a number, or a number and one lowercase letter (18a)"
27099
27081
  },
27100
27082
  out: {
27101
27083
  type: "string",
@@ -27109,8 +27091,8 @@ const planBriefCommand = defineCommand({
27109
27091
  run({ args }) {
27110
27092
  const plan = nonEmpty(args.plan, "plan", "plan:brief");
27111
27093
  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);
27094
+ 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}`);
27095
+ const result = writeTaskBrief(plan, raw, process.cwd(), args.out);
27114
27096
  const out = createCliOutput(args.json === true);
27115
27097
  out.json({
27116
27098
  ok: true,
@@ -27157,7 +27139,7 @@ const planReviewPackageCommand = defineCommand({
27157
27139
  });
27158
27140
  function renderStatus(report) {
27159
27141
  const next = report.next ?? "(none)";
27160
- const task = report.nextTask === null || taskLabelNumber(next) === report.nextTask ? "" : `Task ${String(report.nextTask)} -- `;
27142
+ const task = report.nextTask === null || taskLabelId(next) === report.nextTask ? "" : `Task ${report.nextTask} -- `;
27161
27143
  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
27144
  for (const note of report.notes) lines.push(` [${note.kind}] ${note.task}: ${note.text}`);
27163
27145
  return lines.join("\n");
@@ -27171,7 +27153,7 @@ const planStatusCommand = defineCommand({
27171
27153
  plan: PLAN_ARG,
27172
27154
  json: {
27173
27155
  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."
27156
+ 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
27157
  }
27176
27158
  },
27177
27159
  run({ args }) {
@@ -27344,7 +27326,7 @@ async function runCli(config) {
27344
27326
  //#region src/cli-main.ts
27345
27327
  setLogLevel(resolveLogLevel({}));
27346
27328
  setForgejoLogger(log$1);
27347
- const displayVersion = "0.91.0";
27329
+ const displayVersion = "0.92.1";
27348
27330
  const bstSubCommands = {
27349
27331
  "repo:sync": syncCommand,
27350
27332
  "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.1",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "license": "MIT",
6
6
  "repository": {