@bensandee/tooling 0.84.0 → 0.84.2
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 +148 -62
- package/package.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -269,7 +269,7 @@ function loadEnvFileForBin(rawArgs, bin, io = processIo) {
|
|
|
269
269
|
*/
|
|
270
270
|
const envFileLoad = loadEnvFileForBin(process.argv.slice(2), {
|
|
271
271
|
name: "bst",
|
|
272
|
-
version: "0.84.
|
|
272
|
+
version: "0.84.2"
|
|
273
273
|
});
|
|
274
274
|
//#endregion
|
|
275
275
|
//#region ../cli-kit/src/logger.ts
|
|
@@ -5777,8 +5777,8 @@ function declaredPublishRegistries(targetDir, structure, rootPackageJson) {
|
|
|
5777
5777
|
* shape of custom regex manager in the root `renovate.json` can rewrite both this version and this
|
|
5778
5778
|
* digest (`customManagers` entries scoped to this file).
|
|
5779
5779
|
*/
|
|
5780
|
-
const MISE_VERSION = "2026.
|
|
5781
|
-
const MISE_LINUX_X64_SHA256 = "
|
|
5780
|
+
const MISE_VERSION = "2026.10.0";
|
|
5781
|
+
const MISE_LINUX_X64_SHA256 = "57ced973f968b8fbab07aa8e32bd7077d4a357e200a22356d98963c723c6de0a";
|
|
5782
5782
|
/** Where generated Forgejo workflows fetch the shared mise setup action: a composite action in
|
|
5783
5783
|
* `bensandee/common`, on the forge every fleet repo lives on. GitHub cannot reach it; the
|
|
5784
5784
|
* generators keep inline steps there. */
|
|
@@ -7163,7 +7163,7 @@ function buildManagedDevDeps(ctx) {
|
|
|
7163
7163
|
const devDeps = { ...rootDevDeps(ctx.config) };
|
|
7164
7164
|
if (!isMonorepo) Object.assign(devDeps, perPackageDevDeps(ctx.config));
|
|
7165
7165
|
if (ctx.config.stack !== "none" || ctx.config.ci === "forgejo") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.14.5";
|
|
7166
|
-
if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.84.
|
|
7166
|
+
if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.84.2";
|
|
7167
7167
|
if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.15.2";
|
|
7168
7168
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
7169
7169
|
"@changesets/cli": "3.0.3",
|
|
@@ -11950,6 +11950,10 @@ const GIT_GUIDE_BODY = [
|
|
|
11950
11950
|
"",
|
|
11951
11951
|
"Start a pull request's title with the stage it carries: `Spec:` for a spec, `Plan:` for a plan, and no stem once it carries the implementation. A draft keeps its `WIP:` marker in front (`WIP: Plan: Add retry to uploads`). When a pull request moves on to the next stage, move the stem with it. This is informational -- nothing checks it and nothing fails without it -- but it tells a reviewer at a glance what they are reading.",
|
|
11952
11952
|
"",
|
|
11953
|
+
"**Drop `WIP:` and fix the stem when the work is done.** Once the pull request carries finished work you are handing over for review -- a spec or a plan ready to approve, or an implementation whose check passes -- remove the `WIP:` marker and make the stem match what it now carries: no stem for an implementation. Do it in the same step that reports the work done, without being asked. A marker left behind tells a reviewer the work is still moving.",
|
|
11954
|
+
"",
|
|
11955
|
+
"`pr:update` never changes a pull request's draft state, so it keeps `WIP:` on a retitle. On Forgejo a draft is only that title prefix: retitle the pull request without it (`bst-fj issue:update <n> --title \"<title>\"` where `bst-fj` is installed). On GitHub, run `gh pr ready` and then edit the title.",
|
|
11956
|
+
"",
|
|
11953
11957
|
"## Merging the default branch in",
|
|
11954
11958
|
"",
|
|
11955
11959
|
"**A merge with no conflicts is not yet a merge that works.** Read the incoming diff for what your branch builds on -- a field made required, a shared schema, a size budget -- and run the full check before you push the merge. Fix what it finds in the merge commit itself, so that commit passes on its own.",
|
|
@@ -12692,7 +12696,7 @@ function reportFailures(failed, command) {
|
|
|
12692
12696
|
function generateMigratePrompt(results, config, detected) {
|
|
12693
12697
|
const sections = [];
|
|
12694
12698
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
12695
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.84.
|
|
12699
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.84.2 repo:sync\` on ${timestamp}_\n`);
|
|
12696
12700
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
12697
12701
|
if (changed.length > 0) {
|
|
12698
12702
|
const items = changed.map((r) => {
|
|
@@ -25935,7 +25939,7 @@ async function refusalMessage(res) {
|
|
|
25935
25939
|
try {
|
|
25936
25940
|
const parsed = ErrorBodySchema.safeParse(JSON.parse(text));
|
|
25937
25941
|
if (parsed.success) return parsed.data.message;
|
|
25938
|
-
} catch {}
|
|
25942
|
+
} catch (_error) {}
|
|
25939
25943
|
return text;
|
|
25940
25944
|
}
|
|
25941
25945
|
/** Delete one branch. The name is encoded per segment so `feat/a/b` keeps its slashes. */
|
|
@@ -26179,12 +26183,15 @@ function conventionalActiveDirs(exec) {
|
|
|
26179
26183
|
* prefix replaced by the terminal verb (`docs/plan-foo.md` ->
|
|
26180
26184
|
* `docs/retired/completed-foo.md`); anything else (a `plans`/`specs` dir doc,
|
|
26181
26185
|
* date-prefixed superpowers plan or not) keeps its basename and moves to the
|
|
26182
|
-
* sibling `retired/` directory.
|
|
26183
|
-
*
|
|
26186
|
+
* sibling `retired/` directory. A doc already in a retired directory stays
|
|
26187
|
+
* where it is -- re-retiring one to add its retire paragraph once moved it a
|
|
26188
|
+
* level deeper, into `retired/retired/`. The single place `docs:retire` reads
|
|
26189
|
+
* this convention from, so it can never drift from what `discoverPairs` expects.
|
|
26184
26190
|
*/
|
|
26185
26191
|
function retireDestination(src, verb) {
|
|
26186
26192
|
const dir = path.posix.dirname(src);
|
|
26187
26193
|
const base = path.posix.basename(src);
|
|
26194
|
+
if (RETIRED_DIR_NAMES$1.includes(path.posix.basename(dir))) return src;
|
|
26188
26195
|
if (dir === DOCS_ROOT && LEGACY_ACTIVE_PATTERN.test(base)) return `${DOCS_ROOT}/${RETIRED_DIR}/${verb}-${base.slice(5)}`;
|
|
26189
26196
|
return `${dir}/${RETIRED_DIR}/${base}`;
|
|
26190
26197
|
}
|
|
@@ -27107,7 +27114,7 @@ function buildRelocatePlan(exec, moves) {
|
|
|
27107
27114
|
for (const { from, to } of moves) {
|
|
27108
27115
|
if (moveMap.has(from)) throw new FatalError(`docs:relocate: duplicate source ${from}`);
|
|
27109
27116
|
moveMap.set(from, to);
|
|
27110
|
-
if (exec.exists(from)) plannedMoves.push({
|
|
27117
|
+
if (exec.exists(from) && from !== to) plannedMoves.push({
|
|
27111
27118
|
from,
|
|
27112
27119
|
to,
|
|
27113
27120
|
status: "pending"
|
|
@@ -27311,14 +27318,12 @@ const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
27311
27318
|
function validateRetireArgs(exec, src, opts) {
|
|
27312
27319
|
if (!isRetireVerb(opts.status)) throw new FatalError(`docs:retire: unrecognized --status "${opts.status}" (expected one of: ${RETIRE_VERBS.join(", ")})`);
|
|
27313
27320
|
const verb = opts.status;
|
|
27314
|
-
|
|
27315
|
-
if (!DATE_RE.test(date)) throw new FatalError(`docs:retire: --date must be YYYY-MM-DD, got: ${date}`);
|
|
27321
|
+
if (opts.date !== void 0 && !DATE_RE.test(opts.date)) throw new FatalError(`docs:retire: --date must be YYYY-MM-DD, got: ${opts.date}`);
|
|
27316
27322
|
if (!src.endsWith(".md")) throw new FatalError(`docs:retire: source must be a .md file: ${src}`);
|
|
27317
27323
|
const dest = opts.dest !== void 0 ? path.posix.normalize(opts.dest) : retireDestination(src, verb);
|
|
27318
27324
|
if (src !== dest && exec.exists(src) && exec.exists(dest)) throw new FatalError(`docs:retire: refusing to overwrite existing destination: ${dest}`);
|
|
27319
27325
|
return {
|
|
27320
27326
|
verb,
|
|
27321
|
-
date,
|
|
27322
27327
|
dest
|
|
27323
27328
|
};
|
|
27324
27329
|
}
|
|
@@ -27482,7 +27487,7 @@ function formatRetireLines(src, dest, relocatePlan, lifecycleSummary) {
|
|
|
27482
27487
|
*/
|
|
27483
27488
|
function runDocsRetire(exec, src, opts) {
|
|
27484
27489
|
const vc = { verbose: opts.verbose ?? false };
|
|
27485
|
-
const { verb,
|
|
27490
|
+
const { verb, dest } = validateRetireArgs(exec, src, opts);
|
|
27486
27491
|
const relocatePlan = buildRelocatePlan(exec, [{
|
|
27487
27492
|
from: src,
|
|
27488
27493
|
to: dest
|
|
@@ -27492,6 +27497,7 @@ function runDocsRetire(exec, src, opts) {
|
|
|
27492
27497
|
const relinkRewrite = relocatePlan.rewrites.find((r) => r.file === dest);
|
|
27493
27498
|
const baseContent = relinkRewrite?.content ?? exec.readFile(move.status === "pending" ? src : dest);
|
|
27494
27499
|
if (baseContent === void 0) throw new FatalError(`docs:retire: cannot read ${move.status === "pending" ? src : dest}`);
|
|
27500
|
+
const date = opts.date ?? datedStatusValue(baseContent) ?? localToday(/* @__PURE__ */ new Date());
|
|
27495
27501
|
const { content: lifecycleContent, summary, edits: lifecycle } = applyLifecycleEdits(baseContent, dest, verb, date, opts.shipped);
|
|
27496
27502
|
const delinked = delinkDocument(lifecycleContent, path.posix.dirname(dest));
|
|
27497
27503
|
const finalContent = delinked.content;
|
|
@@ -27561,7 +27567,7 @@ const docsRetireCommand = defineCommand({
|
|
|
27561
27567
|
},
|
|
27562
27568
|
date: {
|
|
27563
27569
|
type: "string",
|
|
27564
|
-
description: "Retirement date, YYYY-MM-DD (defaults to today, local time)"
|
|
27570
|
+
description: "Retirement date, YYYY-MM-DD (defaults to the date of an existing terminal Status: line, else today, local time)"
|
|
27565
27571
|
},
|
|
27566
27572
|
shipped: {
|
|
27567
27573
|
type: "string",
|
|
@@ -27877,13 +27883,39 @@ function normalizeIssue(raw) {
|
|
|
27877
27883
|
};
|
|
27878
27884
|
}
|
|
27879
27885
|
/**
|
|
27886
|
+
* Parse the backing issue number out of a PR body's `Closes #<n>` line. Returns null when the
|
|
27887
|
+
* body carries no such line (a hand-written PR, or a body that could not be read). Lives beside
|
|
27888
|
+
* {@link toIssueSummaries}, which keeps it for every pull request a search returns.
|
|
27889
|
+
*
|
|
27890
|
+
* `Closes` only, deliberately -- narrower than the `Closes`/`Fixes`/`Resolves` vocabulary the
|
|
27891
|
+
* closing-ref machinery in `closing-refs.ts` recognizes, and it must stay
|
|
27892
|
+
* that way. A body whose *first* closing reference names some other issue is what a caller
|
|
27893
|
+
* resolves the backing issue against, and a branch name derived from that number follows it, so
|
|
27894
|
+
* every keyword added here widens the set of bodies that can be misrouted. The
|
|
27895
|
+
* cost of the narrow set is the opposite failure and the cheaper one: an adopted body already
|
|
27896
|
+
* saying `Fixes #<n>` gets no `Closes` line appended (it already closes the issue), so this
|
|
27897
|
+
* returns null and the PR is treated as having no backing issue -- which still closes its issue
|
|
27898
|
+
* on merge and still runs on the PR's own head ref, losing only the issue mapping.
|
|
27899
|
+
*/
|
|
27900
|
+
function backingIssueNumber(prBody) {
|
|
27901
|
+
const match = /Closes #(\d+)/i.exec(prBody);
|
|
27902
|
+
if (match === null) return null;
|
|
27903
|
+
const parsed = Number(match[1]);
|
|
27904
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
27905
|
+
}
|
|
27906
|
+
/**
|
|
27880
27907
|
* Normalize a raw issue list, drop pull requests (both platforms serve them
|
|
27881
27908
|
* through the issues endpoint), and strip bodies down to summaries. Shared by
|
|
27882
27909
|
* the Forgejo and GitHub list transports so the post-processing chain lives
|
|
27883
27910
|
* in exactly one place.
|
|
27884
27911
|
*/
|
|
27885
27912
|
function toIssueSummaries(raw, type = "issues") {
|
|
27886
|
-
const summaries = raw.map(normalizeIssue).map(({ body
|
|
27913
|
+
const summaries = raw.map(normalizeIssue).map(({ body, ...summary }) => {
|
|
27914
|
+
const result = summary;
|
|
27915
|
+
const backingIssue = summary.isPullRequest ? backingIssueNumber(body) : null;
|
|
27916
|
+
if (backingIssue !== null) result.backingIssue = backingIssue;
|
|
27917
|
+
return result;
|
|
27918
|
+
});
|
|
27887
27919
|
return type === "issues" ? summaries.filter((s) => !s.isPullRequest) : summaries;
|
|
27888
27920
|
}
|
|
27889
27921
|
/** Fetch a single issue by number. */
|
|
@@ -30533,6 +30565,71 @@ function writeReviewPackage(planPath, base, head, cwd, out) {
|
|
|
30533
30565
|
};
|
|
30534
30566
|
}
|
|
30535
30567
|
//#endregion
|
|
30568
|
+
//#region src/plan-workspace/task-label.ts
|
|
30569
|
+
/** The line shapes `plan:brief` and `plan:status` both read a task's number from. */
|
|
30570
|
+
const HEADING = /^(#{1,6})[ \t]+(.*)$/;
|
|
30571
|
+
const BOX = /^([ \t]*)[-*] \[[ xX]\][ \t]*(.*)$/;
|
|
30572
|
+
const LABEL = /^Task[ \t]+([0-9]+)(?![0-9])/;
|
|
30573
|
+
/** A line that opens a fence: three or more of one character, after any amount of whitespace. */
|
|
30574
|
+
const FENCE_OPEN = /^[ \t]*(`{3,}|~{3,})/;
|
|
30575
|
+
/** The same shape with nothing but whitespace after the run, which is what closes a fence. */
|
|
30576
|
+
const FENCE_CLOSE = /^[ \t]*(`{3,}|~{3,})[ \t]*$/;
|
|
30577
|
+
/** Whether a line closes the fence opened by `fenceChar` repeated `fenceLength` times. */
|
|
30578
|
+
function closesFence(text, fenceChar, fenceLength) {
|
|
30579
|
+
const run = FENCE_CLOSE.exec(text)?.[1];
|
|
30580
|
+
if (run === void 0) return false;
|
|
30581
|
+
return run[0] === fenceChar && run.length >= fenceLength;
|
|
30582
|
+
}
|
|
30583
|
+
/**
|
|
30584
|
+
* Which lines of a document a rendered markdown viewer would show as code rather than parse as
|
|
30585
|
+
* markdown: true at every fence line and every line inside one, false elsewhere. The rules are
|
|
30586
|
+
* CommonMark's, with two choices that belong to these readers:
|
|
30587
|
+
*
|
|
30588
|
+
* - Any indentation opens a fence, not the 0-3 spaces CommonMark allows, because plans nest
|
|
30589
|
+
* example fences under list items and none of the readers tracks list nesting.
|
|
30590
|
+
* - An unclosed fence runs to the end of the document, which is how a renderer shows one.
|
|
30591
|
+
*
|
|
30592
|
+
* A closer is the opener's own character repeated at least as many times, with nothing after it
|
|
30593
|
+
* but whitespace; a run of the other character, a shorter run, or a run carrying an info string is
|
|
30594
|
+
* code content and keeps the fence open.
|
|
30595
|
+
*
|
|
30596
|
+
* `stripFencedCodeBlocks` in `docs-check/links.ts` walks fences by the same rule but is a
|
|
30597
|
+
* different reader with different rules (it limits the opener's indent and tracks blockquote
|
|
30598
|
+
* depth), which is why this walk is not shared with it.
|
|
30599
|
+
*/
|
|
30600
|
+
function fencedLines(lines) {
|
|
30601
|
+
const fenced = lines.map(() => false);
|
|
30602
|
+
let fenceChar;
|
|
30603
|
+
let fenceLength = 0;
|
|
30604
|
+
for (let index = 0; index < lines.length; index++) {
|
|
30605
|
+
const text = lines[index] ?? "";
|
|
30606
|
+
if (fenceChar === void 0) {
|
|
30607
|
+
const run = FENCE_OPEN.exec(text)?.[1];
|
|
30608
|
+
if (run === void 0) continue;
|
|
30609
|
+
fenceChar = run[0];
|
|
30610
|
+
fenceLength = run.length;
|
|
30611
|
+
fenced[index] = true;
|
|
30612
|
+
continue;
|
|
30613
|
+
}
|
|
30614
|
+
fenced[index] = true;
|
|
30615
|
+
if (closesFence(text, fenceChar, fenceLength)) {
|
|
30616
|
+
fenceChar = void 0;
|
|
30617
|
+
fenceLength = 0;
|
|
30618
|
+
}
|
|
30619
|
+
}
|
|
30620
|
+
return fenced;
|
|
30621
|
+
}
|
|
30622
|
+
/**
|
|
30623
|
+
* The `<n>` of a heading or checkbox text that starts `Task <n>` (after an opening `**` or `__`),
|
|
30624
|
+
* or null. A number with a leading zero reads as null, because `plan:brief <n>` matches the digits
|
|
30625
|
+
* as written and could never find `Task 05`.
|
|
30626
|
+
*/
|
|
30627
|
+
function taskLabelNumber(text) {
|
|
30628
|
+
const digits = LABEL.exec(text.replace(/^(\*\*|__)/, ""))?.[1];
|
|
30629
|
+
if (digits === void 0 || String(Number(digits)) !== digits) return null;
|
|
30630
|
+
return Number(digits);
|
|
30631
|
+
}
|
|
30632
|
+
//#endregion
|
|
30536
30633
|
//#region src/plan-workspace/plan-boxes.ts
|
|
30537
30634
|
/**
|
|
30538
30635
|
* Read a plan document's checkboxes and the notes committed under them. One strict parser, fail-
|
|
@@ -30545,8 +30642,14 @@ function writeReviewPackage(planPath, base, head, cwd, out) {
|
|
|
30545
30642
|
* boundary matters because a batched plan is a sequence of `### <title>`
|
|
30546
30643
|
* sections and a note must never leak across one.
|
|
30547
30644
|
*
|
|
30548
|
-
*
|
|
30549
|
-
*
|
|
30645
|
+
* A line inside a fenced code block (`fencedLines`, `task-label.ts`) is invisible in every role:
|
|
30646
|
+
* a fenced box is not counted, a fenced heading does not end a block, and a fenced note or
|
|
30647
|
+
* declination is not one. The fail-safe direction holds -- an ambiguous line degrades to "not
|
|
30648
|
+
* declined".
|
|
30649
|
+
*
|
|
30650
|
+
* The agent imports this module (`run-outcome.ts`, `run-gates.ts`, `run.ts`, `plan-box-diff.ts`,
|
|
30651
|
+
* `plan-box-watch.ts`, `plan-box-walk.ts`, `plan-progress.ts`), so a change here changes how the
|
|
30652
|
+
* agent reads a plan as well as what `plan:status` reports.
|
|
30550
30653
|
*
|
|
30551
30654
|
* An unchecked box is **declined** when its block carries a line matching
|
|
30552
30655
|
* `^[ \t]*(?:[-*][ \t]+)?\*\*Declined:\*\*[ \t]*(.*)$` whose trimmed remainder is non-empty; that
|
|
@@ -30599,8 +30702,10 @@ function unwrapEmphasis(rawTitle) {
|
|
|
30599
30702
|
/** Parse every checkbox on a plan document, each with its own block already resolved. */
|
|
30600
30703
|
function parsePlanBoxes(planText) {
|
|
30601
30704
|
const lines = planText.split("\n");
|
|
30705
|
+
const fenced = fencedLines(lines);
|
|
30602
30706
|
const boxStarts = [];
|
|
30603
30707
|
for (let index = 0; index < lines.length; index++) {
|
|
30708
|
+
if (fenced[index] === true) continue;
|
|
30604
30709
|
const match = BOX_LINE_REGEX.exec(lines[index] ?? "");
|
|
30605
30710
|
if (match === null) continue;
|
|
30606
30711
|
boxStarts.push({
|
|
@@ -30612,13 +30717,17 @@ function parsePlanBoxes(planText) {
|
|
|
30612
30717
|
return boxStarts.map((box, position) => {
|
|
30613
30718
|
const nextBoxIndex = boxStarts[position + 1]?.index ?? lines.length;
|
|
30614
30719
|
let blockEnd = nextBoxIndex;
|
|
30615
|
-
for (let index = box.index + 1; index < nextBoxIndex; index++)
|
|
30616
|
-
|
|
30617
|
-
|
|
30720
|
+
for (let index = box.index + 1; index < nextBoxIndex; index++) {
|
|
30721
|
+
if (fenced[index] === true) continue;
|
|
30722
|
+
if (HEADING_REGEX.test(lines[index] ?? "")) {
|
|
30723
|
+
blockEnd = index;
|
|
30724
|
+
break;
|
|
30725
|
+
}
|
|
30618
30726
|
}
|
|
30619
30727
|
const title = unwrapEmphasis(box.rawTitle);
|
|
30620
30728
|
const retireTask = RETIRE_TASK_REGEX.test(title);
|
|
30621
30729
|
if (box.checked) return {
|
|
30730
|
+
line: box.index,
|
|
30622
30731
|
checked: true,
|
|
30623
30732
|
declined: false,
|
|
30624
30733
|
reason: null,
|
|
@@ -30628,6 +30737,7 @@ function parsePlanBoxes(planText) {
|
|
|
30628
30737
|
};
|
|
30629
30738
|
const notes = [];
|
|
30630
30739
|
for (let index = box.index + 1; index < blockEnd; index++) {
|
|
30740
|
+
if (fenced[index] === true) continue;
|
|
30631
30741
|
const noteMatch = NOTE_LINE_REGEX.exec(lines[index] ?? "");
|
|
30632
30742
|
if (noteMatch === null) continue;
|
|
30633
30743
|
const text = (noteMatch[2] ?? "").trim();
|
|
@@ -30638,6 +30748,7 @@ function parsePlanBoxes(planText) {
|
|
|
30638
30748
|
}
|
|
30639
30749
|
let reason = null;
|
|
30640
30750
|
for (let index = box.index + 1; index < blockEnd; index++) {
|
|
30751
|
+
if (fenced[index] === true) continue;
|
|
30641
30752
|
const declinedMatch = DECLINED_LINE_REGEX.exec(lines[index] ?? "");
|
|
30642
30753
|
if (declinedMatch === null) continue;
|
|
30643
30754
|
const remainder = (declinedMatch[1] ?? "").trim();
|
|
@@ -30647,6 +30758,7 @@ function parsePlanBoxes(planText) {
|
|
|
30647
30758
|
}
|
|
30648
30759
|
}
|
|
30649
30760
|
return {
|
|
30761
|
+
line: box.index,
|
|
30650
30762
|
checked: false,
|
|
30651
30763
|
declined: reason !== null,
|
|
30652
30764
|
reason,
|
|
@@ -30657,45 +30769,23 @@ function parsePlanBoxes(planText) {
|
|
|
30657
30769
|
});
|
|
30658
30770
|
}
|
|
30659
30771
|
//#endregion
|
|
30660
|
-
//#region src/plan-workspace/task-label.ts
|
|
30661
|
-
/** The line shapes `plan:brief` and `plan:status` both read a task's number from. */
|
|
30662
|
-
const FENCE = /^[ \t]*(```|~~~)/;
|
|
30663
|
-
const HEADING = /^(#{1,6})[ \t]+(.*)$/;
|
|
30664
|
-
const BOX = /^([ \t]*)[-*] \[[ xX]\][ \t]*(.*)$/;
|
|
30665
|
-
const LABEL = /^Task[ \t]+([0-9]+)(?![0-9])/;
|
|
30666
|
-
/**
|
|
30667
|
-
* The `<n>` of a heading or checkbox text that starts `Task <n>` (after an opening `**` or `__`),
|
|
30668
|
-
* or null. A number with a leading zero reads as null, because `plan:brief <n>` matches the digits
|
|
30669
|
-
* as written and could never find `Task 05`.
|
|
30670
|
-
*/
|
|
30671
|
-
function taskLabelNumber(text) {
|
|
30672
|
-
const digits = LABEL.exec(text.replace(/^(\*\*|__)/, ""))?.[1];
|
|
30673
|
-
if (digits === void 0 || String(Number(digits)) !== digits) return null;
|
|
30674
|
-
return Number(digits);
|
|
30675
|
-
}
|
|
30676
|
-
//#endregion
|
|
30677
30772
|
//#region src/plan-workspace/status.ts
|
|
30678
30773
|
const RETIRED_SEGMENT = /(^|\/)retired\//;
|
|
30679
30774
|
/**
|
|
30680
|
-
* The nearest `Task <n>` label at or before the
|
|
30681
|
-
*
|
|
30682
|
-
*
|
|
30775
|
+
* The nearest `Task <n>` label at or before the box on `line`, the box's own title winning. The
|
|
30776
|
+
* walk shares `fencedLines` with `plan:brief`, so a fenced `Task <n>` line is no more a label here
|
|
30777
|
+
* than it is a task boundary there.
|
|
30683
30778
|
*/
|
|
30684
|
-
function enclosingTaskNumber(planText,
|
|
30685
|
-
|
|
30686
|
-
|
|
30779
|
+
function enclosingTaskNumber(planText, line) {
|
|
30780
|
+
const lines = planText.split("\n");
|
|
30781
|
+
const fenced = fencedLines(lines);
|
|
30687
30782
|
let label = null;
|
|
30688
|
-
for (
|
|
30689
|
-
const
|
|
30690
|
-
if (
|
|
30691
|
-
if (
|
|
30692
|
-
|
|
30693
|
-
|
|
30694
|
-
continue;
|
|
30695
|
-
}
|
|
30696
|
-
if (inFence) continue;
|
|
30697
|
-
const text = box?.[2] ?? HEADING.exec(line)?.[2];
|
|
30698
|
-
const n = text === void 0 ? null : taskLabelNumber(text);
|
|
30783
|
+
for (let index = 0; index < lines.length; index++) {
|
|
30784
|
+
const text = lines[index] ?? "";
|
|
30785
|
+
if (index === line) return taskLabelNumber(BOX.exec(text)?.[2] ?? "") ?? label;
|
|
30786
|
+
if (fenced[index] === true) continue;
|
|
30787
|
+
const title = BOX.exec(text)?.[2] ?? HEADING.exec(text)?.[2];
|
|
30788
|
+
const n = title === void 0 ? null : taskLabelNumber(title);
|
|
30699
30789
|
if (n !== null) label = n;
|
|
30700
30790
|
}
|
|
30701
30791
|
return null;
|
|
@@ -30718,7 +30808,7 @@ function planStatus(planText, planPath) {
|
|
|
30718
30808
|
outstanding: open.length,
|
|
30719
30809
|
declined: boxes.filter((box) => box.declined).length,
|
|
30720
30810
|
next: nextBox === void 0 || nextBox.title === "" ? null : nextBox.title,
|
|
30721
|
-
nextTask: nextBox === void 0 ? null : enclosingTaskNumber(planText,
|
|
30811
|
+
nextTask: nextBox === void 0 ? null : enclosingTaskNumber(planText, nextBox.line),
|
|
30722
30812
|
onlyRetireLeft: open.length > 0 && open.every((box) => box.retireTask),
|
|
30723
30813
|
notes
|
|
30724
30814
|
};
|
|
@@ -30735,17 +30825,13 @@ function isTaskLabel(text, n) {
|
|
|
30735
30825
|
*/
|
|
30736
30826
|
function extractTask(planText, n) {
|
|
30737
30827
|
const lines = planText.split("\n");
|
|
30738
|
-
|
|
30828
|
+
const fenced = fencedLines(lines);
|
|
30739
30829
|
let start = -1;
|
|
30740
30830
|
let shape = null;
|
|
30741
30831
|
let end = lines.length;
|
|
30742
30832
|
for (let index = 0; index < lines.length; index++) {
|
|
30833
|
+
if (fenced[index] === true) continue;
|
|
30743
30834
|
const line = lines[index] ?? "";
|
|
30744
|
-
if (FENCE.test(line)) {
|
|
30745
|
-
inFence = !inFence;
|
|
30746
|
-
continue;
|
|
30747
|
-
}
|
|
30748
|
-
if (inFence) continue;
|
|
30749
30835
|
const heading = HEADING.exec(line);
|
|
30750
30836
|
const box = BOX.exec(line);
|
|
30751
30837
|
if (shape === null) {
|
|
@@ -31171,7 +31257,7 @@ setForgejoLogger(log$1);
|
|
|
31171
31257
|
* `local <sha>[-dirty]` otherwise -- so an artifact built from the working tree
|
|
31172
31258
|
* can't pass itself off as the published release whose version it carries.
|
|
31173
31259
|
*/
|
|
31174
|
-
const displayVersion = "0.84.
|
|
31260
|
+
const displayVersion = "0.84.2";
|
|
31175
31261
|
/**
|
|
31176
31262
|
* The registered `bst` subcommand map, exported so both the JSON entry path below (resolving a
|
|
31177
31263
|
* command name before citty parses anything) and the coverage test (enumerating every
|