Skip to content

m1-leftovers: the CLI executes operations, the scanner's per-site hole closes, and the guidance surfaces tell the truth again - #14

Merged
OriNachum merged 27 commits into
mainfrom
spec/m1-leftovers
Jul 28, 2026
Merged

m1-leftovers: the CLI executes operations, the scanner's per-site hole closes, and the guidance surfaces tell the truth again#14
OriNachum merged 27 commits into
mainfrom
spec/m1-leftovers

Conversation

@OriNachum

Copy link
Copy Markdown
Contributor

Closes the gap between "Milestone 1 done" and the issue #1 definition of done.
Full plan-versus-actual accounting is committed at
docs/deliveries/2026-07-28-m1-leftovers.md
— read that as the review map; this description is its headline.

The arc ran the devague flow end to end: /scope/think/challenge
/spec-to-plan/assign-to-workforce/summarize-delivery. The spec,
plan, frame state and delivery summary are all committed, so every claim below
traces to a confirmed claim or a recorded deviation.

What ships

The CLI executes operations for the first time. Three noun groups, all
fronting operations.execute() rather than reimplementing anything:

  • shell fs read|list|stat|write|edit|media, plus a new confined fs.stat
    handler. A missing path is SUCCEEDED with exists: false, deliberately
    diverging from fs.read and pinned so nobody reconciles them by accident.
  • shell process exec|shell. process shell refuses the control profile in
    the library's own words — a raw string is re-interpreted by a shell after
    the gate read it, so which program runs is not settled by what was gated. The
    refusal precedes the preview, so previewing cannot mask it.
  • shell env describe. env create/env destroy are deliberately absent:
    OperationIntent.LIFECYCLE has no registered handler and no ContainerRunner
    exists, so they would be a facade in front of nothing. The catalog says so and
    names Milestone 4.

--apply is now derived from the registry. Previously the CLI hand-picked
which verbs got the flag while the engine independently decided which operations
preview — nothing tied them together, so flipping a handler's intent would have
left a verb previewing forever with no flag to escape it. shell/cli/_operation.py
is now the only module in shell/ that spells --apply, and it asks
normalize(...).requires_apply — the same question execute() asks.
tests/test_cli_fs.py passes byte-unmodified across that refactor, which is
the proof it changed no behaviour.

The issue #7 architectural finding is closed. SITE_BASELINE pins each
module's spawn-site shape, so a new spawn added to an already-allow-listed
module now fails --check. Verified adversarially: appending one benign
subprocess.run(["true"]) to tools.py in a pinned clone yields
tools.py: baseline 2 site(s), observed 3 (+subprocess.run×1) and exit 1 — the
exact case that returned exit 0 with no signal in the original live test.
Line numbers are excluded from the shape deliberately, or any refactor above a
spawn would false-alarm; the baseline is generated by a new --emit-baseline,
never hand-typed, so PINNED_SHA and the baseline must move in one reviewed diff.

The rewrite is behaviour-neutral on real colleague — old and new findings are
byte-identical over the pinned checkout (21 sites / 15 modules / 13 debt) — and
the six false positives from §6 are gone, including the one that flagged a
parameter named os.

What this deliberately does not claim

  • The scanner is still a drift detector, not an enforcement gate.
    Indirection (sp = subprocess, getattr, dynamic import), sibling re-export
    inside colleague/, exec() of a source string, and non-literal shell=
    spellings all remain open, documented in the module docstring and tracked in
    Pin the scanner's remaining evasion classes as expected-to-evade fixtures (successor to #7) #13. Read a green --check as "the inventory has not drifted in the ways
    this scanner can see."
  • Host mode remains a guard, not a sandbox. env describe surfaces
    isolation: none verbatim rather than softening it.
  • Milestones 3 and 4 are not planned. Those two tasks were lifted out of the
    fan-out (deviation d1) because they are full devague arcs whose
    converge/confirm moves are user-only gates.
  • The four cross-repo posts are drafted, not filed. Deviation d2 — filing
    on colleague, devex and this repo is irreversible and outward-facing, so
    posting waits for operator review.

Guidance surfaces

Every wave of this arc falsified at least one guidance surface, so README,
CLAUDE.md and the explain catalog were corrected as each landed. Also fixed:
docs/release-runbook.md claimed "colleague is the second sanctioned base
dependency"
— inverted, since colleague cannot be a dependency of itself
(deviation d3, found while drafting the migration proposal that cites that
very runbook).

