Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Notable changes to house-rules. Versions follow [semantic versioning](https://se

## Unreleased

- `hr-what-next`'s `scripts/stage-line.mjs` reads the `trigger:` clause of an "Approved, not built:" line outside Markdown code spans: a `;`, `recheck:` or `done-when:` quoted in a code span after the real `trigger:` no longer ends the clause early, and a `trigger:` quoted in a code span is not taken for the field, whatever precedes its backtick (spans of any backtick run length, using the same masking as the plan parser).
- `hr-what-next`'s `check-open-work.mjs` judges each offer a "Approved, not built:" line names by that offer's own clause (asked, sent, relayed or awaiting a reply), not by words anywhere in the line, (clauses split at `;`, `,` and "and"; approved, selected or "not sent" closes it; "awaiting the user's reply" and "waiting for the user reply" open it; a `trigger:` clause is not read, but an offer written after it is), so a `trigger: pending local build` no longer makes an approved offer count as unanswered and three stage lines each awaiting a reply now give three unanswered offers; and an open stage line whose `trigger:` waits on another thread (an upstream, other or spelled-out "pull request 4", a maintainer) gets the same missing-`recheck:` warning as the line deferred (the spelled-out forms now count for deferred lines too). The stage-line reading moves to `scripts/stage-line.mjs`.
- `hr-code-review`'s seat client now passes `MODEL_PROVIDER` to a Codex seat (codex-acp reads it to choose the provider for new sessions, and the narrowed environment dropped it; no other seat, and not `DEFAULT_AUTH_REQUEST` or `INITIAL_AGENT_MODE`, gets it), and `references/reviewer-role.md` says the parent leaves protected paths (secrets, `.env` files, credentials) out of a large diff it saves into the review root and lists what it left out.
- `hr-read-reddit` and the README say that Reddit announced RSS support ends November 13, 2026, so feeds can fail, and tell readers to use the partial search fallback when one does (on the cutoff day search feeds answered HTTP 403 while a thread feed still answered); the README's dark banner and overview image URLs carry `?v=1`, per the `web-image-formats` rule (bump it in the commit that changes either image's bytes).
Expand Down
2 changes: 1 addition & 1 deletion skills/hr-what-next/scripts/plan-ledger.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const GITHUB_REF = /^([\w.-]+\/[\w.-]+)#(\d+)$/;
* at the next run of exactly the same length; a run with no closing run is literal text and is skipped whole.
* @param {string} text
*/
function maskCodeSpans(text) {
export function maskCodeSpans(text) {
let out = "";
let at = 0;
while (at < text.length) {
Expand Down
19 changes: 11 additions & 8 deletions skills/hr-what-next/scripts/stage-line.mjs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { maskCodeSpans } from "./plan-ledger.mjs";

// Reading an "Approved, not built:" ledger line: its `trigger:` clause says what the stage waits on, and each
// offer the rest of the line names is judged by its own clause.

// The real `trigger:` field (the last one not written as `trigger:` in backticks) and its clause, which ends at ";",
// `recheck:` or `done-when:`. Offers written after the clause stay part of the rest of the line.
// The real `trigger:` field (the last one outside a Markdown code span) and its clause, which ends at ";", `recheck:`
// or `done-when:` outside code. Offers written after the clause stay part of the rest of the line.
/** @param {string} text @returns {{ trigger: string, rest: string }} */
export function splitStage(text) {
const starts = [...text.matchAll(/(?<!`)\btrigger:/gi)];
const start = starts.at(-1)?.index;
// Code spans are blanked, same length, so a field name or ";" quoted in code is not read as syntax.
const plain = maskCodeSpans(text);
const start = [...plain.matchAll(/\btrigger:/gi)].at(-1)?.index;
if (start === undefined) {
return { trigger: "", rest: text };
}
const after = text.slice(start + "trigger:".length);
const end = after.search(/;|\b(?:recheck|done-when):/i);
const cut = end < 0 ? after.length : end;
return { trigger: after.slice(0, cut), rest: text.slice(0, start) + after.slice(cut) };
const from = start + "trigger:".length;
const end = plain.slice(from).search(/;|\b(?:recheck|done-when):/i);
const cut = end < 0 ? text.length : from + end;
return { trigger: text.slice(from, cut), rest: text.slice(0, start) + text.slice(cut) };
}

// What the line says about an offer's reply: asked, sent or relayed, or a reply, answer or selection awaited.
Expand Down
38 changes: 38 additions & 0 deletions skills/hr-what-next/test/stage-line.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { expect, test } from "bun:test";
import { splitStage } from "../scripts/stage-line.mjs";

test("a semicolon, recheck: or done-when: inside a code span after the real trigger: stays in the trigger clause", () => {
for (const quoted of ["`a; b`", "`recheck: 2099-01-01`", "`done-when: pr-merged x/y#1`"]) {
const { trigger, rest } = splitStage(
`Approved, not built: export trigger: upstream release ${quoted} ships`,
);
expect(trigger).toContain(quoted);
expect(trigger).toContain("ships");
expect(rest).not.toContain("ships");
}
});

test("the clause still ends at a real semicolon, recheck: or done-when: outside code", () => {
expect(splitStage("export trigger: PR 4 lands; asked in Offer X-1").trigger).toBe(" PR 4 lands");
expect(splitStage("export trigger: PR 4 lands recheck: 2099-01-01").trigger).toBe(" PR 4 lands ");
expect(splitStage("export trigger: PR 4 lands done-when: pr-merged a/b#4").trigger).toBe(
" PR 4 lands ",
);
});

test("a trigger: written inside a code span is not the field", () => {
const text = "export, the `trigger: x` field described trigger: PR 4 lands";
expect(splitStage(text).trigger).toBe(" PR 4 lands");
expect(splitStage("export, the `see trigger: x` field").trigger).toBe("");
});

test("code spans of any backtick run length are masked, and an escaped backtick opens none", () => {
expect(splitStage("export, the ``trigger: x`` field").trigger).toBe("");
const quoted = splitStage("Approved, not built: export trigger: ``a; b`` ships").trigger;
expect(quoted).toContain("ships");
expect(quoted).toContain("``a; b``");
expect(splitStage("export trigger: ``a; b`` ships; asked in Offer X-1").trigger).toBe(
" ``a; b`` ships",
);
expect(splitStage("export \\`trigger: PR 4 lands").trigger).toBe(" PR 4 lands");
});
Loading