agentforge-framework 0.2.1__tar.gz → 0.2.3__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.3}/PKG-INFO +23 -4
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/README.md +22 -3
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/pyproject.toml +1 -1
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/__init__.py +1 -1
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/agents/decomposer.py +38 -5
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/config.py +14 -1
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/issues.py +32 -10
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/project.py +8 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/repo.py +15 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/runtime.py +46 -11
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/skills.py +78 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/providers/base.py +35 -12
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/providers/claude.py +7 -4
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/providers/codex.py +8 -4
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework.egg-info/PKG-INFO +23 -4
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_cli.py +2 -3
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_config.py +25 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_decompose.py +74 -1
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_docs.py +23 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_execution_posture.py +2 -2
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_interview.py +1 -1
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_issues.py +38 -9
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_plugins.py +2 -2
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_project.py +6 -4
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_providers.py +138 -7
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_reviewer.py +3 -4
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_runtime.py +66 -10
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_skills.py +37 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_tester.py +1 -1
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/LICENSE +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/setup.cfg +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/.claude-plugin/plugin.json +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/agents/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/agents/architect.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/agents/implementer.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/agents/orchestrator.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/agents/reviewer.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/agents/security.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/agents/tester.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/cli.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/context/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/context/extractors/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/context/extractors/base.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/context/extractors/python.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/context/extractors/sql.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/context/extractors/yaml.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/context/prompt.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/context/resolver.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/commands.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/contracts.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/gates.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/plan_format.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/process.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/registry.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/router.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/workflow.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/plugins/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/plugins/databricks/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/plugins/pyspark/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/plugins/python/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/plugins/sql/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/providers/__init__.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/MANIFEST.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/NOTICE +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/domain-modeling/ADR-FORMAT.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/domain-modeling/CONTEXT-FORMAT.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/domain-modeling/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/domain-modeling/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/grill-with-docs/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/grilling/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/grilling/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/to-spec/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/to-spec/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/to-tickets/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/to-tickets/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/evals/fixtures/silhouette/human_reference.json +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/_lang.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/banned_phrase_scan.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/calibrate_pairs.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/calibrate_score.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/check_packs.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/check_suggestions.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/contribute.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/diff_check.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/extract_constraints.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/harvest_classify.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/harvest_samples.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/readability_metrics.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/refresh_status.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/silhouette_scan.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/structure_scan.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/suggest.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/validate_preservation.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/voice_card.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/voice_profile.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/voice_score.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/unslop/scripts/wiki_sync.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/skills/write-plainly/SKILL.md +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/workflows/bugfix.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/workflows/feature.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/workflows/review.yaml +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework.egg-info/SOURCES.txt +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework.egg-info/dependency_links.txt +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework.egg-info/entry_points.txt +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework.egg-info/requires.txt +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework.egg-info/top_level.txt +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_agents.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_architect.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_context.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_contracts.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_gates.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_plan_format.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/tests/test_security.py +0 -0
- {agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/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.3
|
|
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.3
|
|
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.3-py3-none-any.whl
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
Publishing a release is a wizard rather than a checklist:
|
|
@@ -200,6 +200,14 @@ What each release contains is in [CHANGELOG.md](CHANGELOG.md).
|
|
|
200
200
|
|
|
201
201
|
AgentForge never touches a model API and handles no credentials of its own. Whatever your coding-agent CLI is already authenticated with is what a Run costs.
|
|
202
202
|
|
|
203
|
+
### Editors and IDEs
|
|
204
|
+
|
|
205
|
+
AgentForge is a terminal tool. It runs fine from an IDE's terminal, but the IDE is not a Provider and its assistant is not one either.
|
|
206
|
+
|
|
207
|
+
**Antigravity IDE cannot be a Provider** ([#101](https://github.com/yashmhatre/AgentForge/issues/101)). Checked by running it, so nobody has to derive it again: `antigravity-ide chat` accepts a prompt and reads stdin, which is the right shape for the argument vector — but it is a window launcher. Piping a prompt to it returns exit 0 immediately with `Reading from stdin via: …\code-stdin-XXXX` on stdout and nothing else, ever; the answer goes to a GUI session. A Provider adapter has to read the Agent's result off stdout, so there is nothing for `parse_output` to parse. The same is true of any editor CLI of this shape.
|
|
208
|
+
|
|
209
|
+
**Do not run another agent against a checkout while a Run is going.** A Run commits every change to a file git already tracks, however it got there ([ADR-0015](docs/adr/0015-a-run-commits-what-it-declared.md)) — so a second agent's half-finished edits are committed into the Run's branch and attributed to a Role. Files nothing in the Run claimed are listed in the pull request body ([ADR-0023](docs/adr/0023-a-commit-names-what-nothing-claimed.md)), which makes it visible at Sign-off but does not prevent it. Use a separate clone.
|
|
210
|
+
|
|
203
211
|
## Commands
|
|
204
212
|
|
|
205
213
|
| Command | What it does |
|
|
@@ -331,10 +339,21 @@ code rather than the verdict about the old code.
|
|
|
331
339
|
|
|
332
340
|
Read [`CONTEXT.md`](CONTEXT.md) before writing anything, and [`docs/adr/`](docs/adr/) for the decisions that constrain it.
|
|
333
341
|
|
|
334
|
-
##
|
|
342
|
+
## Changing AgentForge
|
|
343
|
+
|
|
344
|
+
Clone it and install that clone editable. Never patch the installed copy.
|
|
335
345
|
|
|
336
346
|
```console
|
|
347
|
+
$ git clone https://github.com/yashmhatre/AgentForge.git
|
|
348
|
+
$ cd AgentForge
|
|
337
349
|
$ pip install -e ".[dev]"
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Editing `site-packages/agentforge_framework/` appears to work and is the one change nothing will catch: the local install diverges from what ships, so the thing that works is not the thing anybody else gets, and the fix is lost the next time the package is upgraded. If a Run is failing badly enough that patching the install looks like the way forward, that is a bug worth filing — [#101](https://github.com/yashmhatre/AgentForge/issues/101) is what happened the last time somebody reached for it.
|
|
353
|
+
|
|
354
|
+
## Tests
|
|
355
|
+
|
|
356
|
+
```console
|
|
338
357
|
$ pytest
|
|
339
358
|
```
|
|
340
359
|
|
|
@@ -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.3
|
|
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.3-py3-none-any.whl
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
Publishing a release is a wizard rather than a checklist:
|
|
@@ -182,6 +182,14 @@ What each release contains is in [CHANGELOG.md](CHANGELOG.md).
|
|
|
182
182
|
|
|
183
183
|
AgentForge never touches a model API and handles no credentials of its own. Whatever your coding-agent CLI is already authenticated with is what a Run costs.
|
|
184
184
|
|
|
185
|
+
### Editors and IDEs
|
|
186
|
+
|
|
187
|
+
AgentForge is a terminal tool. It runs fine from an IDE's terminal, but the IDE is not a Provider and its assistant is not one either.
|
|
188
|
+
|
|
189
|
+
**Antigravity IDE cannot be a Provider** ([#101](https://github.com/yashmhatre/AgentForge/issues/101)). Checked by running it, so nobody has to derive it again: `antigravity-ide chat` accepts a prompt and reads stdin, which is the right shape for the argument vector — but it is a window launcher. Piping a prompt to it returns exit 0 immediately with `Reading from stdin via: …\code-stdin-XXXX` on stdout and nothing else, ever; the answer goes to a GUI session. A Provider adapter has to read the Agent's result off stdout, so there is nothing for `parse_output` to parse. The same is true of any editor CLI of this shape.
|
|
190
|
+
|
|
191
|
+
**Do not run another agent against a checkout while a Run is going.** A Run commits every change to a file git already tracks, however it got there ([ADR-0015](docs/adr/0015-a-run-commits-what-it-declared.md)) — so a second agent's half-finished edits are committed into the Run's branch and attributed to a Role. Files nothing in the Run claimed are listed in the pull request body ([ADR-0023](docs/adr/0023-a-commit-names-what-nothing-claimed.md)), which makes it visible at Sign-off but does not prevent it. Use a separate clone.
|
|
192
|
+
|
|
185
193
|
## Commands
|
|
186
194
|
|
|
187
195
|
| Command | What it does |
|
|
@@ -313,10 +321,21 @@ code rather than the verdict about the old code.
|
|
|
313
321
|
|
|
314
322
|
Read [`CONTEXT.md`](CONTEXT.md) before writing anything, and [`docs/adr/`](docs/adr/) for the decisions that constrain it.
|
|
315
323
|
|
|
316
|
-
##
|
|
324
|
+
## Changing AgentForge
|
|
325
|
+
|
|
326
|
+
Clone it and install that clone editable. Never patch the installed copy.
|
|
317
327
|
|
|
318
328
|
```console
|
|
329
|
+
$ git clone https://github.com/yashmhatre/AgentForge.git
|
|
330
|
+
$ cd AgentForge
|
|
319
331
|
$ pip install -e ".[dev]"
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Editing `site-packages/agentforge_framework/` appears to work and is the one change nothing will catch: the local install diverges from what ships, so the thing that works is not the thing anybody else gets, and the fix is lost the next time the package is upgraded. If a Run is failing badly enough that patching the install looks like the way forward, that is a bug worth filing — [#101](https://github.com/yashmhatre/AgentForge/issues/101) is what happened the last time somebody reached for it.
|
|
335
|
+
|
|
336
|
+
## Tests
|
|
337
|
+
|
|
338
|
+
```console
|
|
320
339
|
$ pytest
|
|
321
340
|
```
|
|
322
341
|
|
|
@@ -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.3}/src/agentforge_framework/core/config.py
RENAMED
|
@@ -41,6 +41,13 @@ class Config:
|
|
|
41
41
|
#: Command in the glossary's sense: nothing infers anything from it.
|
|
42
42
|
test_suite: tuple[str, ...] = DEFAULT_TEST_SUITE
|
|
43
43
|
|
|
44
|
+
#: Whether the Context Pack comment publishes the symbols and import graph
|
|
45
|
+
#: it resolved, or only their counts. Off by default: the Issue already
|
|
46
|
+
#: carries the pack's file paths in the frozen Plan, and nothing else on it
|
|
47
|
+
#: carries private symbol names. A tracker whose audience matches the
|
|
48
|
+
#: code's turns this on. See ADR-0024.
|
|
49
|
+
publish_pack_inventory: bool = False
|
|
50
|
+
|
|
44
51
|
def capability_for(self, provider: str) -> CapabilityTier:
|
|
45
52
|
return self.provider_capabilities.get(provider, CapabilityTier.FRAGMENT)
|
|
46
53
|
|
|
@@ -55,7 +62,13 @@ def load_config(root: Path | str) -> Config:
|
|
|
55
62
|
capabilities = dict(DEFAULT_CAPABILITIES)
|
|
56
63
|
for name, provider in (data.get("providers") or {}).items():
|
|
57
64
|
capabilities[str(name)] = CapabilityTier(provider["capability_tier"])
|
|
58
|
-
return Config(
|
|
65
|
+
return Config(
|
|
66
|
+
provider_capabilities=capabilities,
|
|
67
|
+
test_suite=_test_suite(data),
|
|
68
|
+
publish_pack_inventory=bool(
|
|
69
|
+
(data.get("context") or {}).get("publish_inventory", False)
|
|
70
|
+
),
|
|
71
|
+
)
|
|
59
72
|
|
|
60
73
|
|
|
61
74
|
def _test_suite(data: dict) -> tuple[str, ...]:
|
{agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/issues.py
RENAMED
|
@@ -381,6 +381,7 @@ def render_context_comment(
|
|
|
381
381
|
context: ContextPack,
|
|
382
382
|
plugins: Sequence[tuple[str, str]] = (),
|
|
383
383
|
skipped: Sequence[str] = (),
|
|
384
|
+
publish_inventory: bool = False,
|
|
384
385
|
) -> str:
|
|
385
386
|
"""The pack this Run's Agents were handed, recorded before the first one ran.
|
|
386
387
|
|
|
@@ -390,6 +391,14 @@ def render_context_comment(
|
|
|
390
391
|
invocation (ADR-0010), so this is a record of a Run rather than a contract
|
|
391
392
|
the next one reads back.
|
|
392
393
|
|
|
394
|
+
`publish_inventory` decides whether the resolved symbols and import graph
|
|
395
|
+
are named or only counted. Off by default: the Issue already carries this
|
|
396
|
+
pack's file paths in the frozen Plan, so the file list discloses nothing
|
|
397
|
+
new, while private symbol names and the graph between modules appear
|
|
398
|
+
nowhere else on the tracker (ADR-0024). The counts stay either way, because
|
|
399
|
+
a pack that resolved more or less than a reader expected is most of what
|
|
400
|
+
this comment is read for.
|
|
401
|
+
|
|
393
402
|
`plugins` and `skipped` are the Plugins that answered for this Run and the
|
|
394
403
|
ones that raised while being asked. Both are named for the same reason the
|
|
395
404
|
pack is: a prompt that grew, or one that did not grow when a reader expected
|
|
@@ -418,16 +427,29 @@ def render_context_comment(
|
|
|
418
427
|
lines += [f"**Files ({len(context.files)}):**", ""]
|
|
419
428
|
lines += [f"- `{path}`" for path in context.files]
|
|
420
429
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
430
|
+
inventory = (("Symbols", context.symbols), ("Reaches for", context.references))
|
|
431
|
+
if publish_inventory:
|
|
432
|
+
for label, values in inventory:
|
|
433
|
+
if values:
|
|
434
|
+
lines += [
|
|
435
|
+
"",
|
|
436
|
+
f"<details><summary>{label} ({len(values)})</summary>",
|
|
437
|
+
"",
|
|
438
|
+
", ".join(f"`{value}`" for value in values),
|
|
439
|
+
"",
|
|
440
|
+
"</details>",
|
|
441
|
+
]
|
|
442
|
+
elif any(values for _, values in inventory):
|
|
443
|
+
lines += [
|
|
444
|
+
"",
|
|
445
|
+
(
|
|
446
|
+
"Symbol and reference counts are in the heading; the names are not "
|
|
447
|
+
"published. The Issue already carries this pack's file paths in the "
|
|
448
|
+
"frozen Plan, and a reader diagnosing a Run reads them against the "
|
|
449
|
+
"repository. Set `context.publish_inventory: true` where the tracker's "
|
|
450
|
+
"audience is the code's audience (ADR-0024)."
|
|
451
|
+
),
|
|
452
|
+
]
|
|
431
453
|
|
|
432
454
|
if context.conventions:
|
|
433
455
|
lines += ["", "**Conventions:**", ""]
|
{agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/project.py
RENAMED
|
@@ -142,6 +142,14 @@ gates:
|
|
|
142
142
|
# The argument vector the `tests` Gate runs, in this repository.
|
|
143
143
|
# {because}
|
|
144
144
|
suite: [{suite}]
|
|
145
|
+
|
|
146
|
+
context:
|
|
147
|
+
# Whether the Context Pack comment names the symbols and imports it resolved,
|
|
148
|
+
# or only counts them (ADR-0024). A Run posts that comment to the Issue, and
|
|
149
|
+
# a tracker can have a wider audience than the code — so the names are off
|
|
150
|
+
# unless this repository says otherwise. The file list is published either
|
|
151
|
+
# way: the frozen Plan on the Issue already carries it.
|
|
152
|
+
publish_inventory: false
|
|
145
153
|
"""
|
|
146
154
|
|
|
147
155
|
|
{agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/repo.py
RENAMED
|
@@ -145,6 +145,21 @@ class Repository:
|
|
|
145
145
|
self._git("push", "--set-upstream", "origin", branch)
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
def unclaimed(committed: Iterable[str], declared: Iterable[str]) -> tuple[str, ...]:
|
|
149
|
+
"""Of the paths committed, the ones nothing in the Run said it would touch.
|
|
150
|
+
|
|
151
|
+
ADR-0015 commits every change to a tracked file however it arrived, and its
|
|
152
|
+
reasoning holds as long as the only writers are the Agents and the commands
|
|
153
|
+
they were allowed to run. A second agent editing the same checkout breaks
|
|
154
|
+
that — its half-finished work is committed into the Run's branch and
|
|
155
|
+
attributed to a Role, and nothing about the file on disk says otherwise
|
|
156
|
+
(#101). This does not change what is committed; it names what nothing
|
|
157
|
+
claimed, so the disclosure reaches the human at Sign-off.
|
|
158
|
+
"""
|
|
159
|
+
named = {_normalize(path) for path in declared}
|
|
160
|
+
return tuple(path for path in committed if _normalize(path) not in named)
|
|
161
|
+
|
|
162
|
+
|
|
148
163
|
def _normalize(path: str) -> str:
|
|
149
164
|
r"""A declared path in the spelling `git status` uses.
|
|
150
165
|
|
{agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/src/agentforge_framework/core/runtime.py
RENAMED
|
@@ -24,7 +24,7 @@ from ..agents.decomposer import Approver, Decomposed, Decomposer, slice_task
|
|
|
24
24
|
from ..agents.orchestrator import Exchange, Interviewer, Orchestrator, Planned
|
|
25
25
|
from ..context.resolver import resolve_pack
|
|
26
26
|
from ..providers import DEFAULT_PROVIDER, get_provider
|
|
27
|
-
from .config import load_config
|
|
27
|
+
from .config import Config, load_config
|
|
28
28
|
from .contracts import (
|
|
29
29
|
AgentResult,
|
|
30
30
|
ContextPack,
|
|
@@ -64,7 +64,13 @@ from .registry import (
|
|
|
64
64
|
fragments_for,
|
|
65
65
|
gates_for,
|
|
66
66
|
)
|
|
67
|
-
from .repo import
|
|
67
|
+
from .repo import (
|
|
68
|
+
PreconditionFailed,
|
|
69
|
+
Repository,
|
|
70
|
+
branch_for_issue,
|
|
71
|
+
open_repository,
|
|
72
|
+
unclaimed,
|
|
73
|
+
)
|
|
68
74
|
from .workflow import Workflow, WorkflowError, load_workflow
|
|
69
75
|
|
|
70
76
|
|
|
@@ -144,7 +150,9 @@ class Forge:
|
|
|
144
150
|
|
|
145
151
|
# --- preconditions -----------------------------------------------------
|
|
146
152
|
|
|
147
|
-
def _prepare(
|
|
153
|
+
def _prepare(
|
|
154
|
+
self, allow_commands: bool = False
|
|
155
|
+
) -> tuple[Repository, GitHub, object, Config]:
|
|
148
156
|
"""Every check that can fail for free, before anything is spent.
|
|
149
157
|
|
|
150
158
|
Absent git, absent remote, absent `gh`, absent coding-agent CLI. A Run
|
|
@@ -157,11 +165,12 @@ class Forge:
|
|
|
157
165
|
raise RunFailed(str(exc)) from exc
|
|
158
166
|
|
|
159
167
|
github = GitHub(self.runner, repo.root)
|
|
168
|
+
config = load_config(repo.root)
|
|
160
169
|
provider = get_provider(
|
|
161
170
|
self.provider_name,
|
|
162
171
|
self.runner,
|
|
163
172
|
allow_commands=allow_commands,
|
|
164
|
-
config=
|
|
173
|
+
config=config,
|
|
165
174
|
)
|
|
166
175
|
try:
|
|
167
176
|
github.preflight()
|
|
@@ -169,7 +178,7 @@ class Forge:
|
|
|
169
178
|
except (IssueError, RuntimeError) as exc:
|
|
170
179
|
raise RunFailed(str(exc)) from exc
|
|
171
180
|
|
|
172
|
-
return repo, github, provider
|
|
181
|
+
return repo, github, provider, config
|
|
173
182
|
|
|
174
183
|
# --- agentforge plan ---------------------------------------------------
|
|
175
184
|
|
|
@@ -208,7 +217,7 @@ class Forge:
|
|
|
208
217
|
the caller supplies an approver that always says yes when the human
|
|
209
218
|
asked for exactly that.
|
|
210
219
|
"""
|
|
211
|
-
repo, github, provider = self._prepare()
|
|
220
|
+
repo, github, provider, _config = self._prepare()
|
|
212
221
|
|
|
213
222
|
# Only when interviewing: every stage is told to change nothing, and two
|
|
214
223
|
# extra `git status` calls on an unattended pass buy nothing.
|
|
@@ -368,7 +377,7 @@ class Forge:
|
|
|
368
377
|
off already suppresses them, and a Run with neither cannot say which of
|
|
369
378
|
the two moved the total. See ADR-0016 for the three conditions.
|
|
370
379
|
"""
|
|
371
|
-
repo, github, provider = self._prepare(allow_commands=allow_commands)
|
|
380
|
+
repo, github, provider, config = self._prepare(allow_commands=allow_commands)
|
|
372
381
|
|
|
373
382
|
if repo.is_dirty():
|
|
374
383
|
raise RunFailed(
|
|
@@ -466,7 +475,10 @@ class Forge:
|
|
|
466
475
|
github.post_comment(
|
|
467
476
|
number,
|
|
468
477
|
render_context_comment(
|
|
469
|
-
state.context,
|
|
478
|
+
state.context,
|
|
479
|
+
contributions(activation),
|
|
480
|
+
activation.skipped,
|
|
481
|
+
publish_inventory=config.publish_pack_inventory,
|
|
470
482
|
),
|
|
471
483
|
)
|
|
472
484
|
role = resolve_role(step.role)
|
|
@@ -535,14 +547,22 @@ class Forge:
|
|
|
535
547
|
)
|
|
536
548
|
|
|
537
549
|
changed = repo.changed_files()
|
|
550
|
+
declared = _declared_surface(state, results)
|
|
538
551
|
committed = repo.commit_declared(
|
|
539
552
|
f"{issue.title}\n\nImplements #{number} via AgentForge.",
|
|
540
|
-
|
|
553
|
+
declared,
|
|
541
554
|
)
|
|
542
555
|
# Left in the working tree on purpose (ADR-0015), and named rather
|
|
543
556
|
# than dropped: the human at Sign-off is the only one who can say
|
|
544
557
|
# whether an undeclared file was an Agent's work or its suite's.
|
|
545
558
|
left = tuple(path for path in changed if path not in committed)
|
|
559
|
+
# The other half of the same disclosure. A tracked file is committed
|
|
560
|
+
# however it changed (ADR-0015), which is right when the only writers
|
|
561
|
+
# are the Agents and their commands, and is how a second agent sharing
|
|
562
|
+
# the checkout gets its work committed and attributed to a Role (#101).
|
|
563
|
+
# Naming these is the difference between a data-loss bug and a line at
|
|
564
|
+
# Sign-off.
|
|
565
|
+
unclaimed_paths = unclaimed(committed, declared)
|
|
546
566
|
base = github.default_branch()
|
|
547
567
|
# An empty working tree is only a failure when the branch has nothing on
|
|
548
568
|
# it either. Plenty of Runs legitimately write nothing here: an audit
|
|
@@ -573,7 +593,7 @@ class Forge:
|
|
|
573
593
|
repo.push(branch)
|
|
574
594
|
url = github.open_draft_pr(
|
|
575
595
|
title=issue.title,
|
|
576
|
-
body=_pr_body(number, state, results, committed, left),
|
|
596
|
+
body=_pr_body(number, state, results, committed, left, unclaimed_paths),
|
|
577
597
|
head=branch,
|
|
578
598
|
base=base,
|
|
579
599
|
)
|
|
@@ -761,7 +781,7 @@ def _end(github: GitHub, issue: Issue, state: RunState) -> RunState:
|
|
|
761
781
|
return state
|
|
762
782
|
|
|
763
783
|
|
|
764
|
-
def _pr_body(number: int, state: RunState, results, committed, left=()) -> str:
|
|
784
|
+
def _pr_body(number: int, state: RunState, results, committed, left=(), unclaimed=()) -> str:
|
|
765
785
|
lines = [
|
|
766
786
|
f"Closes #{number}.",
|
|
767
787
|
"",
|
|
@@ -791,6 +811,21 @@ def _pr_body(number: int, state: RunState, results, committed, left=()) -> str:
|
|
|
791
811
|
"",
|
|
792
812
|
]
|
|
793
813
|
lines += [f"- `{path}`" for path in left]
|
|
814
|
+
if unclaimed:
|
|
815
|
+
lines += [
|
|
816
|
+
"",
|
|
817
|
+
"## Committed, but no Agent claimed them",
|
|
818
|
+
"",
|
|
819
|
+
(
|
|
820
|
+
"These are in the diff. Git already tracked them, so AgentForge committed "
|
|
821
|
+
"them however they changed (ADR-0015) — but neither the Plan nor any Agent "
|
|
822
|
+
"Result named them, so nothing in this Run says they are its work. Read "
|
|
823
|
+
"them before you sign off. Another agent or a person editing this checkout "
|
|
824
|
+
"while the Run was going is the reason worth ruling out."
|
|
825
|
+
),
|
|
826
|
+
"",
|
|
827
|
+
]
|
|
828
|
+
lines += [f"- `{path}`" for path in unclaimed]
|
|
794
829
|
lines += [
|
|
795
830
|
"",
|
|
796
831
|
"---",
|
{agentforge_framework-0.2.1 → agentforge_framework-0.2.3}/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
|
|