Worth recording: an independent re-verification during this arc confirmed that
every line number CLAUDE.md's colleague survey cites still holds at colleague
v1.52.1
, a full minor version after the 1.51.0 snapshot. Nothing drifted.

Evidence

  • tests: 860 passed, 8 skipped (from 635 at arc start — net +225). The 8
    skips are the documented KNOWN GAP, unchanged.
  • black, isort, flake8, bandit, markdownlint-cli2: all clean.
  • teken cli doctor . --strict: exit 0, 26/26.
  • colleague_inventory.py --check against the pinned checkout: exit 0.
  • Boundary verification: zero occurrences of the unnamed second consumer across
    the repo tree, the arc diff, the drafts and .devague state;
    dependencies = [] with test_zero_deps green; no t74b fixture; the five
    chosen-limit test files byte-unchanged across the arc.

Follow-ups filed during the run

#10 (the pinned colleague SHA is not on colleague's main — fragile
provenance, and the reason a fresh developer checkout has a red baseline),
#11 (four evidence-record gaps, including created_resources being a
defined-but-never-populated slot), #12 (preview misses knowable confinement
refusals; the registry lacks accepted_profiles; five copies of _safe_path),
#13 (pin the remaining scanner evasion classes as expected-to-evade fixtures).

Note for the reviewer

Merging publishes 0.14.0 to PyPI. Nothing consumes this package yet, so the
blast radius is the index entry itself.

  • shell-cli (Claude)

OriNachum and others added 22 commits July 28, 2026 09:52
The converged frame for the post-Milestone-1 leftovers arc: 27 confirmed
claims, 24 confirmed honesty conditions, 15 scope entries with provenance.
Re-captures the 12 deliberately uncovered prior-frame targets verbatim
(operator decision q1) so the next plan leg computes coverage natively.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
Three c19 escalation signals (security-sensitive work, hard-to-reverse
publishes/posts, planned concurrency work) made the rigorous form
mandatory. Findings adjudicated by the operator: tracker hygiene (c29),
benign-argv fixture boundary for the scanner slice (c30), re-verify the
second consumer's seam at M4 (c31). One non-blocking park: merge-order
vs version-bump collisions, to mirror as a plan risk. Nine lens/surface
scope entries (s16-s24) record what was examined, including the clean
lenses; probes verified the 12 re-captures byte-identical and no
consumer-name leak in any committed artifact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
13 confirmed tasks covering all 54 targets of the challenged frame,
with per-task operator instructions, acceptance criteria, and an
honest dependency graph (waves: 9-wide opening, then the shared-file
CLI chain, boundary verification, close-out). Four first-class risks,
including the d3/d8 merge-order mirror of park v1. Frame amendment
c32/h27 records that git and env-lifecycle CLI verbs ride their
M3/M4 engines; this arc ships fs, process, and env describe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
…urface

Both files still described the pre-Milestone-1 scaffold. README's Status
section claimed no operation/environment/policy/runner had been built and
the axes matrix claimed neither runner existed; CLAUDE.md's "Not built yet"
list still named process.exec/process.shell (t84) and the policy/operation
CLI nouns (t85) as unbuilt. All false since 0.12.0-0.13.0 shipped HostRunner
execution, fs.*/process.* handlers, and the policy/operation CLI nouns.

Rewrite both to state what is actually built (the operation core, HostRunner,
fs.*/process.* handlers, and the policy/operation CLI nouns) versus what
genuinely is not (ContainerRunner, colleague delegation, and the env/fs/
process/git CLI noun groups), and flip the environment matrix's Built?
column for the Host rows to Yes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
…xample in the release runbook

The runbook said 'colleague is the second sanctioned base dependency',
which inverts the relationship — colleague cannot be a dependency of
itself. shell-cli is the second name colleague's zero-deps guard must
allow-list. The example pin floor was also stale at >=0.8,<0.9 against
a current 0.13.1 release.

Found while drafting the colleague migration proposal, which points
colleague at this runbook. Recorded as deviation d3 of the
m1-leftovers plan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
…dings

Closes three of the four findings from issue #7's adversarial live test of
scripts/colleague_inventory.py.

