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 .mulch/expertise/refactoring.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type":"pattern","classification":"tactical","recorded_at":"2026-06-15T10:27:55.007Z","evidence":{"commit":"c6a07bbae64a7a8c94576195019f9d09e86d5ca8"},"dir_anchors":["src/commands"],"name":"dedupe-numeric-flag-parsers","description":"Local parseStrictPositiveInt/parseStrictNonNegativeNumber copies in src/commands/{ready,prime,rank,compact}.ts replaced by imports from src/utils/numeric-flags.ts (sd mulch-fed1).","files":[".mulch/mulch.config.yaml","src/commands/compact.ts","src/commands/prime.ts","src/commands/rank.ts","src/commands/ready.ts"],"id":"mx-ccfc7c"}
1 change: 1 addition & 0 deletions .mulch/mulch.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ domains:
- convention
- failure
quality-gates: {}
refactoring: {}
governance:
max_entries: 100
warn_entries: 150
Expand Down
6 changes: 3 additions & 3 deletions .seeds/issues.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -332,11 +332,11 @@
{"id":"mulch-ab79","title":"Add the canonical scripts/check-all.ts quiet runner copied byte-identical from templates/l5-toolkit/scripts/check-all.ts, with mulch's exported GATES manifest in the standard's order: lint, typecheck, check:agents, check:dups, check:deps, check:size, check:debt, check:coverage, check:ci-parity (last) -- note lint and typecheck must be ADDED to the manifest (they exist as scripts but are absent from the current && chain). Replace package.json check:all with `bun scripts/check-all.ts`, add `verify`: `bun run check:all`, and add scripts/check-all.test.ts. Confirm the quiet-output contract.","status":"closed","type":"task","priority":2,"plan_step_index":0,"description":"<!-- seeds:plan-backref:start -->\nStep 1 of plan pl-237d.\n\nParent seed: mulch-f16f — Adopt canonical check:all standard\nPlan template: feature\nPlan approach: Swap the && chain for the canonical quiet runner with a GATES manifest in the standard's order (folding in the already-present lint and typecheck scripts), then add the generalized ci-parity gate and the verify alias. mulch has no UI…\n\nRun `sd plan show pl-237d` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n<!-- seeds:plan-backref:end -->","createdAt":"2026-06-12T04:53:28.928Z","updatedAt":"2026-06-12T05:56:59.979Z","plan_id":"pl-237d","blocks":["mulch-c658","mulch-f16f"],"closedAt":"2026-06-12T05:56:59.979Z"}
{"id":"mulch-c658","title":"Add scripts/check-ci-parity.ts (with test) copied from templates/l5-toolkit/scripts, importing the GATES array from check-all.ts; add the check:ci-parity script as the final gate in the manifest. Reconcile any residual non-canonical gate name. Verify check:ci-parity passes against .github/workflows/ci.yml.","status":"closed","type":"task","priority":2,"plan_step_index":1,"description":"<!-- seeds:plan-backref:start -->\nStep 2 of plan pl-237d.\n\nParent seed: mulch-f16f — Adopt canonical check:all standard\nPlan template: feature\nPlan approach: Swap the && chain for the canonical quiet runner with a GATES manifest in the standard's order (folding in the already-present lint and typecheck scripts), then add the generalized ci-parity gate and the verify alias. mulch has no UI…\n\nRun `sd plan show pl-237d` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n<!-- seeds:plan-backref:end -->","createdAt":"2026-06-12T04:53:28.928Z","updatedAt":"2026-06-12T05:57:00.030Z","plan_id":"pl-237d","blocks":["mulch-c6f8","mulch-f16f"],"closedAt":"2026-06-12T05:57:00.030Z"}
{"id":"mulch-c6f8","title":"Wire .github/workflows/ci.yml to invoke the canonical gates (or `bun run check:all`) so the local manifest matches CI. Run `bun run check:all` and `bun run verify` green end-to-end with the quiet-output contract and a passing check:ci-parity.","status":"closed","type":"task","priority":2,"plan_step_index":2,"description":"<!-- seeds:plan-backref:start -->\nStep 3 of plan pl-237d.\n\nParent seed: mulch-f16f — Adopt canonical check:all standard\nPlan template: feature\nPlan approach: Swap the && chain for the canonical quiet runner with a GATES manifest in the standard's order (folding in the already-present lint and typecheck scripts), then add the generalized ci-parity gate and the verify alias. mulch has no UI…\n\nRun `sd plan show pl-237d` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n<!-- seeds:plan-backref:end -->","createdAt":"2026-06-12T04:53:28.928Z","updatedAt":"2026-06-12T05:57:00.083Z","plan_id":"pl-237d","blocks":["mulch-f16f"],"closedAt":"2026-06-12T05:57:00.083Z"}
{"id":"mulch-e3e9","title":"nightwatch patrol: 2026-06-15","status":"open","type":"task","priority":3,"createdAt":"2026-06-15T10:08:07.957Z","updatedAt":"2026-06-15T10:18:09.429Z","labels":["patrol","nightwatch"],"plan_id":"pl-b637","blockedBy":["mulch-fed1","mulch-4bd0","mulch-9465","mulch-a424","mulch-4dc4","mulch-0768"]}
{"id":"mulch-e3e9","title":"nightwatch patrol: 2026-06-15","status":"open","type":"task","priority":3,"createdAt":"2026-06-15T10:08:07.957Z","updatedAt":"2026-06-15T10:28:34.748Z","labels":["patrol","nightwatch"],"plan_id":"pl-b637","blockedBy":["mulch-4bd0","mulch-9465","mulch-a424","mulch-4dc4","mulch-0768"]}
{"id":"mulch-4ca6","title":"Canonicalize JSON `up_to_date` vs `upToDate` across upgrade/onboard output","status":"closed","type":"task","priority":3,"plan_step_index":0,"description":"<!-- seeds:plan-backref:start -->\nStep 1 of plan pl-b637.\n\nParent seed: mulch-e3e9 — nightwatch patrol: 2026-06-15\nPlan template: refactor\nPlan approach: One step per finding so each lands as an isolated PR. Independent steps (1, 2, 3, 4, 5, 6) run in parallel; the release step (7) batches them. Step 1 (JSON key canonicalization) and step 4 (suffix-boundary fix) are the only…\n\nRun `sd plan show pl-b637` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n<!-- seeds:plan-backref:end -->","createdAt":"2026-06-15T10:08:46.513Z","updatedAt":"2026-06-15T10:18:09.429Z","labels":["nightwatch"],"plan_id":"pl-b637","blocks":["mulch-0768","mulch-e3e9"],"closedAt":"2026-06-15T10:18:09.429Z","closeReason":"Landed in 49f3aef: ml upgrade --json now emits up_to_date (snake_case) to match ml onboard --json. Test + CHANGELOG updated. bun run verify 9/9 green."}
{"id":"mulch-fed1","title":"Dedupe local parseStrictPositiveInt/parseStrictNonNegativeNumber copies; import from src/utils/numeric-flags.ts","status":"open","type":"task","priority":3,"plan_step_index":1,"description":"<!-- seeds:plan-backref:start -->\nStep 2 of plan pl-b637.\n\nParent seed: mulch-e3e9 — nightwatch patrol: 2026-06-15\nPlan template: refactor\nPlan approach: One step per finding so each lands as an isolated PR. Independent steps (1, 2, 3, 4, 5, 6) run in parallel; the release step (7) batches them. Step 1 (JSON key canonicalization) and step 4 (suffix-boundary fix) are the only…\n\nRun `sd plan show pl-b637` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n<!-- seeds:plan-backref:end -->","createdAt":"2026-06-15T10:08:46.513Z","updatedAt":"2026-06-15T10:08:46.513Z","labels":["nightwatch"],"plan_id":"pl-b637","blocks":["mulch-0768","mulch-e3e9"]}
{"id":"mulch-fed1","title":"Dedupe local parseStrictPositiveInt/parseStrictNonNegativeNumber copies; import from src/utils/numeric-flags.ts","status":"closed","type":"task","priority":3,"plan_step_index":1,"description":"<!-- seeds:plan-backref:start -->\nStep 2 of plan pl-b637.\n\nParent seed: mulch-e3e9 — nightwatch patrol: 2026-06-15\nPlan template: refactor\nPlan approach: One step per finding so each lands as an isolated PR. Independent steps (1, 2, 3, 4, 5, 6) run in parallel; the release step (7) batches them. Step 1 (JSON key canonicalization) and step 4 (suffix-boundary fix) are the only…\n\nRun `sd plan show pl-b637` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n<!-- seeds:plan-backref:end -->","createdAt":"2026-06-15T10:08:46.513Z","updatedAt":"2026-06-15T10:28:34.748Z","labels":["nightwatch"],"plan_id":"pl-b637","blocks":["mulch-0768","mulch-e3e9"],"closedAt":"2026-06-15T10:28:34.748Z"}
{"id":"mulch-4bd0","title":"Replace `(err as Error).message` with `err instanceof Error ? err.message : String(err)` across src/commands and src/utils","status":"open","type":"task","priority":3,"plan_step_index":2,"description":"<!-- seeds:plan-backref:start -->\nStep 3 of plan pl-b637.\n\nParent seed: mulch-e3e9 — nightwatch patrol: 2026-06-15\nPlan template: refactor\nPlan approach: One step per finding so each lands as an isolated PR. Independent steps (1, 2, 3, 4, 5, 6) run in parallel; the release step (7) batches them. Step 1 (JSON key canonicalization) and step 4 (suffix-boundary fix) are the only…\n\nRun `sd plan show pl-b637` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n<!-- seeds:plan-backref:end -->","createdAt":"2026-06-15T10:08:46.513Z","updatedAt":"2026-06-15T10:08:46.513Z","labels":["nightwatch"],"plan_id":"pl-b637","blocks":["mulch-0768","mulch-e3e9"]}
{"id":"mulch-9465","title":"Fix path-segment boundary in src/utils/git.ts fileMatchesAny + add false-positive coverage","status":"open","type":"task","priority":2,"plan_step_index":3,"description":"<!-- seeds:plan-backref:start -->\nStep 4 of plan pl-b637.\n\nParent seed: mulch-e3e9 — nightwatch patrol: 2026-06-15\nPlan template: refactor\nPlan approach: One step per finding so each lands as an isolated PR. Independent steps (1, 2, 3, 4, 5, 6) run in parallel; the release step (7) batches them. Step 1 (JSON key canonicalization) and step 4 (suffix-boundary fix) are the only…\n\nRun `sd plan show pl-b637` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n<!-- seeds:plan-backref:end -->","createdAt":"2026-06-15T10:08:46.513Z","updatedAt":"2026-06-15T10:08:46.513Z","labels":["nightwatch"],"plan_id":"pl-b637","blocks":["mulch-0768","mulch-e3e9"]}
{"id":"mulch-a424","title":"Unify JSON vs human error wording in src/commands/ready.ts unknown-domain branch","status":"open","type":"task","priority":3,"plan_step_index":4,"description":"<!-- seeds:plan-backref:start -->\nStep 5 of plan pl-b637.\n\nParent seed: mulch-e3e9 — nightwatch patrol: 2026-06-15\nPlan template: refactor\nPlan approach: One step per finding so each lands as an isolated PR. Independent steps (1, 2, 3, 4, 5, 6) run in parallel; the release step (7) batches them. Step 1 (JSON key canonicalization) and step 4 (suffix-boundary fix) are the only…\n\nRun `sd plan show pl-b637` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n<!-- seeds:plan-backref:end -->","createdAt":"2026-06-15T10:08:46.513Z","updatedAt":"2026-06-15T10:08:46.513Z","labels":["nightwatch"],"plan_id":"pl-b637","blocks":["mulch-0768","mulch-e3e9"]}
{"id":"mulch-4dc4","title":"Backfill direct unit tests for src/utils/domain-rules.ts and src/utils/format-helpers.ts","status":"open","type":"task","priority":3,"plan_step_index":5,"description":"<!-- seeds:plan-backref:start -->\nStep 6 of plan pl-b637.\n\nParent seed: mulch-e3e9 — nightwatch patrol: 2026-06-15\nPlan template: refactor\nPlan approach: One step per finding so each lands as an isolated PR. Independent steps (1, 2, 3, 4, 5, 6) run in parallel; the release step (7) batches them. Step 1 (JSON key canonicalization) and step 4 (suffix-boundary fix) are the only…\n\nRun `sd plan show pl-b637` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n<!-- seeds:plan-backref:end -->","createdAt":"2026-06-15T10:08:46.513Z","updatedAt":"2026-06-15T10:08:46.513Z","labels":["nightwatch"],"plan_id":"pl-b637","blocks":["mulch-0768","mulch-e3e9"]}
{"id":"mulch-0768","title":"Release: run /release per .claude/commands/release.md","status":"open","type":"task","priority":3,"plan_step_index":6,"description":"<!-- seeds:plan-backref:start -->\nStep 7 of plan pl-b637.\n\nParent seed: mulch-e3e9 — nightwatch patrol: 2026-06-15\nPlan template: refactor\nPlan approach: One step per finding so each lands as an isolated PR. Independent steps (1, 2, 3, 4, 5, 6) run in parallel; the release step (7) batches them. Step 1 (JSON key canonicalization) and step 4 (suffix-boundary fix) are the only…\n\nRun `sd plan show pl-b637` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n<!-- seeds:plan-backref:end -->","createdAt":"2026-06-15T10:08:46.513Z","updatedAt":"2026-06-15T10:18:09.429Z","labels":["nightwatch"],"plan_id":"pl-b637","blockedBy":["mulch-fed1","mulch-4bd0","mulch-9465","mulch-a424","mulch-4dc4"],"blocks":["mulch-e3e9"]}
{"id":"mulch-0768","title":"Release: run /release per .claude/commands/release.md","status":"open","type":"task","priority":3,"plan_step_index":6,"description":"<!-- seeds:plan-backref:start -->\nStep 7 of plan pl-b637.\n\nParent seed: mulch-e3e9 — nightwatch patrol: 2026-06-15\nPlan template: refactor\nPlan approach: One step per finding so each lands as an isolated PR. Independent steps (1, 2, 3, 4, 5, 6) run in parallel; the release step (7) batches them. Step 1 (JSON key canonicalization) and step 4 (suffix-boundary fix) are the only…\n\nRun `sd plan show pl-b637` for the full plan (context, alternatives, sibling steps, acceptance criteria).\n<!-- seeds:plan-backref:end -->","createdAt":"2026-06-15T10:08:46.513Z","updatedAt":"2026-06-15T10:28:34.748Z","labels":["nightwatch"],"plan_id":"pl-b637","blockedBy":["mulch-4bd0","mulch-9465","mulch-a424","mulch-4dc4"],"blocks":["mulch-e3e9"]}
12 changes: 1 addition & 11 deletions src/commands/compact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getRecordSummary } from "../utils/format.ts";
import { runHooks } from "../utils/hooks.ts";
import { outputJson, outputJsonError } from "../utils/json-output.ts";
import { withFileLock } from "../utils/lock.ts";
import { parseStrictPositiveInt } from "../utils/numeric-flags.ts";
import { accent, brand, isQuiet } from "../utils/palette.ts";

