Skip to content

M0: align guidance surfaces with the operations-plane issue + install the colleague known-debt gate (t72, t73)#5

Merged
OriNachum merged 4 commits into
mainfrom
feat/m0-guidance-and-debt-gate
Jul 19, 2026
Merged

M0: align guidance surfaces with the operations-plane issue + install the colleague known-debt gate (t72, t73)#5
OriNachum merged 4 commits into
mainfrom
feat/m0-guidance-and-debt-gate

Conversation

@OriNachum

Copy link
Copy Markdown
Contributor

Milestone 0 continued: t72 (guidance alignment) and t73 (the colleague
known-debt CI gate), merged locally between worktrees and opened as one PR.
Follows #3 (t71).

t72 — align every guidance surface with the operations-plane issue

README.md still led with "the file-and-shell tool surface … extracted from
colleague"
— the framing issue #1 explicitly supersedes. Anyone arriving at
the repo read a six-tool extraction, not an operations plane, and none of the
plan's scope decisions existed anywhere in the tree.

  • README.md rewritten around the operation lifecycle, the two-axis
    environment model (workspace × runner, rather than one overloaded mode), the
    three profiles, and evidence as a product surface. colleague is named as the
    first consumer, not the owner. The guard-not-a-sandbox section is
    preserved and extended with a note that the future container runner will be
    described separately, so container work cannot retroactively upgrade the
    host claim. tests/test_honesty.py passes on the rewrite.
  • AGENTS.colleague.md was a generic resident prompt that never said where
    it was. It now states the mission and the five contracts a resident can break
    by accident.
  • CLAUDE.md gains an explicit WebGlass peer-seam section: webglass-cli
    is a peer capability, not a layer; web semantics must never become Operation
    kinds here (a web.fetch kind would be the seam collapsing); only
    provider-neutral artifacts cross.
  • The devague artifact trail — converged spec, buildable plan, external
    review record, and scripts/render_plan.py, which projects the plan from
    authoritative devague state with a SHA-256 drift check.

The other half of t72, the six-part Milestone 0/1 plan required by issue #1
§17, is posted on #1.

t73 — colleague spawn-path known-debt gate

scripts/colleague_inventory.py is now tracked, and CI enforces it.

The gate exists so colleague cannot grow new unmediated subprocess paths
while the known ones migrate. It fails immediately on any unclassified spawn
path, and publishes debt_remaining on every run so the number trends visibly
toward the Milestone 3 target of zero. It reports debt without failing on
it — driving 13 → 0 is M3 work, not a gate today.

CI clones the public agentculture/colleague at the SHA read out of the
committed scanner (single source of truth, not duplicated into YAML), then
asserts it actually scanned that commit — without that guard the gate could
pass green against the wrong tree. A clone failure fails the job loudly; a gate
that silently no-ops is worse than no gate.

tests/test_colleague_inventory.py (17 tests) drives the scanner entirely
against synthetic tmp_path fixtures, so the gate is provable in CI with no
colleague checkout present. Both directions are pinned: a fixture with an
unclassified module exits non-zero, an allow-listed-only fixture exits zero.

One behavioural change: a missing or unreadable checkout now reports an
environment error (exit 2) instead of raising SystemExit, so CI can
distinguish a broken clone from a real gate failure — previously both exited 1.

Verification

66 tests (49 → 66), coverage 92.67% against a floor of 60. black, isort,
flake8, bandit, markdownlint (0 errors / 14 files), teken cli doctor --strict,
devex pr lint, and render_plan.py --check clean at cd5f912d7114.

Live-tested against the real colleague checkout at the pin:

colleague inventory @ 28fee290c51f (pinned 28fee290c51f)
  spawn sites : 21 across 15 modules
    control       : 15
    project       : 6
  shell=True  : hooks.py:405, tools.py:1022
  debt modules: 13 (must reach 0 by Milestone 3)

Observed exit codes: real checkout at pin → 0; fixture with unclassified module
→ 1; allow-listed-only fixture → 0; checkout off the pin → 1 (sha guard); no
colleague/ package → 2.

Process

t73 was built by a subagent in an isolated worktree and merged under a TDD
gate — tests green before (49) and after (66) the merge — then the worktree was
reaped. Task agents did not touch pyproject.toml or CHANGELOG.md; the
merging agent bumps once per merge, which removes the guaranteed same-wave
conflict on those two files.