1. Per-site pinning (finding 2, the architectural one). ALLOWLIST answers "may
   this module spawn at all"; it could not see three brand-new spawns added to
   the already-allow-listed tools.py, and --check returned exit 0. SITE_BASELINE
   now pins each module's spawn-site *shape* at PINNED_SHA — the sorted multiset
   of canonical call names, with a :shell marker for a literal shell=True — and
   any delta fails --check. Line numbers are deliberately not part of the
   signature, so an unrelated edit above a spawn is not a false alarm.
   --emit-baseline regenerates the literal, and refuses to emit from a partial
   scan.

2. Module-prefix resolution (finding 1). The 12-entry literal set became a
   per-module table matched on the module prefix, so subprocess.getoutput,
   getstatusoutput, the os.exec*/spawn*/posix_spawn*/fork family, pty, the
   multiprocessing and process-pool constructors and runpy all resolve — and
   asyncio.subprocess.create_subprocess_exec resolves the same as
   asyncio.create_subprocess_exec, which was the sharpest evasion in the report.
   The prefix test is dotted-boundary aware: ostrich.system is not os.system.

3. Scope-aware bindings (finding 6). Name resolution now walks real Python
   scopes (module/function/class/comprehension, with class bodies invisible to
   nested functions) instead of a flat ast.walk, and an unbound base resolves to
   nothing instead of to itself. A parameter named os, os = self.os, for os in
   hosts, from mypkg.compat import subprocess, and a local named run beside a
   real from subprocess import run all stop being findings. A gate that cries
   wolf on a variable named os gets disabled, which is its own failure mode.

Verified against the pinned colleague checkout (28fee29): the old and new
scanners produce byte-identical findings — 21 sites, 15 modules, 13 debt, two
shell=True — so the widened set and the scope rewrite change nothing about real
colleague, and --check still exits 0. With one benign subprocess.run appended to
that checkout's tools.py, the old scanner exits 0 and the new one exits 1 naming
the delta.

debt_remaining is still derived from ALLOWLIST intersected with what the scan
observed; the site baseline is not allowed to become its source, and a test pins
that.

The honest ceiling is unchanged: this detects accidental and careless drift from
a pinned baseline. Indirection (sp = subprocess, getattr, dynamic import),
sibling re-export inside colleague/, exec() of a source string, and non-literal
shell= spellings remain open and are listed in the module docstring rather than
papered over.

Every committed fixture is source text for the AST parser and is never executed;
argv is only `true` or `echo`. test_fixture_sources_stay_benign keeps issue #7's
illustrative payloads out of the repository.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
Six verbs — read, list, stat, write, edit, media — fronting the registered
fs.* operations through `shell.operations.execute()`. The CLI is a front end
over the identical engine, not a second implementation: this module opens no
file, resolves no path, and decides nothing about the root. Two tests enforce
that rather than trusting it — each verb's structured output is compared
against a result the test produced by calling execute() itself, and the
operation ids the CLI reports are retrieved through `shell operation show`,
which reads the evidence record only execute() writes.

New handler `shell/fs/stat.py` (`fs.stat`, intent=OBSERVE): existence, kind,
size_bytes, mtime and permission bits. Two decisions are deliberate and pinned
by tests:

* a missing path is a SUCCEEDED observation reporting `exists: false`, not a
  failure — absence is one of the answers a stat exists to give. This diverges
  from fs.read on purpose, and the divergence is pinned so nobody "fixes" it;
* confinement resolves, description does not. `_safe_path` resolves (following
  symlinks) and refuses anything landing outside the work root, with a
  byte-identical message to fs.read's — asserted as equality, so the two ports
  cannot drift. `lstat` on the literal path then describes what is actually
  there, so an in-root symlink is reported as a symlink rather than silently
  described as its target. Doing either half the other way round would be a
  real defect.

Preview by default: write and edit carry apply=True only when --apply is given.
A preview reports status `previewed`, never `succeeded`; text mode adds a
stderr diagnostic naming the remedy. Exit code is 0 because the CLI did what
was asked — "did the write happen" is the status field's question, not the exit
code's, and the two are not conflated.

Streams: in --json mode the full structured result reaches stdout on every
path including failures, so an agent never has to re-run a mutation to learn
its outcome. In text mode a failure writes nothing to stdout — its rendering IS
the error message, and `shell fs read missing.txt > out.txt` must not land
"no such file" in out.txt looking like file content.

