Commit 20b5dbd
v0.6.0: agent-friendly adoption (detect / init --ci / scan --suggest-patches / apply-patches) (#23)
* Refactor cli/discovery.py into a package (PR 1/8)
Pure code move. The pre-existing module becomes
src/agents_shipgate/cli/discovery/artifacts.py and a new __init__.py
re-exports the public symbols, so callers and downstream tools see no API
change.
This unblocks v0.6.0 work: subsequent PRs add signals.py (detect),
frameworks.py (Pass B synthesize-and-load), template.py, and
ci_workflow.py inside the new package without touching the artifact
discovery code.
No behavior change. All 195 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add `shipgate detect` command and Anthropic glob coverage (PR 2/8)
Introduces signal-scanning logic (Pass A of v0.6 detection) and a
read-only `detect` CLI command that classifies a workspace as an agent
project — and which framework(s) — without invoking the framework
loaders. Detection borrows constants from inputs/* but does not call
load_*_artifacts(), since those gate on a populated manifest.
Scoring:
- Strong (+2): framework imports, decorators, class instantiations,
and unambiguous artifact filenames (anthropic-tools.json,
anthropic-policy.yaml, openai-config.json — addresses the C12
artifact-anchor case for projects that have no Python imports).
- Medium (+1): pyproject/requirements dependency tokens.
- Weak (+0.5): conventional dirs (prompts/, tools/, .agents-shipgate/).
Detected = score ≥ 2.0 AND ≥ 1 strong signal.
Also extends artifacts.py with ANTHROPIC_TOOL_PATTERNS,
ANTHROPIC_POLICY_PATTERNS, and broadens PROMPT_PATTERNS to include .txt
(per C12). Adds discover_anthropic_artifacts() mirroring the OpenAI-API
discovery shape.
Smoke-tested across all five framework samples plus
clean_read_only_agent (correctly classified as non-agent). 11 new tests;
total suite at 206 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Make `shipgate init` auto-detect by default (PR 3/8)
The v0.6 init flow runs detect_workspace() first, then synthesizes a
schema-valid shipgate.yaml from the result. For projects that already
look like agent projects (LangChain / CrewAI / Google ADK / OpenAI
Agents SDK / Anthropic), the manifest is now near-complete on first
write — agents no longer have to fill CHANGE_ME placeholders for tool
sources or framework config blocks.
Per-framework output:
- LangChain / CrewAI / Google ADK / OpenAI Agents SDK → one tool_sources
entry per detected .py candidate (id, type, path).
- Anthropic → manifest.anthropic config block (artifact-based, NOT a
tool_sources entry; per C3).
- OpenAI API artifacts → manifest.openai_api block.
- OpenAPI/MCP candidates from globs → tool_sources entries.
agent.name extraction (corrected from prior plan):
- First static `Agent(name="…")` literal in code (highest)
- Workspace dir name (lowest)
- pyproject.[project].name → seeds project.name only.
When no framework is detected, fall back to a CHANGE_ME stub so the
manifest still satisfies the schema's "≥ 1 source/config" requirement.
`--minimal` reproduces today's v0.5 template byte-for-byte (snapshot
test pins this). `--auto` accepted as a no-op alias.
Validation gate: every generated manifest is run through
AgentsShipgateManifest.model_validate before writing — catches schema
regressions before disk I/O.
8 new tests; 214 pass total.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add `init --ci` workflow generator + cross-workflow detection (PR 4/8)
`shipgate init --ci` now writes .github/workflows/agents-shipgate.yml
from a hard-coded template that references action.yml's documented
inputs (config, ci_mode). The flag is orthogonal to --write: the
workflow file's existence is independent of the manifest's existence,
each gets its own overwrite-refusal check, and the exit code is the
max of per-action outcomes.
Three workflow outcomes (per the v0.6 plan §2 matrix):
- "written" — file created.
- "skipped_existing_target" — agents-shipgate.yml already exists; not
overwriting.
- "skipped_cross_reference" — another workflow file already references
ThreeMoonsLab/agents-shipgate. Avoids creating a duplicate when CI is
already wired in elsewhere.
Cross-reference detection uses a regex on `uses:` keys (line-anchored,
case-insensitive, handles `- uses:` form). Documented parser scope:
matches mentions in `uses:` only — not comments, not `if:` conditions,
not freeform strings.
11 new tests; total 225 passing. Covers all 11 matrix rows from the
plan plus cross-workflow detection edge cases.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add Patch model + report shape changes for v0.6 (PR 5/8)
Schema-additive changes that prepare for `scan --suggest-patches` and
`apply-patches` in PRs 6/7. The JSON contract for non-opting callers
stays byte-identical except for the documented additive bumps.
What's new:
- core/patches.py: Patch discriminated union (SetPointerPatch,
AppendPointerPatch, RemovePointerPatch, ManualPatch).
- Finding.patches: list[Patch] | None = None — populated only when
scan ran with --suggest-patches.
- ReadinessReport.manifest_dir: str | None — absolute path for the
containment check apply-patches will enforce (per C13).
- report_schema_version bumped to "0.6"; new docs/report-schema.v0.6.json
carries the additive changes.
- report_json_payload(report) helper extracted from write_json_report:
Pydantic v2 field_serializer can't truly omit a key, so a small
post-processing pass strips `patches` when None. Used by writer AND
tests so they validate the same shape.
What's load-bearing:
- _run_id() now excludes `patches` from its hash payload (per C11).
Two scans of the same workspace produce the same run_id whether
--suggest-patches is set or not. Test pins this.
- checks/base.py factories accept an optional `patches` kwarg —
per-check generators can attach patches without boilerplate.
7 new tests covering: patch discriminator dispatch, run_id stability,
JSON omission of patches when None, v0.6 schema validation. 232 total
passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add `scan --suggest-patches` + manifest-target generator registry (PR 6/8)
`shipgate scan --suggest-patches` now attaches Patch objects to every
active (unsuppressed) finding. Without the flag, ``patches`` stays
``None`` and is omitted from the JSON — the contract for non-opting
callers stays byte-identical (per C4).
v0.6 generator scope (manifest-target only, per C5/C6/C7/C9/C10):
- High confidence (auto-applied at default ``apply-patches --confidence
high``): RemovePointerPatch for the 3 stale-manifest checks
(SHIP-MANIFEST-STALE-{SUPPRESSION,POLICY,RISK-OVERRIDE}). Pointers
are rederived from ``context.manifest`` by matching evidence fields.
Duplicate matches → fall back to ManualPatch (auto-removal would be
ambiguous).
- Medium confidence (NOT default-applied; requires explicit
``--confidence medium``): AppendPointerPatch for
SHIP-AUTH-SCOPE-COVERAGE-MISSING. Adding scopes to the manifest can
encode policy choices, so it's not auto-safe.
- Permanent ManualPatch with anti-pattern instructions (per C6):
SHIP-API-TRACE-{APPROVAL,CONFIRMATION}-MISSING. Flipping
approved/confirmed in the trace patches the *evidence*, not the
agent's runtime gate.
- Everything else: ManualPatch populated from
CheckMetadata.recommendation.
Coverage rule (per v4 should-fix): with --suggest-patches, every active
finding has at least one patch. Suppressed findings receive nothing —
generators run after apply_suppressions.
13 new generator tests including duplicate handling, JSON-pointer
escaping for tool names with `/` or `~`, the trace-flip prohibition
language, and an end-to-end scan check that suppressed findings keep
``patches=None``. 245 total passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add `shipgate apply-patches` command (PR 7/8)
Applies patches from a scan JSON report against the manifest
(file-grouped, SHA-once-per-file, dry-run default, containment-checked
per C13).
Algorithm:
1. Load report; refuse if manifest_dir is absent (pre-v0.6 reports).
2. Filter patches by --confidence (default high) and --kinds (default:
set_pointer,append_pointer,remove_pointer; ManualPatch never applied).
3. Containment check: every patch's target_file must resolve under
report.manifest_dir. Any violation → exit 5.
4. Group patches by target_file. Per file:
- Read once, hash once.
- Verify every patch's target_sha256 matches the file SHA — ANY
mismatch skips the entire file (per A1 rationale: per-patch SHA
would cause the second patch to fail after the first write).
- Apply all patches in memory, then write once.
5. Print unified diff per file. ManualPatch is never applied.
YAML edits use ruamel.yaml round-trip (preserves comments, key order,
quoting) — added as a dependency. JSON edits use stdlib.
Bumped package version to 0.6.0; updated test fixtures.
8 new tests covering: round-trip apply, dry-run no-mutation, confidence
default skip, containment violation refusal, missing manifest_dir
refusal, SHA drift detection, and the ManualPatch never-applied
guarantee. 253 total passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* End-to-end task + v0.6 docs (PR 8/8)
Closes the v0.6 agent-friendly adoption release.
Tests:
- New tests/test_three_command_flow.py exercises the full canonical
pipeline (detect → init --write --ci → scan --suggest-patches →
apply-patches) against a seeded support_refund_agent copy with a
stale suppression. Verifies: detect classifies, init produces
workflow file, scan attaches patches to every active finding,
trace findings get ManualPatch with the prohibition language, and
apply-patches removes the stale suppression atomically.
- New tests/agent_tasks/02_three_command_flow/ — prompt + starter_repo
+ expected/{run.sh,assertions.py} for the agent-task harness.
- Fixed a template bug surfaced during the end-to-end test: when the
manifest schema requires non-empty agent.declared_purpose for
openai_agents_sdk projects, auto-init now always seeds one CHANGE_ME
entry rather than emitting an empty list.
Docs:
- AGENTS.md: documents the v0.6 single-turn agent flow + each command's
contract.
- README.md: adds the 4-call flow to the user-facing Quick Start; bumps
the action.yml version reference to v0.6.0.
- CHANGELOG.md: full v0.6.0 entry.
- ROADMAP.md: v0.6 marked done; CI-expansion line moved to v0.7.0;
source-provenance enrichment slotted as v0.6.x.
- llms.txt: bumped version + report-schema URL.
Final: 254 tests passing across the v0.6 release line.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address v0.6 review feedback (P1 + P2 + lint)
P1 — apply-patches: multi-remove against same list no longer corrupts.
Two RemovePointerPatch ops against /policies/.../0 and /policies/.../1
applied in report order shifted indexes after the first delete, causing
either IndexError (2-elem list) or silently deleting the wrong entry
(3+ elem list). Fix: partition patches by kind — sets/appends first
(no shifting), then removes sorted so deeper pointers and higher list
indexes fire first. Two new regression tests pin both crash scenarios.
P1 — auto-init: artifact-only OpenAI API projects no longer drop to a
CHANGE_ME stub. The openai_api block previously gated on framework
detection (which only fired for openai-config.json or `from agents
import`), so a workspace with prompts/ + tools/openai-tools.json got
nothing. Same gating bug existed for anthropic. Fix: emit each block
based on artifact discovery alone, with anchor-key gates so the two
adapters don't double-emit shared prompts/.
P2 — generated CI workflow now pins to ``v{__version__}`` instead of
``@main`` so newly-onboarded repos get a reproducible action ref.
``AGENTS_SHIPGATE_WORKFLOW_REF`` env var overrides it.
P2 — report schema generation added to scripts/generate_schemas.py.
``write_report_schema()`` derives the schema from
ReadinessReport.model_json_schema() and writes
docs/report-schema.v{minor}.json with the version pulled from the
model's default. CI's clean-tree assertion catches drift when
Finding.patches or other fields evolve. The v0.6 schema is now
auto-generated; the hand-curated file is overwritten.
Ruff: all 17 reported issues fixed via --fix (typing modernization to
PEP 604 union types, import sorting, unused-import removal).
Local: 258 tests passing, ruff clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address v0.6 follow-up review (P1 detect coverage + P2 schema/JSON)
P1 — `detect` no longer returns false-negative on artifact-only OpenAI
API repos. The Messages API artifact patterns (openai-config.json,
tools/*openai*tools*.json, policies/*openai*.yaml,
policies/*api*.yaml, tests/*openai*cases*.json) are now scored as a
NEW framework type ``openai_api`` — distinct from
``openai_agents_sdk`` (the Python @function_tool surface). This
matches the manifest schema, which has separate ``manifest.openai_api``
and ``tool_sources[*].type == "openai_agents_sdk"``. The existing
``simple_openai_api_agent`` fixture now classifies as ``openai_api``
(score 9.0, high) instead of ``openai_agents_sdk`` (which was a
mislabel).
P2 — Generated v0.6 report schema preserves the v0.5 public contract.
Pydantic auto-generation only marks fields without defaults as
required, dropping schema_version, report_schema_version, findings,
generated_reports, loaded_plugins, tool_inventory, source_warnings
(among others) and turning report_schema_version from a const into any
string. Post-processing in scripts/generate_schemas.py now overrides:
- ``required`` to the v0.5 list (additive only — manifest_dir/patches
remain optional).
- ``schema_version`` and ``report_schema_version`` to const strings.
P2 — `init --json` ``auto_detected.agent_name`` no longer claims a
selection when the YAML still has CHANGE_ME. The previous code took
the first candidate (often workspace_dir), but the template only uses
Agent_name_literal/ADK_name_field. Mirror the template's selection
logic; emit None when the template falls back to CHANGE_ME. Added
``agent_name_candidates`` separately (with source) so agents can
override.
3 new regression tests; 261 total passing. Ruff clean.
``python scripts/generate_schemas.py`` leaves a clean tree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address v0.6 follow-up review #2 (nested schema, agent task, exit codes)
P2 — Generated v0.6 report schema now preserves nested required lists,
not just the top-level ones. Pydantic auto-generation weakens these
because most fields have defaults; post-processing now overrides:
- ``$defs.Finding.required`` to the v0.5 list (id, fingerprint,
check_id, title, severity, category, evidence, confidence,
recommendation, suppressed, baseline_status). ``patches`` stays
optional (additive).
- ``$defs.LoadedPolicyPack.required`` to (id, name, path, rule_count).
- ``properties.tool_inventory.items.required`` to (name, source_type,
risk_tags, auth_scopes, confidence).
- ``properties.loaded_plugins.items.required`` to (name, value,
distribution, version, check_id).
- ``properties.frameworks.properties.{google_adk,langchain,crewai}``
surface-count requireds matching the v0.5 catalog.
P2 — Agent task 02_three_command_flow no longer leaves ``agent.name:
CHANGE_ME``. The LangChain starter has no ``Agent(name="…")`` literal,
so auto-init emits CHANGE_ME for both ``agent.name`` and
``declared_purpose``. The expected/run.sh now replaces both. The
assertion was over-strict (matched CHANGE_ME in comments too) — updated
to parse the YAML and walk values only, ignoring the informational
comments embedded by the auto-init template. Prompt.md updated to
describe both placeholders.
P3 — apply-patches now exits 2 (documented contract) on malformed
``--from`` payloads instead of letting Pydantic raise an uncaught
ValidationError that exits 1. Patch coercion is wrapped in
try/except (ValidationError, BadParameter); a concise
"Malformed patch in report at <path>: <details>" message is printed
to stderr, with structured agent-mode JSON when
AGENTS_SHIPGATE_AGENT_MODE=1.
2 new regression tests; 263 total passing. Ruff clean. Schema regen
leaves a clean tree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent c040fff commit 20b5dbd
37 files changed
Lines changed: 5469 additions & 68 deletions
File tree
- docs
- scripts
- src/agents_shipgate
- checks
- cli
- discovery
- core
- report
- tests
- agent_tasks/02_three_command_flow
- expected
- starter_repo
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
76 | 104 | | |
77 | 105 | | |
78 | 106 | | |
79 | 107 | | |
80 | 108 | | |
| 109 | + | |
81 | 110 | | |
82 | 111 | | |
| 112 | + | |
83 | 113 | | |
84 | 114 | | |
85 | 115 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
3 | 49 | | |
4 | 50 | | |
5 | 51 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
50 | 68 | | |
51 | 69 | | |
52 | 70 | | |
53 | | - | |
| 71 | + | |
54 | 72 | | |
55 | 73 | | |
56 | 74 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
64 | 86 | | |
65 | 87 | | |
66 | | - | |
| 88 | + | |
67 | 89 | | |
68 | | - | |
| 90 | + | |
| 91 | + | |
69 | 92 | | |
70 | 93 | | |
71 | 94 | | |
| |||
80 | 103 | | |
81 | 104 | | |
82 | 105 | | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
83 | 118 | | |
84 | 119 | | |
85 | 120 | | |
| |||
0 commit comments