// Payload sent to `pre-compact` hooks. A hook may print `{ replacement: <full
Expand All @@ -29,17 +30,6 @@ interface PreCompactPayload {
replacement?: ExpertiseRecord;
}

// Strict numeric flag parsing — see mx-5b9578 / src/commands/rank.ts.
// `Number.parseInt("10abc", 10)` silently returns 10; use regex + Number() so
// typos like `--min-group abc` or `--max-records 50xyz` are rejected.
const POSITIVE_INT_RE = /^\d+$/;

function parseStrictPositiveInt(raw: string): number | null {
if (!POSITIVE_INT_RE.test(raw)) return null;
const n = Number(raw);
return Number.isFinite(n) && n >= 1 ? n : null;
}

interface CompactCandidate {
domain: string;
type: RecordType;
Expand Down
12 changes: 1 addition & 11 deletions src/commands/prime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
} from "../utils/git.ts";
import { runHooks } from "../utils/hooks.ts";
import { outputJsonError } from "../utils/json-output.ts";
import { parseStrictPositiveInt } from "../utils/numeric-flags.ts";
import { brand, isQuiet } from "../utils/palette.ts";
import {
buildSurfaceAnnotations,
Expand Down Expand Up @@ -101,17 +102,6 @@ export function estimateRecordText(record: ExpertiseRecord): string {
return def.formatCompactLine(record);
}

// Strict numeric flag parsing — see mx-5b9578 / src/commands/rank.ts.
// `Number.parseInt("10abc", 10)` silently returns 10; use regex + Number() so
// typos like `--budget 5000abc` or `--budget 3.7` are rejected.
const POSITIVE_INT_RE = /^\d+$/;

function parseStrictPositiveInt(raw: string): number | null {
if (!POSITIVE_INT_RE.test(raw)) return null;
const n = Number(raw);
return Number.isFinite(n) && n >= 1 ? n : null;
}

export function registerPrimeCommand(program: Command): void {
program
.command("prime")
Expand Down
16 changes: 1 addition & 15 deletions src/commands/rank.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getExpertisePath, readConfig } from "../utils/config.ts";
import { filterByType, readExpertiseFile } from "../utils/expertise.ts";
import { getRecordSummary } from "../utils/format.ts";
import { outputJson, outputJsonError } from "../utils/json-output.ts";
import { parseStrictNonNegativeNumber, parseStrictPositiveInt } from "../utils/numeric-flags.ts";
import { accent } from "../utils/palette.ts";
import { computeConfirmationScore, type ScoredRecord } from "../utils/scoring.ts";

Expand All @@ -19,21 +20,6 @@ function formatScore(score: number): string {
return Number.isInteger(score) ? String(score) : score.toFixed(1);
}

const POSITIVE_INT_RE = /^\d+$/;
const NON_NEGATIVE_NUMBER_RE = /^\d+(\.\d+)?$/;

function parseStrictPositiveInt(raw: string): number | null {
if (!POSITIVE_INT_RE.test(raw)) return null;
const n = Number(raw);
return Number.isFinite(n) && n >= 1 ? n : null;
}

function parseStrictNonNegativeNumber(raw: string): number | null {
if (!NON_NEGATIVE_NUMBER_RE.test(raw)) return null;
const n = Number(raw);
return Number.isFinite(n) && n >= 0 ? n : null;
}

export function registerRankCommand(program: Command): void {
program
.command("rank")
Expand Down
12 changes: 1 addition & 11 deletions src/commands/ready.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,14 @@ import { getExpertisePath, readConfig } from "../utils/config.ts";
import { readExpertiseFile } from "../utils/expertise.ts";
import { formatTimeAgo, getRecordSummary } from "../utils/format.ts";
import { outputJson, outputJsonError } from "../utils/json-output.ts";
import { parseStrictPositiveInt } from "../utils/numeric-flags.ts";
import { accent } from "../utils/palette.ts";

interface AnnotatedRecord {
domain: string;
record: ExpertiseRecord;
}

// Strict numeric flag parsing — see mx-5b9578 / src/commands/rank.ts.
// `Number.parseInt("10abc", 10)` silently returns 10; use regex + Number() so
// typos like `--limit 10abc` or `--limit 3.7` are rejected.
const POSITIVE_INT_RE = /^\d+$/;

function parseStrictPositiveInt(raw: string): number | null {
if (!POSITIVE_INT_RE.test(raw)) return null;
const n = Number(raw);
return Number.isFinite(n) && n >= 1 ? n : null;
}

function parseDuration(input: string): number {
const match = input.match(/^(\d+)(h|d|w)$/);
if (!match) {
Expand Down
Loading