Also: `KIND` constant added to shell/fs/media.py so the CLI references each
handler's own kind instead of re-spelling a literal; catalog entries for all
eight fs paths; and the Status/Verbs prose in the explain root, `learn` and
`overview` updated — they claimed the fs group was not exposed, which this
commit makes false.

README.md and CLAUDE.md carry the same stale claim and are owned by a sibling
task this wave; they are deliberately untouched here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
…sion classes

t6 closed the architectural finding from issue #7 — SITE_BASELINE pins
each module's spawn-site shape, so a new spawn in an already-allow-listed
module now fails --check. The survey section still asserted that hole as
live. Rewritten to state what closed, what remains open (indirection,
structure, non-literal shell= spellings), and the two deliberate trades
the scope-aware rewrite introduces. The drift-detector framing and the
'read a green --check as...' sentence are unchanged, because they stay
true.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
The CLI hand-picks which fs verbs carry --apply; the engine decides which
operations preview, from the intent each handler declared at registration.
Nothing structural ties those two together, so a handler whose intent changed
would leave a verb that previews forever with no flag to escape it, or a
mutation that applies with no way to preview it first.

Asserts the correspondence over every verb, in both directions -- four verbs
report requires_apply=False and carry no flag, two report True and carry one --
so the guard is not vacuously green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
t7 made three surfaces false at once: README and CLAUDE.md both said no
CLI verb executes an fs operation, and the handler inventory omitted
fs.stat. The learn/overview/catalog text was corrected inside t7 itself;
these two files were outside its scope by the wave's file-ownership rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
…y flag

Part A -- `shell process exec` and `shell process shell`, the first CLI verbs
that run a process. Both build an Operation and dispatch it through
shell.operations.execute(): no second implementation, no reaching around the
pipeline. Both declare intent=EXECUTE, so both preview by default and need
--apply; a preview reports `previewed`, starts nothing, and predicts nothing --
no effects, no exit code.

The profile asymmetry is enforced in the CLI, and enforced *early*. `exec`
accepts project and control; `shell` accepts project only, because a shell
re-interprets a string after the gate has read it, so which program runs is not
settled by what was gated. The handlers already refuse this, but the pipeline's
preview branch sits between the gate and the handler -- so an unapplied
`process shell --profile control` would have come back `previewed`, telling the
caller it was fine and merely unapplied. The CLI therefore refuses before
building the operation, and it does so by calling the handler's own
require_profile() with the kind's own accepted-profile tuple and hint constant,
so the refusal is the library's sentence rather than a paraphrase of it. That
required promoting _EXEC_PROFILES / _SHELL_PROFILES and the two inline hint
strings to public module constants.

Nothing here is path-confined and the surfaces say so rather than leaving it to
be inferred: output.confinement keeps path_confined / uses_shell /
gate_inspects_reinterpreted_string as separate keys on an applied result, every
result including a preview carries the runner's evidence.isolation_note, and a
text-mode preview names the missing confinement on stderr alongside --apply. A
test writes a file outside --root and watches the operation succeed, so the
claim cannot be quietly upgraded later.

The command's own exit code is not passed through. 1 and 2 already mean user
and environment error in this CLI and 3+ is reserved, so a command exiting 1
would be indistinguishable from a bad flag -- and a non-zero exit is not a
failure of the operation, which was "run this and observe it". It lives in
output.exit_code, with a stderr diagnostic in text mode so 0 is never read as
approval.

Part B -- --apply is now derived from the operation registry for both noun
groups. The CLI used to hand-pick which verbs carried the flag while the engine
independently decided which operations preview; t7 pinned the correspondence
but could not prevent a divergence, and a second group was about to repeat the
pattern. shell/cli/_operation.py:add_apply_flag() asks normalize() the same
question execute() asks, and is now the only place in shell/ that spells
--apply -- asserted by a source scan, so a future noun group cannot
reintroduce the list. tests/test_cli_fs.py passes unmodified; its correspondence
assertion is now trivially true and is kept because a regression would make it
load-bearing again. The derivation itself is tested against handlers registered
for the test alone.

The policy-source flags moved into the same shared module: `policy` evaluates a
snapshot and `process` executes under one, and the two must not be able to
resolve one differently. `shell process` is consequently the first CLI verb the
run_command gate actually governs.

learn, overview and the explain root said "no CLI verb runs a shell command
yet". That is now false, so they were corrected -- and still name env and git
as unexposed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
…troy

