convertible-cli 0.2.2__tar.gz → 0.4.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.
Files changed (132) hide show
  1. convertible_cli-0.4.0/.devague/current +1 -0
  2. convertible_cli-0.4.0/.devague/current_plan +1 -0
  3. convertible_cli-0.4.0/.devague/frames/convertible-gains-an-extensibility-layer-like-clau.json +445 -0
  4. convertible_cli-0.4.0/.devague/plans/convertible-gains-an-extensibility-layer-like-clau.json +380 -0
  5. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/CHANGELOG.md +28 -0
  6. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/CLAUDE.md +42 -6
  7. convertible_cli-0.4.0/PKG-INFO +373 -0
  8. convertible_cli-0.4.0/README.md +356 -0
  9. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/cli/__init__.py +8 -0
  10. convertible_cli-0.4.0/convertible/cli/_banner-big.txt +37 -0
  11. convertible_cli-0.4.0/convertible/cli/_banner.py +56 -0
  12. convertible_cli-0.4.0/convertible/cli/_banner.txt +23 -0
  13. convertible_cli-0.4.0/convertible/cli/_commands/commands.py +94 -0
  14. convertible_cli-0.4.0/convertible/cli/_commands/drive.py +256 -0
  15. convertible_cli-0.4.0/convertible/cli/_commands/hooks.py +103 -0
  16. convertible_cli-0.4.0/convertible/cli/_commands/session.py +268 -0
  17. convertible_cli-0.4.0/convertible/commands.py +264 -0
  18. convertible_cli-0.4.0/convertible/configdir.py +122 -0
  19. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/contract.py +82 -5
  20. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/explain/catalog.py +111 -0
  21. convertible_cli-0.4.0/convertible/hooks.py +335 -0
  22. convertible_cli-0.4.0/convertible/loop.py +301 -0
  23. convertible_cli-0.4.0/docs/plans/2026-05-27-convertible-gains-an-extensibility-layer-like-clau.md +96 -0
  24. convertible_cli-0.4.0/docs/specs/2026-05-27-convertible-gains-an-extensibility-layer-like-clau.md +80 -0
  25. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/pyproject.toml +1 -1
  26. convertible_cli-0.4.0/tests/test_artifact.py +158 -0
  27. convertible_cli-0.4.0/tests/test_banner.py +145 -0
  28. convertible_cli-0.4.0/tests/test_boundary.py +406 -0
  29. convertible_cli-0.4.0/tests/test_commands.py +473 -0
  30. convertible_cli-0.4.0/tests/test_commands_cli.py +116 -0
  31. convertible_cli-0.4.0/tests/test_configdir.py +277 -0
  32. convertible_cli-0.4.0/tests/test_contract.py +172 -0
  33. convertible_cli-0.4.0/tests/test_drive.py +295 -0
  34. convertible_cli-0.4.0/tests/test_e2e_extensibility.py +439 -0
  35. convertible_cli-0.4.0/tests/test_hooks.py +454 -0
  36. convertible_cli-0.4.0/tests/test_hooks_cli.py +120 -0
  37. convertible_cli-0.4.0/tests/test_loop.py +449 -0
  38. convertible_cli-0.4.0/tests/test_review_fixes.py +203 -0
  39. convertible_cli-0.4.0/tests/test_session.py +303 -0
  40. convertible_cli-0.4.0/tests/test_zero_deps.py +89 -0
  41. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/uv.lock +1 -1
  42. convertible_cli-0.2.2/.devague/current +0 -1
  43. convertible_cli-0.2.2/.devague/current_plan +0 -1
  44. convertible_cli-0.2.2/PKG-INFO +0 -156
  45. convertible_cli-0.2.2/README.md +0 -139
  46. convertible_cli-0.2.2/convertible/cli/_commands/drive.py +0 -123
  47. convertible_cli-0.2.2/convertible/loop.py +0 -154
  48. convertible_cli-0.2.2/tests/test_artifact.py +0 -71
  49. convertible_cli-0.2.2/tests/test_contract.py +0 -54
  50. convertible_cli-0.2.2/tests/test_drive.py +0 -131
  51. convertible_cli-0.2.2/tests/test_loop.py +0 -93
  52. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/agent-config/SKILL.md +0 -0
  53. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
  54. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
  55. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  56. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  57. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/cicd/SKILL.md +0 -0
  58. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  59. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  60. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  61. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  62. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  63. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/communicate/SKILL.md +0 -0
  64. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  65. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  66. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  67. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  68. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
  69. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  70. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  71. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  72. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
  73. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
  74. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/run-tests/SKILL.md +0 -0
  75. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  76. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
  77. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  78. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  79. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  80. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/think/SKILL.md +0 -0
  81. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/think/scripts/think.sh +0 -0
  82. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/version-bump/SKILL.md +0 -0
  83. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
  84. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.claude/skills.local.yaml.example +0 -0
  85. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.devague/frames/convertible-v0-ships-point-it-at-a-repo-task-and-i.json +0 -0
  86. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.devague/plans/convertible-v0-ships-point-it-at-a-repo-task-and-i.json +0 -0
  87. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.flake8 +0 -0
  88. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.github/workflows/publish.yml +0 -0
  89. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.github/workflows/tests.yml +0 -0
  90. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.gitignore +0 -0
  91. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/.markdownlint-cli2.yaml +0 -0
  92. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/LICENSE +0 -0
  93. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/__init__.py +0 -0
  94. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/__main__.py +0 -0
  95. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/artifact.py +0 -0
  96. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/cli/_commands/__init__.py +0 -0
  97. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/cli/_commands/cli.py +0 -0
  98. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/cli/_commands/doctor.py +0 -0
  99. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/cli/_commands/explain.py +0 -0
  100. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/cli/_commands/learn.py +0 -0
  101. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/cli/_commands/overview.py +0 -0
  102. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/cli/_commands/wheels.py +0 -0
  103. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/cli/_commands/whoami.py +0 -0
  104. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/cli/_errors.py +0 -0
  105. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/cli/_output.py +0 -0
  106. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/config.py +0 -0
  107. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/engine.py +0 -0
  108. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/engines/__init__.py +0 -0
  109. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/engines/mock.py +0 -0
  110. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/engines/vllm_openai.py +0 -0
  111. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/explain/__init__.py +0 -0
  112. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/handoff.py +0 -0
  113. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/registry.py +0 -0
  114. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/convertible/tools.py +0 -0
  115. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/culture.yaml +0 -0
  116. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/docs/plans/2026-05-26-convertible-v0-ships-point-it-at-a-repo-task-and-i.md +0 -0
  117. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/docs/skill-sources.md +0 -0
  118. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/docs/specs/2026-05-26-convertible-v0-ships-point-it-at-a-repo-task-and-i.md +0 -0
  119. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/sonar-project.properties +0 -0
  120. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/__init__.py +0 -0
  121. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/test_cli.py +0 -0
  122. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/test_cli_introspection.py +0 -0
  123. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/test_config.py +0 -0
  124. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/test_e2e_mock.py +0 -0
  125. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/test_engine.py +0 -0
  126. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/test_handoff.py +0 -0
  127. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/test_mock_engine.py +0 -0
  128. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/test_registry.py +0 -0
  129. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/test_tools.py +0 -0
  130. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/test_vllm_live.py +0 -0
  131. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/test_vllm_openai.py +0 -0
  132. {convertible_cli-0.2.2 → convertible_cli-0.4.0}/tests/test_wheels.py +0 -0
