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