Adds shell/cli/_commands/env.py with one real verb, `describe`, plus the
required `overview`. It renders the two-axis Environment (workspace vs
runner, never flattened into one "mode") that `shell fs`/`shell process`
would build from the same --root/--read-only-path/--env flags, as
Environment.to_dict() verbatim, in text and --json. The host runner's
no-isolation posture (isolation="none", isolation_note) surfaces verbatim
in both renderings.

`env create`/`env destroy` are deliberately absent: no OperationIntent
.LIFECYCLE handler is registered and there is no ContainerRunner, so
either verb would front nothing. The catalog entry, the global overview,
and the noun's own overview all say so explicitly and name Milestone 4 as
where they land. `shell env create`/`destroy` fail as ordinary
unrecognized subcommands through the existing CliError path (exit 1,
error:/hint:, no traceback) -- pinned by test, not left to accident.

wired into shell/cli/__init__.py; catalog entries added to
shell/explain/catalog.py; shell/cli/_commands/overview.py's global verb
list and status notes updated to mention env (describe only).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
@OriNachum

Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Wire CLI fs/process/env to operations.execute; add fs.stat; close scanner per-site drift hole

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add shell fs/shell process verbs that execute operations via operations.execute().
• Derive --apply from handler registry intent; prevent CLI/engine preview drift.
• Close scanner per-site allowlist hole with SITE_BASELINE; update CI and docs.
Diagram