@@ -0,0 +1 @@
1
+ convertible-gains-an-extensibility-layer-like-clau
@@ -0,0 +1 @@
1
+ convertible-gains-an-extensibility-layer-like-clau
@@ -0,0 +1,445 @@
1
+ {
2
+ "slug": "convertible-gains-an-extensibility-layer-like-clau",
3
+ "title": "Convertible gains an extensibility layer like Claude Code and Codex: reusable custom commands (named, parameterized task templates discovered from the repo/user config) plus hooks (operator-configured shell commands that fire at tool-loop lifecycle events \u2014 task start, pre-tool-call, post-tool-call, finish \u2014 and can gate, deny, or augment a tool call), both layered on the shared chassis so they fire identically for every engine.",
4
+ "schema_version": 1,
5
+ "status": "exported",
6
+ "created": "2026-05-27T16:12:18Z",
7
+ "updated": "2026-05-27T16:25:36Z",
8
+ "claims": [
9
+ {
10
+ "id": "c1",
11
+ "kind": "announcement",
12
+ "text": "Convertible gains an extensibility layer like Claude Code and Codex: reusable custom commands (named, parameterized task templates discovered from the repo/user config) plus hooks (operator-configured shell commands that fire at tool-loop lifecycle events \u2014 task start, pre-tool-call, post-tool-call, finish \u2014 and can gate, deny, or augment a tool call), both layered on the shared chassis so they fire identically for every engine.",
13
+ "origin": "user",
14
+ "status": "confirmed",
15
+ "honesty_conditions": [
16
+ {
17
+ "id": "h8",
18
+ "text": "One drive run on each engine exercises both halves: a saved command template expands into a Task AND a configured hook fires during that run's loop, with both visible in the result artifact \u2014 demonstrating commands+hooks on the shared chassis.",
19
+ "status": "confirmed"
20
+ }
21
+ ],
22
+ "hard_questions": [],
23
+ "links": []
24
+ },
25
+ {
26
+ "id": "c2",
27
+ "kind": "audience",
28
+ "text": "Convertible operators who drive repos with models and want a safety/policy gate plus reusable task recipes, and engine-wheel developers who need lifecycle behavior to live in the chassis (not per-engine).",
29
+ "origin": "llm",
30
+ "status": "confirmed",
31
+ "honesty_conditions": [
32
+ {
33
+ "id": "h12",
34
+ "text": "A concrete operator can name a recurring task they'd save as a command and a tool call they'd want gated by a hook; the engine-wheel-developer benefit holds because lifecycle behavior lives in the chassis, not in each wheel.",
35
+ "status": "confirmed"
36
+ }
37
+ ],
38
+ "hard_questions": [],
39
+ "links": []
40
+ },
41
+ {
42
+ "id": "c3",
43
+ "kind": "before_state",
44
+ "text": "Today 'convertible drive' takes one raw instruction string and runs the tool-loop with no operator-authored gate: run_command/write_file execute whatever the model emits (D2 trusts the operator env), and every task must be retyped from scratch \u2014 there is no way to deny a tool call by policy or save a reusable recipe.",
45
+ "origin": "llm",
46
+ "status": "confirmed",
47
+ "honesty_conditions": [
48
+ {
49
+ "id": "h13",
50
+ "text": "On main today, 'convertible drive' exposes no command-template flag and no hook config, and run_command/write_file execute unconditionally \u2014 verifiable by inspecting drive.py / loop.py / tools.py.",
51
+ "status": "confirmed"
52
+ }
53
+ ],
54
+ "hard_questions": [],
55
+ "links": []
56
+ },
57
+ {
58
+ "id": "c4",
59
+ "kind": "after_state",
60
+ "text": "An operator drops command templates and a hooks config into convertible config; 'convertible drive --command <name> [args]' expands a saved template into a Task, and during the loop hooks fire at task-start / pre-tool / post-tool / finish \u2014 a pre-tool hook can deny or rewrite a tool call and feed a reason back to the model, a post-tool hook can run a formatter/linter, and every hook firing is recorded in the result artifact.",
61
+ "origin": "llm",
62
+ "status": "rejected",
63
+ "honesty_conditions": [],
64
+ "hard_questions": [],
65
+ "links": []
66
+ },
67
+ {
68
+ "id": "c5",
69
+ "kind": "why_it_matters",
70
+ "text": "Running models against repos needs an operator-owned gate (the parked sandbox is still deferred; hooks are deterministic policy, not isolation) and recurring repo chores need reusable recipes \u2014 both are exactly the extensibility Claude Code and Codex ship, and putting them in the chassis means they bind every engine under the all-engines rule.",
71
+ "origin": "llm",
72
+ "status": "confirmed",
73
+ "honesty_conditions": [
74
+ {
75
+ "id": "h14",
76
+ "text": "The operator gate and reusable recipes cannot be had with today's convertible without editing source; Claude Code and Codex both ship exactly these two mechanisms, confirming the need is real.",
77
+ "status": "confirmed"
78
+ }
79
+ ],
80
+ "hard_questions": [],
81
+ "links": []
82
+ },
83
+ {
84
+ "id": "c6",
85
+ "kind": "boundary",
86
+ "text": "NOT a sandbox or resource-isolation layer (D2's trust model is unchanged; hooks are operator-authored gates, not confinement); NOT an interactive REPL/slash-command UI (convertible stays one-shot \u2014 no UserPromptSubmit/Notification/PreCompact session events); NOT a plugin/marketplace system; commands are Task templates only, not arbitrary code.",
87
+ "origin": "llm",
88
+ "status": "rejected",
89
+ "honesty_conditions": [],
90
+ "hard_questions": [],
91
+ "links": []
92
+ },
93
+ {
94
+ "id": "c7",
95
+ "kind": "success_signal",
96
+ "text": "On both mock and vllm-openai engines: a saved command expands into the same Task shape, a pre-tool deny-hook blocks a run_command and the artifact records the denial + the model's continuation, and a post-tool hook's effect is observable \u2014 proving the lifecycle is chassis-level and engine-agnostic.",
97
+ "origin": "llm",
98
+ "status": "confirmed",
99
+ "honesty_conditions": [
100
+ {
101
+ "id": "h15",
102
+ "text": "A single test/demo runs the same command + hook config on mock and vllm-openai and asserts identical result shape, a working pre-tool deny, and an observable post-tool effect.",
103
+ "status": "confirmed"
104
+ }
105
+ ],
106
+ "hard_questions": [],
107
+ "links": []
108
+ },
109
+ {
110
+ "id": "c8",
111
+ "kind": "requirement",
112
+ "text": "R1 Commands: named task templates discovered from convertible config (repo-level and user-level), each a file with optional metadata (description, engine, constraints, arg-hint) and a body that becomes the instruction with positional/$ARGUMENTS substitution; 'convertible drive --command <name> [args...]' expands it into the same Task the contract already defines.",
113
+ "origin": "llm",
114
+ "status": "confirmed",
115
+ "honesty_conditions": [
116
+ {
117
+ "id": "h1",
118
+ "text": "A command template round-trips into a Task: 'drive --command <name> args' produces the identical Task shape (id/repo/instruction/context/constraints/engine) that a raw 'drive \"...\"' produces, with args substituted, and runs unchanged on both engines.",
119
+ "status": "confirmed"
120
+ }
121
+ ],
122
+ "hard_questions": [],
123
+ "links": []
124
+ },
125
+ {
126
+ "id": "c9",
127
+ "kind": "requirement",
128
+ "text": "R2 Hooks: a hooks config maps lifecycle events to matcher+command entries; on a matching event Convertible runs the operator's shell command. Pre-tool-call hooks can DENY (block the tool, feed the reason back to the model as the tool result) or ALLOW; post-tool-call hooks observe/transform; task-start and finish hooks bracket the drive.",
129
+ "origin": "llm",
130
+ "status": "rejected",
131
+ "honesty_conditions": [
132
+ {
133
+ "id": "h2",
134
+ "text": "A pre-tool hook that exits non-zero blocks the tool call: the executor does NOT run it, and the model receives the hook's stderr as the tool result and can continue \u2014 proven by a test where a deny-hook on run_command stops the command from running.",
135
+ "status": "rejected"
136
+ }
137
+ ],
138
+ "hard_questions": [],
139
+ "links": []
140
+ },
141
+ {
142
+ "id": "c10",
143
+ "kind": "requirement",
144
+ "text": "R3 Hook I/O contract: a hook receives a JSON event payload on stdin (event name, tool name, arguments, task id, repo path) and signals outcome by exit code (0=allow, non-zero=deny with stderr fed back) plus optional structured JSON stdout (decision + additionalContext), mirroring Claude Code's hook protocol.",
145
+ "origin": "llm",
146
+ "status": "rejected",
147
+ "honesty_conditions": [
148
+ {
149
+ "id": "h3",
150
+ "text": "The hook receives well-formed JSON on stdin and its exit-code+stdout are interpreted per the documented contract; a hook that emits structured JSON (decision/additionalContext) changes loop behavior accordingly, and the contract is identical across events.",
151
+ "status": "rejected"
152
+ }
153
+ ],
154
+ "hard_questions": [],
155
+ "links": []
156
+ },
157
+ {
158
+ "id": "c11",
159
+ "kind": "requirement",
160
+ "text": "R4 Lifecycle integration in the chassis: the events fire inside convertible/loop.py (and the drive boundary) so they apply to every engine identically; the mock engine is the reference and a hook firing on mock vs vllm-openai must be indistinguishable in the result shape (all-engines rule; guarded by the e2e shape test).",
161
+ "origin": "llm",
162
+ "status": "confirmed",
163
+ "honesty_conditions": [
164
+ {
165
+ "id": "h4",
166
+ "text": "The same hook config produces the same firings and same artifact entries whether --engine is mock or vllm-openai; the e2e shape test asserts a hook-driven run is identically shaped across both engines.",
167
+ "status": "confirmed"
168
+ }
169
+ ],
170
+ "hard_questions": [],
171
+ "links": []
172
+ },
173
+ {
174
+ "id": "c12",
175
+ "kind": "requirement",
176
+ "text": "R5 Dashboard honesty: the result artifact records every hook firing (event, matched command, decision, exit code) and which command template (if any) produced the Task, so the JSON dashboard stays a faithful trace; a denied tool call appears as a non-ok step with the hook's reason.",
177
+ "origin": "llm",
178
+ "status": "confirmed",
179
+ "honesty_conditions": [
180
+ {
181
+ "id": "h5",
182
+ "text": "Every hook firing and the originating command appear in the result artifact JSON (valid, with event/command/decision fields), and a denied tool call is recorded as a non-ok step carrying the hook's reason.",
183
+ "status": "confirmed"
184
+ }
185
+ ],
186
+ "hard_questions": [],
187
+ "links": []
188
+ },
189
+ {
190
+ "id": "c13",
191
+ "kind": "requirement",
192
+ "text": "R6 Agent-first CLI surface: new verbs 'convertible commands list/overview' and 'convertible hooks list/overview' follow the conventions (register(sub), --json, noun-groups expose overview, an explain catalog entry each) and pass 'teken cli doctor . --strict'.",
193
+ "origin": "llm",
194
+ "status": "confirmed",
195
+ "honesty_conditions": [
196
+ {
197
+ "id": "h6",
198
+ "text": "'convertible commands list --json' and 'convertible hooks list --json' emit structured output, the noun groups expose 'overview', each has an explain entry, and 'teken cli doctor . --strict' passes.",
199
+ "status": "confirmed"
200
+ }
201
+ ],
202
+ "hard_questions": [],
203
+ "links": []
204
+ },
205
+ {
206
+ "id": "c14",
207
+ "kind": "requirement",
208
+ "text": "R7 Zero runtime deps preserved: command/hook discovery, config parsing, and hook execution use only the stdlib (subprocess, json, and a config format with a stdlib reader) \u2014 pyproject 'dependencies' stays [].",
209
+ "origin": "llm",
210
+ "status": "confirmed",
211
+ "honesty_conditions": [
212
+ {
213
+ "id": "h7",
214
+ "text": "After the feature, 'pip show convertible-cli' / pyproject shows dependencies = [] and the test suite runs with no third-party runtime import added.",
215
+ "status": "confirmed"
216
+ }
217
+ ],
218
+ "hard_questions": [],
219
+ "links": []
220
+ },
221
+ {
222
+ "id": "c15",
223
+ "kind": "decision",
224
+ "text": "D-config: commands and hooks live under a '.convertible/' config dir \u2014 commands as individual template files, hooks as a single JSON settings file (JSON keeps the zero-dep rule with a stdlib reader); both resolve repo-level then user-level, mirroring Claude Code's .claude/ layout.",
225
+ "origin": "llm",
226
+ "status": "confirmed",
227
+ "honesty_conditions": [],
228
+ "hard_questions": [],
229
+ "links": []
230
+ },
231
+ {
232
+ "id": "c16",
233
+ "kind": "decision",
234
+ "text": "D-scope: this ships as a post-v0 increment (v0.x), strictly ADDITIVE to the held v0 chassis; hooks+commands are not in v0's excluded set (router/sandbox/daemon/other-drivers), so they need no re-spec of the v0 line \u2014 but they must not reintroduce any excluded item.",
235
+ "origin": "llm",
236
+ "status": "rejected",
237
+ "honesty_conditions": [],
238
+ "hard_questions": [],
239
+ "links": []
240
+ },
241
+ {
242
+ "id": "c17",
243
+ "kind": "decision",
244
+ "text": "D-trust: hooks and commands are trusted operator-authored config under D2's existing trust model (run with operator privileges, no isolation); how repo-shipped (vs user-level) hooks get trusted/enabled is parked, not assumed.",
245
+ "origin": "llm",
246
+ "status": "rejected",
247
+ "honesty_conditions": [],
248
+ "hard_questions": [
249
+ {
250
+ "id": "q1",
251
+ "text": "If repo-local '.convertible/' hooks auto-run on 'convertible drive', a malicious target repo gets arbitrary code execution on the operator's machine. Do repo-shipped hooks run by default, require an explicit opt-in/trust step, or are only user-level hooks honored in this increment?",
252
+ "resolved": false,
253
+ "blocking": false
254
+ },
255
+ {
256
+ "id": "q2",
257
+ "text": "risk: Auto-running repo-shipped hooks is a code-execution vector against the operator; mitigations (trust gate, --enable-hooks flag, user-level only) trade safety against the 'just works' ergonomics Claude/Codex offer.",
258
+ "resolved": false,
259
+ "blocking": false
260
+ }
261
+ ],
262
+ "links": []
263
+ },
264
+ {
265
+ "id": "c18",
266
+ "kind": "non_goal",
267
+ "text": "No interactive-session machinery: no UserPromptSubmit/Stop-requeue/PreCompact/Notification/SessionStart-style events, no MCP server hooks, no plugin marketplace, no networked hook transport \u2014 convertible stays a one-shot harness.",
268
+ "origin": "llm",
269
+ "status": "rejected",
270
+ "honesty_conditions": [],
271
+ "hard_questions": [],
272
+ "links": []
273
+ },
274
+ {
275
+ "id": "c19",
276
+ "kind": "assumption",
277
+ "text": "Operators author their own command templates and hook scripts; Convertible discovers and runs them but never generates or fetches them.",
278
+ "origin": "llm",
279
+ "status": "confirmed",
280
+ "honesty_conditions": [],
281
+ "hard_questions": [],
282
+ "links": []
283
+ },
284
+ {
285
+ "id": "c20",
286
+ "kind": "after_state",
287
+ "text": "An operator drops command templates and a hooks config under .convertible/; they can run 'convertible drive --command <name> [args]' (one-shot) OR open an interactive palette session that browses/selects discovered commands and accepts ad-hoc tasks. During every loop, hooks fire at task-start / pre-tool / post-tool / finish: a pre-tool hook can ALLOW, DENY (reason fed back to the model), or REWRITE the tool arguments in-flight; a post-tool hook runs formatters/linters; repo-shipped hooks run by default. Every hook firing and the originating command are recorded in the result artifact.",
288
+ "origin": "llm",
289
+ "status": "confirmed",
290
+ "honesty_conditions": [
291
+ {
292
+ "id": "h16",
293
+ "text": "After the feature the described flow works end-to-end: a command file expands into a Task, a configured hook fires mid-loop, and both appear in the artifact \u2014 on both engines, with no per-engine code.",
294
+ "status": "confirmed"
295
+ }
296
+ ],
297
+ "hard_questions": [],
298
+ "links": []
299
+ },
300
+ {
301
+ "id": "c21",
302
+ "kind": "boundary",
303
+ "text": "NOT a sandbox / resource-isolation layer (D2's trusted-operator-env model is unchanged; hooks are operator-authored policy gates, not confinement). NOT a long-running daemon/server or networked control plane (the interactive palette is a FOREGROUND TTY session, not a background service). NOT a plugin marketplace or MCP transport. Commands are Task templates, not arbitrary importable code.",
304
+ "origin": "llm",
305
+ "status": "confirmed",
306
+ "honesty_conditions": [
307
+ {
308
+ "id": "h17",
309
+ "text": "No code path opens a socket, forks a daemon, or imports a command file as Python; hooks are only ever executed as subprocesses and the palette runs in the foreground \u2014 verifiable by review.",
310
+ "status": "confirmed"
311
+ }
312
+ ],
313
+ "hard_questions": [],
314
+ "links": []
315
+ },
316
+ {
317
+ "id": "c22",
318
+ "kind": "non_goal",
319
+ "text": "Out of scope: MCP server hooks, a plugin marketplace, networked/remote hook transport, and a background daemon. The interactive palette IS in scope (per the scope decision); only non-foreground and networked machinery is excluded.",
320
+ "origin": "llm",
321
+ "status": "confirmed",
322
+ "honesty_conditions": [],
323
+ "hard_questions": [],
324
+ "links": []
325
+ },
326
+ {
327
+ "id": "c23",
328
+ "kind": "decision",
329
+ "text": "D-scope (RESOLVED): hooks + commands + an interactive palette mode are folded INTO the v0 line (re-spec), not deferred to a later increment. This widens D1's 'CLI-first; REPL/daemon reserved' stance to admit an interactive FOREGROUND palette session (still no daemon/server), and widens the v0 boundary accordingly.",
330
+ "origin": "llm",
331
+ "status": "confirmed",
332
+ "honesty_conditions": [],
333
+ "hard_questions": [],
334
+ "links": []
335
+ },
336
+ {
337
+ "id": "c24",
338
+ "kind": "decision",
339
+ "text": "D-interactive (RESOLVED): convertible gains an interactive foreground session (palette) in ADDITION to one-shot 'drive'; both are thin front-ends over the same chassis (Task contract + loop + hooks + artifact). The palette adds no engine-specific behavior. This is the one change that revises convertible's documented non-interactive identity.",
340
+ "origin": "llm",
341
+ "status": "confirmed",
342
+ "honesty_conditions": [],
343
+ "hard_questions": [
344
+ {
345
+ "id": "q3",
346
+ "text": "risk: The interactive palette reverses convertible's documented non-interactive, agent-first identity (CLAUDE.md: results-to-stdout, one-shot; D1 reserved REPL). Widening v0 here risks scope sprawl and a second interaction model to maintain; mitigated by making the palette a thin front-end over the same chassis with no parallel code path.",
347
+ "resolved": false,
348
+ "blocking": false
349
+ }
350
+ ],
351
+ "links": []
352
+ },
353
+ {
354
+ "id": "c25",
355
+ "kind": "decision",
356
+ "text": "D-trust (RESOLVED): repo-shipped '.convertible/' hooks RUN BY DEFAULT on 'convertible drive', under D2's trusted-operator-env model \u2014 the user accepted the code-execution tradeoff for Claude/Codex-like ergonomics. A per-repo trust gate / escape hatch is a tracked follow-up, not a v0 blocker.",
357
+ "origin": "llm",
358
+ "status": "confirmed",
359
+ "honesty_conditions": [],
360
+ "hard_questions": [
361
+ {
362
+ "id": "q4",
363
+ "text": "risk: Repo-shipped hooks running by default is a code-execution vector: cloning + driving a malicious repo executes its hooks with operator privileges. Accepted under D2 for ergonomics; the follow-up trust gate / --no-hooks escape hatch is the mitigation and must be documented loudly.",
364
+ "resolved": false,
365
+ "blocking": false
366
+ }
367
+ ],
368
+ "links": []
369
+ },
370
+ {
371
+ "id": "c26",
372
+ "kind": "requirement",
373
+ "text": "R2 Hooks (revised): a hooks config maps lifecycle events (task-start, pre-tool, post-tool, finish) to matcher+command entries; a matching event runs the operator's shell command. A pre-tool hook returns one of ALLOW (run as-is), DENY (skip the tool; feed the reason back to the model as the tool result), or REWRITE (run with hook-supplied replacement arguments). Post-tool hooks observe and run side-effects (formatters/linters); task-start/finish bracket the drive.",
374
+ "origin": "llm",
375
+ "status": "confirmed",
376
+ "honesty_conditions": [
377
+ {
378
+ "id": "h9",
379
+ "text": "A pre-tool hook proves all three outcomes: a deny-hook on run_command stops the command and the model receives the reason; a rewrite-hook on write_file changes the path/content actually written; an allow-hook is a no-op \u2014 each observable in the artifact and identical on mock and vllm-openai.",
380
+ "status": "confirmed"
381
+ }
382
+ ],
383
+ "hard_questions": [],
384
+ "links": []
385
+ },
386
+ {
387
+ "id": "c27",
388
+ "kind": "requirement",
389
+ "text": "R3 Hook I/O contract (revised): a hook receives a JSON event payload on stdin (event, tool, arguments, task id, repo path); it signals outcome by exit code (0=allow, non-zero=deny with stderr fed back to the model) PLUS optional structured JSON stdout carrying decision (allow|deny|rewrite), replacement arguments (for rewrite), and additionalContext \u2014 mirroring Claude Code's hook protocol. The contract is identical across all events.",
390
+ "origin": "llm",
391
+ "status": "confirmed",
392
+ "honesty_conditions": [
393
+ {
394
+ "id": "h10",
395
+ "text": "The hook reads the documented JSON payload on stdin and the loop honors exit code AND structured stdout: exit!=0 denies with stderr fed back; stdout {decision:'rewrite', arguments:{...}} replaces the tool arguments; the same payload schema is sent for every event.",
396
+ "status": "confirmed"
397
+ }
398
+ ],
399
+ "hard_questions": [],
400
+ "links": []
401
+ },
402
+ {
403
+ "id": "c28",
404
+ "kind": "requirement",
405
+ "text": "R8 Interactive palette: a foreground session verb (e.g. 'convertible session') opens an interactive palette that lists discovered commands, takes a selection + args (or an ad-hoc instruction), and runs it through the chassis. It is a front-end over 'drive' \u2014 identical Task, loop, hooks, and artifact \u2014 never a parallel code path, and adds no engine-specific behavior.",
406
+ "origin": "llm",
407
+ "status": "confirmed",
408
+ "honesty_conditions": [
409
+ {
410
+ "id": "h11",
411
+ "text": "Running a command via the interactive palette yields a TaskResult + artifact byte-identical in shape to 'drive --command <name>'; the palette imports and calls the same drive path (no duplicated loop), proven by a test that drives one command both ways and asserts identical result shape.",
412
+ "status": "confirmed"
413
+ }
414
+ ],
415
+ "hard_questions": [],
416
+ "links": []
417
+ }
418
+ ],
419
+ "open_vagueness": [
420
+ {
421
+ "id": "v1",
422
+ "text": "Follow-up hardening: an optional per-repo trust gate / --no-hooks escape hatch for repo-shipped hooks. DECIDED this increment: repo hooks run by default (see D-trust c25).",
423
+ "kind": "follow_up",
424
+ "claim_id": null
425
+ },
426
+ {
427
+ "id": "v2",
428
+ "text": "Whether a finish-event hook may requeue/continue the loop (re-drive) or only observe + optionally fail the run; this increment likely observe-only",
429
+ "kind": "follow_up",
430
+ "claim_id": null
431
+ },
432
+ {
433
+ "id": "v3",
434
+ "text": "Exact command-template metadata schema (which keys: description/engine/constraints/arg-hint) and the stdlib-parseable header format",
435
+ "kind": "unknown_nonblocking",
436
+ "claim_id": null
437
+ },
438
+ {
439
+ "id": "v4",
440
+ "text": "Whether post-tool hooks may rewrite a tool result/arguments in-flight, or only observe + run side-effects (formatters)",
441
+ "kind": "unknown_nonblocking",
442
+ "claim_id": null
443
+ }
444
+ ]
445
+ }