Deviations from the approved split plan are recorded in #4 and in devague's
append-only ledger as d1d3 (proposed, awaiting operator --confirm).
Most notably d1: colleague was dropped from the implementer and reviewer roles
after two consecutive drives completed zero steps.

  • shell-cli (Claude)

OriNachum and others added 4 commits July 19, 2026 04:17
README.md still led with "the file-and-shell tool surface ... extracted
from colleague", the framing issue #1 explicitly supersedes. Anyone
arriving at the repo read a six-tool extraction, not an operations plane,
and the plan's own scope decisions were nowhere in the tree.

Rewrite README.md around the operation lifecycle, the two-axis environment
model (workspace x runner, not one overloaded mode), the three profiles,
and evidence as a product surface. colleague is named as the first
consumer, not the owner. The guard-not-a-sandbox section is preserved and
extended with a note that the future container runner will be described
separately — the two claims stay textually separate, so container work
cannot retroactively upgrade the host path.

AGENTS.colleague.md was a generic resident prompt that never said where it
was. It now states the mission and the five contracts a resident can break
by accident: pure-stdlib core, guard-not-a-sandbox, preview-by-default,
version-per-PR, and no leaked tracebacks.

CLAUDE.md gains an explicit WebGlass peer-seam section. webglass-cli is a
peer capability on the same footing, not a layer; web semantics must never
become Operation kinds here (a web.fetch kind would be the seam
collapsing); only provider-neutral artifacts cross, so a webglass-produced
file is just a file fs.read can be asked to open.

