convertible-cli 0.4.0__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/CHANGELOG.md +19 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/CLAUDE.md +20 -4
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/PKG-INFO +75 -3
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/README.md +74 -2
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/__init__.py +24 -0
- convertible_cli-0.6.0/convertible/cli/_commands/agents.py +109 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_commands/drive.py +5 -2
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_commands/overview.py +2 -0
- convertible_cli-0.6.0/convertible/cli/_commands/skills.py +107 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/engine.py +18 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/engines/mock.py +6 -1
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/engines/vllm_openai.py +6 -1
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/explain/catalog.py +76 -5
- convertible_cli-0.6.0/convertible/layers.py +288 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/pyproject.toml +1 -1
- convertible_cli-0.6.0/tests/test_agents_cli.py +82 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_cli.py +23 -1
- convertible_cli-0.6.0/tests/test_layers.py +294 -0
- convertible_cli-0.6.0/tests/test_layers_engine_parity.py +83 -0
- convertible_cli-0.6.0/tests/test_skills_cli.py +87 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_zero_deps.py +1 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/uv.lock +1 -1
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/agent-config/SKILL.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/cicd/SKILL.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/communicate/SKILL.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/run-tests/SKILL.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/think/SKILL.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/think/scripts/think.sh +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/version-bump/SKILL.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.claude/skills.local.yaml.example +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.devague/current +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.devague/current_plan +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.devague/frames/convertible-gains-an-extensibility-layer-like-clau.json +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.devague/frames/convertible-v0-ships-point-it-at-a-repo-task-and-i.json +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.devague/plans/convertible-gains-an-extensibility-layer-like-clau.json +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.devague/plans/convertible-v0-ships-point-it-at-a-repo-task-and-i.json +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.flake8 +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.github/workflows/publish.yml +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.github/workflows/tests.yml +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.gitignore +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/.markdownlint-cli2.yaml +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/LICENSE +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/__init__.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/__main__.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/artifact.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_banner-big.txt +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_banner.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_banner.txt +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_commands/__init__.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_commands/cli.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_commands/commands.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_commands/doctor.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_commands/explain.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_commands/hooks.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_commands/learn.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_commands/session.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_commands/wheels.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_commands/whoami.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_errors.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/cli/_output.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/commands.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/config.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/configdir.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/contract.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/engines/__init__.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/explain/__init__.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/handoff.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/hooks.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/loop.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/registry.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/convertible/tools.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/culture.yaml +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/docs/plans/2026-05-26-convertible-v0-ships-point-it-at-a-repo-task-and-i.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/docs/plans/2026-05-27-convertible-gains-an-extensibility-layer-like-clau.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/docs/skill-sources.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/docs/specs/2026-05-26-convertible-v0-ships-point-it-at-a-repo-task-and-i.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/docs/specs/2026-05-27-convertible-gains-an-extensibility-layer-like-clau.md +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/sonar-project.properties +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/__init__.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_artifact.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_banner.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_boundary.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_cli_introspection.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_commands.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_commands_cli.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_config.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_configdir.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_contract.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_drive.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_e2e_extensibility.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_e2e_mock.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_engine.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_handoff.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_hooks.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_hooks_cli.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_loop.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_mock_engine.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_registry.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_review_fixes.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_session.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_tools.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_vllm_live.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_vllm_openai.py +0 -0
- {convertible_cli-0.4.0 → convertible_cli-0.6.0}/tests/test_wheels.py +0 -0
|
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/). This project
|
|
6
6
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.6.0] - 2026-05-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Layered per-model config: AGENTS instructions (`AGENTS.md` -> `AGENTS.convertible.md` -> `AGENTS.convertible.<model>.md` at the repo root, with a `~/.convertible/` fallback) and skills (`.convertible/skills/*.md` -> `.convertible/<model>/skills/*.md`) compose into the engine system prompt via `convertible/layers.py`
|
|
13
|
+
- `convertible agents` and `convertible skills` introspection nouns (list + overview, `--json`, `--model`)
|
|
14
|
+
- `Engine.system_prompt()` base-class helper injects the layered prompt for every engine (all-engines rule)
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Both engines (mock, vllm-openai) now pass a model-specific `system_prompt` to the loop; behavior is byte-identical when no AGENTS/skills files exist
|
|
19
|
+
|
|
20
|
+
## [0.5.0] - 2026-05-27
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Bare `convertible` (no subcommand) now opens the interactive harness (the `session` palette) when run at a terminal — the natural "get in and drive" gesture. Piped, redirected, or otherwise non-interactive, it still prints usage, preserving the discoverable surface for scripts and agents. `-h/--help` is unaffected.
|
|
25
|
+
- Reframed `convertible drive` help and `explain` text to lead with the goal/instruction ("drive toward a goal") rather than "run a repo task"; the repo is the target, not the headline. No behavior change.
|
|
26
|
+
|
|
8
27
|
## [0.4.0] - 2026-05-27
|
|
9
28
|
|
|
10
29
|
### Added
|
|
@@ -34,6 +34,16 @@ The car metaphor *is* the architecture:
|
|
|
34
34
|
code path, no daemon.
|
|
35
35
|
- **Config resolution** — `convertible/configdir.py`: repo-level
|
|
36
36
|
`.convertible/` overrides user-level `~/.convertible/`.
|
|
37
|
+
- **Layered per-model config** — `convertible/layers.py`: AGENTS instructions
|
|
38
|
+
(`AGENTS.md` → `AGENTS.convertible.md` → `AGENTS.convertible.<model>.md`, at
|
|
39
|
+
the repo root with a `~/.convertible/` fallback) and skills
|
|
40
|
+
(`.convertible/skills/*.md` → `.convertible/<model>/skills/*.md`) compose into
|
|
41
|
+
the engine system prompt. Resolution builds exact paths for the current model
|
|
42
|
+
and never globs sibling models — per-model isolation is structural. Injected
|
|
43
|
+
once on the `Engine` base class (`system_prompt()`), so every engine inherits
|
|
44
|
+
it (all-engines rule). Surfaced via the `agents` / `skills` introspection
|
|
45
|
+
nouns. **MCP layering is not built** — convertible reads no `mcp.json` and has
|
|
46
|
+
no `mcp` verb; a live MCP client is a re-spec (see scope below).
|
|
37
47
|
|
|
38
48
|
The buildable spec and plan this implementation converged from live in
|
|
39
49
|
[`docs/specs/`](docs/specs/) and [`docs/plans/`](docs/plans/) (authored via the
|
|
@@ -43,14 +53,20 @@ The buildable spec and plan this implementation converged from live in
|
|
|
43
53
|
|
|
44
54
|
In scope: the chassis, the entry-point wheel contract, exactly two engines
|
|
45
55
|
(`mock`, `vllm-openai`), the git/PR handoff, command templates, lifecycle
|
|
46
|
-
hooks,
|
|
56
|
+
hooks, the foreground interactive palette, and layered per-model AGENTS/skills
|
|
57
|
+
config (`convertible/layers.py`).
|
|
47
58
|
|
|
48
59
|
**Out of scope for v0** — do not add without re-speccing: a multi-engine
|
|
49
60
|
router/policy "gearbox", an execution sandbox, a daemon/server mode,
|
|
50
|
-
Codex/Claude/Gemini drivers,
|
|
61
|
+
Codex/Claude/Gemini drivers, a per-repo hook trust gate / `--no-hooks`
|
|
51
62
|
escape hatch (planned follow-up hardening — not yet built; document this gap
|
|
52
|
-
honestly, never invent a `--no-hooks` flag)
|
|
53
|
-
|
|
63
|
+
honestly, never invent a `--no-hooks` flag), and an **MCP execution runtime**
|
|
64
|
+
(a live MCP client — stdio/socket transport, tool discovery, dynamic tool
|
|
65
|
+
registration). The layered config ships AGENTS + skills only; `mcp.json` is
|
|
66
|
+
**not** read and there is no `mcp` verb. A live MCP client would breach the
|
|
67
|
+
no-deps / no-socket / no-daemon conventions and needs its own spec — document
|
|
68
|
+
this gap honestly, never invent an `mcp` surface. Adding an excluded feature
|
|
69
|
+
means scope crept.
|
|
54
70
|
|
|
55
71
|
## The all-engines rule
|
|
56
72
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: convertible-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Convertible CLI is a swappable coder-agent harness that turns different models into repo workers behind one shared task contract.
|
|
5
5
|
Project-URL: Homepage, https://github.com/agentculture/convertible
|
|
6
6
|
Project-URL: Issues, https://github.com/agentculture/convertible/issues
|
|
@@ -17,6 +17,27 @@ Description-Content-Type: text/markdown
|
|
|
17
17
|
|
|
18
18
|
# convertible
|
|
19
19
|
|
|
20
|
+
```text
|
|
21
|
+
,"^,::,:::::I::::::^
|
|
22
|
+
^!'` `"^,. ::`
|
|
23
|
+
`,;;i;,,' .!^'`",. "<," " ;,^
|
|
24
|
+
^ '"^";"^```,":::','"'^`"."'.,:^,i::,"".
|
|
25
|
+
.I. ' I-l '",I;!ii,I.,,l;;IlIl:i!:::::,,,^^;",,'
|
|
26
|
+
'^ ""::``':. .`";,"```"^.. '^^'":,''.
|
|
27
|
+
::,`;!"..^..'^"^ .' .."" ..`"^ ..`"`'"".
|
|
28
|
+
;;l!i:' ^ '^"`. `"`.. ',^.. `` '^
|
|
29
|
+
:i>I!lII". .' .''^` '`^' .`". ^. :;lI.
|
|
30
|
+
",l!`;"':l;,,`. .' '",'. ^...'^`'"...^l'":;Ii!!l:!';.
|
|
31
|
+
^''^., '`^^`:l;"^"',!~ .^^^,""..'`^. .^l,I''^.:l!;;;i>|+,!IlI::^
|
|
32
|
+
`;. :>i":` "`, ',^^" "; I.'. :::"^:;iil;'":^; '"
|
|
33
|
+
'^"'"IlI,.:,::;:' .`;"!l:``,,"``',,,^,,:ll!,,l^
|
|
34
|
+
":^:,I^:"!;:: :I>l:::"`""::,:I>!<i^ " ;.
|
|
35
|
+
",;"Il;i^: ',":;"`'`'^, ><[il```'
|
|
36
|
+
" ;;I"l:^.^;^''lI" :^::",:,'
|
|
37
|
+
,`:;,I.^ ''
|
|
38
|
+
`. " "
|
|
39
|
+
```
|
|
40
|
+
|
|
20
41
|
> Convertible CLI is a swappable coder-agent harness that turns different models
|
|
21
42
|
> into repo workers behind one shared task contract.
|
|
22
43
|
>
|
|
@@ -106,10 +127,13 @@ engine, so it binds equally to `mock`, `vllm-openai`, and any future wheel.
|
|
|
106
127
|
uv sync
|
|
107
128
|
uv run pytest -n auto # full suite, no network needed
|
|
108
129
|
|
|
130
|
+
# Open the interactive harness (the session palette) at a terminal:
|
|
131
|
+
uv run convertible
|
|
132
|
+
|
|
109
133
|
# Discover the engines installed in this environment:
|
|
110
134
|
uv run convertible wheels list
|
|
111
135
|
|
|
112
|
-
# Drive a
|
|
136
|
+
# Drive toward a goal with the deterministic mock engine (no model, no network):
|
|
113
137
|
uv run convertible drive "add a CONTRIBUTING.md stub" --repo . --engine mock --no-pr
|
|
114
138
|
```
|
|
115
139
|
|
|
@@ -297,10 +321,54 @@ loop, hooks, and artifact — no parallel code path):
|
|
|
297
321
|
uv run convertible session --repo /path/to/repo --engine vllm-openai
|
|
298
322
|
```
|
|
299
323
|
|
|
324
|
+
Running `convertible` with no arguments **at a terminal** opens this same palette
|
|
325
|
+
(with the default engine and repo) — the natural "get in and drive" gesture.
|
|
326
|
+
Piped, redirected, or otherwise non-interactive, bare `convertible` prints usage
|
|
327
|
+
instead, so scripts and agents keep a discoverable surface.
|
|
328
|
+
|
|
300
329
|
The session loops until the user enters `q`, `quit`, or an empty line. Any
|
|
301
330
|
driver flags accepted by `drive` (`--engine`, `--no-pr`, `--base-url`, etc.)
|
|
302
331
|
are also accepted by `session`.
|
|
303
332
|
|
|
333
|
+
## Per-model instructions & skills
|
|
334
|
+
|
|
335
|
+
Convertible composes a model-specific **system prompt** for every drive from two
|
|
336
|
+
layered families, resolved *relative to the model currently driving*. Strict
|
|
337
|
+
per-model isolation: driving model X reads only X's overlay plus the shared base
|
|
338
|
+
— it never even opens model Y's files (isolation is structural, built from exact
|
|
339
|
+
paths, not filtered).
|
|
340
|
+
|
|
341
|
+
**AGENTS instructions** cascade from the **repo root** (the cross-tool standard
|
|
342
|
+
location — sibling agent tools read `AGENTS.md` there too), general → specific,
|
|
343
|
+
with a `~/.convertible/` user-level fallback:
|
|
344
|
+
|
|
345
|
+
```text
|
|
346
|
+
AGENTS.md # shared base
|
|
347
|
+
AGENTS.convertible.md # convertible overlay
|
|
348
|
+
AGENTS.convertible.<model>.md # model overlay
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Skills** are markdown capability docs under `.convertible/`, folded into the
|
|
352
|
+
prompt as a compact name + one-line-summary catalog (a skill is instructional
|
|
353
|
+
text only — there is no skill *execution* in v0):
|
|
354
|
+
|
|
355
|
+
```text
|
|
356
|
+
.convertible/skills/*.md # base
|
|
357
|
+
.convertible/<model>/skills/*.md # model overlay (shadows base by stem)
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
`<model>` is sanitized to a filename-safe token (e.g. `Qwen/Qwen3-32B` →
|
|
361
|
+
`Qwen-Qwen3-32B`). Inspect what resolves for a model:
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
uv run convertible agents list --model Qwen/Qwen3-32B --repo .
|
|
365
|
+
uv run convertible skills list --model Qwen/Qwen3-32B --repo .
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
> **MCP layering is not built yet.** Convertible does not read `mcp.json` or
|
|
369
|
+
> connect to any MCP server today; a live MCP client needs its own spec. There
|
|
370
|
+
> is no `mcp` verb — don't rely on a non-existent surface.
|
|
371
|
+
|
|
304
372
|
## ⚠ Security: repo-shipped hooks run by default
|
|
305
373
|
|
|
306
374
|
> **This is a code-execution risk. Read before driving an untrusted repo.**
|
|
@@ -333,12 +401,16 @@ rely on a non-existent flag.
|
|
|
333
401
|
|
|
334
402
|
| Verb | What it does |
|
|
335
403
|
|------|--------------|
|
|
336
|
-
| `drive <
|
|
404
|
+
| `drive <goal>` | Drive toward a goal/instruction: work autonomously through a coder engine; write the artifact; hand off. |
|
|
337
405
|
| `drive --command <name> [args…]` | Expand a saved command template and drive it. |
|
|
338
406
|
| `commands list` | List discovered command templates for a repo. |
|
|
339
407
|
| `commands overview` | Describe the commands surface. |
|
|
340
408
|
| `hooks list` | List configured hook entries for a repo. |
|
|
341
409
|
| `hooks overview` | Describe the hooks surface. |
|
|
410
|
+
| `agents list` | List resolved AGENTS instruction layers for a model. |
|
|
411
|
+
| `agents overview` | Describe the agents surface. |
|
|
412
|
+
| `skills list` | List resolved skill docs for a model. |
|
|
413
|
+
| `skills overview` | Describe the skills surface. |
|
|
342
414
|
| `session` | Open a foreground interactive palette. |
|
|
343
415
|
| `wheels list` | List discovered engine wheels (the garage). |
|
|
344
416
|
| `whoami` | Report this agent's nick, version, backend, and model. |
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# convertible
|
|
2
2
|
|
|
3
|
+
```text
|
|
4
|
+
,"^,::,:::::I::::::^
|
|
5
|
+
^!'` `"^,. ::`
|
|
6
|
+
`,;;i;,,' .!^'`",. "<," " ;,^
|
|
7
|
+
^ '"^";"^```,":::','"'^`"."'.,:^,i::,"".
|
|
8
|
+
.I. ' I-l '",I;!ii,I.,,l;;IlIl:i!:::::,,,^^;",,'
|
|
9
|
+
'^ ""::``':. .`";,"```"^.. '^^'":,''.
|
|
10
|
+
::,`;!"..^..'^"^ .' .."" ..`"^ ..`"`'"".
|
|
11
|
+
;;l!i:' ^ '^"`. `"`.. ',^.. `` '^
|
|
12
|
+
:i>I!lII". .' .''^` '`^' .`". ^. :;lI.
|
|
13
|
+
",l!`;"':l;,,`. .' '",'. ^...'^`'"...^l'":;Ii!!l:!';.
|
|
14
|
+
^''^., '`^^`:l;"^"',!~ .^^^,""..'`^. .^l,I''^.:l!;;;i>|+,!IlI::^
|
|
15
|
+
`;. :>i":` "`, ',^^" "; I.'. :::"^:;iil;'":^; '"
|
|
16
|
+
'^"'"IlI,.:,::;:' .`;"!l:``,,"``',,,^,,:ll!,,l^
|
|
17
|
+
":^:,I^:"!;:: :I>l:::"`""::,:I>!<i^ " ;.
|
|
18
|
+
",;"Il;i^: ',":;"`'`'^, ><[il```'
|
|
19
|
+
" ;;I"l:^.^;^''lI" :^::",:,'
|
|
20
|
+
,`:;,I.^ ''
|
|
21
|
+
`. " "
|
|
22
|
+
```
|
|
23
|
+
|
|
3
24
|
> Convertible CLI is a swappable coder-agent harness that turns different models
|
|
4
25
|
> into repo workers behind one shared task contract.
|
|
5
26
|
>
|
|
@@ -89,10 +110,13 @@ engine, so it binds equally to `mock`, `vllm-openai`, and any future wheel.
|
|
|
89
110
|
uv sync
|
|
90
111
|
uv run pytest -n auto # full suite, no network needed
|
|
91
112
|
|
|
113
|
+
# Open the interactive harness (the session palette) at a terminal:
|
|
114
|
+
uv run convertible
|
|
115
|
+
|
|
92
116
|
# Discover the engines installed in this environment:
|
|
93
117
|
uv run convertible wheels list
|
|
94
118
|
|
|
95
|
-
# Drive a
|
|
119
|
+
# Drive toward a goal with the deterministic mock engine (no model, no network):
|
|
96
120
|
uv run convertible drive "add a CONTRIBUTING.md stub" --repo . --engine mock --no-pr
|
|
97
121
|
```
|
|
98
122
|
|
|
@@ -280,10 +304,54 @@ loop, hooks, and artifact — no parallel code path):
|
|
|
280
304
|
uv run convertible session --repo /path/to/repo --engine vllm-openai
|
|
281
305
|
```
|
|
282
306
|
|
|
307
|
+
Running `convertible` with no arguments **at a terminal** opens this same palette
|
|
308
|
+
(with the default engine and repo) — the natural "get in and drive" gesture.
|
|
309
|
+
Piped, redirected, or otherwise non-interactive, bare `convertible` prints usage
|
|
310
|
+
instead, so scripts and agents keep a discoverable surface.
|
|
311
|
+
|
|
283
312
|
The session loops until the user enters `q`, `quit`, or an empty line. Any
|
|
284
313
|
driver flags accepted by `drive` (`--engine`, `--no-pr`, `--base-url`, etc.)
|
|
285
314
|
are also accepted by `session`.
|
|
286
315
|
|
|
316
|
+
## Per-model instructions & skills
|
|
317
|
+
|
|
318
|
+
Convertible composes a model-specific **system prompt** for every drive from two
|
|
319
|
+
layered families, resolved *relative to the model currently driving*. Strict
|
|
320
|
+
per-model isolation: driving model X reads only X's overlay plus the shared base
|
|
321
|
+
— it never even opens model Y's files (isolation is structural, built from exact
|
|
322
|
+
paths, not filtered).
|
|
323
|
+
|
|
324
|
+
**AGENTS instructions** cascade from the **repo root** (the cross-tool standard
|
|
325
|
+
location — sibling agent tools read `AGENTS.md` there too), general → specific,
|
|
326
|
+
with a `~/.convertible/` user-level fallback:
|
|
327
|
+
|
|
328
|
+
```text
|
|
329
|
+
AGENTS.md # shared base
|
|
330
|
+
AGENTS.convertible.md # convertible overlay
|
|
331
|
+
AGENTS.convertible.<model>.md # model overlay
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Skills** are markdown capability docs under `.convertible/`, folded into the
|
|
335
|
+
prompt as a compact name + one-line-summary catalog (a skill is instructional
|
|
336
|
+
text only — there is no skill *execution* in v0):
|
|
337
|
+
|
|
338
|
+
```text
|
|
339
|
+
.convertible/skills/*.md # base
|
|
340
|
+
.convertible/<model>/skills/*.md # model overlay (shadows base by stem)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
`<model>` is sanitized to a filename-safe token (e.g. `Qwen/Qwen3-32B` →
|
|
344
|
+
`Qwen-Qwen3-32B`). Inspect what resolves for a model:
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
uv run convertible agents list --model Qwen/Qwen3-32B --repo .
|
|
348
|
+
uv run convertible skills list --model Qwen/Qwen3-32B --repo .
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
> **MCP layering is not built yet.** Convertible does not read `mcp.json` or
|
|
352
|
+
> connect to any MCP server today; a live MCP client needs its own spec. There
|
|
353
|
+
> is no `mcp` verb — don't rely on a non-existent surface.
|
|
354
|
+
|
|
287
355
|
## ⚠ Security: repo-shipped hooks run by default
|
|
288
356
|
|
|
289
357
|
> **This is a code-execution risk. Read before driving an untrusted repo.**
|
|
@@ -316,12 +384,16 @@ rely on a non-existent flag.
|
|
|
316
384
|
|
|
317
385
|
| Verb | What it does |
|
|
318
386
|
|------|--------------|
|
|
319
|
-
| `drive <
|
|
387
|
+
| `drive <goal>` | Drive toward a goal/instruction: work autonomously through a coder engine; write the artifact; hand off. |
|
|
320
388
|
| `drive --command <name> [args…]` | Expand a saved command template and drive it. |
|
|
321
389
|
| `commands list` | List discovered command templates for a repo. |
|
|
322
390
|
| `commands overview` | Describe the commands surface. |
|
|
323
391
|
| `hooks list` | List configured hook entries for a repo. |
|
|
324
392
|
| `hooks overview` | Describe the hooks surface. |
|
|
393
|
+
| `agents list` | List resolved AGENTS instruction layers for a model. |
|
|
394
|
+
| `agents overview` | Describe the agents surface. |
|
|
395
|
+
| `skills list` | List resolved skill docs for a model. |
|
|
396
|
+
| `skills overview` | Describe the skills surface. |
|
|
325
397
|
| `session` | Open a foreground interactive palette. |
|
|
326
398
|
| `wheels list` | List discovered engine wheels (the garage). |
|
|
327
399
|
| `whoami` | Report this agent's nick, version, backend, and model. |
|
|
@@ -61,7 +61,19 @@ def _argv_has_json(argv: list[str] | None) -> bool:
|
|
|
61
61
|
return any(t == "--json" or t.startswith("--json=") for t in tokens)
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def _stdio_is_interactive() -> bool:
|
|
65
|
+
"""Whether stdin and stdout are both interactive terminals.
|
|
66
|
+
|
|
67
|
+
Bare ``convertible`` opens the interactive harness only at a real terminal;
|
|
68
|
+
isolated as a module function so tests can force the interactive branch
|
|
69
|
+
without a TTY (mirrors :func:`convertible.cli._banner._isatty`). Both streams
|
|
70
|
+
must be a TTY: the palette reads from stdin and renders its chrome to stdout.
|
|
71
|
+
"""
|
|
72
|
+
return sys.stdin.isatty() and sys.stdout.isatty()
|
|
73
|
+
|
|
74
|
+
|
|
64
75
|
def _build_parser() -> argparse.ArgumentParser:
|
|
76
|
+
from convertible.cli._commands import agents as _agents_group
|
|
65
77
|
from convertible.cli._commands import cli as _cli_group
|
|
66
78
|
from convertible.cli._commands import commands as _commands_group
|
|
67
79
|
from convertible.cli._commands import doctor as _doctor_cmd
|
|
@@ -71,6 +83,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
71
83
|
from convertible.cli._commands import learn as _learn_cmd
|
|
72
84
|
from convertible.cli._commands import overview as _overview_cmd
|
|
73
85
|
from convertible.cli._commands import session as _session_cmd
|
|
86
|
+
from convertible.cli._commands import skills as _skills_group
|
|
74
87
|
from convertible.cli._commands import wheels as _wheels_group
|
|
75
88
|
from convertible.cli._commands import whoami as _whoami_cmd
|
|
76
89
|
|
|
@@ -99,6 +112,9 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
99
112
|
# Extensibility layer: command templates + lifecycle hooks.
|
|
100
113
|
_commands_group.register(sub)
|
|
101
114
|
_hooks_group.register(sub)
|
|
115
|
+
# Layered per-model config: AGENTS instructions + skills.
|
|
116
|
+
_agents_group.register(sub)
|
|
117
|
+
_skills_group.register(sub)
|
|
102
118
|
# Interactive foreground palette (c28/R8).
|
|
103
119
|
_session_cmd.register(sub)
|
|
104
120
|
|
|
@@ -136,6 +152,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
136
152
|
args = parser.parse_args(argv)
|
|
137
153
|
|
|
138
154
|
if args.command is None:
|
|
155
|
+
# Bare `convertible` opens the interactive harness at a terminal; piped /
|
|
156
|
+
# redirected / non-interactive it prints usage so scripts and agents keep
|
|
157
|
+
# a discoverable surface. `-h/--help` is handled by argparse before here,
|
|
158
|
+
# so the help surface (and the teken rubric, which probes --help) stay
|
|
159
|
+
# available either way. Re-parsing ["session"] reuses the session
|
|
160
|
+
# subparser's defaults and func wiring — no parallel code path.
|
|
161
|
+
if _stdio_is_interactive():
|
|
162
|
+
return _dispatch(parser.parse_args(["session"]))
|
|
139
163
|
parser.print_help()
|
|
140
164
|
return 0
|
|
141
165
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""``convertible agents`` — inspect layered AGENTS instruction files.
|
|
2
|
+
|
|
3
|
+
``agents list`` resolves the AGENTS instruction cascade for a model
|
|
4
|
+
(``AGENTS.md`` -> ``AGENTS.convertible.md`` -> ``AGENTS.convertible.<model>.md``;
|
|
5
|
+
repo root with a ``~/.convertible/`` fallback) and reports the layers that
|
|
6
|
+
exist, in general -> specific order. ``agents overview`` describes the noun
|
|
7
|
+
(satisfying the agent-first rubric: any noun with action-verbs must also expose
|
|
8
|
+
``overview``).
|
|
9
|
+
|
|
10
|
+
These layers are composed (with the engine default and the skills catalog) into
|
|
11
|
+
the system prompt every drive sends — so what ``agents list`` reports for a model
|
|
12
|
+
is exactly what that model is instructed with. Per-model isolation is structural:
|
|
13
|
+
only the named model's overlay is read, never a sibling model's.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from convertible.cli._commands.overview import emit_overview
|
|
22
|
+
from convertible.cli._output import emit_result
|
|
23
|
+
from convertible.config import EngineConfig
|
|
24
|
+
from convertible.layers import resolve_agents
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _agents_sections() -> list[dict[str, object]]:
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
"title": "What it does",
|
|
31
|
+
"items": [
|
|
32
|
+
"Resolves AGENTS instruction layers for the current model",
|
|
33
|
+
"Cascade (general -> specific): AGENTS.md, AGENTS.convertible.md, "
|
|
34
|
+
"AGENTS.convertible.<model>.md",
|
|
35
|
+
"Read from the repo root, with a ~/.convertible/ user-level fallback",
|
|
36
|
+
"Composed into the system prompt every drive sends to the engine",
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"title": "Per-model isolation",
|
|
41
|
+
"items": [
|
|
42
|
+
"<model> is sanitized (e.g. 'Qwen/Qwen3-32B' -> 'Qwen-Qwen3-32B')",
|
|
43
|
+
"Only the named model's overlay is read — never a sibling model's",
|
|
44
|
+
"MCP layering is not built yet (no mcp.json reader); tracked separately",
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"title": "Verbs",
|
|
49
|
+
"items": [
|
|
50
|
+
"agents list [--model M] [--repo PATH] — list resolved AGENTS layers",
|
|
51
|
+
"agents overview — describe the agents surface (this command)",
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def cmd_agents_overview(args: argparse.Namespace) -> int:
|
|
58
|
+
emit_overview(
|
|
59
|
+
"convertible agents",
|
|
60
|
+
_agents_sections(),
|
|
61
|
+
json_mode=bool(getattr(args, "json", False)),
|
|
62
|
+
)
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def cmd_agents_list(args: argparse.Namespace) -> int:
|
|
67
|
+
repo = Path(getattr(args, "repo", ".")).expanduser()
|
|
68
|
+
model = getattr(args, "model", None) or EngineConfig.resolve().model
|
|
69
|
+
json_mode = bool(getattr(args, "json", False))
|
|
70
|
+
|
|
71
|
+
layers = resolve_agents(repo, model)
|
|
72
|
+
|
|
73
|
+
if json_mode:
|
|
74
|
+
items = [{"scope": layer.scope, "path": str(layer.path)} for layer in layers]
|
|
75
|
+
emit_result({"model": model, "agents": items}, json_mode=True)
|
|
76
|
+
elif not layers:
|
|
77
|
+
emit_result("(no AGENTS layers found)", json_mode=False)
|
|
78
|
+
else:
|
|
79
|
+
lines = [f"{layer.scope}\t{layer.path}" for layer in layers]
|
|
80
|
+
emit_result("\n".join(lines), json_mode=False)
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _no_verb(args: argparse.Namespace) -> int:
|
|
85
|
+
return cmd_agents_overview(args)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
89
|
+
p = sub.add_parser(
|
|
90
|
+
"agents",
|
|
91
|
+
help="Inspect layered AGENTS instruction files (see 'convertible agents overview').",
|
|
92
|
+
)
|
|
93
|
+
p.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
94
|
+
p.set_defaults(func=_no_verb, json=False)
|
|
95
|
+
noun_sub = p.add_subparsers(dest="agents_command", parser_class=type(p))
|
|
96
|
+
|
|
97
|
+
lst = noun_sub.add_parser("list", help="List resolved AGENTS instruction layers.")
|
|
98
|
+
lst.add_argument("--repo", default=".", help="Path to the target repository (default: cwd).")
|
|
99
|
+
lst.add_argument(
|
|
100
|
+
"--model",
|
|
101
|
+
default=None,
|
|
102
|
+
help="Model to resolve layers for (default: the resolved engine model).",
|
|
103
|
+
)
|
|
104
|
+
lst.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
105
|
+
lst.set_defaults(func=cmd_agents_list)
|
|
106
|
+
|
|
107
|
+
ov = noun_sub.add_parser("overview", help="Describe the agents surface.")
|
|
108
|
+
ov.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
109
|
+
ov.set_defaults(func=cmd_agents_overview)
|
|
@@ -224,7 +224,10 @@ def cmd_drive(args: argparse.Namespace) -> int:
|
|
|
224
224
|
def register(sub: argparse._SubParsersAction) -> None:
|
|
225
225
|
p = sub.add_parser(
|
|
226
226
|
"drive",
|
|
227
|
-
help=
|
|
227
|
+
help=(
|
|
228
|
+
"Drive toward a goal: work autonomously on a request or instruction "
|
|
229
|
+
"through a coder engine, then hand off the result."
|
|
230
|
+
),
|
|
228
231
|
)
|
|
229
232
|
# ``instruction`` is now zero-or-more positional tokens (nargs="*") so
|
|
230
233
|
# ``--command`` can be the sole input without argparse raising an error.
|
|
@@ -232,7 +235,7 @@ def register(sub: argparse._SubParsersAction) -> None:
|
|
|
232
235
|
"instruction",
|
|
233
236
|
nargs="*",
|
|
234
237
|
help=(
|
|
235
|
-
"
|
|
238
|
+
"A goal or instruction to pursue autonomously. "
|
|
236
239
|
"Mutually exclusive with --command. "
|
|
237
240
|
"When --command is used, any positional tokens are passed as template arguments."
|
|
238
241
|
),
|
|
@@ -27,6 +27,8 @@ _ARTIFACTS = [
|
|
|
27
27
|
_VERBS = [
|
|
28
28
|
"drive <instruction> — run a repo task through a coder engine",
|
|
29
29
|
"wheels list — list discovered engine wheels",
|
|
30
|
+
"agents list — inspect layered AGENTS instruction files for a model",
|
|
31
|
+
"skills list — inspect layered skill docs for a model",
|
|
30
32
|
"whoami — identity probe (nick, version, backend, model)",
|
|
31
33
|
"learn — structured self-teaching prompt",
|
|
32
34
|
"explain <path> — markdown docs for a topic",
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""``convertible skills`` — inspect layered skill docs.
|
|
2
|
+
|
|
3
|
+
``skills list`` resolves the skill docs for a model — ``.convertible/skills/*.md``
|
|
4
|
+
(base) overlaid by ``.convertible/<model>/skills/*.md`` (model overlay shadows
|
|
5
|
+
base by stem) — and reports them with their winning scope. ``skills overview``
|
|
6
|
+
describes the noun (satisfying the agent-first rubric).
|
|
7
|
+
|
|
8
|
+
A skill is purely instructional: convertible folds a compact name +
|
|
9
|
+
one-line-summary catalog of the resolved skills into the system prompt every
|
|
10
|
+
drive sends. There is no skill *execution* model (an execution sandbox is out of
|
|
11
|
+
v0 scope); invokable skills are a tracked follow-up.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from convertible.cli._commands.overview import emit_overview
|
|
20
|
+
from convertible.cli._output import emit_result
|
|
21
|
+
from convertible.config import EngineConfig
|
|
22
|
+
from convertible.layers import resolve_skills
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _skills_sections() -> list[dict[str, object]]:
|
|
26
|
+
return [
|
|
27
|
+
{
|
|
28
|
+
"title": "What it does",
|
|
29
|
+
"items": [
|
|
30
|
+
"Resolves skill docs for the current model",
|
|
31
|
+
"base: .convertible/skills/*.md",
|
|
32
|
+
"model overlay: .convertible/<model>/skills/*.md (shadows base by stem)",
|
|
33
|
+
"Folded into the system prompt as a name + one-line-summary catalog",
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"title": "Scope",
|
|
38
|
+
"items": [
|
|
39
|
+
"A skill is instructional text only — no execution model in v0",
|
|
40
|
+
"<model> is sanitized; only the named model's overlay is read",
|
|
41
|
+
"Invokable skills (skills as procedures) are a tracked follow-up",
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"title": "Verbs",
|
|
46
|
+
"items": [
|
|
47
|
+
"skills list [--model M] [--repo PATH] — list resolved skills",
|
|
48
|
+
"skills overview — describe the skills surface (this command)",
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def cmd_skills_overview(args: argparse.Namespace) -> int:
|
|
55
|
+
emit_overview(
|
|
56
|
+
"convertible skills",
|
|
57
|
+
_skills_sections(),
|
|
58
|
+
json_mode=bool(getattr(args, "json", False)),
|
|
59
|
+
)
|
|
60
|
+
return 0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def cmd_skills_list(args: argparse.Namespace) -> int:
|
|
64
|
+
repo = Path(getattr(args, "repo", ".")).expanduser()
|
|
65
|
+
model = getattr(args, "model", None) or EngineConfig.resolve().model
|
|
66
|
+
json_mode = bool(getattr(args, "json", False))
|
|
67
|
+
|
|
68
|
+
skills = resolve_skills(repo, model)
|
|
69
|
+
ordered = [skills[name] for name in sorted(skills)]
|
|
70
|
+
|
|
71
|
+
if json_mode:
|
|
72
|
+
items = [{"name": s.name, "scope": s.scope} for s in ordered]
|
|
73
|
+
emit_result({"model": model, "skills": items}, json_mode=True)
|
|
74
|
+
elif not ordered:
|
|
75
|
+
emit_result("(no skills found)", json_mode=False)
|
|
76
|
+
else:
|
|
77
|
+
lines = [f"{s.scope}\t{s.name}" for s in ordered]
|
|
78
|
+
emit_result("\n".join(lines), json_mode=False)
|
|
79
|
+
return 0
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _no_verb(args: argparse.Namespace) -> int:
|
|
83
|
+
return cmd_skills_overview(args)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
87
|
+
p = sub.add_parser(
|
|
88
|
+
"skills",
|
|
89
|
+
help="Inspect layered skill docs (see 'convertible skills overview').",
|
|
90
|
+
)
|
|
91
|
+
p.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
92
|
+
p.set_defaults(func=_no_verb, json=False)
|
|
93
|
+
noun_sub = p.add_subparsers(dest="skills_command", parser_class=type(p))
|
|
94
|
+
|
|
95
|
+
lst = noun_sub.add_parser("list", help="List resolved skill docs.")
|
|
96
|
+
lst.add_argument("--repo", default=".", help="Path to the target repository (default: cwd).")
|
|
97
|
+
lst.add_argument(
|
|
98
|
+
"--model",
|
|
99
|
+
default=None,
|
|
100
|
+
help="Model to resolve skills for (default: the resolved engine model).",
|
|
101
|
+
)
|
|
102
|
+
lst.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
103
|
+
lst.set_defaults(func=cmd_skills_list)
|
|
104
|
+
|
|
105
|
+
ov = noun_sub.add_parser("overview", help="Describe the skills surface.")
|
|
106
|
+
ov.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
107
|
+
ov.set_defaults(func=cmd_skills_overview)
|
|
@@ -36,3 +36,21 @@ class Engine(abc.ABC):
|
|
|
36
36
|
same result shape regardless of the model underneath.
|
|
37
37
|
"""
|
|
38
38
|
raise NotImplementedError
|
|
39
|
+
|
|
40
|
+
def system_prompt(self, task: Task, config: EngineConfig) -> str | None:
|
|
41
|
+
"""Compose the model-specific system prompt (AGENTS + skills layers).
|
|
42
|
+
|
|
43
|
+
Resolved here on the base class — not in each ``drive`` — so *every*
|
|
44
|
+
engine wheel inherits the layered instruction injection for free (the
|
|
45
|
+
all-engines rule), mirroring how hooks are inherited via the loop.
|
|
46
|
+
Subclasses pass the return value as ``system_prompt=`` to
|
|
47
|
+
:func:`convertible.loop.run`. Returns ``None`` when no AGENTS/skills
|
|
48
|
+
layers exist for ``config.model``, so the loop falls back to its own
|
|
49
|
+
default and behavior is byte-identical to a layer-free run.
|
|
50
|
+
"""
|
|
51
|
+
# Imported lazily to keep this module's import surface minimal and avoid
|
|
52
|
+
# pulling the whole loop in at engine import time.
|
|
53
|
+
from convertible.layers import system_prompt_for
|
|
54
|
+
from convertible.loop import _DEFAULT_SYSTEM
|
|
55
|
+
|
|
56
|
+
return system_prompt_for(task.repo_path, config.model, base=_DEFAULT_SYSTEM)
|
|
@@ -51,4 +51,9 @@ class MockEngine(Engine):
|
|
|
51
51
|
name = "mock"
|
|
52
52
|
|
|
53
53
|
def drive(self, task: Task, config: EngineConfig) -> TaskResult:
|
|
54
|
-
return run(
|
|
54
|
+
return run(
|
|
55
|
+
_script(task),
|
|
56
|
+
task,
|
|
57
|
+
max_steps=config.max_steps,
|
|
58
|
+
system_prompt=self.system_prompt(task, config),
|
|
59
|
+
)
|
|
@@ -94,4 +94,9 @@ class VllmOpenAIEngine(Engine):
|
|
|
94
94
|
return complete
|
|
95
95
|
|
|
96
96
|
def drive(self, task: Task, config: EngineConfig) -> TaskResult:
|
|
97
|
-
return run(
|
|
97
|
+
return run(
|
|
98
|
+
self._make_complete(config),
|
|
99
|
+
task,
|
|
100
|
+
max_steps=config.max_steps,
|
|
101
|
+
system_prompt=self.system_prompt(task, config),
|
|
102
|
+
)
|