@bensandee/tooling 0.95.0 → 0.96.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 +230 -32
- package/package.json +1 -1
- package/tooling.schema.json +6 -2
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.96.0"
|
|
179
179
|
});
|
|
180
180
|
//#endregion
|
|
181
181
|
//#region ../cli-kit/src/logger.ts
|
|
@@ -1747,7 +1747,8 @@ const LintSweepConfigSchema = z.strictObject({
|
|
|
1747
1747
|
const AgentEgressConfigSchema = z.strictObject({ browseHosts: z.array(z.string().min(1)).optional().meta({ description: "Host patterns this repo's agent containers may fetch from, on top of the hosts every run needs: an exact host (`fastdl.mongodb.org`) or a wildcard over its leftmost label (`*.example.com`). No ports. These join the browse lane, which serves GET and HEAD only." }) });
|
|
1748
1748
|
const AgentConfigSchema = z.strictObject({
|
|
1749
1749
|
enabled: z.boolean().optional().meta({ description: "The repo's explicit consent to the agent (default false). Only `true`, read from the repo's **default branch**, lets the agent work the repo: claim units, run sweeps, recover orphans, merge, act on a chat proposal or start a new spec. A missing key, `false`, a missing `.tooling.json` or a read that fails all mean not enabled, and the answer is re-read within about a minute. Every other `agent.*` key below has no effect without this one; `repo:sync` warns when the block sets one of them and this key is not `true`. Where this is `true`, `repo:sync` also installs `@bensandee/agent-cli`. `pnpm exec bst setup:agent` sets it to true and runs the sync. Provisioning the agent's labels is not consent, and is never gated by this key." }),
|
|
1750
|
-
autonomy: z.boolean().optional().meta({ description: "
|
|
1750
|
+
autonomy: z.boolean().optional().meta({ description: "Retired and ignored -- it armed neither the advance rung nor the sweeps it once did. Advance is now the unit's own `agent-flags/advance` or `agent-flags/autopilot` label, and the trunk-red detector and world-changed retrigger sweep read `agent.trunkRed`. The key is still accepted so an existing config keeps syncing; the agent warns once per launch for a repo that still sets it." }),
|
|
1751
|
+
trunkRed: z.boolean().optional().meta({ description: "Whether this repo's trunk-red detector and world-changed retrigger sweep run (default: on). The detector files a tracking issue when the default branch's combined status settles red and labels it agent/spec, and closes that issue with a comment if the branch recovers on its own before a human acts on it. The retrigger sweep re-queues a pull request labelled agent-flags/autopilot parked at a -stuck or -failed terminal when the fact its park recorded stops being true (the default branch went green, or moved past a conflicted base). A missing key means on; false disarms both." }),
|
|
1751
1752
|
preRunRebase: z.boolean().optional().meta({ description: "Merge the current default branch into the work branch before the container starts (default true) -- for an implement (execute) run, and for a spec or plan rerun on an existing branch. A clean merge or an already-up-to-date branch runs the container as is; a conflicting merge is aborted, recorded on the run's status comment, and the run proceeds on the un-synced branch -- a conflict, timeout or failed merge never fails the run, for any phase. Only the execute phase keeps the two-strike rule: two consecutive conflicting attempts hand the unit to agent/repair before any container runs, while a docs run just tries again on its next slice. Set false to skip the attempt entirely for this repo, in every phase: no fetch, no merge, no abort, and the branch is worked exactly as cloned." }),
|
|
1752
1753
|
backlog: z.boolean().optional().meta({ description: "Whether the agent may self-trigger backlog runs consuming this repo's .agent/findings/ ledger (default: on wherever the ledger exists, which only happens in a repo whose own review loop wrote it). A backlog run opens a pull request that fixes or refutes the pending findings; false disarms it, leaving the ledger to accumulate for a human. A run starts only once at least three findings are pending or one is older than seven days." }),
|
|
1753
1754
|
lintSweep: LintSweepConfigSchema.optional().meta({ description: "The lint-warning sweep: a periodic agent job that measures this repo's oxlint warnings on its default branch in a container and, past minWarnings, opens a pull request carrying a generated plan for an agent/implement run to work off." }),
|
|
@@ -1829,7 +1830,7 @@ const ToolingConfigSchema = z.strictObject({
|
|
|
1829
1830
|
]).optional().meta({ description: "What compiles the project. Detected from the package.json build script, then config-file presence. Setting this key pins its value, even one that equals detection, and repo:sync keeps it until the key is deleted." }),
|
|
1830
1831
|
checks: ChecksConfigSchema.optional().meta({ description: "Check-pipeline overrides: extra steps to add, and steps to skip (unioned with CI defaults)." }),
|
|
1831
1832
|
lint: LintConfigSchema.optional().meta({ description: "bst lint:types (the type-aware oxlint gate) overrides. See lint.strictTypeAware." }),
|
|
1832
|
-
agent: AgentConfigSchema.optional().meta({ description: "agent:start behaviour overrides. Opt-in:
|
|
1833
|
+
agent: AgentConfigSchema.optional().meta({ description: "agent:start behaviour overrides. Opt-in: agent.enabled is the consent gate -- every other key in the block does nothing without it." }),
|
|
1833
1834
|
branchPrune: BranchPruneConfigSchema.optional().meta({ description: "bst branch:prune overrides: mergedDays/backupDays windows, and extra never-touch exclusions layered on top of the hard-coded list." }),
|
|
1834
1835
|
issues: IssuesConfigSchema.optional().meta({ description: "Repo-level issue policy -- currently just the default reviewer(s) `bst issue:create` assigns. The natural home for any future issue-policy key." })
|
|
1835
1836
|
});
|
|
@@ -2613,6 +2614,13 @@ function resolveComposeFileSet(readFile, projectRoot, composeFiles) {
|
|
|
2613
2614
|
//#endregion
|
|
2614
2615
|
//#region ../forgejo/src/fetcher.ts
|
|
2615
2616
|
const LONG_FETCH_TIMEOUT_MS = 6e5;
|
|
2617
|
+
function morePagesFollow(res, pageLength, received, pageSize) {
|
|
2618
|
+
if (pageLength === 0) return false;
|
|
2619
|
+
const raw = res.headers.get("x-total-count")?.trim() ?? "";
|
|
2620
|
+
const total = raw === "" ? NaN : Number(raw);
|
|
2621
|
+
if (!Number.isSafeInteger(total) || total < 0) return pageLength >= pageSize;
|
|
2622
|
+
return received < total;
|
|
2623
|
+
}
|
|
2616
2624
|
//#endregion
|
|
2617
2625
|
//#region ../forgejo/src/fetch-retry.ts
|
|
2618
2626
|
const DEFAULT_RETRY_POLICY = {
|
|
@@ -5524,7 +5532,7 @@ function buildManagedDevDeps(ctx) {
|
|
|
5524
5532
|
const devDeps = { ...rootDevDeps(ctx.config) };
|
|
5525
5533
|
if (!isMonorepo) Object.assign(devDeps, perPackageDevDeps(ctx.config));
|
|
5526
5534
|
if (ctx.config.stack !== "none") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.15.0";
|
|
5527
|
-
if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.
|
|
5535
|
+
if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.96.0";
|
|
5528
5536
|
if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.17.0";
|
|
5529
5537
|
if (wantsAgentCli(ctx)) devDeps[AGENT_CLI_PACKAGE] = "0.1.3";
|
|
5530
5538
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
@@ -9535,11 +9543,12 @@ const PLANNING_GUIDE_BODY = [
|
|
|
9535
9543
|
"",
|
|
9536
9544
|
"## Working through a plan",
|
|
9537
9545
|
"",
|
|
9538
|
-
`Do the tasks in order, commit after each, and tick a box only once its result is verified. Each task starts red -- write its test, watch it fail, make it pass -- and its commit body carries the testing guide's \`${TEST_EVIDENCE_PREFIXES.red}\` or \`${TEST_EVIDENCE_PREFIXES.noTest}\` line. A box you cannot finish keeps its text exactly as written and gets one
|
|
9546
|
+
`Do the tasks in order, commit after each, and tick a box only once its result is verified. Each task starts red -- write its test, watch it fail, make it pass -- and its commit body carries the testing guide's \`${TEST_EVIDENCE_PREFIXES.red}\` or \`${TEST_EVIDENCE_PREFIXES.noTest}\` line. A box you cannot finish, or cannot finish in full, keeps its text exactly as written and gets one of four notes underneath:`,
|
|
9539
9547
|
"",
|
|
9540
9548
|
"- `**Progress:**` -- partly done: what landed, what remains, where to pick up. The next pass resumes it.",
|
|
9541
9549
|
"- `**Blocked:**` -- tried and failing: the error. The next pass retries it.",
|
|
9542
9550
|
"- `**Declined:**` -- must not be done, with the reason: a standing rule, a repo convention, or a check that found it moot. No pass touches it again. \"Too hard\" or \"out of budget\" is Progress, not Declined.",
|
|
9551
|
+
"- `**Deferred:** Task <n> -- <reason>` -- part of the box waits on a later task. Split that box first, so the work that waits is a box of its own, and defer only that one; deferring the whole box drops the part that does not wait. The note says which task and what in it the box waits on, both on that one line. The same plan-only commit adds `- [ ] **Carried from Task <source>:** <the work>` to that task, which is the work going on there; the target cannot be the retire task. The deferral then resolves the box: it says \"later\", not \"never\". With no carried box in the target, nothing is deferred and the box stays outstanding, counted against every later pass.",
|
|
9543
9552
|
"",
|
|
9544
9553
|
"To run a plan through subagents, see the subagents guide."
|
|
9545
9554
|
].join("\n");
|
|
@@ -10163,7 +10172,7 @@ function reportFailures(failed, command) {
|
|
|
10163
10172
|
function generateMigratePrompt(results, config, detected) {
|
|
10164
10173
|
const sections = [];
|
|
10165
10174
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
10166
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.
|
|
10175
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.96.0 repo:sync\` on ${timestamp}_\n`);
|
|
10167
10176
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
10168
10177
|
if (changed.length > 0) {
|
|
10169
10178
|
const items = changed.map((r) => {
|
|
@@ -14139,7 +14148,7 @@ const LabelSchema$1 = z.object({
|
|
|
14139
14148
|
exclusive: z.boolean().optional()
|
|
14140
14149
|
});
|
|
14141
14150
|
const LabelListSchema = z.array(LabelSchema$1);
|
|
14142
|
-
const LABELS_PAGE_SIZE =
|
|
14151
|
+
const LABELS_PAGE_SIZE = 50;
|
|
14143
14152
|
const LABELS_MAX_PAGES = 20;
|
|
14144
14153
|
async function fetchAllForgejoLabels(executor, conn) {
|
|
14145
14154
|
const labels = [];
|
|
@@ -14152,7 +14161,7 @@ async function fetchAllForgejoLabels(executor, conn) {
|
|
|
14152
14161
|
const parsed = LabelListSchema.safeParse(await res.json());
|
|
14153
14162
|
if (!parsed.success) throw new UnexpectedError(`Unexpected label-list response: ${parsed.error.message}`);
|
|
14154
14163
|
labels.push(...parsed.data);
|
|
14155
|
-
if (parsed.data.length
|
|
14164
|
+
if (!morePagesFollow(res, parsed.data.length, labels.length, LABELS_PAGE_SIZE)) break;
|
|
14156
14165
|
}
|
|
14157
14166
|
return labels;
|
|
14158
14167
|
}
|
|
@@ -14276,7 +14285,7 @@ async function listForgejoComments(executor, conn, number, options = {}) {
|
|
|
14276
14285
|
}, issueNotFoundMessage(conn, number));
|
|
14277
14286
|
const parsed = parseCommentListOrThrow(await res.json());
|
|
14278
14287
|
comments.push(...parsed.map(normalizeComment));
|
|
14279
|
-
if (parsed.length
|
|
14288
|
+
if (!morePagesFollow(res, parsed.length, comments.length, COMMENTS_PAGE_SIZE)) break;
|
|
14280
14289
|
if (page === COMMENTS_MAX_PAGES) {
|
|
14281
14290
|
log.warn(`Comment listing for #${String(number)} hit the ${String(COMMENTS_MAX_PAGES)}-page cap; results may be truncated`);
|
|
14282
14291
|
options.onPageCap?.();
|
|
@@ -14392,7 +14401,10 @@ async function fetchIssueListPage(executor, conn, params) {
|
|
|
14392
14401
|
verb: "list issues",
|
|
14393
14402
|
resource: conn.repository
|
|
14394
14403
|
}, repoNotFoundMessage(conn));
|
|
14395
|
-
return
|
|
14404
|
+
return {
|
|
14405
|
+
raw: parseIssueListOrThrow(await res.json()),
|
|
14406
|
+
res
|
|
14407
|
+
};
|
|
14396
14408
|
}
|
|
14397
14409
|
function normalizeDetailedIssues(raw, type) {
|
|
14398
14410
|
const normalized = raw.map(normalizeIssue);
|
|
@@ -14403,7 +14415,7 @@ const DETAILED_LIST_MAX_PAGES = 20;
|
|
|
14403
14415
|
async function listForgejoIssuesDetailedPaged(executor, conn, query, options) {
|
|
14404
14416
|
const issues = [];
|
|
14405
14417
|
for (let page = 1; page <= DETAILED_LIST_MAX_PAGES; page++) {
|
|
14406
|
-
const raw = await fetchIssueListPage(executor, conn, issueListParams(query.type, {
|
|
14418
|
+
const { raw } = await fetchIssueListPage(executor, conn, issueListParams(query.type, {
|
|
14407
14419
|
...query,
|
|
14408
14420
|
limit: DETAILED_LIST_PAGE_SIZE,
|
|
14409
14421
|
page
|
|
@@ -18316,22 +18328,90 @@ function taskLabelId(text) {
|
|
|
18316
18328
|
return `${digits}${match?.[2] ?? ""}`;
|
|
18317
18329
|
}
|
|
18318
18330
|
//#endregion
|
|
18331
|
+
//#region src/plan-workspace/plan-units.ts
|
|
18332
|
+
const AFTER_LABEL = /^[ \t]*(?::|\.|--?)?[ \t]*/;
|
|
18333
|
+
function planTask(headingText, label) {
|
|
18334
|
+
const unwrapped = headingText.replace(/^(\*\*|__)/, "").replace(/(\*\*|__)$/, "");
|
|
18335
|
+
return {
|
|
18336
|
+
label,
|
|
18337
|
+
title: unwrapped.slice(unwrapped.indexOf(label) + label.length).replace(AFTER_LABEL, "").trim()
|
|
18338
|
+
};
|
|
18339
|
+
}
|
|
18340
|
+
function groupPlanUnits(text, boxes) {
|
|
18341
|
+
const lines = text.split("\n");
|
|
18342
|
+
const fenced = fencedLines(lines);
|
|
18343
|
+
const boxAtLine = /* @__PURE__ */ new Map();
|
|
18344
|
+
for (const box of boxes) boxAtLine.set(box.line, box);
|
|
18345
|
+
const units = [];
|
|
18346
|
+
const open = [];
|
|
18347
|
+
for (const [index, rawLine] of lines.entries()) {
|
|
18348
|
+
if (fenced[index] === true) continue;
|
|
18349
|
+
const line = rawLine ?? "";
|
|
18350
|
+
const heading = HEADING.exec(line);
|
|
18351
|
+
if (heading !== null) {
|
|
18352
|
+
const level = (heading[1] ?? "").length;
|
|
18353
|
+
while (open.length > 0 && (open[open.length - 1]?.level ?? 0) >= level) open.pop();
|
|
18354
|
+
const headingText = (heading[2] ?? "").trim();
|
|
18355
|
+
const label = taskLabelId(headingText);
|
|
18356
|
+
if (label === null) continue;
|
|
18357
|
+
const unit = {
|
|
18358
|
+
key: headingText,
|
|
18359
|
+
task: planTask(headingText, label),
|
|
18360
|
+
boxes: []
|
|
18361
|
+
};
|
|
18362
|
+
units.push(unit);
|
|
18363
|
+
open.push({
|
|
18364
|
+
level,
|
|
18365
|
+
unit
|
|
18366
|
+
});
|
|
18367
|
+
continue;
|
|
18368
|
+
}
|
|
18369
|
+
const box = boxAtLine.get(index);
|
|
18370
|
+
if (box === void 0) continue;
|
|
18371
|
+
const section = open[open.length - 1];
|
|
18372
|
+
if (section !== void 0) {
|
|
18373
|
+
section.unit.boxes.push(box);
|
|
18374
|
+
continue;
|
|
18375
|
+
}
|
|
18376
|
+
units.push({
|
|
18377
|
+
key: box.title,
|
|
18378
|
+
task: null,
|
|
18379
|
+
boxes: [box]
|
|
18380
|
+
});
|
|
18381
|
+
}
|
|
18382
|
+
return units;
|
|
18383
|
+
}
|
|
18384
|
+
//#endregion
|
|
18319
18385
|
//#region src/plan-workspace/plan-boxes.ts
|
|
18320
18386
|
const BOX_LINE_REGEX = /^[ \t]*[-*] \[([ xX])\][ \t]*(.*)$/;
|
|
18321
18387
|
const HEADING_REGEX = /^#{1,6} /;
|
|
18322
18388
|
const DECLINED_LINE_REGEX = /^[ \t]*(?:[-*][ \t]+)?\*\*Declined:\*\*[ \t]*(.*)$/;
|
|
18323
|
-
const NOTE_LINE_REGEX = /^[ \t]*(?:[-*][ \t]+)?\*\*(Progress|Blocked|Declined):\*\*[ \t]*(.*)$/;
|
|
18389
|
+
const NOTE_LINE_REGEX = /^[ \t]*(?:[-*][ \t]+)?\*\*(Progress|Blocked|Declined|Deferred):\*\*[ \t]*(.*)$/;
|
|
18390
|
+
const DEFERRED_LINE_REGEX = /^[ \t]*(?:[-*][ \t]+)?\*\*Deferred:\*\*[ \t]*(.*)$/;
|
|
18391
|
+
const DEFERRAL_SEPARATOR_REGEX = /^[ \t]*(?::|\.|--|-)?[ \t]*/;
|
|
18324
18392
|
const RETIRE_TASK_REGEX = /^(?:\*\*|__)?Retire this plan(?![A-Za-z0-9])/;
|
|
18393
|
+
function describeDeferralCheck(check) {
|
|
18394
|
+
switch (check) {
|
|
18395
|
+
case "empty-reason": return "the note names no reason";
|
|
18396
|
+
case "no-target-label": return "the note names no target task";
|
|
18397
|
+
case "no-own-task": return "the box names no task of its own";
|
|
18398
|
+
case "target-not-found": return "no task in the plan carries that label";
|
|
18399
|
+
case "target-not-later": return "the target task does not come later in the plan";
|
|
18400
|
+
case "target-is-retire": return "the target is the retire task";
|
|
18401
|
+
case "no-carried-box": return "the target task holds no box carried from this one";
|
|
18402
|
+
}
|
|
18403
|
+
}
|
|
18325
18404
|
function isOutstanding(box, options) {
|
|
18326
|
-
if (box.checked || box.declined) return false;
|
|
18405
|
+
if (box.checked || box.declined || box.deferred) return false;
|
|
18327
18406
|
return !(box.retireTask && options?.planRetired === true);
|
|
18328
18407
|
}
|
|
18329
18408
|
function noteKind(label) {
|
|
18330
18409
|
if (label === "Progress") return "progress";
|
|
18331
18410
|
if (label === "Blocked") return "blocked";
|
|
18411
|
+
if (label === "Deferred") return "deferred";
|
|
18332
18412
|
return "declined";
|
|
18333
18413
|
}
|
|
18334
|
-
const PARAGRAPH_BREAK_REGEX = /^[ \t]*(?:[-*+][ \t]|[0-9]+[.)][ \t]|\*\*(?:Progress|Blocked|Declined):\*\*|<!--)/;
|
|
18414
|
+
const PARAGRAPH_BREAK_REGEX = /^[ \t]*(?:[-*+][ \t]|[0-9]+[.)][ \t]|\*\*(?:Progress|Blocked|Declined|Deferred):\*\*|<!--)/;
|
|
18335
18415
|
const ABBREVIATION_REGEX = /(?:^|[^A-Za-z])(?:e\.g|i\.e|etc|vs|cf)$/i;
|
|
18336
18416
|
function boxParagraph(lines, fenced, start, end, first) {
|
|
18337
18417
|
const parts = [first];
|
|
@@ -18365,6 +18445,100 @@ function unwrapEmphasis(rawTitle) {
|
|
|
18365
18445
|
if (emphasis === void 0 || !rawTitle.endsWith(emphasis)) return rawTitle;
|
|
18366
18446
|
return rawTitle.slice(emphasis.length, -emphasis.length).trim();
|
|
18367
18447
|
}
|
|
18448
|
+
function deferredLineOf(lines, fenced, start, blockEnd) {
|
|
18449
|
+
for (let index = start + 1; index < blockEnd; index++) {
|
|
18450
|
+
if (fenced[index] === true) continue;
|
|
18451
|
+
const match = DEFERRED_LINE_REGEX.exec(lines[index] ?? "");
|
|
18452
|
+
if (match === null) continue;
|
|
18453
|
+
return (match[1] ?? "").trim();
|
|
18454
|
+
}
|
|
18455
|
+
return null;
|
|
18456
|
+
}
|
|
18457
|
+
function readDeferralNote(text) {
|
|
18458
|
+
if (text === "") return {
|
|
18459
|
+
target: null,
|
|
18460
|
+
reason: "",
|
|
18461
|
+
failed: "empty-reason"
|
|
18462
|
+
};
|
|
18463
|
+
const target = taskLabelId(text);
|
|
18464
|
+
if (target === null) return {
|
|
18465
|
+
target: null,
|
|
18466
|
+
reason: "",
|
|
18467
|
+
failed: "no-target-label"
|
|
18468
|
+
};
|
|
18469
|
+
const reason = text.slice(text.indexOf(target) + target.length).replace(DEFERRAL_SEPARATOR_REGEX, "").trim();
|
|
18470
|
+
if (reason === "") return {
|
|
18471
|
+
target,
|
|
18472
|
+
reason: "",
|
|
18473
|
+
failed: "empty-reason"
|
|
18474
|
+
};
|
|
18475
|
+
return {
|
|
18476
|
+
target,
|
|
18477
|
+
reason,
|
|
18478
|
+
failed: null
|
|
18479
|
+
};
|
|
18480
|
+
}
|
|
18481
|
+
function ownTaskLabel(box, units) {
|
|
18482
|
+
const unit = units.find((candidate) => candidate.boxes.some((other) => other.line === box.line));
|
|
18483
|
+
const fromSection = unit?.task?.label ?? null;
|
|
18484
|
+
if (fromSection !== null) return fromSection;
|
|
18485
|
+
return unit?.task === void 0 ? null : taskLabelId(box.title);
|
|
18486
|
+
}
|
|
18487
|
+
function holdsCarriedBox(unit, sourceLabel) {
|
|
18488
|
+
const escaped = sourceLabel.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18489
|
+
const carried = new RegExp(`^Carried from Task ${escaped}(?![0-9A-Za-z]):`);
|
|
18490
|
+
return unit.boxes.some((box) => carried.test(box.title));
|
|
18491
|
+
}
|
|
18492
|
+
function resolveDeferrals(boxes, units) {
|
|
18493
|
+
for (const { box, deferredLine } of boxes) {
|
|
18494
|
+
if (deferredLine === null || box.checked || box.declined) continue;
|
|
18495
|
+
const deferral = readDeferralNote(deferredLine);
|
|
18496
|
+
box.deferral = deferral;
|
|
18497
|
+
if (deferral.failed !== null || deferral.target === null) continue;
|
|
18498
|
+
const ownIndex = units.findIndex((unit) => unit.boxes.some((other) => other.line === box.line));
|
|
18499
|
+
const sourceLabel = ownTaskLabel(box, units);
|
|
18500
|
+
if (sourceLabel === null) {
|
|
18501
|
+
box.deferral = {
|
|
18502
|
+
...deferral,
|
|
18503
|
+
failed: "no-own-task"
|
|
18504
|
+
};
|
|
18505
|
+
continue;
|
|
18506
|
+
}
|
|
18507
|
+
const targetIndexes = units.flatMap((unit, index) => unit.task?.label === deferral.target ? [index] : []);
|
|
18508
|
+
if (targetIndexes.length !== 1) {
|
|
18509
|
+
box.deferral = {
|
|
18510
|
+
...deferral,
|
|
18511
|
+
failed: "target-not-found"
|
|
18512
|
+
};
|
|
18513
|
+
continue;
|
|
18514
|
+
}
|
|
18515
|
+
const targetIndex = targetIndexes[0] ?? -1;
|
|
18516
|
+
if (targetIndex <= ownIndex) {
|
|
18517
|
+
box.deferral = {
|
|
18518
|
+
...deferral,
|
|
18519
|
+
failed: "target-not-later"
|
|
18520
|
+
};
|
|
18521
|
+
continue;
|
|
18522
|
+
}
|
|
18523
|
+
const target = units[targetIndex];
|
|
18524
|
+
if (target === void 0) continue;
|
|
18525
|
+
if (target.boxes.some((other) => other.retireTask) || target.task !== null && RETIRE_TASK_REGEX.test(target.task.title)) {
|
|
18526
|
+
box.deferral = {
|
|
18527
|
+
...deferral,
|
|
18528
|
+
failed: "target-is-retire"
|
|
18529
|
+
};
|
|
18530
|
+
continue;
|
|
18531
|
+
}
|
|
18532
|
+
if (!holdsCarriedBox(target, sourceLabel)) {
|
|
18533
|
+
box.deferral = {
|
|
18534
|
+
...deferral,
|
|
18535
|
+
failed: "no-carried-box"
|
|
18536
|
+
};
|
|
18537
|
+
continue;
|
|
18538
|
+
}
|
|
18539
|
+
box.deferred = true;
|
|
18540
|
+
}
|
|
18541
|
+
}
|
|
18368
18542
|
function parsePlanBoxes(planText) {
|
|
18369
18543
|
const lines = planText.split("\n");
|
|
18370
18544
|
const fenced = fencedLines(lines);
|
|
@@ -18379,7 +18553,7 @@ function parsePlanBoxes(planText) {
|
|
|
18379
18553
|
rawTitle: (match[2] ?? "").trim()
|
|
18380
18554
|
});
|
|
18381
18555
|
}
|
|
18382
|
-
|
|
18556
|
+
const built = boxStarts.map((box, position) => {
|
|
18383
18557
|
const nextBoxIndex = boxStarts[position + 1]?.index ?? lines.length;
|
|
18384
18558
|
let blockEnd = nextBoxIndex;
|
|
18385
18559
|
for (let index = box.index + 1; index < nextBoxIndex; index++) {
|
|
@@ -18392,13 +18566,18 @@ function parsePlanBoxes(planText) {
|
|
|
18392
18566
|
const title = stripStrong(unwrapEmphasis(firstSentence$1(boxParagraph(lines, fenced, box.index, blockEnd, box.rawTitle))));
|
|
18393
18567
|
const retireTask = RETIRE_TASK_REGEX.test(title);
|
|
18394
18568
|
if (box.checked) return {
|
|
18395
|
-
|
|
18396
|
-
|
|
18397
|
-
|
|
18398
|
-
|
|
18399
|
-
|
|
18400
|
-
|
|
18401
|
-
|
|
18569
|
+
box: {
|
|
18570
|
+
line: box.index,
|
|
18571
|
+
checked: true,
|
|
18572
|
+
declined: false,
|
|
18573
|
+
reason: null,
|
|
18574
|
+
title,
|
|
18575
|
+
retireTask,
|
|
18576
|
+
notes: [],
|
|
18577
|
+
deferred: false,
|
|
18578
|
+
deferral: null
|
|
18579
|
+
},
|
|
18580
|
+
deferredLine: null
|
|
18402
18581
|
};
|
|
18403
18582
|
const notes = [];
|
|
18404
18583
|
for (let index = box.index + 1; index < blockEnd; index++) {
|
|
@@ -18423,15 +18602,22 @@ function parsePlanBoxes(planText) {
|
|
|
18423
18602
|
}
|
|
18424
18603
|
}
|
|
18425
18604
|
return {
|
|
18426
|
-
|
|
18427
|
-
|
|
18428
|
-
|
|
18429
|
-
|
|
18430
|
-
|
|
18431
|
-
|
|
18432
|
-
|
|
18605
|
+
box: {
|
|
18606
|
+
line: box.index,
|
|
18607
|
+
checked: false,
|
|
18608
|
+
declined: reason !== null,
|
|
18609
|
+
reason,
|
|
18610
|
+
title,
|
|
18611
|
+
retireTask,
|
|
18612
|
+
notes,
|
|
18613
|
+
deferred: false,
|
|
18614
|
+
deferral: null
|
|
18615
|
+
},
|
|
18616
|
+
deferredLine: deferredLineOf(lines, fenced, box.index, blockEnd)
|
|
18433
18617
|
};
|
|
18434
18618
|
});
|
|
18619
|
+
if (built.some((entry) => entry.deferredLine !== null)) resolveDeferrals(built, groupPlanUnits(planText, built.map((entry) => entry.box)));
|
|
18620
|
+
return built.map((entry) => entry.box);
|
|
18435
18621
|
}
|
|
18436
18622
|
function countOutstandingBoxes(planText, options) {
|
|
18437
18623
|
return parsePlanBoxes(planText).filter((box) => isOutstanding(box, options)).length;
|
|
@@ -18462,6 +18648,11 @@ function planStatus(planText, planPath) {
|
|
|
18462
18648
|
kind: note.kind,
|
|
18463
18649
|
text: note.text
|
|
18464
18650
|
})));
|
|
18651
|
+
const deferrals = boxes.flatMap((box) => box.deferral === null ? [] : [{
|
|
18652
|
+
task: box.title,
|
|
18653
|
+
target: box.deferral.target,
|
|
18654
|
+
failed: box.deferral.failed
|
|
18655
|
+
}]);
|
|
18465
18656
|
const nextBox = open[0];
|
|
18466
18657
|
return {
|
|
18467
18658
|
plan: planPath,
|
|
@@ -18469,6 +18660,8 @@ function planStatus(planText, planPath) {
|
|
|
18469
18660
|
checked: boxes.filter((box) => box.checked).length,
|
|
18470
18661
|
outstanding: open.length,
|
|
18471
18662
|
declined: boxes.filter((box) => box.declined).length,
|
|
18663
|
+
deferred: boxes.filter((box) => box.deferred).length,
|
|
18664
|
+
deferrals,
|
|
18472
18665
|
next: nextBox === void 0 || nextBox.title === "" ? null : nextBox.title,
|
|
18473
18666
|
nextTask: nextBox === void 0 ? null : enclosingTaskLabel(planText, nextBox.line),
|
|
18474
18667
|
onlyRetireLeft: open.length > 0 && open.every((box) => box.retireTask),
|
|
@@ -18821,7 +19014,7 @@ async function docsSection({ git, docsExtraDirs, resolved }) {
|
|
|
18821
19014
|
path,
|
|
18822
19015
|
status: {
|
|
18823
19016
|
...status,
|
|
18824
|
-
total: status.checked + status.outstanding + status.declined
|
|
19017
|
+
total: status.checked + status.outstanding + status.declined + status.deferred
|
|
18825
19018
|
}
|
|
18826
19019
|
});
|
|
18827
19020
|
}
|
|
@@ -29006,7 +29199,12 @@ function renderStatus(report) {
|
|
|
29006
29199
|
const next = report.next ?? "(none)";
|
|
29007
29200
|
const task = report.nextTask === null || taskLabelId(next) === report.nextTask ? "" : `Task ${report.nextTask} -- `;
|
|
29008
29201
|
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" : ""}`];
|
|
29202
|
+
if (report.deferred > 0) lines.splice(1, 0, `${String(report.deferred)} deferred to a later task`);
|
|
29009
29203
|
for (const note of report.notes) lines.push(` [${note.kind}] ${note.task}: ${note.text}`);
|
|
29204
|
+
for (const deferral of report.deferrals) {
|
|
29205
|
+
if (deferral.failed === null) continue;
|
|
29206
|
+
lines.push(` [deferred -- not counted] ${deferral.task}: ${describeDeferralCheck(deferral.failed)}.`);
|
|
29207
|
+
}
|
|
29010
29208
|
return lines.join("\n");
|
|
29011
29209
|
}
|
|
29012
29210
|
const planStatusCommand = defineCommand({
|
|
@@ -29018,7 +29216,7 @@ const planStatusCommand = defineCommand({
|
|
|
29018
29216
|
plan: PLAN_ARG,
|
|
29019
29217
|
json: {
|
|
29020
29218
|
type: "boolean",
|
|
29021
|
-
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."
|
|
29219
|
+
description: "Emit one JSON document: plan, retired, checked, outstanding, declined, deferred, deferrals ([{task, target (a task label string or null), failed (empty-reason | no-target-label | no-own-task | target-not-found | target-not-later | target-is-retire | no-carried-box, or null when valid)}]), next (or null), nextTask (a task label string such as \"3\" or \"18a\", or null), onlyRetireLeft, notes ([{task, kind (progress | blocked | declined | deferred), text}]). next is null when there is none; nextTask is null when no task label encloses it."
|
|
29022
29220
|
}
|
|
29023
29221
|
},
|
|
29024
29222
|
run({ args }) {
|
|
@@ -29191,7 +29389,7 @@ async function runCli(config) {
|
|
|
29191
29389
|
//#region src/cli-main.ts
|
|
29192
29390
|
setLogLevel(resolveLogLevel({}));
|
|
29193
29391
|
setForgejoLogger(log$1);
|
|
29194
|
-
const displayVersion = "0.
|
|
29392
|
+
const displayVersion = "0.96.0";
|
|
29195
29393
|
const bstSubCommands = {
|
|
29196
29394
|
"repo:sync": syncCommand,
|
|
29197
29395
|
"checks:run": runChecksCommand,
|
package/package.json
CHANGED
package/tooling.schema.json
CHANGED
|
@@ -698,7 +698,7 @@
|
|
|
698
698
|
"additionalProperties": false
|
|
699
699
|
},
|
|
700
700
|
"agent": {
|
|
701
|
-
"description": "agent:start behaviour overrides. Opt-in:
|
|
701
|
+
"description": "agent:start behaviour overrides. Opt-in: agent.enabled is the consent gate -- every other key in the block does nothing without it.",
|
|
702
702
|
"type": "object",
|
|
703
703
|
"properties": {
|
|
704
704
|
"enabled": {
|
|
@@ -706,7 +706,11 @@
|
|
|
706
706
|
"type": "boolean"
|
|
707
707
|
},
|
|
708
708
|
"autonomy": {
|
|
709
|
-
"description": "
|
|
709
|
+
"description": "Retired and ignored -- it armed neither the advance rung nor the sweeps it once did. Advance is now the unit's own `agent-flags/advance` or `agent-flags/autopilot` label, and the trunk-red detector and world-changed retrigger sweep read `agent.trunkRed`. The key is still accepted so an existing config keeps syncing; the agent warns once per launch for a repo that still sets it.",
|
|
710
|
+
"type": "boolean"
|
|
711
|
+
},
|
|
712
|
+
"trunkRed": {
|
|
713
|
+
"description": "Whether this repo's trunk-red detector and world-changed retrigger sweep run (default: on). The detector files a tracking issue when the default branch's combined status settles red and labels it agent/spec, and closes that issue with a comment if the branch recovers on its own before a human acts on it. The retrigger sweep re-queues a pull request labelled agent-flags/autopilot parked at a -stuck or -failed terminal when the fact its park recorded stops being true (the default branch went green, or moved past a conflicted base). A missing key means on; false disarms both.",
|
|
710
714
|
"type": "boolean"
|
|
711
715
|
},
|
|
712
716
|
"preRunRebase": {
|