Also commits the devague artifact trail this work was derived from — the
converged spec, the buildable plan, the external review record, and
scripts/render_plan.py, which projects the plan from authoritative devague
state with a SHA-256 drift check. The projection exists because devague
cannot export a milestone-scoped plan (devague#85); delete it when that
lands.

Covers t72 (plan: guarded-local-operations-plane), less the issue #1 plan
post, which follows as a comment. Per deviation d2 (#4), the CLAUDE.md
rewrite was authored during the spec phase and rides along here rather
than being re-authored.
shell-cli only becomes the single mediated local-operations plane if
colleague stops growing NEW unmediated subprocess paths while the known
ones migrate. Absent a mechanical gate, that invariant decays silently:
a hand-written inventory was already wrong once (it reported 16 modules
where the AST scan finds 15), and it survived a day in committed
guidance. This makes the invariant enforced rather than asserted.

Commit the pinned AST scanner and wire `--check` into CI as a gate that
fails the moment a spawn literal appears in a module outside ALLOWLIST,
while publishing `debt_remaining` — the count of modules still owning
their own spawn — on every run via ::notice:: and the step summary.

The gate is a countdown, not a red test. It passes today at 13 debt
modules and must reach 0 by the end of Milestone 3; a debt entry is a
scheduled migration, never a permanent exemption. This commit installs
and reports the counter ONLY — no migration is attempted here.

Design notes:

- CI has no colleague checkout, and agentculture/colleague is public, so
  the job clones it at the pinned SHA for a hermetic, reproducible gate.
  A clone failure fails the job, and the run asserts `sha_matches` before
  trusting the numbers — a gate that silently no-ops against the wrong
  tree is worse than no gate.
- The pin is read out of the committed scanner, so PINNED_SHA stays the
  single source of truth rather than being duplicated into the workflow.
- `debt_remaining` publishes BEFORE the gate runs, so the counter appears
  even on a failing run.
- Exit 2 (environment error) is now distinct from exit 1 (gate failure),
  so CI can tell "the clone is broken" from "a new unclassified path
  landed". Previously a missing checkout raised SystemExit from scan();
  it is now an InventoryError rendered as error:/hint: with no traceback,
  matching the repo's CLI error contract.

Tests drive both directions against synthetic fixtures rather than a real
checkout, so the failing direction is actually proven and the suite still
runs in CI, where no colleague clone exists.

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

Copy link
Copy Markdown
Contributor Author

/agentic_review

@sonarqubecloud

Copy link
Copy Markdown

@OriNachum
OriNachum merged commit f2859fb into main Jul 19, 2026
11 checks passed
@OriNachum
OriNachum deleted the feat/m0-guidance-and-debt-gate branch July 19, 2026 01:27
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Align ops-plane guidance and add colleague known-debt subprocess gate

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

Grey Divider

AI Description

• Reframe README/CLAUDE/AGENTS around the guarded local operations-plane model.
• Add a pinned AST scanner and CI job to block new unclassified colleague spawn paths.
• Commit spec/plan artifacts plus a renderer that drift-checks generated plan projections.
Diagram

graph TD
  CI(["GitHub Actions"]) --> WF["tests.yml"] --> INV["colleague_inventory.py"] --> COL{{"colleague repo (pinned)"}}
  TESTS["test_colleague_inventory.py"] --> INV
  DEV[(".devague plan JSON")] --> RENDER["render_plan.py"] --> OUT["docs/plans projection"]
  DEV --> SPEC["docs/specs export"]
  GUIDES["README/CLAUDE/AGENTS"] --> SPEC
  GUIDES --> OUT

  subgraph Legend
    direction LR
    _svc(["CI/Job"]) ~~~ _file["File/Script"] ~~~ _db[("State/Artifacts")] ~~~ _ext{{"External repo"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use colleague’s existing boundary test instead of a new scanner gate
  • ➕ No external clone/network dependency in this repo’s CI
  • ➕ Leverages an already-established invariant in colleague
  • ➖ Doesn’t enforce per-module classification (project/control/observe) or publish debt_remaining here
  • ➖ Harder to keep the pin + reporting single-sourced from shell-cli artifacts
2. Use CodeQL/static analysis rules for spawn detection
  • ➕ Richer analysis possible beyond direct call-site matching
  • ➕ Could extend to more security-sensitive patterns
  • ➖ Higher operational complexity and slower iteration for a Milestone 0 gate
  • ➖ Harder to keep the scan strictly reproducible and pinned with simple JSON outputs
3. Vendor a colleague snapshot (submodule/subtree) and scan locally
  • ➕ Removes network dependency at CI runtime
  • ➕ Scan always runs on checked-in content
  • ➖ Ongoing duplication/maintenance burden
  • ➖ Snapshot drift risk can mask upstream reality

Recommendation: Keep the current approach: a pinned-SHA clone + pure-stdlib AST scanner is lightweight, reproducible, and directly enforces “no new unclassified spawn paths” while still reporting existing debt. The explicit sha_matches assertion is a key robustness improvement to avoid green runs against the wrong tree.

Files changed (20) +6546 / -195

Enhancement (2) +482 / -0
colleague_inventory.pyAdd pinned AST scanner + allowlist-based known-debt gate +257/-0

Add pinned AST scanner + allowlist-based known-debt gate

• Introduces a pure-stdlib AST-based scanner that inventories process-spawn call sites, classifies them via an allowlist with debt tracking, emits JSON (including sha_matches and debt_remaining), and provides --check with distinct exit codes for gate failures vs environment errors.

scripts/colleague_inventory.py

render_plan.pyAdd devague plan projection renderer with drift check +225/-0

Add devague plan projection renderer with drift check

• Adds a renderer that projects authoritative .devague plan JSON into docs/plans with an embedded SHA-256 and a --check mode to fail when projections are stale.

scripts/render_plan.py

Tests (1) +208 / -0
test_colleague_inventory.pyAdd synthetic-fixture tests for inventory scanner and exit codes +208/-0

Add synthetic-fixture tests for inventory scanner and exit codes

• Adds tests importing the scanner by path, building synthetic colleague fixtures, proving fail/pass behavior, validating JSON payload fields (including debt_remaining and shell=True reporting), and asserting exit-code semantics (1 for gate failure, 2 for environment errors).

tests/test_colleague_inventory.py

Documentation (12) +5748 / -194
guarded-local-operations-plane.jsonAdd delivery deviations record +48/-0

Add delivery deviations record

• Introduces a delivery record capturing proposed deviations (role changes, partial work reuse, and strict PR sequencing) for auditability.

.devague/deliveries/guarded-local-operations-plane.json

guarded-local-operations-plane.jsonAdd devague frame JSON for operations plane +1505/-0

Add devague frame JSON for operations plane

• Commits the devague frame capturing targets/claims for the guarded local operations-plane effort.

.devague/frames/guarded-local-operations-plane.json

guarded-local-operations-plane.jsonCommit authoritative devague plan state +2585/-0

Commit authoritative devague plan state

• Adds the full devague plan JSON (source of truth) used by the plan projection renderer and artifact trail.

.devague/plans/guarded-local-operations-plane.json

default__public.jsonlAdd public eidetic memory note entry +1/-0

Add public eidetic memory note entry

• Adds a single JSONL memory note record to the default public eidetic store.

.eidetic/memory/default__public.jsonl

AGENTS.colleague.mdDefine repo mission and resident contracts +47/-0

Define repo mission and resident contracts

• Adds an explicit “where you are” section plus five key contracts (stdlib-only core, guard-not-sandbox, preview-by-default, version-per-PR, no tracebacks) and capability seam boundaries.

AGENTS.colleague.md

CHANGELOG.mdDocument guidance rewrite and inventory gate releases +25/-0

Document guidance rewrite and inventory gate releases

• Adds 0.8.1/0.8.2 entries describing the new ops-plane guidance/artifacts, the scanner + CI gate, and updated error/exit-code behavior.

CHANGELOG.md

CLAUDE.mdRewrite contributor guide to operations-plane framing and seams +625/-170

Rewrite contributor guide to operations-plane framing and seams

• Replaces the “extract six tools” framing with the operation lifecycle model, two-axis environments, profiles, evidence posture, and explicit peer seams (notably webglass as a peer capability).

CLAUDE.md

README.mdRewrite README around operations, environments, profiles, and non-goals +102/-24

Rewrite README around operations, environments, profiles, and non-goals

• Updates repo framing to the guarded local operations plane, adds the two-axis environment table and three profiles, makes evidence a first-class surface, and enumerates non-goals including the webglass seam.

README.md

18-07-2026.sol.challenge.mdAdd external challenge-pass review record +49/-0

Add external challenge-pass review record

• Adds a verbatim external review identifying a colleague policy bypass and recommending sequencing and corrections.

docs/external-review/18-07-2026.sol.challenge.md

19-07-2026.sol.plan.mdAdd external review of spec/plan readiness +187/-0

Add external review of spec/plan readiness

• Adds a verbatim external review calling out remaining contradictions, sequencing fixes, publishing hardening, and plan-execution issues.

docs/external-review/19-07-2026.sol.plan.md

2026-07-18-guarded-local-operations-plane.mdAdd generated plan projection with SHA-256 drift marker +347/-0

Add generated plan projection with SHA-256 drift marker

• Adds a generated plan projection including an embedded SHA-256 of the source plan JSON and execution waves; explicitly marked non-editable.

docs/plans/2026-07-18-guarded-local-operations-plane.md

2026-07-18-guarded-local-operations-plane.mdAdd guarded local operations-plane spec +227/-0

Add guarded local operations-plane spec

• Adds the converged spec defining the operations-plane model, constraints, and Milestone 0 requirements including the known-debt gate.

docs/specs/2026-07-18-guarded-local-operations-plane.md

Other (5) +108 / -1
currentSet current devague frame slug +1/-0

Set current devague frame slug

• Adds the current devague frame pointer for the guarded local operations-plane work.

.devague/current

current_planSet current devague plan slug +1/-0

Set current devague plan slug

• Adds the current devague plan pointer to align tooling with the committed plan artifacts.

.devague/current_plan

tests.ymlAdd colleague spawn inventory known-debt gate job +96/-0

Add colleague spawn inventory known-debt gate job

• Adds an inventory-gate job that reads PINNED_SHA from the scanner, clones colleague at that SHA, runs the scanner to JSON, asserts sha_matches, publishes debt_remaining, then gates on --check (no unclassified spawn paths).

.github/workflows/tests.yml

.markdownlint-cli2.yamlExclude generated specs/plans from markdownlint +9/-0

Exclude generated specs/plans from markdownlint

• Ignores docs/specs/** and docs/plans/** since they’re generated and can legitimately trigger markdownlint emphasis/strong rules.

.markdownlint-cli2.yaml

pyproject.tomlBump version to 0.8.2 +1/-1

Bump version to 0.8.2

• Updates the project version as required by the release-per-PR contract.

pyproject.toml

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (1) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 10 rules
✅ Skills: version-bump

Grey Divider


Action required

1. Alias imports evade scanner 🐞 Bug ≡ Correctness
Description
scan_file() only flags spawns when the call expression renders to an exact dotted string in
_SPAWN_CALLS (e.g. subprocess.run), so common patterns like `import subprocess as sp;
sp.run(...) or from subprocess import run; run(...)` are missed. This enables new unmediated spawn
paths to land without failing the CI --check gate.
Code

scripts/colleague_inventory.py[R119-149]

+def _dotted(node: ast.AST) -> str:
+    """Render an attribute/name chain as a dotted string, else ''."""
+    parts: list[str] = []
+    while isinstance(node, ast.Attribute):
+        parts.append(node.attr)
+        node = node.value
+    if isinstance(node, ast.Name):
+        parts.append(node.id)
+        return ".".join(reversed(parts))
+    return ""
+
+
+def scan_file(path: Path, rel: str) -> list[Finding]:
+    try:
+        tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
+    except (SyntaxError, UnicodeDecodeError):
+        return []
+
+    found: list[Finding] = []
+    for node in ast.walk(tree):
+        if not isinstance(node, ast.Call):
+            continue
+        name = _dotted(node.func)
+        if name not in _SPAWN_CALLS:
+            continue
+        uses_shell = any(
+            kw.arg == "shell" and isinstance(kw.value, ast.Constant) and kw.value.value is True
+            for kw in node.keywords
+        )
+        found.append(Finding(module=rel, line=node.lineno, call=name, uses_shell=uses_shell))
+    return found
Evidence
The scanner derives the call name purely from syntactic ast.Attribute/ast.Name chains and
compares it to a fixed set of canonical strings, with no import-binding resolution, so
alias/from-import spellings will not match. The tests only exercise the canonical `import
subprocess; subprocess.run(...)` form, leaving the alias/from-import gap untested.

scripts/colleague_inventory.py[44-59]
scripts/colleague_inventory.py[119-149]
tests/test_colleague_inventory.py[50-53]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`scripts/colleague_inventory.py` detects spawn sites by rendering the AST call target to a dotted string and comparing it to `_SPAWN_CALLS`. This misses spawns invoked via import aliases (`import subprocess as sp; sp.run`) and from-imports (`from subprocess import run; run()`), producing false negatives and weakening the CI gate.

## Issue Context
The scanner is used as a CI known-debt gate (`--check`) to prevent *new* unclassified spawn paths from appearing in colleague. False negatives defeat that purpose.

## Fix Focus Areas
- scripts/colleague_inventory.py[119-149]
- scripts/colleague_inventory.py[131-156]
- tests/test_colleague_inventory.py[50-208]

## Implementation notes
- In `scan_file()`, pre-scan the module for `ast.Import` / `ast.ImportFrom` to build a binding map:
 - module aliases: `sp -> subprocess`, `aio -> asyncio`, etc.
 - direct function imports: `run -> subprocess.run`, `Popen -> subprocess.Popen`, `system -> os.system`, etc.
- When evaluating a `Call`, map:
 - `ast.Attribute` base name through module-alias bindings (so `sp.run` becomes `subprocess.run`).
 - `ast.Name` through direct-function bindings (so `run` becomes `subprocess.run`).
- Add tests proving the gate fails on an unclassified module that uses:
 - `import subprocess as sp; sp.run([...])`
 - `from subprocess import run; run([...])`
 - and one nested allowlisted module case if relevant.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Scan failures are fail-open 🐞 Bug ☼ Reliability
Description
scan_file() returns an empty result on SyntaxError or UnicodeDecodeError, silently skipping
files instead of reporting that the inventory is incomplete. This can make --check pass (and debt
metrics look clean) even though parts of the checkout were not scanned.
Code

scripts/colleague_inventory.py[R131-135]

+def scan_file(path: Path, rel: str) -> list[Finding]:
+    try:
+        tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
+    except (SyntaxError, UnicodeDecodeError):
+        return []
Evidence
scan_file() catches SyntaxError/UnicodeDecodeError and returns [], making skipped files
indistinguishable from spawn-free files; the test suite asserts this skip behavior, confirming the
fail-open design.

scripts/colleague_inventory.py[131-136]
tests/test_colleague_inventory.py[170-176]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The scanner currently treats parse/decode failures as “no findings” by returning `[]`. That makes the inventory incomplete in an unobservable way and can allow the CI gate to pass even though scanning did not actually succeed.

## Issue Context
This tool is explicitly used as a CI enforcement gate; enforcement tools should not silently degrade to a partial scan.

## Fix Focus Areas
- scripts/colleague_inventory.py[131-149]
- scripts/colleague_inventory.py[152-168]
- scripts/colleague_inventory.py[214-252]
- tests/test_colleague_inventory.py[170-176]

## Implementation notes
- Track skipped files explicitly (e.g., `Inventory.skipped: list[str]` with reason).
- Include `skipped` in the JSON payload and in the human output.
- Make `--check` fail if any file was skipped (prefer exit code 2 as an environment/scanner error, since the gate cannot be trusted), or at minimum print an error and return non-zero.
- Update/replace the existing test that asserts “syntax error is skipped” to instead assert the new behavior (reported + non-zero under `--check`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. README claims container isolation 📘 Rule violation § Compliance
Description
README.md’s environment table states a Container runner provides “Execution isolation”, but the
same README states no runner has been built yet. This risks overstating isolation guarantees in
documentation without an implemented enforcement mechanism.
Code

README.md[R77-82]

+| Workspace | Runner | Guarantee |
+|---|---|---|
+| Checkout | Host | Guarded host execution; no isolation |
+| Worktree | Host | Reviewable/recoverable changes; no process isolation |
+| Checkout | Container | Execution isolation against the selected checkout |
+| Worktree | Container | Preferred autonomous mode |
Evidence
The documentation explicitly says no runner exists yet, but then presents Container runner rows as
having “Execution isolation”, which is an isolation guarantee not supported by the current
implementation state.

Rule 2184424: Do not overstate sandbox or isolation guarantees in documentation
README.md[53-54]
README.md[77-82]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`README.md` currently presents `Container` runner rows with an isolation “Guarantee” even though the project states no runner is implemented yet. This can be read as a present-tense security/isolation guarantee.

## Issue Context
The compliance requirement prohibits documentation implying sandbox/isolation guarantees that are not actually enforced by the implementation.

## Fix Focus Areas
- README.md[53-54]
- README.md[77-82]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Allowlist breaks on Windows 🐞 Bug ≡ Correctness
Description
scan() uses str(path.relative_to(pkg)), which yields platform-dependent separators; on Windows
this produces backslashes that won’t match forward-slash allowlist keys like resident/steward.py.
This causes false “unclassified” results (and potentially gate failures) when run on Windows.
Code

scripts/colleague_inventory.py[R161-167]

+    for path in sorted(pkg.rglob("*.py")):
+        rel = str(path.relative_to(pkg))
+        for finding in scan_file(path, rel):
+            inv.findings.append(finding)
+            inv.modules.add(rel)
+            if rel not in ALLOWLIST:
+                inv.unclassified.append(finding)
Evidence
The allowlist contains forward-slash module paths (e.g. resident/steward.py), while rel is
derived from Path stringification, which is separator-dependent; this mismatch is guaranteed on
Windows for nested paths.

scripts/colleague_inventory.py[68-85]
scripts/colleague_inventory.py[161-168]
tests/test_colleague_inventory.py[185-192]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Allowlist matching is done by string equality between `rel` and `ALLOWLIST` keys. Today `rel` is produced via `str(Path.relative_to(...))`, which uses OS-specific separators.

## Issue Context
CI currently runs on Linux, but this script is a developer-facing tool and should behave consistently across platforms.

## Fix Focus Areas
- scripts/colleague_inventory.py[68-85]
- scripts/colleague_inventory.py[161-168]

## Implementation notes
- Change `rel = str(path.relative_to(pkg))` to `rel = path.relative_to(pkg).as_posix()`.
- Keep `ALLOWLIST` entries in forward-slash form.
- Optionally add a unit test by refactoring path normalization into a helper that can be tested without depending on the host OS path separator.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread README.md
Comment thread scripts/colleague_inventory.py
Comment thread scripts/colleague_inventory.py
Comment thread scripts/colleague_inventory.py
@OriNachum

OriNachum commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

Delivery Summary — guarded local operations plane

plan: guarded-local-operations-plane · run: partial (blocked) · date: 2026-07-19
baseline: .devague/plans/guarded-local-operations-plane.json (19 confirmed tasks)

Intent

shell-cli is the guarded local operations plane for AI agents: every
work-affecting local operation is normalized into one Operation, evaluated
against an operator policy snapshot, executed in an explicitly selected
environment, and returned with structured evidence. colleague decides why,
when, and which.

This run executed the Milestone 0 opening slice. It did not reach Milestone 1,
and no operation, environment, policy, or runner has been built.

Actual Delivery

Only confirmed tasks are listed. The plan's 68 rejected tasks from earlier
iterations are excluded — devague summary currently renders them as planned
work, which is a devague defect, not a record of intent.

Plan task Status What actually landed
t71 delivered PR #3, merged. publish.yml gains gates + smoke; both publish paths depend on [test, gates, smoke]. docs/release-runbook.md. Released 0.8.0.
t72 delivered PR #5, open and green. README/AGENTS/CLAUDE realigned to the operations-plane framing; WebGlass peer seam documented; devague artifact trail committed; six-part plan posted on #1.
t73 delivered PR #5, open and green. Scanner tracked; inventory-gate CI job live; 17 synthetic-fixture tests.
t69, t70 not started — not ours colleague-repo security lane. Not filed. See Blocked below.
t74 blocked Requires a fixed colleague release (deps t69, t70).
t75t85 blocked Transitively blocked behind t74.
t86, t87 blocked — not ours colleague-repo M2 cutover; additionally depends on all of M1.

3 of 19 confirmed tasks delivered. 12 blocked. 4 belong to another repository.

Delivery Claims (with evidence)

Claims are stated at the strength the evidence supports, and no higher.

Claim Evidence Confidence
A release can no longer publish while any quality gate is red publish/test-publish now needs: [test, gates, smoke]; all three observed passing on PR #3 and #5 in real CI High — verified in CI, not just written
The published wheel imports, exposes the shell console script, and declares zero runtime dependencies smoke job green in CI; also live-tested locally against a real built wheel before commit High
A new unclassified subprocess path in colleague will fail shell-cli's CI inventory-gate green in CI; synthetic fixture with an unclassified module exits 1; allow-listed-only fixture exits 0 High — both directions pinned by test
colleague has 21 spawn sites across 15 modules, 13 of them debt Scanner output at pinned SHA 28fee29, asserted by the CI sha_matches guard High — mechanically derived, not transcribed
Every guidance surface now describes the operations plane README, AGENTS.colleague.md, CLAUDE.md rewritten; tests/test_honesty.py green Medium — no test asserts framing, only the honesty posture
The extraction seam is proven None. Not claimed. No Operation type exists yet.

Mid-work Decisions

  • Version bumps moved from task agents to the merging agent. The plan's
    global invariant put "version bumped" on every task, which would have made
    pyproject.toml and CHANGELOG.md a guaranteed conflict in every multi-task
    wave. Task agents now skip both; the merging agent bumps once per merge. The
    invariant still holds where CI checks it — at PR level.
  • t73 gained a sha_matches assertion not in its acceptance criteria.
    Without it the gate could pass green against a colleague tree other than the
    pinned one. Added by the task agent, kept.
  • colleague_inventory.py exit-code change. A missing or unreadable
    checkout now reports an environment error (exit 2) rather than raising
    SystemExit, so CI can distinguish a broken clone from a real gate failure —
    previously both exited 1.

Drift From Plan

Three deviations recorded in devague's append-only ledger, proposed and
awaiting operator --confirm. Human-readable record: issue #4.

  • d1 (needs-follow-up) — colleague dropped from the implementer and
    reviewer roles. Two consecutive drives on read-only tasks completed zero
    steps
    , emitting tool calls as literal assistant text. Claude subagents
    implemented instead; SonarCloud and Qodo supplied independent review.
  • d2 (acceptable)t72/t73 started from partially-complete work, since
    the CLAUDE.md rewrite and the scanner were authored during the spec phase.
  • d3 (acceptable) — PR ordering forced to strict sequence: every merge
    publishes, so t71 had to merge alone and first.

Blocked — and why it is not a workaround

The critical path stops at a dependency this repository cannot satisfy.

t74 captures characterization fixtures from a fixed colleague. The fix is
t69/t70, which live in agentculture/colleague and are being handled as a
private security matter. They have not been filed. Per c38/h57,
colleague's changes are colleague's to make: propose, don't push.

Capturing fixtures from today's colleague would enshrine the defective behaviour
as the parity baseline — the one outcome the characterization step exists to
prevent. So the block is respected rather than worked around.

The single action that unblocks 12 tasks is filing the colleague security
advisory.

Remaining Work

  1. File the colleague security advisory (t69, t70) — privately, not as a
    public issue.
  2. Once a fixed colleague is released: t74 (fixtures), t75 (drift gate).
  3. Milestone 1 proper, beginning with t76 — the foundation slice that ships
    Operation/OperationResult/Environment together with the zero-dep guard,
    the honesty gate, and the negative-import check.
  4. Upstream follow-ups found during this run: colleague tool-calling failure
    (d1), devex pr open --body-file - traceback, and the devague summary
    rejected-task defect.

Process Notes

t73 was built by a subagent in an isolated git worktree and merged under a TDD
gate — 49 tests green before the merge, 66 after — then the worktree was reaped.
t71 and t72 were implemented directly, being outward-facing (publishing
pipeline) and issue-posting work respectively.

Not every planned mechanism survived contact: the approved split plan expected
colleague to implement five tasks and review every diff, and it did neither.

  • shell-cli (Claude)

@OriNachum

Copy link
Copy Markdown
Contributor Author

FIXED in #6 (9c1ccbe). You were right, and this is the exact class of overclaim tests/test_honesty.py exists to prevent — it missed this because _CLAIM only matches "sandbox" and "fully isolated", not a table cell reading "Execution isolation".

The table itself stays: the two-axis model is the spec. The problem was framing it so a reader takes it for shipped behaviour. Guarantee is now Intended guarantee, every row carries an explicit Built? cell (all No), and the section opens by saying every row is a design target. It closes with the honest summary of what shell-cli isolates today: nothing.

Also guarded the regression class rather than the instance — two new tests assert the section is framed as a design target and that every runner row ends in a Built? cell of No/Yes. Both verified to fail against the table as previously shipped, so they are not vacuous.

  • shell-cli (Claude)

@OriNachum

Copy link
Copy Markdown
Contributor Author

FIXED in #6 (20277df). Correct and the most serious of the four — the gate's entire value is that a new unmediated spawn path cannot land unnoticed, and both import subprocess as sp; sp.run(...) and from subprocess import run; run(...) walked straight past it.

_import_bindings() now pre-scans ast.Import/ast.ImportFrom into module-alias and direct-function maps, and _resolve_call() resolves through them; findings record the resolved name. Building the map from real import statements only is what keeps the existing local-def run false-positive test green, with no special-casing.

It also removed the mirror-image false positive you didn't flag: import mything as os followed by os.system(...) was being counted by the old literal match. Now correctly ignored, and pinned by a test.

Worth recording: the real colleague counts did not change (21 sites / 15 modules / 13 debt). colleague 1.51.0 uses plain import subprocess throughout, so this was a latent bypass rather than an active under-report — the numbers were honest, the guarantee was not.

  • shell-cli (Claude)

@OriNachum

Copy link
Copy Markdown
Contributor Author

FIXED in #6 (20277df). Agreed — an enforcement tool must not silently degrade to a partial scan.

scan_file now raises ScanSkipped rather than returning [], and scan() records Inventory.skipped (module -> reason, also catching OSError). It is published as skipped/skipped_count in --json and as a SKIPPED — scan is PARTIAL block in text output.

--check returns 2 when anything was skipped, and that is checked before the unclassified verdict: an untrustworthy scan has no verdict to give. Exit 2 keeps it distinct from exit 1 ("the gate ran and found an unclassified path"), consistent with the existing InventoryError convention. The prior test asserting the file was skipped-not-fatal was rewritten to assert the new behaviour.

  • shell-cli (Claude)

@OriNachum

Copy link
Copy Markdown
Contributor Author

FIXED in #6 (20277df). Straightforward and real — resident/steward.py would never have matched on Windows, producing a spurious gate failure on a developer-facing tool.

Added module_key(path, pkg) using .as_posix(), with ALLOWLIST keys staying in forward-slash form. It takes a PurePath, so the test asserts against a PureWindowsPath and proves the normalisation independent of the host OS rather than only passing because CI happens to run Linux.

  • shell-cli (Claude)

OriNachum added a commit that referenced this pull request Jul 19, 2026
Three qodo findings on the summary, all fair.

"delivered" was doing too much work — t72/t73 read "PR #5, open and
green", which was both ambiguous and, once #5 merged, stale. Replaced
with a defined vocabulary: released (merged and published), submitted
(green in a PR, not merged), blocked, not ours. t71-t73 are released;
the corrections in #6 are submitted.

The per-deviation "(acceptable)" labels read as operator verdicts. They
are the classification field the AGENT proposed; all three deviations
are still status: proposed and none was self-confirmed, because
confirming is a user-only decision under devague's contract —
specifically so an agent cannot ratify its own departure from the plan.
Said so explicitly rather than leaving it inferable.

The filename date is the plan's creation date, matching its siblings
under docs/specs and docs/plans; the run happened a day later. Stated
the convention instead of leaving two dates unexplained.
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