graph TD
  A["CLI noun groups\n(shell fs/process/env)"] --> B(["CLI shared helpers\n(shell/cli/_operation.py)"]) --> C["Operation engine\n(shell.operations.execute)"] --> D["FS handlers\n(fs.* incl fs.stat)"] --> E["Process handlers\n(process.exec/shell)"]
  C --> F["Policy gate\n(shell.policy)"]
  C --> G[("Evidence store\n--evidence-dir")]
  H["CI drift gate\ncolleague_inventory --check"] --> I["Scanner\nSITE_BASELINE"]

  subgraph Legend
    direction LR
    _cli["CLI"] ~~~ _mod(["Shared module"]) ~~~ _db[("Persisted data")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Generate CLI verbs directly from operation registry
  • ➕ Eliminates duplicated kind/intent knowledge and reduces wiring drift risk further
  • ➕ Could standardize common flags/exit code behavior across all future nouns
  • ➖ Hard to express noun-specific arguments/UX (e.g., fs write stdin/content groups, process profile semantics) without leaking abstraction
  • ➖ Risk of less explicit help text and weaker “truthful guidance surfaces” goal
2. Keep --apply lists per noun, add runtime guardrails
  • ➕ Lower refactor churn in CLI parser construction
  • ➕ Can still prevent 'preview forever' by erroring when apply missing
  • ➖ Still maintains two sources of truth (parser vs registry) and is easier to regress
  • ➖ Runtime-only failure is worse UX than correct flag presence and help output
3. Ship env create/destroy as stubs (preview-only)
  • ➕ Surface completeness for users exploring the CLI
  • ➕ Could reserve CLI contract early
  • ➖ Creates a facade with no engine/runner behind it; misleading and increases future compatibility burden
  • ➖ Confuses intent/profile/preview semantics for lifecycle operations before they exist

Recommendation: Keep the PR’s explicit noun-group CLI with registry-derived --apply, plus the new scanner SITE_BASELINE. It strikes the best balance: UX stays explicit and truthful, while the highest-risk drift (apply/preview mismatch; allow-listed module spawn growth) is structurally prevented and pinned by tests.

Files changed (35) +8099 / -261

Enhancement (6) +1775 / -0
__init__.pyRegister new CLI noun groups: env, fs, process +6/-0

Register new CLI noun groups: env, fs, process

• Wires 'shell env', 'shell fs', and 'shell process' into the top-level CLI parser so the new command groups are exposed.

shell/cli/init.py

env.pyAdd 'shell env describe' and env overview +288/-0

Add 'shell env describe' and env overview

• Introduces the env noun group with 'describe' to render the two-axis Environment (workspace + runner) using the library’s real construction path and 'to_dict()' output. Deliberately omits create/destroy and documents Milestone 4 deferral.

shell/cli/_commands/env.py

fs.pyAdd 'shell fs' verbs that dispatch through operations.execute +474/-0

Add 'shell fs' verbs that dispatch through operations.execute

• Implements 'shell fs read|list|stat|write|edit|media' as thin frontends that build Operations and call 'operations.execute()', with consistent exit-code and stdout/stderr policy. Uses registry-derived '--apply' for mutations and supports evidence persistence via '--evidence-dir'.

shell/cli/_commands/fs.py

process.pyAdd 'shell process exec|shell' verbs with early profile refusal +568/-0

Add 'shell process exec|shell' verbs with early profile refusal

• Implements process execution verbs that dispatch via 'operations.execute()' with registry-derived '--apply', shared policy flags, env allow-listing, and resource flags. Enforces exec vs shell profile asymmetry before previewing by quoting the handler’s own profile refusal text.

shell/cli/_commands/process.py

_operation.pyCentralize CLI operation plumbing and derive --apply from registry +228/-0

Centralize CLI operation plumbing and derive --apply from registry

• Adds a shared helper module for CLI noun groups, including 'add_apply_flag()' derived from 'operations.normalize().requires_apply', root/evidence helpers, and shared policy-source args/loading. Establishes the rule that this is the only module in 'shell/' that spells '--apply'.

shell/cli/_operation.py

stat.pyAdd confined 'fs.stat' operation handler +211/-0

Add confined 'fs.stat' operation handler

• Introduces a new observe-intent handler that returns path metadata (exists/kind/size/mtime/mode) under work-root confinement. Deliberately treats missing paths as SUCCEEDED with 'exists: false', and uses lstat to report symlinks truthfully after confinement check.

shell/fs/stat.py

Bug fix (1) +548 / -97
colleague_inventory.pyClose per-site drift hole; improve spawn resolution and matching +548/-97

Close per-site drift hole; improve spawn resolution and matching

• Adds 'SITE_BASELINE' and '--emit-baseline' to pin per-module spawn-site shapes at 'PINNED_SHA', making new spawns in already-allow-listed modules fail '--check'. Refactors spawn detection to use module-prefix matching and scope-aware name resolution, reducing false positives and closing previously missed call surfaces.

scripts/colleague_inventory.py

Refactor (4) +63 / -94
policy.pyRefactor policy CLI to reuse shared policy arg plumbing +22/-75

Refactor policy CLI to reuse shared policy arg plumbing

• Moves policy snapshot selection flags and parsing into 'shell.cli._operation' so 'shell policy' and 'shell process' share identical policy resolution behavior; also updates imports to include fs.stat registration for explain output.

shell/cli/_commands/policy.py

media.pyExport fs.media KIND constant for consumers +6/-1

Export fs.media KIND constant for consumers

• Adds a 'KIND = "fs.media"' constant and uses it for registration so CLI and other consumers can reference the operation kind without re-spelling literals.

shell/fs/media.py

exec.pyExport accepted profiles and hint for CLI early validation +20/-8

Export accepted profiles and hint for CLI early validation

• Makes exec’s accepted profile set and refusal hint public ('EXEC_PROFILES', 'EXEC_PROFILE_HINT') so the CLI can reject invalid profiles before previewing while still emitting handler-owned wording.

shell/process/exec.py

shell.pyExport accepted profiles and hint; reuse handler wording in CLI +15/-10

Export accepted profiles and hint; reuse handler wording in CLI

• Exports 'SHELL_PROFILES' and 'SHELL_PROFILE_HINT' to support early CLI refusal of control profile for shell strings without duplicating policy text.

shell/process/shell.py

Tests (6) +3366 / -4
test_cli_apply_flag.pyTest '--apply' derivation invariants and single-source rule +160/-0

Test '--apply' derivation invariants and single-source rule

• Adds tests that '--apply' presence follows handler intent via the registry, fails loudly for unregistered kinds, and asserts no other module adds '--apply' flags directly (source scan).

tests/test_cli_apply_flag.py

test_cli_env.pyAdd tests for env describe equivalence and deferred verbs +292/-0

Add tests for env describe equivalence and deferred verbs

• Adds tests asserting 'shell env describe --json' matches a directly built 'Environment.to_dict()' (minus ephemeral id) and that create/destroy remain unrecognized with structured errors.

tests/test_cli_env.py

test_cli_fs.pyAdd end-to-end tests for 'shell fs' fronting execute() +769/-0

Add end-to-end tests for 'shell fs' fronting execute()

• Adds comprehensive tests proving fs verbs dispatch through 'operations.execute()' (CLI/library result equivalence on stable fields) and that evidence round-trips through 'shell operation show'. Pins preview-by-default behavior for write/edit and correct stdout/stderr behavior in text vs JSON modes.

tests/test_cli_fs.py

test_cli_process.pyAdd end-to-end tests for 'shell process' fronting execute() +1236/-0

Add end-to-end tests for 'shell process' fronting execute()

• Adds extensive tests running real subprocesses to prove the CLI is not a second implementation, that preview-by-default is enforced, and that profile asymmetry is rejected before previewing. Validates env allow-list behavior and result/evidence semantics.

tests/test_cli_process.py

test_colleague_inventory.pyExpand scanner tests for SITE_BASELINE drift and fixture hygiene +584/-4

Expand scanner tests for SITE_BASELINE drift and fixture hygiene

• Strengthens the colleague inventory test suite with synthetic AST fixtures, per-site baseline pinning tests for allow-listed-module drift, and additional hygiene guards to keep fixtures benign and non-executed.

tests/test_colleague_inventory.py

test_fs_stat.pyAdd tests pinning fs.stat semantics and confinement behavior +325/-0

Add tests pinning fs.stat semantics and confinement behavior

• Adds tests for registration/intent, metadata reporting, missing-path success semantics, and symlink behavior (resolved confinement vs lstat description), including pinning stat/read divergence on missing files.

tests/test_fs_stat.py

Documentation (15) +2333 / -58
currentPoint devague to the m1-leftovers frame +1/-1

Point devague to the m1-leftovers frame

• Updates the current devague pointer to the Milestone 1 leftovers workstream artifacts.

.devague/current

current_planPoint devague to the m1-leftovers plan +1/-1

Point devague to the m1-leftovers plan

• Updates the current plan pointer to the Milestone 1 leftovers plan artifact.

.devague/current_plan

m1-leftovers.jsonAdd delivery accounting for m1-leftovers +45/-0

Add delivery accounting for m1-leftovers

• Commits the machine-readable delivery record for the m1-leftovers arc (confirmed claims/targets and deviations).

.devague/deliveries/m1-leftovers.json

m1-leftovers.jsonAdd converged m1-leftovers frame +763/-0

Add converged m1-leftovers frame

• Adds the full devague frame JSON capturing scope, honesty conditions, and confirmed claims for the arc.

.devague/frames/m1-leftovers.json

m1-leftovers.jsonAdd buildable m1-leftovers plan +567/-0

Add buildable m1-leftovers plan

• Adds the detailed plan JSON mapping the frame’s targets into tasks with provenance.

.devague/plans/m1-leftovers.json

CHANGELOG.mdDocument 0.14.0 release: CLI executes ops, fs.stat, SITE_BASELINE +21/-0

Document 0.14.0 release: CLI executes ops, fs.stat, SITE_BASELINE

• Adds a 0.14.0 entry detailing the new CLI operation-executing nouns, registry-derived --apply, fs.stat semantics, and the scanner SITE_BASELINE closure for issue #7 finding.

CHANGELOG.md

CLAUDE.mdRefresh guidance surfaces to match shipped CLI/engine reality +57/-16

Refresh guidance surfaces to match shipped CLI/engine reality

• Rewrites status and safety posture sections to accurately reflect the now-shipped fs/process CLI nouns, env describe, and the closed scanner architectural hole; removes stale claims about missing primitives/CLI-only introspection.

CLAUDE.md

README.mdUpdate README status and capability claims for new CLI nouns +33/-13

Update README status and capability claims for new CLI nouns

• Corrects the project status: operation core and HostRunner are built; CLI now executes fs/process operations; env describe exists; container lifecycle remains deferred. Updates the workspace/runner truth table accordingly.

README.md

2026-07-28-m1-leftovers.mdAdd milestone leftovers delivery report +186/-0

Add milestone leftovers delivery report

• Adds the plan-vs-actual accounting and review map for the m1-leftovers arc, tying shipped changes back to confirmed claims and deviations.

docs/deliveries/2026-07-28-m1-leftovers.md

2026-07-28-m1-leftovers.mdAdd milestone leftovers build plan +111/-0

Add milestone leftovers build plan

• Adds the buildable plan document for m1-leftovers tasks and sequencing.

docs/plans/2026-07-28-m1-leftovers.md

release-runbook.mdFix release-runbook dependency example and wording +3/-3

Fix release-runbook dependency example and wording

• Corrects misleading dependency guidance (colleague cannot depend on itself) and refreshes stale version pin examples.

docs/release-runbook.md

2026-07-28-m1-leftovers.mdAdd milestone leftovers spec +158/-0

Add milestone leftovers spec

• Adds the spec describing the scope closure between “Milestone 1 done” and the issue-defined DoD for m1-leftovers.

docs/specs/2026-07-28-m1-leftovers.md

learn.pyUpdate learn output for new fs/process CLI surface +64/-10

Update learn output for new fs/process CLI surface

• Updates the 'learn' command’s status and command list to include the new fs/process verbs and preview-by-default semantics.

shell/cli/_commands/learn.py

overview.pyUpdate overview to list fs/process/env nouns +17/-3

Update overview to list fs/process/env nouns

• Extends the top-level overview text to include the new operation-executing nouns and env describe, with correct confinement and preview semantics.

shell/cli/_commands/overview.py

catalog.pyUpdate explain catalog to reflect shipped fs/process/env nouns +306/-11

Update explain catalog to reflect shipped fs/process/env nouns

• Refreshes catalog status and adds detailed explain text for 'shell fs' and related semantics (preview, confinement, exit codes, and env deferral).

shell/explain/catalog.py

Other (3) +14 / -8
tests.ymlTighten colleague spawn drift gate wording and semantics +12/-6

Tighten colleague spawn drift gate wording and semantics

• Updates CI documentation/comments to reflect that per-site baseline drift is now checked (not only unclassified spawn paths), and renames the gate step accordingly.

.github/workflows/tests.yml

pyproject.tomlBump project version to 0.14.0 +1/-1

Bump project version to 0.14.0

• Updates the package version to reflect the new shipped CLI surfaces and scanner changes.

pyproject.toml

uv.lockUpdate lockfile for 0.14.0 release state +1/-1

Update lockfile for 0.14.0 release state

• Updates the uv lockfile to reflect the current resolved environment after the version bump.

uv.lock

OriNachum and others added 5 commits July 28, 2026 11:46
… call per pytest.raises block (S5778)

_emit always returned 0 on every non-raising path, so the -> int
signature misled a reader into thinking it computed a variable exit
code. Extract the terminal raise ladder (DENIED + catch-all) into
_raise_for_status(result) -> NoReturn, and change _emit to -> None;
the docstring's denial explanation travels with the branch that moved.
The call site in _run() now calls _emit() then returns EXIT_SUCCESS
explicitly. Mirrors the parallel refactor of process.py's _emit so the
two modules stay structurally aligned.

Also hoist the argv list above two `with pytest.raises(SystemExit)`
blocks in test_cli_fs.py that called str(work) inside the block,
leaving exactly one invocation (main(argv)) that can raise. Swept the
whole file for the same pattern; these were the only two instances.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
S3516/S3776 (process.py:332): _emit was typed -> int but every path either
returned 0 or raised CliError, so the return value carried no information
and the branching pushed cognitive complexity to 17. Extract the DENIED /
TIMED_OUT / catch-all raise ladder into a new `_raise_for_status(result) ->
NoReturn` helper; _emit now returns None and delegates to it. The docstring
sentence about a policy denial landing in a redirected build.log travels
with the ladder into _raise_for_status, since that is the code it now
describes. The call site in _run replaces `return _emit(...)` with
`_emit(...)` followed by `return EXIT_SUCCESS`. Verified with the
`cognitive_complexity` package: _emit drops from 17 to 12 (_raise_for_status
is 5), both under the 15 threshold.

S5778 (tests/test_cli_process.py:1158): hoisted the argv list for
test_an_invalid_profile_is_a_structured_error above the `pytest.raises`
block so only `main(argv)` remains inside it.

shell/cli/_commands/fs.py and tests/test_cli_fs.py are untouched — a
sibling agent owns the identical S3516 fix there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkC4zsVUmYrRCwSWSe4aLp
@sonarqubecloud

Copy link
Copy Markdown

@qodo-code-review

Copy link
Copy Markdown

Qodo is busy working

Check back in a few minutes. Qodo's code review agents are on it.

Grey Divider

@OriNachum
OriNachum merged commit e58d384 into main Jul 28, 2026
11 checks passed
@OriNachum
OriNachum deleted the spec/m1-leftovers branch July 28, 2026 19:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant