ai-push-hooks 0.1.9 → 0.1.11
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.
- package/README.md +184 -141
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/ai_push_hooks/executors/llm.py +3 -2
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# ai-push-hooks
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`ai-push-hooks` is a configurable pre-push workflow runner for local Git repositories.
|
|
4
|
+
|
|
5
|
+
It runs module-based steps (collect, LLM, apply, exec, assert) before push so you can automate checks and optional repo maintenance tasks.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
|
-
### Python
|
|
9
|
+
### Python
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
12
|
uv tool install ai-push-hooks
|
|
@@ -12,7 +14,7 @@ uv tool install ai-push-hooks
|
|
|
12
14
|
pipx install ai-push-hooks
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
###
|
|
17
|
+
### Node (wrapper around Python package)
|
|
16
18
|
|
|
17
19
|
```bash
|
|
18
20
|
npm install --save-dev ai-push-hooks
|
|
@@ -20,65 +22,198 @@ npm install --save-dev ai-push-hooks
|
|
|
20
22
|
pnpm add -D ai-push-hooks
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
Requirements:
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
- Python 3.10+ (`python3` or `python`) is required, including npm installs.
|
|
28
|
+
- `opencode-cli` (or `opencode`) is required for `llm` and `apply` steps.
|
|
29
|
+
- `gh` is required only if you use PR creation via `gh_pr_create`.
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
## Quick start
|
|
28
32
|
|
|
29
|
-
1.
|
|
30
|
-
2.
|
|
31
|
-
3. Push commit + tag: `git push && git push --tags`.
|
|
33
|
+
1. Install the CLI.
|
|
34
|
+
2. Generate a starter config:
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
```bash
|
|
37
|
+
ai-push-hooks init --template minimal-docs
|
|
38
|
+
```
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
- runs tests
|
|
37
|
-
- builds and validates Python distributions
|
|
38
|
-
- smoke-tests the installed Python CLI
|
|
39
|
-
- publishes to PyPI (Trusted Publishing)
|
|
40
|
-
- publishes to npm (`NPM_TOKEN` secret)
|
|
40
|
+
3. Wire it into Lefthook:
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
```yaml
|
|
43
|
+
pre-push:
|
|
44
|
+
commands:
|
|
45
|
+
ai-push-hooks:
|
|
46
|
+
run: ai-push-hooks hook {1} {2}
|
|
47
|
+
```
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
- Add repository secret `NPM_TOKEN` with publish access to `ai-push-hooks`.
|
|
49
|
+
4. Push as usual. The workflow runs automatically before push completes.
|
|
46
50
|
|
|
47
51
|
## Commands
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
ai-push-hooks
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
53
|
+
| Command | What it does |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `ai-push-hooks hook <remote-name> <remote-url>` | Runs the configured pre-push workflow. |
|
|
56
|
+
| `ai-push-hooks init --template minimal-docs` | Writes `.ai-push-hooks.toml` starter config. |
|
|
57
|
+
| `ai-push-hooks init --template minimal-docs --force` | Overwrites an existing config file. |
|
|
58
|
+
|
|
59
|
+
## Configuration overview
|
|
60
|
+
|
|
61
|
+
- Config file lookup order: `.ai-push-hooks.toml`, then `ai-push-hooks.toml`.
|
|
62
|
+
- If no config file is present, built-in defaults are used.
|
|
63
|
+
- File values are deep-merged over defaults.
|
|
64
|
+
- Prompt resolution precedence for `llm` and `apply` steps:
|
|
65
|
+
1. `prompt`
|
|
66
|
+
2. `prompt_file`
|
|
67
|
+
3. `fallback_prompt_id`
|
|
68
|
+
|
|
69
|
+
## Configuration reference
|
|
70
|
+
|
|
71
|
+
### Top-level keys
|
|
72
|
+
|
|
73
|
+
| Key | Type | Required | Default |
|
|
74
|
+
| --- | --- | --- | --- |
|
|
75
|
+
| `general` | table | no | built-in values |
|
|
76
|
+
| `llm` | table | no | built-in values |
|
|
77
|
+
| `logging` | table | no | built-in values |
|
|
78
|
+
| `workflow` | table | yes | `{ modules = ["docs"] }` |
|
|
79
|
+
| `modules` | table | yes | `{ docs = ... }` |
|
|
80
|
+
|
|
81
|
+
### `[general]`
|
|
82
|
+
|
|
83
|
+
| Key | Type | Default | Description |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| `enabled` | bool | `true` | Enables or disables the hook globally. |
|
|
86
|
+
| `allow_push_on_error` | bool | `false` | If `true`, push continues even when workflow fails. |
|
|
87
|
+
| `require_clean_worktree` | bool | `false` | If `true`, aborts when local changes exist. |
|
|
88
|
+
| `skip_on_sync_branch` | bool | `true` | If `true`, skips on sync branch/worktree context. |
|
|
89
|
+
|
|
90
|
+
### `[llm]`
|
|
91
|
+
|
|
92
|
+
| Key | Type | Default | Description |
|
|
93
|
+
| --- | --- | --- | --- |
|
|
94
|
+
| `runner` | string | `"opencode"` | LLM runner label (currently OpenCode flow). |
|
|
95
|
+
| `model` | string | `"openai/gpt-5.3-codex-spark"` | Model passed to OpenCode. |
|
|
96
|
+
| `variant` | string | `""` | Optional OpenCode variant. |
|
|
97
|
+
| `timeout_seconds` | int | `800` | Timeout per LLM invocation and related OpenCode calls. |
|
|
98
|
+
| `max_parallel` | int | `2` | Max concurrent read-only steps (`collect`, `llm`). |
|
|
99
|
+
| `json_max_retries` | int | `2` | Retry count for invalid JSON responses. |
|
|
100
|
+
| `invalid_json_feedback_max_chars` | int | `6000` | Max invalid output included in retry feedback. |
|
|
101
|
+
| `json_retry_new_session` | bool | `true` | Starts a new OpenCode session on JSON retry. |
|
|
102
|
+
| `delete_session_after_run` | bool | `true` | Deletes OpenCode sessions after completion. |
|
|
103
|
+
| `max_diff_bytes` | int | `180000` | Max bytes of git diff sent into workflow artifacts. |
|
|
104
|
+
| `session_title_prefix` | string | `"ai-push-hooks"` | Prefix for OpenCode session titles. |
|
|
105
|
+
|
|
106
|
+
### `[logging]`
|
|
107
|
+
|
|
108
|
+
| Key | Type | Default | Description |
|
|
109
|
+
| --- | --- | --- | --- |
|
|
110
|
+
| `level` | string | `"status"` | Console verbosity (`status`, `info`, `debug`). |
|
|
111
|
+
| `jsonl` | bool | `true` | Enables JSONL event logging. |
|
|
112
|
+
| `dir` | string | `".git/ai-push-hooks/logs"` | Directory for `hook.jsonl`. |
|
|
113
|
+
| `capture_llm_transcript` | bool | `true` | Exports OpenCode session transcripts. |
|
|
114
|
+
| `transcript_dir` | string | `".git/ai-push-hooks/transcripts"` | Transcript export directory. |
|
|
115
|
+
| `summary_dir` | string | `".git/ai-push-hooks/summaries"` | Per-run summary JSON directory. |
|
|
116
|
+
| `print_llm_output` | bool | `false` | Mirrors raw OpenCode JSON stream to stdout. |
|
|
117
|
+
|
|
118
|
+
### `[workflow]`
|
|
119
|
+
|
|
120
|
+
| Key | Type | Required | Description |
|
|
121
|
+
| --- | --- | --- | --- |
|
|
122
|
+
| `modules` | array of strings | yes | Ordered module IDs to run. Must contain at least one module and each ID must exist under `[modules]`. |
|
|
123
|
+
|
|
124
|
+
### `[modules.<module_id>]`
|
|
125
|
+
|
|
126
|
+
| Key | Type | Required | Description |
|
|
127
|
+
| --- | --- | --- | --- |
|
|
128
|
+
| `enabled` | bool | no | Enables or disables that module. Default `true`. |
|
|
129
|
+
| `steps` | array of step tables | yes | Ordered workflow steps for the module. Must be non-empty. |
|
|
130
|
+
|
|
131
|
+
### `[[modules.<module_id>.steps]]`
|
|
132
|
+
|
|
133
|
+
| Key | Type | Required | Applies to | Description |
|
|
134
|
+
| --- | --- | --- | --- | --- |
|
|
135
|
+
| `id` | string | yes | all step types | Unique step identifier inside the module. |
|
|
136
|
+
| `type` | string | yes | all step types | One of: `collect`, `llm`, `apply`, `exec`, `assert`. |
|
|
137
|
+
| `inputs` | array of strings | no | non-`collect` steps | Artifact references from earlier steps. |
|
|
138
|
+
| `output` | string | yes | `llm` | Output artifact filename (often `.json`). |
|
|
139
|
+
| `schema` | string | no | `llm` | Validates parsed model output shape. |
|
|
140
|
+
| `prompt` | string | conditional | `llm`, `apply` | Highest-priority prompt source. |
|
|
141
|
+
| `prompt_file` | string | conditional | `llm`, `apply` | Repo-relative or absolute prompt file path. |
|
|
142
|
+
| `fallback_prompt_id` | string | conditional | `llm`, `apply` | Built-in prompt ID used when no higher source resolves. |
|
|
143
|
+
| `collector` | string | yes | `collect` | Collector handler ID. |
|
|
144
|
+
| `allow_paths` | array of strings | yes | `apply` | File glob allowlist for edits. |
|
|
145
|
+
| `executor` | string | yes | `exec` | Exec handler ID. |
|
|
146
|
+
| `assertion` | string | yes | `assert` | Assertion handler ID. |
|
|
147
|
+
| `when_env` | string | no | any step | Runs step only when env var parses as true. |
|
|
148
|
+
|
|
149
|
+
`llm` and `apply` are promptable step types: at least one of `prompt`, `prompt_file`, or `fallback_prompt_id` must be set.
|
|
150
|
+
|
|
151
|
+
### Supported handler and schema values
|
|
152
|
+
|
|
153
|
+
#### Collectors
|
|
154
|
+
|
|
155
|
+
| Value | Purpose |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `docs_context` | Collects docs-related context and diff artifacts. |
|
|
158
|
+
| `beads_status_context` | Collects branch/beads alignment context. |
|
|
159
|
+
| `pr_context` | Collects PR composition context. |
|
|
160
|
+
|
|
161
|
+
#### LLM schemas
|
|
162
|
+
|
|
163
|
+
| Value | Expected payload |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| `string_array` | JSON array of strings. |
|
|
166
|
+
| `docs_issue_array` | JSON array of issue objects with at least `file` and `description`. |
|
|
167
|
+
| `beads_alignment_result` | JSON object, optionally with `commands` string array. |
|
|
168
|
+
| `pr_create_payload` | JSON object for PR creation fields. |
|
|
169
|
+
|
|
170
|
+
#### Exec handlers
|
|
171
|
+
|
|
172
|
+
| Value | Purpose |
|
|
173
|
+
| --- | --- |
|
|
174
|
+
| `beads_alignment` | Runs non-interactive Beads commands and writes action report when needed. |
|
|
175
|
+
| `gh_pr_create` | Creates (or reuses) a GitHub PR via `gh`. |
|
|
176
|
+
|
|
177
|
+
#### Assertion handlers
|
|
178
|
+
|
|
179
|
+
| Value | Purpose |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| `docs_apply_requires_manual_commit` | Fails when docs were auto-edited and still need user review/commit. |
|
|
182
|
+
| `beads_alignment_clean` | Fails when Beads alignment reports unresolved work. |
|
|
183
|
+
|
|
184
|
+
#### Built-in fallback prompt IDs
|
|
185
|
+
|
|
186
|
+
| Value | Purpose |
|
|
187
|
+
| --- | --- |
|
|
188
|
+
| `docs-query-basic` | Generate doc search queries from diff. |
|
|
189
|
+
| `docs-analysis-basic` | Identify factual documentation drift. |
|
|
190
|
+
| `docs-apply-basic` | Apply minimal doc fixes within allowlist. |
|
|
191
|
+
| `beads-plan-basic` | Build Beads alignment command/report payload. |
|
|
192
|
+
| `pr-compose-basic` | Draft PR title/body/base/head payload. |
|
|
193
|
+
|
|
194
|
+
## Environment variable overrides
|
|
195
|
+
|
|
196
|
+
Boolean env parsing accepts: `1`, `true`, `yes`, `y`, `on` and `0`, `false`, `no`, `n`, `off`.
|
|
197
|
+
|
|
198
|
+
| Env var | Effect |
|
|
199
|
+
| --- | --- |
|
|
200
|
+
| `AI_PUSH_HOOKS_SKIP` | If true, sets `general.enabled = false`. |
|
|
201
|
+
| `AI_PUSH_HOOKS_ALLOW_PUSH_ON_ERROR` | Overrides `general.allow_push_on_error`. |
|
|
202
|
+
| `AI_PUSH_HOOKS_REQUIRE_CLEAN` | Overrides `general.require_clean_worktree`. |
|
|
203
|
+
| `AI_PUSH_HOOKS_ALLOW_DIRTY` | If true, forces `general.require_clean_worktree = false`. |
|
|
204
|
+
| `AI_PUSH_HOOKS_LOG_LEVEL` | Overrides `logging.level`. |
|
|
205
|
+
| `AI_PUSH_HOOKS_PRINT_LLM_OUTPUT` | Overrides `logging.print_llm_output`. |
|
|
206
|
+
| `AI_PUSH_HOOKS_MODEL` | Overrides `llm.model`. |
|
|
207
|
+
| `AI_PUSH_HOOKS_VARIANT` | Overrides `llm.variant`. |
|
|
208
|
+
| `AI_PUSH_HOOKS_TIMEOUT_SECONDS` | Overrides `llm.timeout_seconds` (integer). |
|
|
209
|
+
|
|
210
|
+
`when_env` is step-level and can point to any env var. A common example is `AI_PUSH_HOOKS_CREATE_PR` to gate PR creation steps.
|
|
211
|
+
|
|
212
|
+
## Example: docs + PR with opt-in creation
|
|
78
213
|
|
|
79
214
|
```toml
|
|
80
215
|
[workflow]
|
|
81
|
-
modules = ["docs"]
|
|
216
|
+
modules = ["docs", "pr"]
|
|
82
217
|
|
|
83
218
|
[modules.docs]
|
|
84
219
|
enabled = true
|
|
@@ -91,82 +226,6 @@ collector = "docs_context"
|
|
|
91
226
|
[[modules.docs.steps]]
|
|
92
227
|
id = "query"
|
|
93
228
|
type = "llm"
|
|
94
|
-
prompt = "Return a JSON array of documentation search queries. JSON only."
|
|
95
|
-
inputs = ["collect/push.diff", "collect/changed-files.txt"]
|
|
96
|
-
output = "queries.json"
|
|
97
|
-
schema = "string_array"
|
|
98
|
-
|
|
99
|
-
[[modules.docs.steps]]
|
|
100
|
-
id = "analyze"
|
|
101
|
-
type = "llm"
|
|
102
|
-
prompt = "Return JSON issues only for factual documentation drift."
|
|
103
|
-
inputs = ["collect/push.diff", "collect/docs-context.txt", "query/queries.json", "collect/recent-commits.txt"]
|
|
104
|
-
output = "issues.json"
|
|
105
|
-
schema = "docs_issue_array"
|
|
106
|
-
|
|
107
|
-
[[modules.docs.steps]]
|
|
108
|
-
id = "apply"
|
|
109
|
-
type = "apply"
|
|
110
|
-
prompt = "Apply the minimum Markdown fixes required."
|
|
111
|
-
inputs = ["collect/push.diff", "collect/docs-context.txt", "analyze/issues.json"]
|
|
112
|
-
allow_paths = ["README.md", "docs/**/*.md"]
|
|
113
|
-
|
|
114
|
-
[[modules.docs.steps]]
|
|
115
|
-
id = "assert"
|
|
116
|
-
type = "assert"
|
|
117
|
-
assertion = "docs_apply_requires_manual_commit"
|
|
118
|
-
inputs = ["apply/result.json"]
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Example config that recreates the current docs + beads + PR behavior through configuration only:
|
|
122
|
-
|
|
123
|
-
The sample below is runnable as-is because each `prompt_file` step also declares a built-in `fallback_prompt_id`. If you add local prompt files, they override the built-ins.
|
|
124
|
-
|
|
125
|
-
```toml
|
|
126
|
-
[workflow]
|
|
127
|
-
modules = ["beads", "docs", "pr"]
|
|
128
|
-
|
|
129
|
-
[modules.beads]
|
|
130
|
-
enabled = true
|
|
131
|
-
|
|
132
|
-
[[modules.beads.steps]]
|
|
133
|
-
id = "collect"
|
|
134
|
-
type = "collect"
|
|
135
|
-
collector = "beads_status_context"
|
|
136
|
-
|
|
137
|
-
[[modules.beads.steps]]
|
|
138
|
-
id = "plan"
|
|
139
|
-
type = "llm"
|
|
140
|
-
prompt_file = ".ai-push-hooks.prompts/beads-status.txt"
|
|
141
|
-
fallback_prompt_id = "beads-plan-basic"
|
|
142
|
-
inputs = ["collect/branch-context.txt", "collect/changed-files.txt", "collect/push.diff", "collect/commits.txt"]
|
|
143
|
-
output = "beads-plan.json"
|
|
144
|
-
schema = "beads_alignment_result"
|
|
145
|
-
|
|
146
|
-
[[modules.beads.steps]]
|
|
147
|
-
id = "apply"
|
|
148
|
-
type = "exec"
|
|
149
|
-
executor = "beads_alignment"
|
|
150
|
-
inputs = ["plan/beads-plan.json"]
|
|
151
|
-
|
|
152
|
-
[[modules.beads.steps]]
|
|
153
|
-
id = "assert"
|
|
154
|
-
type = "assert"
|
|
155
|
-
assertion = "beads_alignment_clean"
|
|
156
|
-
inputs = ["plan/beads-plan.json"]
|
|
157
|
-
|
|
158
|
-
[modules.docs]
|
|
159
|
-
enabled = true
|
|
160
|
-
|
|
161
|
-
[[modules.docs.steps]]
|
|
162
|
-
id = "collect"
|
|
163
|
-
type = "collect"
|
|
164
|
-
collector = "docs_context"
|
|
165
|
-
|
|
166
|
-
[[modules.docs.steps]]
|
|
167
|
-
id = "query"
|
|
168
|
-
type = "llm"
|
|
169
|
-
prompt_file = ".ai-push-hooks.prompts/query.txt"
|
|
170
229
|
fallback_prompt_id = "docs-query-basic"
|
|
171
230
|
inputs = ["collect/push.diff", "collect/changed-files.txt"]
|
|
172
231
|
output = "queries.json"
|
|
@@ -175,7 +234,6 @@ schema = "string_array"
|
|
|
175
234
|
[[modules.docs.steps]]
|
|
176
235
|
id = "analyze"
|
|
177
236
|
type = "llm"
|
|
178
|
-
prompt_file = ".ai-push-hooks.prompts/analysis.txt"
|
|
179
237
|
fallback_prompt_id = "docs-analysis-basic"
|
|
180
238
|
inputs = ["collect/push.diff", "collect/docs-context.txt", "query/queries.json", "collect/recent-commits.txt"]
|
|
181
239
|
output = "issues.json"
|
|
@@ -184,7 +242,6 @@ schema = "docs_issue_array"
|
|
|
184
242
|
[[modules.docs.steps]]
|
|
185
243
|
id = "apply"
|
|
186
244
|
type = "apply"
|
|
187
|
-
prompt_file = ".ai-push-hooks.prompts/apply.txt"
|
|
188
245
|
fallback_prompt_id = "docs-apply-basic"
|
|
189
246
|
inputs = ["collect/push.diff", "collect/docs-context.txt", "analyze/issues.json"]
|
|
190
247
|
allow_paths = ["README.md", "docs/**/*.md"]
|
|
@@ -206,7 +263,6 @@ collector = "pr_context"
|
|
|
206
263
|
[[modules.pr.steps]]
|
|
207
264
|
id = "compose"
|
|
208
265
|
type = "llm"
|
|
209
|
-
prompt_file = ".ai-push-hooks.prompts/create-pr.txt"
|
|
210
266
|
fallback_prompt_id = "pr-compose-basic"
|
|
211
267
|
inputs = ["collect/pr-context.txt", "collect/changed-files.txt", "collect/push.diff", "collect/commits.txt"]
|
|
212
268
|
output = "pr-draft.json"
|
|
@@ -219,16 +275,3 @@ executor = "gh_pr_create"
|
|
|
219
275
|
when_env = "AI_PUSH_HOOKS_CREATE_PR"
|
|
220
276
|
inputs = ["compose/pr-draft.json"]
|
|
221
277
|
```
|
|
222
|
-
|
|
223
|
-
## Layout
|
|
224
|
-
|
|
225
|
-
- `src/ai_push_hooks/cli.py` - CLI entrypoint
|
|
226
|
-
- `src/ai_push_hooks/config.py` - config loading and validation
|
|
227
|
-
- `src/ai_push_hooks/engine.py` - scheduler and workflow runtime
|
|
228
|
-
- `src/ai_push_hooks/artifacts.py` - run-directory artifact store
|
|
229
|
-
- `src/ai_push_hooks/prompts_builtin.py` - built-in fallback prompts
|
|
230
|
-
- `src/ai_push_hooks/modules/` - docs, beads, and PR collectors
|
|
231
|
-
- `src/ai_push_hooks/executors/` - LLM, apply, exec, and assertion handlers
|
|
232
|
-
- `run.sh` - source checkout wrapper
|
|
233
|
-
- `bin/ai-push-hooks.js` - npm bin wrapper
|
|
234
|
-
- `.ai-push-hooks.toml` - sample config
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -249,6 +249,7 @@ def run_llm_step(
|
|
|
249
249
|
last_error = ""
|
|
250
250
|
last_output = ""
|
|
251
251
|
wants_json = bool(step.schema)
|
|
252
|
+
expects_json_array = step.schema in {"string_array", "docs_issue_array"}
|
|
252
253
|
for attempt in range(1, total_attempts + 1):
|
|
253
254
|
result = call_opencode(
|
|
254
255
|
context,
|
|
@@ -269,7 +270,7 @@ def run_llm_step(
|
|
|
269
270
|
if not wants_json:
|
|
270
271
|
finalize_opencode_session(context, stage_name, session_id)
|
|
271
272
|
return result.output_text
|
|
272
|
-
if
|
|
273
|
+
if expects_json_array:
|
|
273
274
|
payload = extract_json_array(result.output_text)
|
|
274
275
|
else:
|
|
275
276
|
payload = extract_json_object(result.output_text)
|
|
@@ -281,7 +282,7 @@ def run_llm_step(
|
|
|
281
282
|
if attempt >= total_attempts:
|
|
282
283
|
break
|
|
283
284
|
snippet = last_output[: context.config.llm.invalid_json_feedback_max_chars]
|
|
284
|
-
if
|
|
285
|
+
if expects_json_array:
|
|
285
286
|
suffix = "Return ONLY valid JSON array."
|
|
286
287
|
else:
|
|
287
288
|
suffix = "Return ONLY valid JSON object."
|