agentforge-framework 0.2.1__tar.gz → 0.2.2__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.
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/PKG-INFO +3 -3
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/README.md +2 -2
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/pyproject.toml +1 -1
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/__init__.py +1 -1
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/agents/decomposer.py +38 -5
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/skills.py +78 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/providers/base.py +35 -12
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/providers/claude.py +7 -4
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/providers/codex.py +8 -4
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework.egg-info/PKG-INFO +3 -3
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_cli.py +2 -3
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_decompose.py +74 -1
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_docs.py +23 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_execution_posture.py +2 -2
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_interview.py +1 -1
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_plugins.py +2 -2
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_providers.py +138 -7
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_reviewer.py +3 -4
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_runtime.py +10 -10
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_skills.py +37 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_tester.py +1 -1
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/LICENSE +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/setup.cfg +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/.claude-plugin/plugin.json +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/agents/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/agents/architect.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/agents/implementer.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/agents/orchestrator.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/agents/reviewer.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/agents/security.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/agents/tester.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/cli.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/context/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/context/extractors/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/context/extractors/base.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/context/extractors/python.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/context/extractors/sql.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/context/extractors/yaml.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/context/prompt.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/context/resolver.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/commands.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/config.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/contracts.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/gates.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/issues.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/plan_format.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/process.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/project.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/registry.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/repo.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/router.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/runtime.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/workflow.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/plugins/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/plugins/databricks/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/plugins/pyspark/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/plugins/python/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/plugins/sql/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/providers/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/MANIFEST.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/NOTICE +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/domain-modeling/ADR-FORMAT.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/domain-modeling/CONTEXT-FORMAT.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/domain-modeling/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/domain-modeling/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/grill-with-docs/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/grilling/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/grilling/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/to-spec/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/to-spec/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/to-tickets/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/to-tickets/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/evals/fixtures/silhouette/human_reference.json +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/_lang.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/banned_phrase_scan.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/calibrate_pairs.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/calibrate_score.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/check_packs.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/check_suggestions.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/contribute.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/diff_check.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/extract_constraints.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/harvest_classify.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/harvest_samples.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/readability_metrics.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/refresh_status.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/silhouette_scan.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/structure_scan.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/suggest.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/validate_preservation.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/voice_card.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/voice_profile.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/voice_score.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/unslop/scripts/wiki_sync.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/skills/write-plainly/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/workflows/bugfix.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/workflows/feature.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/workflows/review.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework.egg-info/SOURCES.txt +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework.egg-info/dependency_links.txt +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework.egg-info/entry_points.txt +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework.egg-info/requires.txt +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework.egg-info/top_level.txt +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_agents.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_architect.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_config.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_context.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_contracts.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_gates.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_issues.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_plan_format.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_project.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_security.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/tests/test_workflow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentforge-framework
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A modular framework for coordinating specialized software agents.
|
|
5
5
|
Author: Yash Mhatre
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -145,7 +145,7 @@ without notice. See
|
|
|
145
145
|
```console
|
|
146
146
|
$ pip install agentforge-framework
|
|
147
147
|
$ agentforge --version
|
|
148
|
-
agentforge 0.2.
|
|
148
|
+
agentforge 0.2.2
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
That is the whole of it. The two routes below the naming note are for a machine
|
|
@@ -169,7 +169,7 @@ from the [latest release](https://github.com/yashmhatre/AgentForge/releases/late
|
|
|
169
169
|
and install the file:
|
|
170
170
|
|
|
171
171
|
```console
|
|
172
|
-
$ pip install agentforge_framework-0.2.
|
|
172
|
+
$ pip install agentforge_framework-0.2.2-py3-none-any.whl
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
Publishing a release is a wizard rather than a checklist:
|
|
@@ -127,7 +127,7 @@ without notice. See
|
|
|
127
127
|
```console
|
|
128
128
|
$ pip install agentforge-framework
|
|
129
129
|
$ agentforge --version
|
|
130
|
-
agentforge 0.2.
|
|
130
|
+
agentforge 0.2.2
|
|
131
131
|
```
|
|
132
132
|
|
|
133
133
|
That is the whole of it. The two routes below the naming note are for a machine
|
|
@@ -151,7 +151,7 @@ from the [latest release](https://github.com/yashmhatre/AgentForge/releases/late
|
|
|
151
151
|
and install the file:
|
|
152
152
|
|
|
153
153
|
```console
|
|
154
|
-
$ pip install agentforge_framework-0.2.
|
|
154
|
+
$ pip install agentforge_framework-0.2.2-py3-none-any.whl
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
Publishing a release is a wizard rather than a checklist:
|
|
@@ -45,6 +45,8 @@ from ..core.contracts import (
|
|
|
45
45
|
Task,
|
|
46
46
|
)
|
|
47
47
|
from ..core.plan_format import (
|
|
48
|
+
RESULT_CLOSE,
|
|
49
|
+
RESULT_OPEN,
|
|
48
50
|
SLICES_CLOSE,
|
|
49
51
|
SLICES_OPEN,
|
|
50
52
|
SPEC_CLOSE,
|
|
@@ -105,8 +107,9 @@ not publish the spec, do not open an issue, and do not apply a label.
|
|
|
105
107
|
|
|
106
108
|
## Required output
|
|
107
109
|
|
|
108
|
-
End your reply with this
|
|
109
|
-
|
|
110
|
+
End your reply with these two blocks, in this order, and nothing after them.
|
|
111
|
+
|
|
112
|
+
First the spec. Markdown inside it, not JSON:
|
|
110
113
|
|
|
111
114
|
{spec_open}
|
|
112
115
|
## Problem Statement
|
|
@@ -128,10 +131,22 @@ JSON:
|
|
|
128
131
|
...
|
|
129
132
|
{spec_close}
|
|
130
133
|
|
|
134
|
+
Then your own verdict:
|
|
135
|
+
|
|
136
|
+
{result_open}
|
|
137
|
+
```json
|
|
138
|
+
{{"outcome": "completed", "summary": "one line on what the spec covers"}}
|
|
139
|
+
```
|
|
140
|
+
{result_close}
|
|
141
|
+
|
|
131
142
|
Cover the whole source. Anything in it that reaches no user story reaches no \
|
|
132
143
|
Issue and does not get built. Scale the spec to what you were given: a \
|
|
133
144
|
one-sentence Task earns a short spec, and padding one out invents scope nobody \
|
|
134
|
-
asked for
|
|
145
|
+
asked for.
|
|
146
|
+
|
|
147
|
+
If the source is too thin to synthesize without inventing most of it, write the \
|
|
148
|
+
verdict block with `"outcome": "escalated"` and a summary naming what you need \
|
|
149
|
+
the human to supply. Omit the spec block in that case.\
|
|
135
150
|
"""
|
|
136
151
|
|
|
137
152
|
SLICES = """\
|
|
@@ -186,7 +201,9 @@ publish anything, do not open an issue, and do not apply a label.
|
|
|
186
201
|
|
|
187
202
|
## Required output
|
|
188
203
|
|
|
189
|
-
End your reply with this
|
|
204
|
+
End your reply with these two blocks, in this order, and nothing after them.
|
|
205
|
+
|
|
206
|
+
First the cut:
|
|
190
207
|
|
|
191
208
|
{slices_open}
|
|
192
209
|
```json
|
|
@@ -202,7 +219,19 @@ End your reply with this block and nothing after it:
|
|
|
202
219
|
]
|
|
203
220
|
}}
|
|
204
221
|
```
|
|
205
|
-
{slices_close}
|
|
222
|
+
{slices_close}
|
|
223
|
+
|
|
224
|
+
Then your own verdict:
|
|
225
|
+
|
|
226
|
+
{result_open}
|
|
227
|
+
```json
|
|
228
|
+
{{"outcome": "completed", "summary": "one line on how you cut it and why"}}
|
|
229
|
+
```
|
|
230
|
+
{result_close}
|
|
231
|
+
|
|
232
|
+
If the spec cannot be cut without guessing at something it left open, write the \
|
|
233
|
+
verdict block with `"outcome": "escalated"` and a summary naming what has to be \
|
|
234
|
+
settled first. Omit the slices block in that case.\
|
|
206
235
|
"""
|
|
207
236
|
|
|
208
237
|
#: What one approved Slice looks like when it is handed to a planning pass. The
|
|
@@ -298,6 +327,8 @@ class Decomposer:
|
|
|
298
327
|
cwd=cwd,
|
|
299
328
|
spec_open=SPEC_OPEN,
|
|
300
329
|
spec_close=SPEC_CLOSE,
|
|
330
|
+
result_open=RESULT_OPEN,
|
|
331
|
+
result_close=RESULT_CLOSE,
|
|
301
332
|
),
|
|
302
333
|
cwd,
|
|
303
334
|
)
|
|
@@ -311,6 +342,8 @@ class Decomposer:
|
|
|
311
342
|
cap=MAX_SLICES,
|
|
312
343
|
slices_open=SLICES_OPEN,
|
|
313
344
|
slices_close=SLICES_CLOSE,
|
|
345
|
+
result_open=RESULT_OPEN,
|
|
346
|
+
result_close=RESULT_CLOSE,
|
|
314
347
|
),
|
|
315
348
|
cwd,
|
|
316
349
|
)
|
{agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/core/skills.py
RENAMED
|
@@ -14,6 +14,13 @@ one task. `COMPOSED` says what each expands to, because a composite has to
|
|
|
14
14
|
survive both Capability Tiers — natively it fans out through the Skill tool, and
|
|
15
15
|
as a Fragment there is no tool to fan out with, so the delivery path inlines what
|
|
16
16
|
it names.
|
|
17
|
+
|
|
18
|
+
A skill's author can also forbid autonomous invocation outright, by marking
|
|
19
|
+
`disable-model-invocation` in the frontmatter. ADR-0005 chose delivery from one
|
|
20
|
+
input — what the Provider can do — and had none for what the skill permits, so a
|
|
21
|
+
marked skill offered natively is refused by the Skill tool and the Role escalates
|
|
22
|
+
instead of working. `fragment_only` reads the mark from the bundle as shipped, so
|
|
23
|
+
the rule survives a refresh that marks a skill we have never heard of.
|
|
17
24
|
"""
|
|
18
25
|
|
|
19
26
|
from __future__ import annotations
|
|
@@ -22,8 +29,11 @@ import json
|
|
|
22
29
|
import sys
|
|
23
30
|
from collections.abc import Sequence
|
|
24
31
|
from dataclasses import dataclass, field
|
|
32
|
+
from functools import lru_cache
|
|
25
33
|
from pathlib import Path
|
|
26
34
|
|
|
35
|
+
import yaml
|
|
36
|
+
|
|
27
37
|
from .process import CommandRunner, SubprocessRunner
|
|
28
38
|
|
|
29
39
|
SKILLS_ROOT = Path(__file__).resolve().parent.parent / "skills"
|
|
@@ -69,6 +79,74 @@ def expand(names: Sequence[str]) -> tuple[str, ...]:
|
|
|
69
79
|
return tuple(ordered)
|
|
70
80
|
|
|
71
81
|
|
|
82
|
+
#: The frontmatter key by which a skill's author says a model may not invoke it
|
|
83
|
+
#: autonomously — only a human typing the command may.
|
|
84
|
+
MODEL_INVOCATION_KEY = "disable-model-invocation"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _frontmatter(text: str) -> dict:
|
|
88
|
+
"""The YAML block a SKILL.md opens with, or nothing if it has none."""
|
|
89
|
+
if not text.startswith("---"):
|
|
90
|
+
return {}
|
|
91
|
+
parts = text.split("---", 2)
|
|
92
|
+
if len(parts) < 3:
|
|
93
|
+
return {}
|
|
94
|
+
data = yaml.safe_load(parts[1])
|
|
95
|
+
return data if isinstance(data, dict) else {}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@lru_cache(maxsize=1)
|
|
99
|
+
def fragment_only() -> frozenset[str]:
|
|
100
|
+
"""Skills the bundle forbids a model to invoke, read from the bundle itself.
|
|
101
|
+
|
|
102
|
+
Hand-listing these would have exactly the problem vendoring exists to
|
|
103
|
+
prevent: a refresh that marks a third skill would be picked up by nobody,
|
|
104
|
+
and the failure arrives as an escalation from a planning pass rather than as
|
|
105
|
+
anything a reader would connect to a bundle bump.
|
|
106
|
+
|
|
107
|
+
Frontmatter that will not parse counts as marked. Fragment delivery costs
|
|
108
|
+
tokens; getting this wrong the other way costs the Run.
|
|
109
|
+
"""
|
|
110
|
+
marked = set()
|
|
111
|
+
for path in sorted(SKILLS_ROOT.iterdir()):
|
|
112
|
+
source = path / "SKILL.md"
|
|
113
|
+
if not source.is_file():
|
|
114
|
+
continue
|
|
115
|
+
try:
|
|
116
|
+
data = _frontmatter(source.read_text(encoding="utf-8"))
|
|
117
|
+
except yaml.YAMLError:
|
|
118
|
+
marked.add(path.name)
|
|
119
|
+
continue
|
|
120
|
+
if data.get(MODEL_INVOCATION_KEY):
|
|
121
|
+
marked.add(path.name)
|
|
122
|
+
return frozenset(marked)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def forbids_model_invocation(name: str) -> bool:
|
|
126
|
+
"""Whether a skill, or anything it composes, may not be model-invoked.
|
|
127
|
+
|
|
128
|
+
A composite is checked through its parts as well: natively it fans out
|
|
129
|
+
through the Skill tool, so a composite naming a marked skill would reach the
|
|
130
|
+
same refusal one indirection later.
|
|
131
|
+
"""
|
|
132
|
+
marked = fragment_only()
|
|
133
|
+
return any(part in marked for part in expand((name,)))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def split_delivery(names: Sequence[str]) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
137
|
+
"""Partition declared skills into the natively deliverable and the rest.
|
|
138
|
+
|
|
139
|
+
This is consulted before the Capability Tier, not after it. A marked skill
|
|
140
|
+
has no native delivery at any tier, so the Fragment is not the degraded form
|
|
141
|
+
here — it is the only one.
|
|
142
|
+
"""
|
|
143
|
+
native: list[str] = []
|
|
144
|
+
fragment: list[str] = []
|
|
145
|
+
for name in names:
|
|
146
|
+
(fragment if forbids_model_invocation(name) else native).append(name)
|
|
147
|
+
return tuple(native), tuple(fragment)
|
|
148
|
+
|
|
149
|
+
|
|
72
150
|
class SkillNotFound(LookupError):
|
|
73
151
|
"""A skill was requested that is not in the vendored bundle."""
|
|
74
152
|
|
{agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework/providers/base.py
RENAMED
|
@@ -35,7 +35,7 @@ from ..core.contracts import (
|
|
|
35
35
|
)
|
|
36
36
|
from ..core.plan_format import extract_result_block
|
|
37
37
|
from ..core.process import CommandResult, CommandRunner, MissingBinary, require
|
|
38
|
-
from ..core.skills import expand, read_skill
|
|
38
|
+
from ..core.skills import expand, read_skill, split_delivery
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class ProviderError(RuntimeError):
|
|
@@ -133,9 +133,9 @@ class CliProvider(Provider):
|
|
|
133
133
|
) from exc
|
|
134
134
|
|
|
135
135
|
@abstractmethod
|
|
136
|
-
def build_argv(
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
def build_argv(self, model: str, native_skills: tuple[str, ...] = ()) -> Sequence[str]:
|
|
137
|
+
"""The invocation, without the prompt. See `invoke` for why it is absent."""
|
|
138
|
+
...
|
|
139
139
|
|
|
140
140
|
@abstractmethod
|
|
141
141
|
def parse_output(self, result: CommandResult) -> ProviderOutput: ...
|
|
@@ -149,9 +149,20 @@ class CliProvider(Provider):
|
|
|
149
149
|
tier: ModelTier,
|
|
150
150
|
cwd: Path,
|
|
151
151
|
) -> AgentResult:
|
|
152
|
+
"""The prompt travels on stdin, never in the argument vector.
|
|
153
|
+
|
|
154
|
+
Windows caps a command line at 32,767 characters, and an AgentForge
|
|
155
|
+
prompt carries a Context Pack, a Spec, or both — `decompose` crossed the
|
|
156
|
+
cap on a 30k plan document and died as `[WinError 206]`. Linux allows
|
|
157
|
+
roughly 2MB, so every `ubuntu-latest` job in CI passed throughout (#100).
|
|
158
|
+
|
|
159
|
+
Always stdin, never "stdin when the prompt is long": a size threshold
|
|
160
|
+
would make this the rarely-taken path and the bug would return the first
|
|
161
|
+
time a prompt landed the other side of it.
|
|
162
|
+
"""
|
|
152
163
|
prompt, native_skills = self._deliver_skills(role, prompt)
|
|
153
|
-
argv = self.build_argv(
|
|
154
|
-
completed = self.runner.run(argv, cwd=cwd, timeout=self.timeout)
|
|
164
|
+
argv = self.build_argv(self.model_for(tier), native_skills)
|
|
165
|
+
completed = self.runner.run(argv, cwd=cwd, stdin=prompt, timeout=self.timeout)
|
|
155
166
|
output = self.parse_output(completed)
|
|
156
167
|
return to_agent_result(role=role, tier=tier, output=output)
|
|
157
168
|
|
|
@@ -166,6 +177,10 @@ class CliProvider(Provider):
|
|
|
166
177
|
here and every body travels. Without that, a skill whose text says "run
|
|
167
178
|
these two" reaches a Provider that cannot run anything and the Role is
|
|
168
179
|
left with an instruction pointing at nothing.
|
|
180
|
+
|
|
181
|
+
A skill whose author forbids model invocation is a Fragment whatever the
|
|
182
|
+
tier, so the two deliveries are not exclusive: a Role can declare one of
|
|
183
|
+
each and the prompt carries both.
|
|
169
184
|
"""
|
|
170
185
|
if not role.skills:
|
|
171
186
|
return prompt, ()
|
|
@@ -173,16 +188,24 @@ class CliProvider(Provider):
|
|
|
173
188
|
if self.capability_tier is CapabilityTier.NATIVE:
|
|
174
189
|
for name in role.skills:
|
|
175
190
|
read_skill(name) # refuse a name nothing answers for, before the CLI runs
|
|
176
|
-
|
|
191
|
+
native, inlined = split_delivery(role.skills)
|
|
192
|
+
else:
|
|
193
|
+
native, inlined = (), tuple(role.skills)
|
|
194
|
+
|
|
195
|
+
if native:
|
|
196
|
+
commands = ", ".join(f"/agentforge:{name}" for name in native)
|
|
177
197
|
instruction = (
|
|
178
198
|
f"Use the declared native AgentForge skills before doing this work: {commands}."
|
|
179
199
|
)
|
|
180
|
-
|
|
200
|
+
prompt = f"{instruction}\n\n{prompt}"
|
|
201
|
+
|
|
202
|
+
if inlined:
|
|
203
|
+
fragments = [
|
|
204
|
+
f"## Skill: {name}\n\n{read_skill(name).rstrip()}" for name in expand(inlined)
|
|
205
|
+
]
|
|
206
|
+
prompt = f"{prompt}\n\n" + "\n\n".join(fragments) + "\n"
|
|
181
207
|
|
|
182
|
-
|
|
183
|
-
f"## Skill: {name}\n\n{read_skill(name).rstrip()}" for name in expand(role.skills)
|
|
184
|
-
]
|
|
185
|
-
return f"{prompt}\n\n" + "\n\n".join(fragments) + "\n", ()
|
|
208
|
+
return prompt, native
|
|
186
209
|
|
|
187
210
|
|
|
188
211
|
def to_agent_result(*, role: Role, tier: ModelTier, output: ProviderOutput) -> AgentResult:
|
|
@@ -47,13 +47,16 @@ class ClaudeProvider(CliProvider):
|
|
|
47
47
|
def permission_mode(self) -> str:
|
|
48
48
|
return self.PERMITTED if self.allow_commands else self.DENIED
|
|
49
49
|
|
|
50
|
-
def build_argv(
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
def build_argv(self, model: str, native_skills: tuple[str, ...] = ()) -> Sequence[str]:
|
|
51
|
+
"""`-p` carries no prompt argument: with none, the CLI reads stdin.
|
|
52
|
+
|
|
53
|
+
Verified against the installed CLI rather than the help text, which
|
|
54
|
+
documents `-p` as "print response and exit" and says nothing about where
|
|
55
|
+
the prompt comes from.
|
|
56
|
+
"""
|
|
53
57
|
argv = (
|
|
54
58
|
self.binary,
|
|
55
59
|
"-p",
|
|
56
|
-
prompt,
|
|
57
60
|
"--model",
|
|
58
61
|
model,
|
|
59
62
|
"--output-format",
|
|
@@ -74,11 +74,15 @@ class CodexProvider(CliProvider):
|
|
|
74
74
|
#: rather than an edit here.
|
|
75
75
|
REASONING_EFFORT: ClassVar[str] = "medium"
|
|
76
76
|
|
|
77
|
-
def build_argv(
|
|
78
|
-
self, prompt: str, model: str, native_skills: tuple[str, ...] = ()
|
|
79
|
-
) -> Sequence[str]:
|
|
77
|
+
def build_argv(self, model: str, native_skills: tuple[str, ...] = ()) -> Sequence[str]:
|
|
80
78
|
"""Options precede the subcommand: `codex [OPTIONS] <COMMAND> [ARGS]`.
|
|
81
79
|
|
|
80
|
+
The prompt argument is `-`, which `codex exec --help` documents as "read
|
|
81
|
+
instructions from stdin". Omitting it entirely reads stdin too, but the
|
|
82
|
+
sentinel says so out loud, and the same help says a prompt supplied
|
|
83
|
+
alongside piped stdin gets the stdin appended as a `<stdin>` block —
|
|
84
|
+
which is the failure mode the explicit `-` rules out.
|
|
85
|
+
|
|
82
86
|
This adapter previously passed `--full-auto` after `exec`, which fails
|
|
83
87
|
twice over: that flag does not exist in the current CLI, and options
|
|
84
88
|
placed after the subcommand are rejected regardless.
|
|
@@ -98,7 +102,7 @@ class CodexProvider(CliProvider):
|
|
|
98
102
|
"--ask-for-approval",
|
|
99
103
|
self.PERMITTED if self.allow_commands else self.DENIED,
|
|
100
104
|
"exec",
|
|
101
|
-
|
|
105
|
+
"-",
|
|
102
106
|
)
|
|
103
107
|
|
|
104
108
|
def parse_output(self, result: CommandResult) -> ProviderOutput:
|
{agentforge_framework-0.2.1 → agentforge_framework-0.2.2}/src/agentforge_framework.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentforge-framework
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A modular framework for coordinating specialized software agents.
|
|
5
5
|
Author: Yash Mhatre
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -145,7 +145,7 @@ without notice. See
|
|
|
145
145
|
```console
|
|
146
146
|
$ pip install agentforge-framework
|
|
147
147
|
$ agentforge --version
|
|
148
|
-
agentforge 0.2.
|
|
148
|
+
agentforge 0.2.2
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
That is the whole of it. The two routes below the naming note are for a machine
|
|
@@ -169,7 +169,7 @@ from the [latest release](https://github.com/yashmhatre/AgentForge/releases/late
|
|
|
169
169
|
and install the file:
|
|
170
170
|
|
|
171
171
|
```console
|
|
172
|
-
$ pip install agentforge_framework-0.2.
|
|
172
|
+
$ pip install agentforge_framework-0.2.2-py3-none-any.whl
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
Publishing a release is a wizard rather than a checklist:
|
|
@@ -272,7 +272,7 @@ def test_the_control_run_is_one_flag(runner, capsys):
|
|
|
272
272
|
|
|
273
273
|
assert run(["implement", "12", "--allow-commands", "--no-context-pack"], runner) == 0
|
|
274
274
|
|
|
275
|
-
prompts =
|
|
275
|
+
prompts = runner.prompts_to("claude")
|
|
276
276
|
assert prompts and all("## Context Pack" not in prompt for prompt in prompts)
|
|
277
277
|
|
|
278
278
|
|
|
@@ -516,8 +516,7 @@ def test_the_document_reaches_the_synthesis_pass(runner, tmp_path):
|
|
|
516
516
|
|
|
517
517
|
run(["decompose", str(plan), "--yes"], runner)
|
|
518
518
|
|
|
519
|
-
|
|
520
|
-
prompt = first[first.index("-p") + 1]
|
|
519
|
+
prompt = runner.prompts_to("claude")[0]
|
|
521
520
|
assert "Late-arriving facts need a handler." in prompt
|
|
522
521
|
assert "PLAN.md" in prompt
|
|
523
522
|
|
|
@@ -23,6 +23,11 @@ from agentforge_framework.agents.decomposer import (
|
|
|
23
23
|
)
|
|
24
24
|
from agentforge_framework.core.contracts import Slice
|
|
25
25
|
from agentforge_framework.core.plan_format import (
|
|
26
|
+
RESULT_CLOSE,
|
|
27
|
+
RESULT_OPEN,
|
|
28
|
+
SLICES_OPEN,
|
|
29
|
+
SPEC_CLOSE,
|
|
30
|
+
SPEC_OPEN,
|
|
26
31
|
PlanFormatError,
|
|
27
32
|
extract_slices,
|
|
28
33
|
order_slices,
|
|
@@ -71,7 +76,7 @@ def a_runner(issues=(12,)) -> FakeRunner:
|
|
|
71
76
|
|
|
72
77
|
def _prompts(runner: FakeRunner) -> list[str]:
|
|
73
78
|
"""What each stage was actually asked, in order. `claude -p <prompt>`."""
|
|
74
|
-
return
|
|
79
|
+
return runner.prompts_to("claude")
|
|
75
80
|
|
|
76
81
|
|
|
77
82
|
def bodies(runner: FakeRunner) -> list[str]:
|
|
@@ -500,3 +505,71 @@ def test_the_decomposer_reports_every_invocation_so_the_pass_can_be_priced():
|
|
|
500
505
|
assert cut.ok
|
|
501
506
|
assert len(cut.results) == 2
|
|
502
507
|
assert len(cut.slices) == 3
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
# --- every stage asks for a verdict ----------------------------------------
|
|
511
|
+
#
|
|
512
|
+
# The bug this file did not catch (#96). Each stage's prompt asked for its own
|
|
513
|
+
# block and stopped, and `providers/base.py` requires a result block from every
|
|
514
|
+
# invocation — so `decompose` failed on its first call against a real model
|
|
515
|
+
# while 27 tests passed. The fixtures were more generous than the prompts: they
|
|
516
|
+
# appended a result block nothing had asked the model to write.
|
|
517
|
+
#
|
|
518
|
+
# So these read the prompt rather than a fake response. A fixture cannot prove
|
|
519
|
+
# that a prompt asks for what the parser needs; only the prompt can.
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _stage_prompts(runner: FakeRunner) -> list[str]:
|
|
523
|
+
return runner.prompts_to("claude")
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def test_every_stage_asks_the_model_for_a_result_block():
|
|
527
|
+
"""Whatever else a stage wants, it wants a verdict. `providers/base.py`
|
|
528
|
+
fails any invocation that comes back without one."""
|
|
529
|
+
runner = a_runner()
|
|
530
|
+
runner.script("claude", stdout=pipeline())
|
|
531
|
+
|
|
532
|
+
forge(runner).plan("add a retry", approver=_yes)
|
|
533
|
+
|
|
534
|
+
prompts = _stage_prompts(runner)
|
|
535
|
+
assert len(prompts) == 3, "spec, cut, and one planning pass"
|
|
536
|
+
for index, prompt in enumerate(prompts):
|
|
537
|
+
assert RESULT_OPEN in prompt, f"stage {index} never asks for a result block"
|
|
538
|
+
assert RESULT_CLOSE in prompt
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def test_the_spec_stage_asks_for_the_spec_block_and_then_the_verdict():
|
|
542
|
+
runner = a_runner()
|
|
543
|
+
runner.script("claude", stdout=pipeline())
|
|
544
|
+
|
|
545
|
+
forge(runner).plan("add a retry", approver=_yes)
|
|
546
|
+
|
|
547
|
+
prompt = _stage_prompts(runner)[0]
|
|
548
|
+
assert prompt.index(SPEC_OPEN) < prompt.index(RESULT_OPEN), "in that order"
|
|
549
|
+
assert '"outcome": "escalated"' in prompt, "a source too thin to use has a way out"
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def test_the_cut_stage_asks_for_the_slices_block_and_then_the_verdict():
|
|
553
|
+
runner = a_runner()
|
|
554
|
+
runner.script("claude", stdout=pipeline())
|
|
555
|
+
|
|
556
|
+
forge(runner).plan("add a retry", approver=_yes)
|
|
557
|
+
|
|
558
|
+
prompt = _stage_prompts(runner)[1]
|
|
559
|
+
assert prompt.index(SLICES_OPEN) < prompt.index(RESULT_OPEN), "in that order"
|
|
560
|
+
assert '"outcome": "escalated"' in prompt
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def test_a_stage_that_answers_with_only_its_own_block_is_the_bug_that_shipped():
|
|
564
|
+
"""Reproduces #96 exactly: a model that does what the old prompt asked, and
|
|
565
|
+
nothing more, gets a failed Agent Result rather than a spec."""
|
|
566
|
+
runner = a_runner()
|
|
567
|
+
runner.script(
|
|
568
|
+
"claude",
|
|
569
|
+
stdout=_envelope(f"{SPEC_OPEN}\n## Problem Statement\n\nNo verdict.\n{SPEC_CLOSE}"),
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
outcome = forge(runner).plan("add a retry", approver=_yes)
|
|
573
|
+
|
|
574
|
+
assert outcome.failure is not None
|
|
575
|
+
assert "without reporting a result block" in outcome.failure.summary
|
|
@@ -391,3 +391,26 @@ def test_the_matrix_runs_every_python_the_metadata_claims():
|
|
|
391
391
|
tested = [int(version.split(".")[1]) for version in matrix]
|
|
392
392
|
|
|
393
393
|
assert tested == list(range(floor, max(tested) + 1))
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def test_the_suite_runs_on_windows_somewhere_in_ci():
|
|
397
|
+
"""#100 was a Windows process limit that every `ubuntu-latest` job stepped
|
|
398
|
+
over without noticing, on a project whose author develops on Windows. It
|
|
399
|
+
survived a release that way.
|
|
400
|
+
|
|
401
|
+
This asks only that some job runs the suite on Windows — which job, and on
|
|
402
|
+
which Python, is left open.
|
|
403
|
+
"""
|
|
404
|
+
workflow = yaml.safe_load(
|
|
405
|
+
(REPO_ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
on_windows = [
|
|
409
|
+
job
|
|
410
|
+
for job in workflow["jobs"].values()
|
|
411
|
+
if "windows" in str(job.get("runs-on", "")) + str(job.get("strategy", ""))
|
|
412
|
+
]
|
|
413
|
+
assert on_windows, "no CI job runs on Windows"
|
|
414
|
+
assert any(
|
|
415
|
+
"pytest" in str(step.get("run", "")) for job in on_windows for step in job.get("steps", [])
|
|
416
|
+
), "a Windows job exists but nothing runs the suite on it"
|
|
@@ -108,7 +108,7 @@ def test_codex_options_precede_the_subcommand():
|
|
|
108
108
|
|
|
109
109
|
for flag in ("--model", "--sandbox", "--ask-for-approval"):
|
|
110
110
|
assert argv.index(flag) < exec_at, f"{flag} must precede `exec`"
|
|
111
|
-
assert argv[-1]
|
|
111
|
+
assert argv[-1] == "-", "the subcommand's argument is the stdin sentinel, not the prompt"
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
def test_codex_no_longer_passes_a_flag_the_cli_does_not_have():
|
|
@@ -205,7 +205,7 @@ def test_a_denied_role_is_told_to_report_rather_than_substitute_inspection():
|
|
|
205
205
|
|
|
206
206
|
Forge(cwd=ROOT, provider="claude", runner=runner).implement(12)
|
|
207
207
|
|
|
208
|
-
prompt = runner.
|
|
208
|
+
prompt = runner.prompt_to("claude")
|
|
209
209
|
assert "cannot run commands" in prompt.lower()
|
|
210
210
|
assert "escalate" in prompt.lower()
|
|
211
211
|
|
|
@@ -68,7 +68,7 @@ def an_orchestrator(*replies: str) -> tuple[Orchestrator, FakeRunner]:
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
def prompts(runner: FakeRunner) -> list[str]:
|
|
71
|
-
return
|
|
71
|
+
return runner.prompts_to("claude")
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
# --- the interview -----------------------------------------------------------
|
|
@@ -228,7 +228,7 @@ def test_fragments_never_travel_in_an_issue_body():
|
|
|
228
228
|
|
|
229
229
|
def prompts_from(runner: FakeRunner) -> list[str]:
|
|
230
230
|
"""What each Agent was actually handed, read off the argument vector."""
|
|
231
|
-
return
|
|
231
|
+
return runner.prompts_to("claude")
|
|
232
232
|
|
|
233
233
|
|
|
234
234
|
def a_python_run() -> FakeRunner:
|
|
@@ -298,7 +298,7 @@ def test_a_fragment_reaches_both_capability_tiers_the_same_way():
|
|
|
298
298
|
runner = a_python_run()
|
|
299
299
|
Forge(cwd=ROOT, provider="codex", runner=runner).implement(12, allow_commands=True)
|
|
300
300
|
|
|
301
|
-
prompts =
|
|
301
|
+
prompts = runner.prompts_to("codex")
|
|
302
302
|
assert "Type-annotate new public functions" in prompts[0]
|
|
303
303
|
|
|
304
304
|
|