Skip to content

ci: verify the integrated headless CLI candidate - #257

Merged
byapparov merged 5 commits into
test/issue-248-token-budgets-20261021from
integrate/cli-headless-candidate-20261021
Oct 22, 2026
Merged

byapparov merged 5 commits into
test/issue-248-token-budgets-20261021from
integrate/cli-headless-candidate-20261021

Conversation

@byapparov

@byapparov byapparov commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

No linked issue. Refs #251, #252, #253, #254, #255, #256. Feature ownership remains with the individual PRs. Priority: P2.

Intent

Verify that the combined headless CLI candidate works as one CI job: select its work tools, decide whether to consume stdin, pass command operands without reinterpreting them, and disable automatic project instructions. This PR also closes the remaining .opencode/AGENTS.md and nested-instruction discovery paths when project configuration is disabled.

Review scope: this is stacked on #255 (test/issue-248-token-budgets-20261021). The selector, stdin and command-argument implementations come from earlier PRs; #257 adds their combined acceptance tests, the instruction-discovery correction, fixture hardening and documentation. The comparison below is released v0.5.4 → combined candidate, not a claim that every feature is introduced by this diff. The latest published release is still v0.5.4.

Expected Impact on Users

Workflow Released v0.5.4 Combined candidate at 2999d0f7
Select tools for a review or explanation Use permission configuration to restrict the available tools. Use --tools read,glob,grep for this invocation, or tool_allowlist in config as a default. Permission denials still win. Selector supplied by #254.
Pipe context alongside a prompt Non-terminal stdin is read automatically, which can wait for an unrelated open pipe. Add a standalone - to consume the stream: `producer
Disable instructions from the checkout AICTRL_DISABLE_PROJECT_CONFIG=true still leaves .opencode/AGENTS.md and nested instructions reachable. The same flag also excludes those automatic instruction sources. Trusted global/profile and explicit caller instructions remain. This PR carries the #251 isolation correction.
Pass operands to a configured command The legacy text-argument path can lose operand boundaries. CLI forwards { argv: string[], stdin?: string }; quoted/empty operands and selected stdin stay separate until the documented template projection. Supplied by #256; exercised here.
Interpret NDJSON tool_catalog Documentation overstates MCP inventory as dispatch exposure. Documentation makes clear that inventory presence does not prove a tool was sent on a model turn. Inspect the actual request for that assertion.

CLI examples

These examples target a build containing this candidate. Configure the selected provider's credentials and set MODEL to a supported provider/model; installing the current npm release does not establish these new contracts.

# Review with only these work tools; configured permissions still apply.
aictrl run --model "$MODEL" --tools read,glob,grep \
  "Review the changes on this branch"

# Explain supplied text without exposing any work tools.
aictrl run --model "$MODEL" --tools '' \
  "Explain this expression: items.filter(Boolean)"

# Migration: prompt + piped context now explicitly selects stdin with '-'.
git diff origin/main...HEAD | aictrl run --model "$MODEL" \
  --tools read,glob,grep --format json "Review this diff" -

# With no prompt operands, non-terminal stdin is still selected implicitly.
printf '%s\n' 'Explain what this repository does' | aictrl run --model "$MODEL"

Selected stdin waits for EOF; this change adds no stdin deadline. Close the producer or supervise the job. --model - and --command - treat the hyphen as their option value; they do not select stdin.

Configuration: defaults, permissions and trusted CI policy

For a trusted CI profile, put this in /opt/aictrl/ci-profile/aictrl.json and supply the referenced review.md there:

{
  "tool_allowlist": ["read", "glob", "grep"],
  "permission": {
    "*": "deny",
    "read": "allow",
    "glob": "allow",
    "grep": "allow"
  },
  "instructions": ["review.md"]
}
AICTRL_CONFIG_DIR=/opt/aictrl/ci-profile \
AICTRL_DISABLE_PROJECT_CONFIG=true \
aictrl run --model "$MODEL" --format json "Review this checkout"
  • Without --tools, use final config tool_allowlist; without either, preserve normal tool availability. [] means no work tools. A CLI selection replaces the config preference for that invocation, while permissions can still deny selected tools.
  • Names are exact and case-sensitive; malformed, unknown or unavailable names fail before inference. Tool selection is not a permission grant.
  • In ordinary project-enabled runs, the preference can live in aictrl.json; when disabling project config, supply trusted policy through the profile or caller config instead. AICTRL_CONFIG_CONTENT is the inline caller alternative.
  • With project config disabled, relative instructions resolve from AICTRL_CONFIG_DIR; without that directory they are skipped. Absolute paths and URLs remain explicit inputs. A profile AGENTS.md takes precedence over the global fallback; both are trusted sources.

Expected Outcomes

  • The same invocation preserves selected tools, command operands and stdin while excluding disabled project instructions, including after a real Read tool call.
  • An empty selection stays empty; selecting read does not undo a permission denial.
  • Invalid selectors and unsupported explicit attach selections fail before selected stdin is consumed or the receiver is called.
  • An HTTP request whose JSON body has not finished cannot be mistaken for “no request arrived.”

Implementation

  • Guard .opencode/AGENTS.md discovery and the nested InstructionPrompt.resolve path with the existing disable flag.
  • Add 19 combined CLI scenarios across normal/headless entry points, config/CLI precedence, permission denial, Read-triggered instructions, stdin EOF and attach/error controls.
  • Count fixture request arrival before parsing JSON and test that observer with an incomplete request body.
  • Run the token fixture's actual loopback tests in CI; clarify stdin ownership and event inventory documentation.

Pinned references: tool selection/config, stdin normalization, instruction gates, combined acceptance cases.

Scope Caveat

This candidate remains unreleased. --attach rejects any explicit or configured work-tool selection, including an empty list; the required receiver capability is not implemented here. Structured attached-command compatibility also remains unverified. tool_catalog is inventory, not an effective per-request allowlist. Schema output still requires its own permissions; a read-only work-tool example does not grant write or StructuredOutput access. Project discovery remains enabled by default. Trusted command templates/plugins and tools still require an isolated runner and scoped credentials.

No real-model quality, provider-billing, Windows or new native-binary claim is made. The 18 paired real-task evaluations and receiver/lifecycle acceptance remain open. Keep release-specific instruction-file workarounds until the installed artifact includes and verifies the fix.

Test Plan

  • Combined tool selection, stdin, command-data and instruction-isolation cases, including an actual nested-file Read.
  • Empty selections, permission denial and invalid/attach selector failures.
  • Incomplete-body request-arrival controls and actual loopback-binding tests.
  • Hosted build, typecheck and full test suite on 2999d0f7.
  • Real-provider paired quality evaluations and supporting receiver/lifecycle verification.

Verification

Hosted CI 39630140079, for head 2999d0f7b7bf3bbca9ed93d00e73fbd931279732, is successful:

  • Build: 3/3 tasks; typecheck: 6/6 tasks.
  • Tests: 2,287 pass, 10 skips, 0 failures, 16,137 assertions.
  • Separate token-fixture loopback step: 4 pass, 20 assertions.

These are existing CI results, rechecked for this description; no tests or model calls were rerun for the wording update. Current gh pr checks lists verify as passed. Review/security/release approval is separate from this result.

Risks and Rollout

Update prompt-plus-pipe callers to include - when adopting the candidate. Keep #255 as the stacked dependency; no branch retarget, merge or release is part of this description update. Existing review dispositions remain linked in the audit below.

Detailed verification history and review dispositions (historical evidence)

The private tested source has 620 pinned entries. All 13 stages finished with exit0 and no watchdog:

  • 503 core tests across 19 files, four existing skips, zero failures; 2,650 assertions. The 19 combined scenarios include every instruction-isolation guard, real CLI Read behavior and 22 owned loopback requests.
  • Direct CLI, SDK, plugin and canonical-wire consumer type checks passed.
  • Four actual cold offline captures reproduced 3,441 default / 2,001 four-tool prefix reference tokens; full first-input counts were 3,453 / 2,013. No budget violations. These are reference-token counts, not provider billing, cache, speed or coding-quality evidence.
  • 17 protocol and 10 accounting Python tests passed; four actual stub tests passed with 20 assertions.

After the tested source was verified, one documentation-only repair restored the stdin example and instruction-isolation section while retaining the accepted schema-mode clarification. The other 619 source pins are unchanged; no runtime rerun is implied. Full source/QA hashes and exact process-cleanup observations are retained locally. The fixture reports its owned groups gone; it does not provide an independent child-PID inventory for all 19 cases.

Hosted verification and release gates

Published head: 2999d0f7b7bf3bbca9ed93d00e73fbd931279732, parent f3e7483. This review cleanup changes only two files: root README qualifies scalar required-value hyphen ownership, and the integration helper drops an unused import and template constant. The other618source entries remain unchanged; no parser, permission, SDK, instruction-policy or CI behavior changed. Normal push completed21October at15:55:44UTC. Normal pre-push checks passed six tasks (five cached, with a fresh CLI compiler check); cached SDK generation/build output is replayed logging. All620committed source bindings and literal symlink payloads were verified.

Focused actual tests on the cleanup passed 127 tests / 0 skips / 0 failures / 780 assertions:19integrated cases and108stdin cases, plus direct CLI typecheck. Independent source review accepted the bounded change. QA disclosed an initial private wrong-README-path assertion and tests starting before corrected independent preflight; the owner's full620freeze preceded launch, and corrected preflight/fullpostchecks verify the same candidate. No prelaunch independent pass or product runtime failure is inferred.

Fresh hosted CI39630140079 for 2999d0f7 passed 2,287tests /10skips /0failures /16,137assertions across159files, with a separate actual stub step4pass/20assertions. Immutable logs identify checkout 940b2862327c0b4decaf4c3278ed8d6596213d07, parents ef49+2999, whose98aetree and all620Gitblob/modebindings exactly match the published correction. Build3/3 and typecheck6/6 passed. Independent inert-artifact audit verified all four reconstructed canonical prefixes and complete raw request bodies byte-for-byte against the previously audited f3e evidence. Reference counts3,441/2,001 (fullfirst3,453/2,013) are reused by exact byte identity; no fresh tokenizer execution or recount is claimed. Original1,250payload files are unchanged; recipe bindings come from actual checkout940b, not a separate embedded recipe-SHA manifest. Hosted cleanup assertions do not prove independently observed remote PID absence. The original six-poll observation window ended normally at 16:25:48UTC, after its16:25:44UTC cap. Across that window, no automated review on2999 was observed and no read failed; no current-head AI check was visible at the final poll. Absence of review is not approval. The window was not restarted; fresh CI is green while the review gate remains open. The original f3e window ended at15:34:48UTC; its AI review subsequently completed15:36:28UTC with three findings, addressed below. CodeQL remains untriggered under default-setup target policy because this PR targets an unprotected feature branch; no current-head security clearance is inferred.

Earlier-head evidence: CI39622496092 on f3e passed2,287tests/10skips/0failures,16,137assertions/159files, separate stub4/20 and27Python controls. Actual checkout5bf20a27 had parents ef49+f3e and the exact previouse749tree/all620blob-modebindings. Its artifact independently reproduced four prefixes3,441/2,001, fullfirst3,453/2,013, with rawbytes unchanged. Recipe hashes bind through that checkout; there is no separate embedded recipe-SHA manifest or independently probed remote PID absence. This does not claim fresh hosted validation of2999. Earlier91f native/release evidence remains earlier-head evidence.

Three-finding review response: two cleanup fixes and one instruction-default/startup-visibility hardening deferral under existing245, manager review23October. All three exact native verdicts were written and read back, zero failures; lifecycle verification remains separate. Discovery is still enabled unless explicitly disabled, and there is no untrusted-checkout safety clearance.

Six-finding review response: two fixes, one manager-owned P2 guard-policy deferral for23October and three explained ignores; all six exact native verdicts were written and read back, with zero failures. The guard is explicitly partial instrumentation, not network confinement.

Configured, skill and MCP templates remain trusted executable code. Data binding is not an execution or network sandbox. Receiver compatibility, attached lifecycle acceptance and all18 real paired quality trials remain open. This correction has no new native-binary, provider-backed, Windows, billing or release qualification. The selector and combined candidate remain unreleased.

@byapparov
byapparov changed the base branch from main to test/issue-248-token-budgets-20261021 October 21, 2026 06:59
@byapparov
byapparov marked this pull request as ready for review October 21, 2026 10:00
@byapparov byapparov added the P2 Could have - Medium label Oct 21, 2026
const server = Bun.serve({
hostname: "127.0.0.1",
port: 0,
async fetch(request) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Spawn/kill harness and userText() copied 4x.

Suggested change
async fetch(request) {
Extract the spawn/deadline/escalation/staged-stdin block and userText() into a shared module under packages/cli/test/cli/fixture/ and import it from the four suites, keeping only per-file Input knobs.
🤖 Fix with your agent
Fix this code review finding (aictrl-dev/cli PR #257, packages/cli/test/cli/run-command-arguments.test.ts:21-213):

Problem: Spawn/kill harness and userText() copied 4x
Detail: This file defines a local capture() plus a byte-identical userText(), while test/cli/fixture/command-template-capture.ts and test/cli/fixture/integrated-capture.ts each carry the same ~150-line spawn/process-group/deadline/staged-stdin harness and the same userText(); run-stdin.test.ts re-implements it again as run(). Four near-identical harnesses (~700 overlapping lines) will drift independently (e.g. command-template-capture asserts expect(code).toBe(0) while integrated-capture does not; only some filter stdout lines to JSON candidates). A shared test/cli/fixture helper for spawn+group-kill+stdin-staging and one exported userText would match the repo's existing fixture-module pattern.
Suggested fix: Extract the spawn/deadline/escalation/staged-stdin block and userText() into a shared module under packages/cli/test/cli/fixture/ and import it from the four suites, keeping only per-file Input knobs.

Implement the fix on the PR head branch and add a regression test that fails before the fix and passes after.
Why this matters

This file defines a local capture() plus a byte-identical userText(), while test/cli/fixture/command-template-capture.ts and test/cli/fixture/integrated-capture.ts each carry the same ~150-line spawn/process-group/deadline/staged-stdin harness and the same userText(); run-stdin.test.ts re-implements it again as run(). Four near-identical harnesses (~700 overlapping lines) will drift independently (e.g. command-template-capture asserts expect(code).toBe(0) while integrated-capture does not; only some filter stdout lines to JSON candidates). A shared test/cli/fixture helper for spawn+group-kill+stdin-staging and one exported userText would match the repo's existing fixture-module pattern.

Comment thread README.md Outdated

The marker can appear before or after `--`; repeated markers still read stdin once. Markers are removed from the model message, command arguments and derived session title. `--` ends option parsing, so `run -- -` also selects stdin. An operand such as `"literal -"` remains ordinary text. An explicit empty operand (`run ""`) does not select implicit stdin. Selected stdin is read to EOF and appended after one actual newline. Ordinary prompt operands are joined with spaces without adding artificial quotes or backslash escapes; quotes, backslashes and whitespace inside an operand remain its text.

Option values belong to their options: `--model=-` or `--command=-` does not select stdin, and scalar options also accept a separated hyphen value. Use `--file=-` to attach a file literally named `-`; `--file -` is missing its required file value. Multiple file values and repeated `--skill` flags retain their existing behavior. Required option values are checked by the argument parser; explicit `--title ""` remains supported.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 README hyphen-value claim untrue for --port.

🤖 Fix with your agent
Fix this code review finding (aictrl-dev/cli PR #257, README.md:66):

Problem: README hyphen-value claim untrue for --port
Detail: README states "scalar options also accept a separated hyphen value", but this PR's own test (packages/cli/test/cli/run-stdin.test.ts, "optional no-value port leaves standalone marker selected") proves `--port -` does NOT consume the hyphen: `--port` is declared `type: "number"` without `requiresArg`, so the bare `-` stays a message operand and selects stdin. Only options carrying `requiresArg` (model, command, session, agent, format, output-schema, output-result, file, title, attach, dir, variant, output-schema-retries) own a following hyphen value. The sentence over-generalizes and can mislead users into expecting `--port -` to bind.
Suggested fix: Qualify the sentence, e.g. "options that require a value (--model, --command, --title, --variant, …) also accept a separated hyphen value; optional-value flags such as --port do not consume one, so a following bare `-` still selects stdin."

Implement the fix on the PR head branch and add a regression test that fails before the fix and passes after.
Why this matters

README states "scalar options also accept a separated hyphen value", but this PR's own test (packages/cli/test/cli/run-stdin.test.ts, "optional no-value port leaves standalone marker selected") proves --port - does NOT consume the hyphen: --port is declared type: "number" without requiresArg, so the bare - stays a message operand and selects stdin. Only options carrying requiresArg (model, command, session, agent, format, output-schema, output-result, file, title, attach, dir, variant, output-schema-retries) own a following hyphen value. The sentence over-generalizes and can mislead users into expecting --port - to bind.

- name: Install pinned public release without package scripts
shell: bash
run: |
npm install --prefix "$RUNNER_TEMP/aictrl-owned-cli" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Version/SHA pins duplicated across three files.

🤖 Fix with your agent
Fix this code review finding (aictrl-dev/cli PR #257, .github/workflows/released-smoke.yml:33-42):

Problem: Version/SHA pins duplicated across three files
Detail: The pinned release version (@aictrl/cli@0.5.4) and binary SHA-256 f12a5425… are duplicated verbatim in .github/workflows/released-smoke.yml (lines 33-42), examples/released-smoke/workflow.example.yml, and examples/released-smoke/README.md with no cross-reference between the three. The active/example split is documented and deliberate, but any version bump must be edited in three places or the workflows and docs silently diverge from the artifact actually smoked.
Suggested fix: Add a "keep in sync with …" cross-reference comment in both workflow files and the README pin block, or source the version/SHA from a single shared file the runner reads.

Implement the fix on the PR head branch and add a regression test that fails before the fix and passes after.
Why this matters

The pinned release version (@aictrl/cli@0.5.4) and binary SHA-256 f12a5425… are duplicated verbatim in .github/workflows/released-smoke.yml (lines 33-42), examples/released-smoke/workflow.example.yml, and examples/released-smoke/README.md with no cross-reference between the three. The active/example split is documented and deliberate, but any version bump must be edited in three places or the workflows and docs silently diverge from the artifact actually smoked.

)
}, 100),
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ requestsBeforeEOF races the server's async push.

Suggested change
}
Push a placeholder into `requests` at request start (before awaiting request.json()), or re-check requests.length after a short settle delay before asserting 0.
🤖 Fix with your agent
Fix this code review finding (aictrl-dev/cli PR #257, packages/cli/test/cli/fixture/command-template-capture.ts:148):

Problem: requestsBeforeEOF races the server's async push
Detail: The "no provider request before stdin EOF" guard reads requestsBeforeEOF = requests.length inside a setTimeout, but the Bun.serve fetch handler only pushes into `requests` after `await request.json()` resolves (line 31). There is no happens-before edge guaranteeing a request the CLI already sent before EOF is visible in the array when the timer fires, so a genuine read-stdin-after-provider-request violation could transiently be counted as 0 and pass the expect(requestsBeforeEOF).toBe(0) assertion. Same pattern is replicated in integrated-capture.ts and run-stdin.test.ts.
Suggested fix: Push a placeholder into `requests` at request start (before awaiting request.json()), or re-check requests.length after a short settle delay before asserting 0.

Implement the fix on the PR head branch and add a regression test that fails before the fix and passes after.
Why this matters

The "no provider request before stdin EOF" guard reads requestsBeforeEOF = requests.length inside a setTimeout, but the Bun.serve fetch handler only pushes into requests after await request.json() resolves (line 31). There is no happens-before edge guaranteeing a request the CLI already sent before EOF is visible in the array when the timer fires, so a genuine read-stdin-after-provider-request violation could transiently be counted as 0 and pass the expect(requestsBeforeEOF).toBe(0) assertion. Same pattern is replicated in integrated-capture.ts and run-stdin.test.ts.

: first && typeof first === "object" && "cmd" in first && Array.isArray(first.cmd)
? first.cmd
: undefined
if (command && /(?:^|\/)bun$/.test(String(command[0])) && ["install", "add"].includes(String(command[1]))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Install guard misses bun aliases and other package managers.

Suggested change
if (command && /(?:^|\/)bun$/.test(String(command[0])) && ["install", "add"].includes(String(command[1]))) {
Normalize the first two command tokens and match a denylist: /^(?:.*\/)?(?:bun|bunx|npm|npx|pnpm|yarn)$/ for cmd[0] plus ['install','add','i','a','inst','exec','x'] for cmd[1].
🤖 Fix with your agent
Fix this code review finding (aictrl-dev/cli PR #257, packages/cli/test/cli/fixture/stdin-guard.ts:13-14):

Problem: Install guard misses bun aliases and other package managers
Detail: The preload guard meant to prove the CLI-under-test never installs packages only blocks cmd[0] matching /(?:^|\/)bun$/ with cmd[1] exactly "install" or "add". "bun i"/"bun a" aliases and npm/npx/pnpm/yarn/bunx invocations pass through unblocked, and since the fetch patch only covers the current process, a spawned package manager has unguarded network access. This weakens the egress/install invariant the surrounding tests assert on (any network.some(allowed===false) throw).
Suggested fix: Normalize the first two command tokens and match a denylist: /^(?:.*\/)?(?:bun|bunx|npm|npx|pnpm|yarn)$/ for cmd[0] plus ['install','add','i','a','inst','exec','x'] for cmd[1].

Implement the fix on the PR head branch and add a regression test that fails before the fix and passes after.
Why this matters

The preload guard meant to prove the CLI-under-test never installs packages only blocks cmd[0] matching /(?:^|/)bun$/ with cmd[1] exactly "install" or "add". "bun i"/"bun a" aliases and npm/npx/pnpm/yarn/bunx invocations pass through unblocked, and since the fetch patch only covers the current process, a spawned package manager has unguarded network access. This weakens the egress/install invariant the surrounding tests assert on (any network.some(allowed===false) throw).

const original = globalThis.fetch\nconst spawn = Bun.spawn.bind(Bun)\nBun.spawn = ((...args: Parameters<typeof Bun.spawn>) => {\n  const first: unknown = args[0]\n  const command = Array.isArray(first)\n    ? first\n    : first && typeof first === "object" && "cmd" in first && Array.isArray(first.cmd)\n      ? first.cmd\n      : undefined\n  if (command && /(?:^|\\/)bun$/.test(String(command[0])) && [\"install\", \"add\"].includes(String(command[1]))) {

function failure(result: Awaited<ReturnType<typeof run>>, code: string) {
expect(result.code, result.stderr + result.stdout).toBe(1)
expect(result.requests).toHaveLength(0)
const events = result.stdout

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ failure() JSON.parses every stdout line, no filter.

Suggested change
const events = result.stdout
Filter to JSON candidate lines before parsing, as run-command-arguments.test.ts does: .filter((line) => line.startsWith("{")).
🤖 Fix with your agent
Fix this code review finding (aictrl-dev/cli PR #257, packages/cli/test/cli/run-stdin.test.ts:266-270):

Problem: failure() JSON.parses every stdout line, no filter
Detail: failure() parses stdout with .split("\n").filter(Boolean).map(JSON.parse) — unlike the sibling suites which filter to lines starting with "{". Any non-JSON stdout line emitted by the CLI or Bun runtime on these failing paths (warning, notice, partial NDJSON chunk boundary) makes JSON.parse throw a SyntaxError, turning the intended exit-code/INVOCATION_* classification assertion into an opaque test crash.
Suggested fix: Filter to JSON candidate lines before parsing, as run-command-arguments.test.ts does: .filter((line) => line.startsWith("{")).

Implement the fix on the PR head branch and add a regression test that fails before the fix and passes after.
Why this matters

failure() parses stdout with .split("\n").filter(Boolean).map(JSON.parse) — unlike the sibling suites which filter to lines starting with "{". Any non-JSON stdout line emitted by the CLI or Bun runtime on these failing paths (warning, notice, partial NDJSON chunk boundary) makes JSON.parse throw a SyntaxError, turning the intended exit-code/INVOCATION_* classification assertion into an opaque test crash.

@aictrl-dev

aictrl-dev Bot commented Oct 21, 2026

Copy link
Copy Markdown

Code review

Verdict: Looks good — only minor / nit comments below. · 🔴 0 · 🟠 0 · 🟡 2 · ⚪ 4 · 0/6 resolved

  • ⚪ .github/workflows/released-smoke.yml:33-42 — Version/SHA pins duplicated across three files
  • ⚪ packages/cli/test/cli/fixture/command-template-capture.ts:148 — requestsBeforeEOF races the server's async push
  • ⚪ packages/cli/test/cli/fixture/stdin-guard.ts:13-14 — Install guard misses bun aliases and other package managers
  • 🟡 packages/cli/test/cli/run-command-arguments.test.ts:21-213 — Spawn/kill harness and userText() copied 4x
  • ⚪ packages/cli/test/cli/run-stdin.test.ts:266-270 — failure() JSON.parses every stdout line, no filter
  • 🟡 README.md:66 — README hyphen-value claim untrue for --port
🤖 Fix all 6 open findings with your agent
Fix the following code review findings on aictrl-dev/cli PR #257 (head branch).
Run the relevant tests/linters after each change.

1. .github/workflows/released-smoke.yml:33-42 — Version/SHA pins duplicated across three files
   Detail: The pinned release version (@aictrl/cli@0.5.4) and binary SHA-256 f12a5425… are duplicated verbatim in .github/workflows/released-smoke.yml (lines 33-42), examples/released-smoke/workflow.example.yml, and examples/released-smoke/README.md with no cross-reference between the three. The active/example split is documented and deliberate, but any version bump must be edited in three places or the workflows and docs silently diverge from the artifact actually smoked.
   Suggested fix: Add a "keep in sync with …" cross-reference comment in both workflow files and the README pin block, or source the version/SHA from a single shared file the runner reads.
2. packages/cli/test/cli/fixture/command-template-capture.ts:148 — requestsBeforeEOF races the server's async push
   Detail: The "no provider request before stdin EOF" guard reads requestsBeforeEOF = requests.length inside a setTimeout, but the Bun.serve fetch handler only pushes into `requests` after `await request.json()` resolves (line 31). There is no happens-before edge guaranteeing a request the CLI already sent before EOF is visible in the array when the timer fires, so a genuine read-stdin-after-provider-request violation could transiently be counted as 0 and pass the expect(requestsBeforeEOF).toBe(0) assertion. Same pattern is replicated in integrated-capture.ts and run-stdin.test.ts.
   Suggested fix: Push a placeholder into `requests` at request start (before awaiting request.json()), or re-check requests.length after a short settle delay before asserting 0.
3. packages/cli/test/cli/fixture/stdin-guard.ts:13-14 — Install guard misses bun aliases and other package managers
   Detail: The preload guard meant to prove the CLI-under-test never installs packages only blocks cmd[0] matching /(?:^|\/)bun$/ with cmd[1] exactly "install" or "add". "bun i"/"bun a" aliases and npm/npx/pnpm/yarn/bunx invocations pass through unblocked, and since the fetch patch only covers the current process, a spawned package manager has unguarded network access. This weakens the egress/install invariant the surrounding tests assert on (any network.some(allowed===false) throw).
   Suggested fix: Normalize the first two command tokens and match a denylist: /^(?:.*\/)?(?:bun|bunx|npm|npx|pnpm|yarn)$/ for cmd[0] plus ['install','add','i','a','inst','exec','x'] for cmd[1].
4. packages/cli/test/cli/run-command-arguments.test.ts:21-213 — Spawn/kill harness and userText() copied 4x
   Detail: This file defines a local capture() plus a byte-identical userText(), while test/cli/fixture/command-template-capture.ts and test/cli/fixture/integrated-capture.ts each carry the same ~150-line spawn/process-group/deadline/staged-stdin harness and the same userText(); run-stdin.test.ts re-implements it again as run(). Four near-identical harnesses (~700 overlapping lines) will drift independently (e.g. command-template-capture asserts expect(code).toBe(0) while integrated-capture does not; only some filter stdout lines to JSON candidates). A shared test/cli/fixture helper for spawn+group-kill+stdin-staging and one exported userText would match the repo's existing fixture-module pattern.
   Suggested fix: Extract the spawn/deadline/escalation/staged-stdin block and userText() into a shared module under packages/cli/test/cli/fixture/ and import it from the four suites, keeping only per-file Input knobs.
5. packages/cli/test/cli/run-stdin.test.ts:266-270 — failure() JSON.parses every stdout line, no filter
   Detail: failure() parses stdout with .split("\n").filter(Boolean).map(JSON.parse) — unlike the sibling suites which filter to lines starting with "{". Any non-JSON stdout line emitted by the CLI or Bun runtime on these failing paths (warning, notice, partial NDJSON chunk boundary) makes JSON.parse throw a SyntaxError, turning the intended exit-code/INVOCATION_* classification assertion into an opaque test crash.
   Suggested fix: Filter to JSON candidate lines before parsing, as run-command-arguments.test.ts does: .filter((line) => line.startsWith("{")).
6. README.md:66 — README hyphen-value claim untrue for --port
   Detail: README states "scalar options also accept a separated hyphen value", but this PR's own test (packages/cli/test/cli/run-stdin.test.ts, "optional no-value port leaves standalone marker selected") proves `--port -` does NOT consume the hyphen: `--port` is declared `type: "number"` without `requiresArg`, so the bare `-` stays a message operand and selects stdin. Only options carrying `requiresArg` (model, command, session, agent, format, output-schema, output-result, file, title, attach, dir, variant, output-schema-retries) own a following hyphen value. The sentence over-generalizes and can mislead users into expecting `--port -` to bind.
   Suggested fix: Qualify the sentence, e.g. "options that require a value (--model, --command, --title, --variant, …) also accept a separated hyphen value; optional-value flags such as --port do not consume one, so a following bare `-` still selects stdin."
📋 Out-of-diff findings (6)
Sev Location Finding
⚪ .github/workflows/released-smoke.yml:33-42 Version/SHA pins duplicated across three files
⚪ packages/cli/test/cli/fixture/command-template-capture.ts:148 requestsBeforeEOF races the server's async push
⚪ packages/cli/test/cli/fixture/stdin-guard.ts:13-14 Install guard misses bun aliases and other package managers
🟡 packages/cli/test/cli/run-command-arguments.test.ts:21-213 Spawn/kill harness and userText() copied 4x
⚪ packages/cli/test/cli/run-stdin.test.ts:266-270 failure() JSON.parses every stdout line, no filter
🟡 README.md:66 README hyphen-value claim untrue for --port

Reviewed 31 files · 0 inline · view all 6 findings ↗ · AI-generated — verify findings before acting · AI guidance and feedback


aictrl · AI code review for fast-moving teams · aictrl.dev

@byapparov

byapparov commented Oct 21, 2026 •

Copy link
Copy Markdown
Member Author

Review response — PR #257

The six findings from review 5598361180 on 91f913fb have been assessed against the published correction f3e7483ee6bf879fcc6d94e2ad2983c5e0eb7a37. Totals: 2 FIX / 1 DEFER / 3 IGNORE; 5 TRUE / 1 FALSE.

Issues addressed

Request-arrival observations now precede asynchronous body parsing in all three staged-stdin helpers. The README distinguishes required-value options from optional --port when a separated hyphen follows.

Native finding Location Verdict Action Reason
1799e271ffcc996c — Install guard misses bun aliases and other package managers packages/cli/test/cli/fixture/stdin-guard.ts:13 TRUE DEFER The guard covers Bun add/install, not aliases or arbitrary subprocess networking. Current integration docs explicitly qualify it as a partial observation, and the known installer uses the covered path. Broader package-manager policy and hostile positive controls share the existing manager-owned P2 follow-up for 23 October; no OS sandbox claim.
5f99b76ff7dea6d5 — requestsBeforeEOF races the server's async push packages/cli/test/cli/fixture/command-template-capture.ts:148 TRUE FIX All three staged-stdin helpers now increment an arrival counter synchronously before awaiting the request body and sample that counter before EOF. Actual-handler incomplete-JSON controls and the accepted integrated scenarios cover the correction. This proves server-observed arrival, not a global packet-send ordering guarantee.
d67d971d01f1835e — failure() JSON.parses every stdout line, no filter packages/cli/test/cli/run-stdin.test.ts:266 FALSE IGNORE failure() parses the complete collected stdout and requires exactly three structured terminal events. Non-JSON stdout is a real contract violation; filtering it away would hide it. Transport chunks are accumulated before splitting, so a chunk boundary does not justify skipping lines.
e2b62340a03aafca — README hyphen-value claim untrue for --port README.md:66 TRUE FIX README now limits separated-hyphen ownership to required-value options and explicitly documents optional --port leaving '-' as the stdin operand. Existing actual CLI operand controls pass; parser behavior was not changed.
e4921850a4a33eea — Version/SHA pins duplicated across three files .github/workflows/released-smoke.yml:33 TRUE IGNORE The active workflow, copyable example and README intentionally carry matching v0.5.4/hash pins. No current mismatch exists. Shared runtime pin machinery or synchronization comments are optional maintenance changes.
f7500c2c0999e1bd — Spawn/kill harness and userText() copied 4x packages/cli/test/cli/run-command-arguments.test.ts:21 TRUE IGNORE The fixtures repeat harness code but differ in protocol, environment and assertions. Concrete arrival/setup/cleanup faults are corrected in the accepted source; a shared-helper extraction would be a separate refactor without a demonstrated remaining behavior defect.

Not addressed

Broader package-manager alias coverage shares the existing manager-owned P2 guard-policy follow-up for 23 October. Fetch/spawn instrumentation is partial and is not an OS network sandbox. Pin synchronization and broad helper extraction are optional maintenance work, with no current pin mismatch or remaining behavior defect demonstrated by those findings.

Verified false

Strict parsing of every collected stdout line deliberately detects NDJSON violations. Filtering warnings away would weaken the protocol check; transport chunks are accumulated before line splitting.

Test plan

  • All 13 accepted local stages finished with exit0 and no watchdog: 503 core tests / 4 existing skips / 0 failures / 2,650 assertions, four actual stub tests/20 assertions and 27 Python tests.
  • Nineteen actual CLI combined scenarios include all instruction-isolation controls and 22 owned loopback requests. Actual-handler incomplete-JSON controls validate arrival observation before body completion.
  • Direct CLI/SDK/plugin/canonical-wire consumer type checks and four actual cold offline prefix captures passed: 3,441 default / 2,001 selected reference tokens, full first-input3,453/2,013. These are not provider billing, speed or quality results.
  • The 620-entry runtime source remains the tested tree; a subsequent single README repair restores stdin/isolation documentation while retaining schema wording. CI-only target-filter removal and the normal parent merge preserve that accepted runtime source.
  • Fresh hosted CI39622496092 passed2,287tests/10skips/0failures,16,137assertions/159files; separate stub4/20 and27Python controls passed. Actual immutable-log checkout5bf20a27 has parents ef49+f3e, the exact published e749tree and all620blob/mode bindings. Independently reconstructed/recounted all4artifact prefixes3,441/2,001; payload preserved. Artifact has no separate embedded recipe-SHA manifest; trusted bindings come from the actual checkout. Hosted cleanup records do not establish independently observed remote PID absence.
  • Current-head security and automated review remain open. The AI review is in progress at15:19:48UTC, without a formal review yet. CodeQL default setup does not automatically scan this unprotected feature target. Prior91f hosted/native evidence does not qualify the new correction.

Configured templates/plugins remain trusted executable code. Receiver compatibility, attached lifecycle, Windows and the 18 real paired C1 trials remain open. No native-binary, release or paid-model qualification is claimed.

Native findings returned no GitHub comment IDs. Each exact native ID was resolved by its unique review/file/line/native severity/title and full claim; no GitHub comment ID was used as a persistence target.

}
files.push(path.join(Global.Path.config, "AGENTS.md"))
files.push(path.join(Instance.directory, ".opencode", "AGENTS.md"))
if (!Flag.AICTRL_DISABLE_PROJECT_CONFIG) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Project-instruction isolation fails open by default.

🤖 Fix with your agent
Fix this code review finding (aictrl-dev/cli PR #257, packages/cli/src/session/instruction.ts:22):

Problem: Project-instruction isolation fails open by default
Detail: The new AICTRL_DISABLE_PROJECT_CONFIG gating is strictly opt-in: with the env var unset (the default — Flag parses it via truthy(), and the headless entrypoint never defaults it on), root AGENTS.md, .opencode/AGENTS.md and nested AGENTS.md from the repo checkout are still auto-injected into the system prompt of an agent that may hold CI credentials. A missed or misspelled env var therefore silently re-enables untrusted project instructions, and nothing is logged or asserted at startup that isolation engaged. The PR docs acknowledge the residual risk (they tell CI to keep instruction-file deletion workarounds), so this is a hardening recommendation — fail closed for unattended/headless runs and/or emit a startup event listing attached instruction files — rather than a regression introduced by the diff.
Suggested fix: For unattended use, default the gate ON in the headless/run entrypoint (e.g. treat headless as project-config-disabled unless AICTRL_ENABLE_PROJECT_CONFIG is set), or at minimum emit a startup log/structured event enumerating the instruction files attached (or stating discovery is disabled) so CI jobs can assert the isolation state instead of trusting a silently-absent env var.

Implement the fix on the PR head branch and add a regression test that fails before the fix and passes after.
Why this matters

The new AICTRL_DISABLE_PROJECT_CONFIG gating is strictly opt-in: with the env var unset (the default — Flag parses it via truthy(), and the headless entrypoint never defaults it on), root AGENTS.md, .opencode/AGENTS.md and nested AGENTS.md from the repo checkout are still auto-injected into the system prompt of an agent that may hold CI credentials. A missed or misspelled env var therefore silently re-enables untrusted project instructions, and nothing is logged or asserted at startup that isolation engaged. The PR docs acknowledge the residual risk (they tell CI to keep instruction-file deletion workarounds), so this is a hardening recommendation — fail closed for unattended/headless runs and/or emit a startup event listing attached instruction files — rather than a regression introduced by the diff.

    files.push(path.join(Flag.AICTRL_CONFIG_DIR, "AGENTS.md"))
  }
  files.push(path.join(Global.Path.config, "AGENTS.md"))
  if (!Flag.AICTRL_DISABLE_PROJECT_CONFIG) {
    files.push(path.join(Instance.directory, ".opencode", "AGENTS.md"))
  }
  return files
}

Comment thread README.md Outdated
The marker can appear before or after `--`; repeated markers still read stdin once. Markers are removed from the model message, command arguments and derived session title. `--` ends option parsing, so `run -- -` also selects stdin. An operand such as `"literal -"` remains ordinary text. An explicit empty operand (`run ""`) does not select implicit stdin. Selected stdin is read to EOF and appended after one actual newline. Ordinary prompt operands are joined with spaces without adding artificial quotes or backslash escapes; quotes, backslashes and whitespace inside an operand remain its text.

Option values belong to their options: `--model=-` or `--command=-` does not select stdin, and scalar options also accept a separated hyphen value. Use `--file=-` to attach a file literally named `-`; `--file -` is missing its required file value. Multiple file values and repeated `--skill` flags retain their existing behavior. Required option values are checked by the argument parser; explicit `--title ""` remains supported.
Required-value options own a separated hyphen: `--model -` and `--command -` do not select stdin, nor do their `=` forms. Optional `--port` leaves a separated `-` as the stdin operand (`--port -`). Use `--file=-` to attach a file literally named `-`; `--file -` is missing its required file value. Multiple file values and repeated `--skill` flags retain their existing behavior. Required option values are checked by the argument parser; explicit `--title ""` remains supported.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Hyphen rule vs --file sentence contradict each other.

🤖 Fix with your agent
Fix this code review finding (aictrl-dev/cli PR #257, README.md:66):

Problem: Hyphen rule vs --file sentence contradict each other
Detail: The rewritten paragraph now states the general rule "Required-value options own a separated hyphen", but two sentences later retains "`--file -` is missing its required file value" — and --file is itself a required-value option. Either the new rule over-generalizes across option shapes (scalar vs multi-value file options), or the older --file sentence is wrong; as written the paragraph contradicts itself, the same over-generalization class as the just-fixed --port claim. Readers cannot tell which options bind a separated "-" without testing.
Suggested fix: Qualify the rule to the option shape it holds for, e.g. "Scalar required-value options own a separated hyphen: `--model -` and `--command -` do not select stdin, nor do their `=` forms. Multi-value options do not: `--file -` is missing its required file value — use `--file=-` to attach a file literally named `-`." (Match whichever behavior the parser actually has for --file.)

Implement the fix on the PR head branch and add a regression test that fails before the fix and passes after.
Why this matters

The rewritten paragraph now states the general rule "Required-value options own a separated hyphen", but two sentences later retains "--file - is missing its required file value" — and --file is itself a required-value option. Either the new rule over-generalizes across option shapes (scalar vs multi-value file options), or the older --file sentence is wrong; as written the paragraph contradicts itself, the same over-generalization class as the just-fixed --port claim. Readers cannot tell which options bind a separated "-" without testing.

The marker can appear before or after `--`; repeated markers still read stdin once. Markers are removed from the model message, command arguments and derived session title. `--` ends option parsing, so `run -- -` also selects stdin. An operand such as `"literal -"` remains ordinary text. An explicit empty operand (`run ""`) does not select implicit stdin. Selected stdin is read to EOF and appended after one actual newline. Ordinary prompt operands are joined with spaces without adding artificial quotes or backslash escapes; quotes, backslashes and whitespace inside an operand remain its text.

Required-value options own a separated hyphen: `--model -` and `--command -` do not select stdin, nor do their `=` forms. Optional `--port` leaves a separated `-` as the stdin operand (`--port -`). Use `--file=-` to attach a file literally named `-`; `--file -` is missing its required file value. Multiple file values and repeated `--skill` flags retain their existing behavior. Required option values are checked by the argument parser; explicit `--title ""` remains supported.

@@ -0,0 +1,405 @@
import { expect } from "bun:test"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Unused expect import and template const in new fixture.

--- a/packages/cli/test/cli/fixture/integrated-capture.ts
+++ b/packages/cli/test/cli/fixture/integrated-capture.ts
@@ -1,9 +1,7 @@
-import { expect } from "bun:test"
 import { spawn, spawnSync } from "node:child_process"
 import fs from "node:fs/promises"
 import { existsSync, readFileSync } from "node:fs"
 import os from "node:os"
 import { Database } from "bun:sqlite"
 import path from "node:path"
 
-const template = "FIRST<$1>\nSECOND<$2>\nREST<$3>"
 const guard = path.join(import.meta.dir, "stdin-guard.ts")
🤖 Fix with your agent
Fix this code review finding (aictrl-dev/cli PR #257, packages/cli/test/cli/fixture/integrated-capture.ts:1):

Problem: Unused expect import and template const in new fixture
Detail: The new fixture imports `expect` from "bun:test" but never calls it (all assertions live in run-integrated-headless.test.ts, unlike sibling command-template-capture.ts), and declares `const template = "FIRST<$1>\nSECOND<$2>\nREST<$3>"` that is never referenced — the config uses an inline "STATIC<!…>" template string instead. Both are copy-paste residue from command-template-capture.ts that misleads readers into thinking the fixture asserts something or exercises the FIRST/SECOND/REST template.
Suggested fix: Delete the unused `expect` import and the unused `template` constant.

Suggested patch:
--- a/packages/cli/test/cli/fixture/integrated-capture.ts
+++ b/packages/cli/test/cli/fixture/integrated-capture.ts
@@ -1,9 +1,7 @@
-import { expect } from "bun:test"
 import { spawn, spawnSync } from "node:child_process"
 import fs from "node:fs/promises"
 import { existsSync, readFileSync } from "node:fs"
 import os from "node:os"
 import { Database } from "bun:sqlite"
 import path from "node:path"
 
-const template = "FIRST<$1>\nSECOND<$2>\nREST<$3>"
 const guard = path.join(import.meta.dir, "stdin-guard.ts")


Implement the fix on the PR head branch and add a regression test that fails before the fix and passes after.
Why this matters

The new fixture imports expect from "bun:test" but never calls it (all assertions live in run-integrated-headless.test.ts, unlike sibling command-template-capture.ts), and declares const template = "FIRST<$1>\nSECOND<$2>\nREST<$3>" that is never referenced — the config uses an inline "STATIC<!…>" template string instead. Both are copy-paste residue from command-template-capture.ts that misleads readers into thinking the fixture asserts something or exercises the FIRST/SECOND/REST template.

import { expect } from "bun:test"
import { spawn, spawnSync } from "node:child_process"
import fs from "node:fs/promises"
import { existsSync, readFileSync } from "node:fs"
import os from "node:os"
import { Database } from "bun:sqlite"
import path from "node:path"

const template = "FIRST<$1>\nSECOND<$2>\nREST<$3>"
const guard = path.join(import.meta.dir, "stdin-guard.ts")

@aictrl-dev

aictrl-dev Bot commented Oct 21, 2026

Copy link
Copy Markdown

Code review

Verdict: Looks good — only minor / nit comments below. · 🔴 0 · 🟠 0 · 🟡 2 · ⚪ 1 · 0/3 resolved

  • 🟡 packages/cli/src/session/instruction.ts:22 — Project-instruction isolation fails open by default
  • ⚪ packages/cli/test/cli/fixture/integrated-capture.ts:1 — Unused expect import and template const in new fixture
  • 🟡 README.md:66 — Hyphen rule vs --file sentence contradict each other
🤖 Fix all 3 open findings with your agent
Fix the following code review findings on aictrl-dev/cli PR #257 (head branch).
Run the relevant tests/linters after each change.

1. packages/cli/src/session/instruction.ts:22 — Project-instruction isolation fails open by default
   Detail: The new AICTRL_DISABLE_PROJECT_CONFIG gating is strictly opt-in: with the env var unset (the default — Flag parses it via truthy(), and the headless entrypoint never defaults it on), root AGENTS.md, .opencode/AGENTS.md and nested AGENTS.md from the repo checkout are still auto-injected into the system prompt of an agent that may hold CI credentials. A missed or misspelled env var therefore silently re-enables untrusted project instructions, and nothing is logged or asserted at startup that isolation engaged. The PR docs acknowledge the residual risk (they tell CI to keep instruction-file deletion workarounds), so this is a hardening recommendation — fail closed for unattended/headless runs and/or emit a startup event listing attached instruction files — rather than a regression introduced by the diff.
   Suggested fix: For unattended use, default the gate ON in the headless/run entrypoint (e.g. treat headless as project-config-disabled unless AICTRL_ENABLE_PROJECT_CONFIG is set), or at minimum emit a startup log/structured event enumerating the instruction files attached (or stating discovery is disabled) so CI jobs can assert the isolation state instead of trusting a silently-absent env var.
2. packages/cli/test/cli/fixture/integrated-capture.ts:1 — Unused expect import and template const in new fixture
   Detail: The new fixture imports `expect` from "bun:test" but never calls it (all assertions live in run-integrated-headless.test.ts, unlike sibling command-template-capture.ts), and declares `const template = "FIRST<$1>\nSECOND<$2>\nREST<$3>"` that is never referenced — the config uses an inline "STATIC<!…>" template string instead. Both are copy-paste residue from command-template-capture.ts that misleads readers into thinking the fixture asserts something or exercises the FIRST/SECOND/REST template.
   Suggested fix: Delete the unused `expect` import and the unused `template` constant.
3. README.md:66 — Hyphen rule vs --file sentence contradict each other
   Detail: The rewritten paragraph now states the general rule "Required-value options own a separated hyphen", but two sentences later retains "`--file -` is missing its required file value" — and --file is itself a required-value option. Either the new rule over-generalizes across option shapes (scalar vs multi-value file options), or the older --file sentence is wrong; as written the paragraph contradicts itself, the same over-generalization class as the just-fixed --port claim. Readers cannot tell which options bind a separated "-" without testing.
   Suggested fix: Qualify the rule to the option shape it holds for, e.g. "Scalar required-value options own a separated hyphen: `--model -` and `--command -` do not select stdin, nor do their `=` forms. Multi-value options do not: `--file -` is missing its required file value — use `--file=-` to attach a file literally named `-`." (Match whichever behavior the parser actually has for --file.)
📋 Out-of-diff findings (3)
Sev Location Finding
🟡 packages/cli/src/session/instruction.ts:22 Project-instruction isolation fails open by default
⚪ packages/cli/test/cli/fixture/integrated-capture.ts:1 Unused expect import and template const in new fixture
🟡 README.md:66 Hyphen rule vs --file sentence contradict each other

Reviewed 12 files · 0 inline · view all 3 findings ↗ · AI-generated — verify findings before acting · AI guidance and feedback


aictrl · AI code review for fast-moving teams · aictrl.dev

@byapparov

byapparov commented Oct 21, 2026 •

Copy link
Copy Markdown
Member Author

Review response — PR #257

The three findings from review 5602210306 on f3e7483 were verified against correction 2999d0f7b7bf3bbca9ed93d00e73fbd931279732. 2 FIX / 1 DEFER; all three TRUE. Earlier six dispositions remain unchanged.

Issues addressed (pushed to this PR)

  • Scalar option hyphen wording — root README now qualifies the rule as scalar required-value options. It preserves optional --port -, the rejected multi-value --file -, and --file=- for a literal hyphen path. Parser behavior is unchanged.
  • Unused fixture declarations — removed the unused expect import and FIRST/SECOND/REST constant. The active command template and assertions in the consuming suite are unchanged.

Both changes are in 2999d0f7b7bf3bbca9ed93d00e73fbd931279732: two files, one insertion and three deletions.

Exact native finding Location Verdict Action Reason
1b4cf80dc5d3e9aa — Project-instruction isolation fails open by default packages/cli/src/session/instruction.ts:22 TRUE DEFER Project instruction discovery is intentionally enabled unless the caller disables project configuration. The integration enforces that explicit gate; default-on isolation or a new observable-state event changes compatibility/prompt or event contracts. Track the decision under existing CI-adoption security work #245, manager-owned review23October. Existing CI recipes explicitly disable project config; no untrusted-checkout safety clearance is claimed.
8faffa9568bb3a40 — Unused expect import and template const in new fixture packages/cli/test/cli/fixture/integrated-capture.ts:1 TRUE FIX Remove the unused bun:test expect import and FIRST/SECOND/REST template constant from the integrated fixture. Its real STATIC command template and assertions in the calling suite remain unchanged.
aa257eb2764786e5 — Hyphen rule vs --file sentence contradict each other README.md:66 TRUE FIX Qualify the hyphen-ownership rule to scalar required-value options. Preserve the explicit optional --port behavior and multi-value --file exception, including --file=- for a literal hyphen path; parser behavior is unchanged.

Review claims verified false (no change needed)

None in this review.

Not addressed here

Default-on project-instruction isolation / startup visibility is a real hardening recommendation, retained under existing CI-adoption security work245, manager review 23 October. Discovery remains enabled unless explicitly disabled. Changing that default affects repository instruction behavior; a startup instruction manifest would introduce an event/privacy contract. These need a separately scoped decision and compatibility tests. Current CI recipes explicitly disable project config; this is not an untrusted-checkout safety clearance. Released0.5.4 still requires the documented instruction-file workarounds until a corrected release is available.

Test plan

  • Existing actual integration suite: 19pass /369assertions.
  • Entire existing stdin suite, including scalar/file/port controls: 108pass /411assertions. Combined: 127pass /0skip /0fail /780assertions; direct CLI typecheck exit0.
  • Independent review and root source verification: all620candidate entries bound to the commit; two changed paths, other618 unchanged. No permission, parser, SDK, CI or instruction-policy changes.
  • QA sequencing qualification recorded: an initial private wrong-README-path assertion failed, and tests started before its corrected independent preflight. Owner full620freeze preceded execution; corrected preflight and full post-check confirm the same candidate. This is not presented as a product runtime failure or a prelaunch independent pass.
  • Fresh CI39630140079 on2999 passed2,287tests/10skips/0failures/16,137assertions/159files, separate stub4/20, build3/3 and typecheck6/6. Actual immutable-log checkout940b2862 has parents ef49+2999 and the exact98aetree/all620Gitblob-modebindings. Fresh artifact audit verified exact identity of four reconstructed canonical prefixes and complete raw bodies to independently audited f3e evidence;3,441/2,001 (fullfirst3,453/2,013) counts are reused by byte identity, with no fresh tokenizer execution/recount. Original1,250payloadfiles unchanged; recipe bindings derive from actualcheckout940b, without a separate embedded recipe-SHA manifest or independent remote PID probe.
  • The original six-poll window ended normally16:25:48UTC after its16:25:44UTC cap: no current2999 review or visible AI check was observed at the final poll, with0readerrors. No restart was requested. This is an open review gate, not approval. Default CodeQL setup does not scan this unprotected feature target; no security clearance is inferred.

No new native binary, receiver/Windows, real-task model, paid-provider or release qualification is claimed. Native IDs were resolved by unique review/file/line/severity/title/fullclaim; GitHub comment IDs were null.

@byapparov byapparov self-assigned this Oct 22, 2026
@byapparov
byapparov merged commit 44ae6b6 into test/issue-248-token-budgets-20261021 Oct 22, 2026
1 check passed
@byapparov
byapparov deleted the integrate/cli-headless-candidate-20261021 branch October 22, 2026 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Could have - Medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant