diff --git a/CHANGELOG.md b/CHANGELOG.md index 00e0a471..864a95df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ Each release of Research-Kit, newest first. The version is `KIT_VERSION` in ## Unreleased +- `decompose --limit` consistently requires a whole number of at least 1 before + transport selection. `--max-scrapes 0` remains supported (#320). + - ZIP archives declare UTF-8 names in both headers, so ordinary extractors preserve non-ASCII paths declared by the package manifest (#345). - Machine config saves preserve unpatched raw role and posture values, including typos diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a35be350..e1bab2a3 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -268,7 +268,7 @@ build approval. The shared verdict library and all gated formatting stay unchang (→ evaluate; `--staged-stdin` reads the piped path list; `--posture` answers 0 allow / 1 fail-closed / 2 unreadable-and-closed, so no shell has to parse JSON), `doctor.mjs` (`--fix-arity`), `install.mjs` (deploy + `--into` per-project binding, role-aware next steps; collector keyless instructions name the selected machine config rather than a per-run research flag, builder instructions require complete/current review, and an unresolved role prints diagnostics before role-specific advice (2026-10-08, ADR-0150); `tolerateClosedStdout` before its first write, so a reader that leaves early does not turn a finished deploy into exit 1, 2026-09-30), `install-hooks.mjs` (`--fail-closed`, `--role`; a `CONFIG-UNREADABLE` save refusal is named with exit 2 before the requested config patch is written, #364), -`new-project.mjs` (its first next step omits `--topic` once the project has one, ADR-0056), `decompose.mjs` (phase 0 CLI, refused on a builder by role), +`new-project.mjs` (its first next step omits `--topic` once the project has one, ADR-0056), `decompose.mjs` (phase 0 CLI, refused on a builder by role; `--limit` requires at least 1 at the CLI boundary, matching the library's positive search-result limit, while `--max-scrapes` still allows 0, #320), `timeline.mjs`, `audit.mjs` (lists and resolves versions through the manifest's reader), `brief.mjs` (drafts the handoff with all evaluated warning reasons, remedies and row/unknown targets, labeled as whole-corpus observations; it reports the brief's state; it, diff --git a/research-kit/README.md b/research-kit/README.md index c06356d9..466832c7 100644 --- a/research-kit/README.md +++ b/research-kit/README.md @@ -568,7 +568,7 @@ node research-kit/bin/selftest.mjs # all of it node research-kit/bin/selftest.mjs gate hook # just these files ``` -1845 tests, offline, no key and no network. The runner **awaits** every test, so `ok` means +1846 tests, offline, no key and no network. The runner **awaits** every test, so `ok` means the assertions settled (ADR-0021), and each test is raced against a watchdog (`RESEARCH_KIT_TEST_TIMEOUT`, default 60s). diff --git a/research-kit/bin/decompose.mjs b/research-kit/bin/decompose.mjs index 397e96f3..16844ce8 100644 --- a/research-kit/bin/decompose.mjs +++ b/research-kit/bin/decompose.mjs @@ -18,7 +18,7 @@ refuseUnknownFlags(flags, ['help', 'topic', 'dry-run', 'force', 'limit', 'max-sc // made 1, so `--max-scrapes` with no value quietly paid for one page. checkFlagValues(flags, { topic: 'value', recipe: 'value', transport: 'value', 'search-transport': 'value', - limit: { int: true, min: 0 }, 'max-scrapes': { int: true, min: 0 }, + limit: { int: true, min: 1 }, 'max-scrapes': { int: true, min: 0 }, }); const root = process.cwd(); diff --git a/research-kit/test/cli.test.mjs b/research-kit/test/cli.test.mjs index 3f6c7a1d..547f4811 100644 --- a/research-kit/test/cli.test.mjs +++ b/research-kit/test/cli.test.mjs @@ -1032,7 +1032,9 @@ test('a flag given a bad value, or none, is refused before anything runs', () => // Number() makes 1 - so a real run paid for one page nobody asked for. ['decompose.mjs', ['--dry-run', '--max-scrapes'], /--max-scrapes needs a value/], ['decompose.mjs', ['--dry-run', '--limit'], /--limit needs a value/], - ['decompose.mjs', ['--dry-run', '--limit', 'abc'], /--limit must be a whole number of at least 0, not "abc"/], + ['decompose.mjs', ['--dry-run', '--limit', 'abc'], /--limit must be a whole number of at least 1, not "abc"/], + ['decompose.mjs', ['--dry-run', '--limit', '0'], /--limit must be a whole number of at least 1, not "0"/], + ['decompose.mjs', ['--dry-run', '--limit', '-1'], /--limit must be a whole number of at least 1, not "-1"/], ['preflight.mjs', ['--check'], /--check needs a value/], ['gate.mjs', ['--gate', 'push'], /--gate must be one of/], ['prior.mjs', ['--file'], /--file needs a value/], @@ -1047,6 +1049,13 @@ test('a flag given a bad value, or none, is refused before anything runs', () => assert.equal(only.status, 2, `preflight --only is accepted and never read:\n${only.all.slice(0, 300)}`); }); +test('decompose accepts a search limit of one while max-scrapes still accepts zero', () => { + const r = run('decompose.mjs', ['--dry-run', '--limit', '1', '--max-scrapes', '0', '--transport', 'http-keyless'], { + root: planned('budget-floor'), + }); + assert.equal(r.status, 0, r.all); +}); + // Found 2026-09-27: new-project over an existing project with a different --topic reported // "wrote 0, kept 13" and printed a fresh project's next steps. The topic asked for was not // applied, and nothing said so.