@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.
- package/dist/bin.mjs +31 -49
- 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.
|
|
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.
|
|
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.
|
|
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-
|
|
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
|
|
17592
|
-
const
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 :
|
|
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 :
|
|
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,
|
|
27000
|
-
return
|
|
26981
|
+
function isTaskLabel(text, label) {
|
|
26982
|
+
return taskLabelId(text) === label;
|
|
27001
26983
|
}
|
|
27002
|
-
function extractTask(planText,
|
|
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] ?? "",
|
|
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] ?? "",
|
|
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,
|
|
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,
|
|
27047
|
-
if (task === null) throw new FatalError(`task ${
|
|
27048
|
-
const target = out === void 0 ? path.join(resolvePlanWorkspace(planPath, cwd), `task-${
|
|
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
|
|
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]
|
|
27113
|
-
const result = writeTaskBrief(plan,
|
|
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 ||
|
|
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.
|
|
27329
|
+
const displayVersion = "0.92.1";
|
|
27348
27330
|
const bstSubCommands = {
|
|
27349
27331
|
"repo:sync": syncCommand,
|
|
27350
27332
|
"checks:run": runChecksCommand,
|