@gr8ful/spf 0.4.0 → 0.5.1
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 +122 -4
- package/assets/defaults/spf.config.yaml +6 -0
- package/assets/prompts/reviewer/system.md +1 -1
- package/assets/skill/SKILL.md +1 -0
- package/assets/skill/cookbooks/authoring_chains.md +90 -7
- package/assets/skill/cookbooks/ocr_reviewer.md +196 -0
- package/assets/skill/cookbooks/roster.md +15 -4
- package/assets/skill/cookbooks/spf_overview.md +1 -0
- package/assets/skill/references/config.md +69 -4
- package/assets/skill/references/observability.md +11 -2
- package/assets/templates/ts-flue-ollama.spf.config.yaml +67 -0
- package/assets/templates/ts.spf.config.yaml +5 -0
- package/dist/chains/context.d.ts +30 -0
- package/dist/chains/index.d.ts +94 -10
- package/dist/chains/index.js +70 -5
- package/dist/chains/repo_chains.d.ts +139 -0
- package/dist/chains/repo_chains.js +428 -0
- package/dist/chains/simple_sdlc.d.ts +74 -1
- package/dist/chains/simple_sdlc.js +134 -4
- package/dist/chains/steps.d.ts +215 -20
- package/dist/chains/steps.js +429 -61
- package/dist/cli/ask.d.ts +14 -1
- package/dist/cli/ask.js +32 -2
- package/dist/cli/commands/doctor.d.ts +1 -1
- package/dist/cli/commands/doctor.js +319 -11
- package/dist/cli/commands/init.d.ts +12 -0
- package/dist/cli/commands/init.js +78 -1
- package/dist/cli/commands/list.js +42 -5
- package/dist/cli/commands/run.js +25 -2
- package/dist/cli/commands/watch.d.ts +18 -0
- package/dist/cli/commands/watch.js +158 -10
- package/dist/cli/index.js +60 -3
- package/dist/cli/interview.js +65 -10
- package/dist/core/agent_cc.d.ts +40 -1
- package/dist/core/agent_cc.js +51 -4
- package/dist/core/agent_flue.js +28 -4
- package/dist/core/agents.d.ts +8 -0
- package/dist/core/agents.js +43 -3
- package/dist/core/data_types.d.ts +104 -4
- package/dist/core/data_types.js +99 -2
- package/dist/core/git_helper.d.ts +29 -0
- package/dist/core/git_helper.js +41 -1
- package/dist/core/ollama_provider.d.ts +70 -0
- package/dist/core/ollama_provider.js +208 -0
- package/dist/core/otel.d.ts +352 -0
- package/dist/core/otel.js +793 -0
- package/dist/core/paths.d.ts +3 -0
- package/dist/core/paths.js +48 -1
- package/dist/core/providers.js +4 -0
- package/dist/core/refine.js +11 -3
- package/dist/core/session.js +39 -2
- package/dist/core/tracer.d.ts +31 -2
- package/dist/core/tracer.js +69 -11
- package/dist/core/watch.d.ts +11 -0
- package/dist/core/watch.js +17 -2
- package/dist/test/chains.test.js +8 -3
- package/dist/test/data_types.test.js +140 -2
- package/dist/test/git_helper.test.d.ts +1 -0
- package/dist/test/git_helper.test.js +59 -0
- package/dist/test/hermetic_git.d.ts +1 -0
- package/dist/test/hermetic_git.js +22 -0
- package/dist/test/init_command.test.d.ts +14 -1
- package/dist/test/init_command.test.js +54 -1
- package/dist/test/interview.test.d.ts +15 -1
- package/dist/test/interview.test.js +127 -0
- package/dist/test/ollama_provider.test.d.ts +1 -0
- package/dist/test/ollama_provider.test.js +103 -0
- package/dist/test/otel.test.d.ts +26 -0
- package/dist/test/otel.test.js +512 -0
- package/dist/test/paths.test.d.ts +1 -0
- package/dist/test/paths.test.js +68 -0
- package/dist/test/refine.test.js +64 -1
- package/dist/test/repo_chains.test.d.ts +21 -0
- package/dist/test/repo_chains.test.js +416 -0
- package/dist/test/signoff.test.d.ts +1 -0
- package/dist/test/signoff.test.js +329 -0
- package/dist/test/ui_server.test.d.ts +7 -1
- package/dist/test/ui_server.test.js +1 -0
- package/dist/test/watch.test.js +124 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -120,7 +120,7 @@ Config defines who an agent **is**. The chain call site defines how it is **used
|
|
|
120
120
|
|
|
121
121
|
### A second backend: Claude Code
|
|
122
122
|
|
|
123
|
-
Set `coding_agent: claude_code` on any agent (or in `defaults`) to run it on your own installed [Claude Code](https://claude.com/product/claude-code) CLI instead of Flue — `spf doctor` checks
|
|
123
|
+
Set `coding_agent: claude_code` on any agent (or in `defaults`) to run it on your own installed [Claude Code](https://claude.com/product/claude-code) CLI instead of Flue — `spf doctor` checks whatever `SPF_CLAUDE_CMD`'s first token resolves to on `PATH` (`claude` itself, by default). Model names follow Claude Code's own vocabulary (a bare alias like `sonnet`, not `provider/model-id`); everything else — `tools`, `writes`, `thinking` — stays the same shape. Pointing a `claude_code` agent at a local or cloud [Ollama](https://ollama.com) server needs no config at all — just `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` set before you run `spf`, since Claude Code's CLI reads those itself.
|
|
124
124
|
|
|
125
125
|
#### Proxy or wrapper launchers
|
|
126
126
|
|
|
@@ -128,17 +128,52 @@ To route the `claude` command through a wrapper, proxy server, or launcher (e.g.
|
|
|
128
128
|
|
|
129
129
|
```bash
|
|
130
130
|
# Route through Ollama's launcher
|
|
131
|
-
export SPF_CLAUDE_CMD="ollama launch claude"
|
|
131
|
+
export SPF_CLAUDE_CMD="ollama launch claude --model granite4.1:8b"
|
|
132
132
|
spf build "your prompt"
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
+
`ollama launch <cmd>` uses cobra flag parsing, which treats anything typed after it as its own flags unless a literal `--` says otherwise — without one, `claude`'s own flags (`-p`, `--json-schema`, ...) fail with `unknown shorthand flag: 'p' in -p` before `claude` is ever reached. `agent_cc.ts` detects exactly this `ollama launch ...` shape and inserts that `--` automatically, so you never add the separator by hand for this specific launcher.
|
|
136
|
+
|
|
137
|
+
That `--` alone is not enough to reach `claude`, though: `ollama launch` also needs its OWN `--model <tag>` flag (a tag from `ollama list`), typed BEFORE the auto-inserted `--`, whenever it runs headless — which it always does under SPF, since SPF spawns with piped stdio. Without it, `ollama launch` falls back to an interactive model picker that can never run, and fails one step later than the `--` problem, with `model selection requires an interactive terminal; use --model to run in headless mode`. A `--model` typed after the `--` doesn't help — at that point it belongs to `claude`, not to `ollama launch`. So `SPF_CLAUDE_CMD` must include `ollama launch`'s `--model` yourself, exactly as written above; `spf doctor` hard-fails if it's missing.
|
|
138
|
+
|
|
135
139
|
```bash
|
|
136
140
|
# Or use a custom wrapper script
|
|
137
141
|
export SPF_CLAUDE_CMD=/path/to/my-wrapper
|
|
138
142
|
spf build "your prompt"
|
|
139
143
|
```
|
|
140
144
|
|
|
141
|
-
The command/launcher must support the full Claude Code CLI interface: `-p` for prompt, `--json-schema`, `--model`, `--session-id`/`--resume`, `--output-format stream-json`, and all other flags `agent_cc` uses. When unset, `SPF_CLAUDE_CMD` defaults to `claude` (resolved from `PATH` normally).
|
|
145
|
+
The command/launcher must support the full Claude Code CLI interface: `-p` for prompt, `--json-schema`, `--model`, `--session-id`/`--resume`, `--output-format stream-json`, and all other flags `agent_cc` uses. When unset, `SPF_CLAUDE_CMD` defaults to `claude` (resolved from `PATH` normally). A cmdSpec that already contains its own literal `--` is left completely alone — `agent_cc.ts` never inserts a second one.
|
|
146
|
+
|
|
147
|
+
### flue + local Ollama
|
|
148
|
+
|
|
149
|
+
Point the default `flue` backend at a local Ollama server the same way you'd pick any other Flue provider — the model string's own prefix, `ollama/<tag>` (whatever `ollama list` shows on your machine) instead of `openai/...`/`anthropic/...`:
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
defaults:
|
|
153
|
+
coding_agent: flue
|
|
154
|
+
model: ollama/qwen3.8:27b-mlx
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
export OLLAMA_BASE_URL=http://localhost:11434/v1 # default if unset
|
|
159
|
+
spf build "your prompt"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
That's the whole config change. `ollama` is a keyless provider — `spf doctor` and the `spf init` interview both know this: no API key is ever asked for or checked, and interview.ts asks for `OLLAMA_BASE_URL` instead when you pick `ollama` as your provider. A ready-to-run starting point ships at [`assets/templates/ts-flue-ollama.spf.config.yaml`](assets/templates/ts-flue-ollama.spf.config.yaml) (`spf init --template ts-flue-ollama`).
|
|
163
|
+
|
|
164
|
+
Two things worth knowing before pointing a full roster at local models: tool-calling — the injected `sf_report` contract every agent's structured output rides on — worked reliably in testing down to a 3B-parameter model, which isn't a "only frontier models get tools" situation. And context-window occupancy reporting is disabled for `ollama/*` models specifically, which turns off threshold-based compaction rather than reporting a number Ollama's OpenAI-compatible API doesn't actually provide per model.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Isolation: post-hoc, not a sandbox
|
|
169
|
+
|
|
170
|
+
Agent isolation in SPF is enforced **after the fact**, not upfront. This matters because the `tools:` capability list is not a sandbox — a `bash` tool can run `git checkout` to discard changes, and a `write` tool can reach any path, regardless of the allowlist you write. So permission is verified the way every other claim in this system is: the working tree is fingerprinted before an agent runs, then compared after. If the agent touched anything outside its `writes:` allowlist (or a `protected_files:` path it wasn't given access to), the phase fails and anything the agent *introduced* outside its allowlist is rolled back. What it cannot undo, it names: a file that was already dirty before the agent ran is left alone rather than discarded, and uncommitted work the agent reverted cannot be restored. And because the check is post-hoc and tree-scoped, nothing outside the working tree is in scope at all — a push, a network call, a write above `repo_root`. When running an agent on the claude_code backend, SPF spawns it with `--permission-mode bypassPermissions --dangerously-skip-permissions` because the backend has no need to enforce — SPF will enforce in code instead, within those bounds.
|
|
171
|
+
|
|
172
|
+
For unattended work, `spf watch`'s PR-merge is the only human accountability checkpoint: the daemon opens a PR for each automated run, and a human approves the merge. For any other workflow, this enforcement is the backbone of letting a bounded agent proposal survive code's inspection without a human having to read it in between — but it is bounded, not a sandbox, and the scope above is what a reviewer should actually rely on. (Attended `simple-sdlc` runs get a second checkpoint of their own — see below.)
|
|
173
|
+
|
|
174
|
+
See `core/permissions.ts` for the implementation — the snapshot/compare logic, not the config terms.
|
|
175
|
+
|
|
176
|
+
`simple-sdlc`'s `commit_build` phase is the one place in this codebase where an AI reviewer's `approved` flag alone would otherwise gate a commit — everywhere else the reviewer either doesn't run (`plan-build-test`, ...) or nothing commits on its verdict (`build-review` has no commit step). Run attended, that phase asks before committing — default **no**, never `review.approved` itself, bounded by `review.signoff_timeout_seconds` (default 300s; expiry answers no) — and only an explicit, recorded "yes" ever earns a `Signed-off-by:` trailer, built from `git config user.name`/`user.email` at the repo, never an env var. Run unattended (`spf watch`, CI), there is nobody to ask: `spf watch`'s own human gate is the PR merge above, informed by the reviewer's digest in the PR body, and the phase either proceeds on the AI verdict alone with a loud warning (`review.require_human_signoff: false`, this release's default — fail-open until `spf watch` itself becomes signoff-aware) or fails closed and asks you to rerun attended (`require_human_signoff: true`). See `assets/skill/references/config.md`'s `review` section for the two knobs, and [`assets/skill/cookbooks/ocr_reviewer.md`](assets/skill/cookbooks/ocr_reviewer.md) if you want the reviewer to fold a third-party review tool's findings into its own verdict as one more piece of evidence.
|
|
142
177
|
|
|
143
178
|
---
|
|
144
179
|
|
|
@@ -214,7 +249,7 @@ Files stay the raw record (`envelope.json`, `agent_map.json`, Flue's own convers
|
|
|
214
249
|
|
|
215
250
|
```bash
|
|
216
251
|
spf list # every chain, its phases, what it needs
|
|
217
|
-
spf <chain> "<prompt or path/to/prompt.md>" [--config <path>] [--adw-id a1b2c3d4] [--cwd <dir>]
|
|
252
|
+
spf <chain> "<prompt or path/to/prompt.md>" [--config <path>] [--adw-id a1b2c3d4] [--cwd <dir>] [--suite <name>]
|
|
218
253
|
```
|
|
219
254
|
|
|
220
255
|
| Chain | Phases | Reach for it when |
|
|
@@ -232,6 +267,8 @@ spf <chain> "<prompt or path/to/prompt.md>" [--config <path>] [--adw-id a1b2c3d4
|
|
|
232
267
|
| `document` | code(git diff), documenter | write up what just shipped |
|
|
233
268
|
| `simple-sdlc` | plan, build, test, review, document | the work is real and its shape is not obvious |
|
|
234
269
|
|
|
270
|
+
A chain with a compiled-in quality suite (`build-test`, `plan-build-test`, `plan-build-test-quality`, `quality`) accepts `--suite <name>` to override it for one run — `spf list` marks which chains this applies to with `(--suite overrides)`. `simple-sdlc`'s suite is not overridable this way; passing `--suite` to it is rejected rather than silently ignored.
|
|
271
|
+
|
|
235
272
|
`--adw-id` is optional everywhere. Omit it and a fresh id is minted and printed. Supply it and the run joins that session — same session directory, each agent **resumes its existing context window** instead of starting cold. That's how you chain runs:
|
|
236
273
|
|
|
237
274
|
```bash
|
|
@@ -239,6 +276,36 @@ spf plan "add a /health endpoint" # prints adw_id a1b2c3d
|
|
|
239
276
|
spf build-test "implement the plan" --adw-id a1b2c3d4
|
|
240
277
|
```
|
|
241
278
|
|
|
279
|
+
### Repo-local chains
|
|
280
|
+
|
|
281
|
+
Every chain above is built in. A target repo can also compose its own out of
|
|
282
|
+
the same step factories, with zero forking and zero build step, by dropping a
|
|
283
|
+
file in `.spf/chains/*.yaml` — `spf init` scaffolds `.spf/chains/example.yaml`
|
|
284
|
+
(fully commented out) showing the shape:
|
|
285
|
+
|
|
286
|
+
```yaml
|
|
287
|
+
# .spf/chains/ship-it.yaml — spf ship-it "<prompt>" / spf run ship-it "<prompt>"
|
|
288
|
+
name: ship-it
|
|
289
|
+
describe: plan, build, test, land — with our own reviewer in the loop
|
|
290
|
+
steps:
|
|
291
|
+
- step: request
|
|
292
|
+
- step: plan
|
|
293
|
+
owner: architect
|
|
294
|
+
- step: build
|
|
295
|
+
extraGates: [jsonParses] # additive only — a step's built-in gates can never be removed
|
|
296
|
+
- step: fixLoop
|
|
297
|
+
suite: test
|
|
298
|
+
- step: commit
|
|
299
|
+
onlyIfAccepted: true
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
It shows up in `spf list`/`spf doctor` exactly like a built-in, and runs on
|
|
303
|
+
the identical `stepChain()` driver — there is no second interpreter. The full
|
|
304
|
+
step vocabulary, the gate rules, and the `spf watch` main-anchor divergence
|
|
305
|
+
(a chain edited on an issue branch is *not* what an in-flight `spf watch` run
|
|
306
|
+
uses) are documented in the installed skill's "Repo-local chains" section
|
|
307
|
+
(`.claude/skills/spf/cookbooks/authoring_chains.md`, after `spf init`).
|
|
308
|
+
|
|
242
309
|
---
|
|
243
310
|
|
|
244
311
|
## `spf watch`
|
|
@@ -278,6 +345,29 @@ watch:
|
|
|
278
345
|
project_key: PROJ
|
|
279
346
|
```
|
|
280
347
|
|
|
348
|
+
```yaml
|
|
349
|
+
# .spf/spf.config.yaml — GitHub issues + Bitbucket PRs, in TWO DIFFERENT repos
|
|
350
|
+
watch:
|
|
351
|
+
issue_provider: github
|
|
352
|
+
code_host: bitbucket
|
|
353
|
+
repo: workspace/repo_slug # Bitbucket's repo — the CODE HOST always reads plain `repo`
|
|
354
|
+
issue_repo: owner/name # GitHub's repo — only needed here, where issue tracker != code host
|
|
355
|
+
label_prefix: spf # polls GitHub issues labeled spf:ready
|
|
356
|
+
chain: plan-build-test
|
|
357
|
+
base_branch: main
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
`repo` always names the code host's own repo; `issue_repo` overrides it for
|
|
361
|
+
the issue-tracker side. This only matters for `issue_provider: github` +
|
|
362
|
+
`code_host: bitbucket` — the one combination where they're genuinely
|
|
363
|
+
different repos in different systems, not the same repo worn two ways
|
|
364
|
+
(`github`+`github` is one repo by construction; `issue_provider: jira` never
|
|
365
|
+
reads `repo` at all, so it's never ambiguous). Leave `issue_repo` unset
|
|
366
|
+
everywhere else. `spf doctor` flags this specific combination when
|
|
367
|
+
`issue_repo` is missing, since the silent failure mode — polling GitHub with
|
|
368
|
+
the Bitbucket identifier — finds no matching issues and looks exactly like
|
|
369
|
+
nothing being configured at all.
|
|
370
|
+
|
|
281
371
|
```bash
|
|
282
372
|
spf watch init # idempotently seed tracker state (no-op for Jira — see below); run this first
|
|
283
373
|
spf watch # foreground daemon; Ctrl-C drains in-flight claims first
|
|
@@ -422,6 +512,34 @@ genuinely informative, just worth expecting.
|
|
|
422
512
|
Full field reference: `spf install-skill`'s installed skill
|
|
423
513
|
(`references/config.md`).
|
|
424
514
|
|
|
515
|
+
## Observability
|
|
516
|
+
|
|
517
|
+
Every run produces a complete trace: all events, phases, agent calls, and tool invocations stream into SQLite as they happen. The local trace stays the source of truth — prompts, envelopes, tool arguments, and your source code never leave the machine. Token counts and costs ride alongside.
|
|
518
|
+
|
|
519
|
+
```bash
|
|
520
|
+
spf ui # browser-based visualizer over the trace
|
|
521
|
+
spf events <adw_id> --follow # live event stream, tailable
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
The default export is SQLite only (`.spf/data/spf.db`). Optionally, you can export **spans only** (phase/agent/tool timing and allowlisted metadata) to an OpenTelemetry collector for integration with a trace UI or observability platform:
|
|
525
|
+
|
|
526
|
+
```yaml
|
|
527
|
+
observability:
|
|
528
|
+
db: .spf/data/spf.db
|
|
529
|
+
poll_ms: 500
|
|
530
|
+
otel:
|
|
531
|
+
endpoint: https://your-otel-collector/v1/traces
|
|
532
|
+
service_name: spf # optional; default "spf"
|
|
533
|
+
headers: # optional; e.g. auth headers
|
|
534
|
+
Authorization: Bearer ...
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
OTEL export is **explicit config only** — an unrelated shell variable cannot become a data-egress switch. OTEL is strictly a spans-only export: each phase is a span with child spans for agent calls and tool calls, annotated with phase status, agent model, token/cost counts, and gate results. This export never blocks a run: if the collector is slow or unreachable, SPF continues normally and logs a single line per run when export fails (not one per batch), and reports the number of dropped spans on the final flush as `spf.otel.dropped_spans` for the backend to surface. The complete trace stays in SQLite regardless.
|
|
538
|
+
|
|
539
|
+
**Attribute allowlist**: only phase name/kind/owner/status, chain name, adw_id, agent name/model/coding_agent, gate name + passed + violation count, token counts and cost, and durations. Prompts, envelopes, tool arguments, and your source code never leave — that guarantee is enforced in code, not just in documentation.
|
|
540
|
+
|
|
541
|
+
See `assets/skill/references/config.md`'s `observability` section for the full field reference.
|
|
542
|
+
|
|
425
543
|
## What's in this repo
|
|
426
544
|
|
|
427
545
|
```
|
|
@@ -34,6 +34,12 @@ defaults:
|
|
|
34
34
|
observability:
|
|
35
35
|
db: .spf/data/spf.db # tracer writes here directly; the UI polls it
|
|
36
36
|
poll_ms: 500 # visualizer live-poll cadence
|
|
37
|
+
# Optional OpenTelemetry spans export (explicit config only; no ambient env activation)
|
|
38
|
+
# otel:
|
|
39
|
+
# endpoint: https://your-otel-collector/v1/traces
|
|
40
|
+
# service_name: spf # optional; default "spf"
|
|
41
|
+
# headers: # optional; e.g. auth headers
|
|
42
|
+
# Authorization: Bearer ...
|
|
37
43
|
|
|
38
44
|
# No default quality checks or suites are shipped — an unconfigured chain
|
|
39
45
|
# that needs one fails loudly at agents.validate() time (before anything
|
|
@@ -13,4 +13,4 @@ Confirm that what was built is what was asked for. This is not testing.
|
|
|
13
13
|
- Change nothing. Findings go back to the builder — that is the only repair path.
|
|
14
14
|
- `approved` is true ONLY when every requirement is met and `blocking` is empty. Every blocking item names the specific gap, so the builder can fix it without guessing.
|
|
15
15
|
- You inherit the operator's shell environment — their PATH, toolchains and credentials are already live. Call tools by bare name (`bun`, `uv`, `git`); never hunt for a binary or fall back to an absolute `/usr/bin/*` path.
|
|
16
|
-
- Judge any command you run by its exit status, never by scanning its output for words. `error` or `not found` inside passing output is text, not a failure.
|
|
16
|
+
- Judge any command you run by its exit status, never by scanning its output for words — except tools whose output is the product, such as `git diff`, or a review tool you were asked to consult, which you read as evidence, never as a verdict. `error` or `not found` inside passing output is text, not a failure.
|
package/assets/skill/SKILL.md
CHANGED
|
@@ -45,6 +45,7 @@ phases`/`spf events` exist so nobody has to guess the schema again).
|
|
|
45
45
|
| "add/retune an agent or model" | `roster.md` |
|
|
46
46
|
| "add a chain / a phase / an output type / a gate" | `authoring_chains.md` |
|
|
47
47
|
| "what is SPF" | `spf_overview.md` |
|
|
48
|
+
| "fold a third-party review tool's findings into the reviewer" | `cookbooks/ocr_reviewer.md` |
|
|
48
49
|
| envelope/gate/session contract | `references/handoff.md` |
|
|
49
50
|
| trace schema, spend vs. context | `references/observability.md` |
|
|
50
51
|
| config field reference | `references/config.md` |
|
|
@@ -1,13 +1,86 @@
|
|
|
1
1
|
# Authoring Chains
|
|
2
2
|
|
|
3
3
|
Composing a new chain, extending an existing one, and adding the engine
|
|
4
|
-
primitives a chain needs (an output type, a gate, a step)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
primitives a chain needs (an output type, a gate, a step) is one skill with
|
|
5
|
+
four doors. Engine primitives (a new gate, a new envelope type, a modified
|
|
6
|
+
phase primitive) live in `src/` inside the SPF package itself — those do
|
|
7
|
+
require forking the package, because they change what every repo's chains
|
|
8
|
+
can mean. But COMPOSING a chain out of the existing step factories does not:
|
|
9
|
+
that's what a repo-local chain (below) is for. Read that section first if
|
|
10
|
+
you just want your own chain in your own repo — reach for the rest of this
|
|
11
|
+
document only once you've decided you need a new primitive.
|
|
12
|
+
|
|
13
|
+
## Repo-local chains (`.spf/chains/*.yaml`)
|
|
14
|
+
|
|
15
|
+
The zero-fork door: a `.spf/chains/*.yaml` file in a target repo names
|
|
16
|
+
existing step factories from `spf`'s own `src/chains/steps.ts` and passes
|
|
17
|
+
them params — never imports or runs code from the target repo. `spf init`
|
|
18
|
+
scaffolds `.spf/chains/example.yaml`, fully commented out, showing the shape.
|
|
19
|
+
|
|
20
|
+
```yaml
|
|
21
|
+
# .spf/chains/ship-it.yaml — spf ship-it "<prompt>" / spf run ship-it "<prompt>"
|
|
22
|
+
name: ship-it
|
|
23
|
+
describe: plan, build, test, land — with our own reviewer in the loop
|
|
24
|
+
steps:
|
|
25
|
+
- step: request # every chain opens with this
|
|
26
|
+
- step: plan
|
|
27
|
+
owner: architect # any agent named in spf.config.yaml
|
|
28
|
+
- step: build
|
|
29
|
+
retries: 2
|
|
30
|
+
extraGates: [jsonParses] # additive — see below
|
|
31
|
+
- step: fixLoop
|
|
32
|
+
suite: test
|
|
33
|
+
- step: commit
|
|
34
|
+
onlyIfAccepted: true
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Shape.** One file, one chain — the filename is a handle a problem can point
|
|
38
|
+
at. `steps` is a flat list; each entry names a `step:` and its params sit as
|
|
39
|
+
FLAT SIBLINGS of `step:` — never nested under a `params:` key. Param names are
|
|
40
|
+
exactly the step factory's `opts` keys, camelCase (`extraGates`, `fixExtraGates`,
|
|
41
|
+
`onlyIfAccepted`, ...). An unknown param, an unknown step, or a param of the
|
|
42
|
+
wrong type is a load-time problem naming the step index and what's allowed —
|
|
43
|
+
never a silently-ignored typo and never a runtime surprise.
|
|
44
|
+
|
|
45
|
+
**Step vocabulary** (`repo_chains.STEP_NAMES`) — the same factories the table
|
|
46
|
+
in Step 2 below documents: `request`, `plan`, `build`, `scout`, `promptOnly`,
|
|
47
|
+
`qualityCheck`, `fixLoop`, `reviseLoop`, `commit`, `changes`, `document`,
|
|
48
|
+
`refine`, `publishIssues`.
|
|
49
|
+
|
|
50
|
+
**Gates are additive only, and per-step.** A step's built-in gates
|
|
51
|
+
(`diffMatchesClaims` on `build`, `verdictConsistent` on `reviseLoop`'s review
|
|
52
|
+
phase, ...) are non-removable — there is no `gates:` param that replaces
|
|
53
|
+
them, and there must never be one (see `GATE_ALLOWLIST`'s comment in
|
|
54
|
+
`steps.ts` for why: the first thing anyone deletes under deadline pressure is
|
|
55
|
+
the gate that keeps failing). `extraGates` can only ADD from an explicit
|
|
56
|
+
allowlist SCOPED to what that param's envelope actually supports:
|
|
57
|
+
`artifactsExist`/`filesNonEmpty`/`jsonParses` anywhere (they only read
|
|
58
|
+
`envelope.artifacts`, present on every envelope); `diffMatchesClaims` only on
|
|
59
|
+
a param whose phase produces a `BuildOutput` (`build.extraGates`,
|
|
60
|
+
`fixLoop.fixExtraGates`, `reviseLoop.reviseExtraGates`); `verdictConsistent`
|
|
61
|
+
only on `reviseLoop.extraGates` (the review phase, a `ReviewOutput`). Naming
|
|
62
|
+
a gate outside its param's list is a load problem, not a chain that loads
|
|
63
|
+
clean and then fails (or vacuously passes) its gate on every run.
|
|
64
|
+
|
|
65
|
+
**Names are guarded.** A chain name must be lowercase letters/digits/`._-`
|
|
66
|
+
(it's typed on the command line). It cannot collide with an `spf` subcommand
|
|
67
|
+
(`run`, `list`, `init`, `watch`, ...) — `spf watch` would always run the
|
|
68
|
+
daemon, never a same-named chain. Two repo-chain files can't claim the same
|
|
69
|
+
`name` either: the trace's `chain_name` column has to keep meaning one thing.
|
|
70
|
+
|
|
71
|
+
**The `spf watch` divergence.** `spf watch` registers `.spf/chains/` from the
|
|
72
|
+
MAIN repo anchor ONCE, at daemon start — not per-issue, not per-worktree. A
|
|
73
|
+
chain file edited on an issue branch (inside the worktree `spf watch` checks
|
|
74
|
+
that branch out into) is NOT what runs for that issue; the daemon keeps using
|
|
75
|
+
whatever `.spf/chains/` looked like when it started. This is deliberate: the
|
|
76
|
+
disposer stays the OPERATOR's, never the branch's, which is exactly what
|
|
77
|
+
keeps an agent from rewriting its own quality gate mid-run by editing a chain
|
|
78
|
+
file as part of the change it's making.
|
|
79
|
+
|
|
80
|
+
Everything below this section is about the OTHER three doors: designing a
|
|
81
|
+
brand-new built-in chain, adding a step to the vocabulary above, or adding an
|
|
82
|
+
engine primitive (gate/envelope type/phase primitive) — none of which a
|
|
83
|
+
target repo needs, or can do, on its own.
|
|
11
84
|
|
|
12
85
|
## Step 1 — design the chain before writing code
|
|
13
86
|
|
|
@@ -24,6 +97,16 @@ the step list itself — see Step 2 — so it can't drift from what actually run
|
|
|
24
97
|
| test | code | quality | — | (suite pass/fail is the phase's own result) |
|
|
25
98
|
| commit | code | git | — | — |
|
|
26
99
|
|
|
100
|
+
If you need to change engine behavior for a specific target repo without forking
|
|
101
|
+
the package, that's `spf eject`. It copies the installed engine's compiled
|
|
102
|
+
artifacts (`dist/core/` and `dist/chains/`) out to `.spf/engine/` for reference
|
|
103
|
+
or reading — but understand that these files are **not wired into any spf
|
|
104
|
+
command**: editing them changes nothing about how spf itself runs. Engine-level
|
|
105
|
+
changes (a new gate, a new envelope type, a modified phase primitive) have no
|
|
106
|
+
config surface by design, because the codebase is a single shared engine all
|
|
107
|
+
repos trust to sequence their work the same way. If you are genuinely changing
|
|
108
|
+
the engine semantics, you are forking the package itself, not ejecting a copy.
|
|
109
|
+
|
|
27
110
|
**Ownership rules**, non-negotiable:
|
|
28
111
|
|
|
29
112
|
- **`engineer`** phases capture input only — no agent, no code decision.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Using `alibaba/open-code-review` as reviewer evidence
|
|
2
|
+
|
|
3
|
+
Opt-in, repo-level, docs-only: nothing in the packaged reviewer prompt
|
|
4
|
+
mentions `open-code-review` ("ocr"), and nothing ships that runs it. This
|
|
5
|
+
cookbook is for a repo that wants its *own* reviewer to shell out to ocr and
|
|
6
|
+
fold its findings in — a second opinion the reviewer reads as evidence, never
|
|
7
|
+
a second decision-maker.
|
|
8
|
+
|
|
9
|
+
**The rule this whole cookbook exists to protect: the reviewer agent stays
|
|
10
|
+
the sole producer of `ReviewOutput`.** `ocr review`/`ocr scan` is a bash
|
|
11
|
+
command whose stdout the reviewer reads, the same as `git diff` — nothing
|
|
12
|
+
new gates a commit, because the reviewer's own `approved`/`findings`/
|
|
13
|
+
`blocking` is still the only envelope anything downstream ever consumes. If
|
|
14
|
+
you want that packaged-prompt wording, see the one line
|
|
15
|
+
`assets/prompts/reviewer/system.md` already carries: "except tools whose
|
|
16
|
+
output is the product ... which you read as evidence, never as a verdict."
|
|
17
|
+
|
|
18
|
+
You wire this in with a repo-local prompt override, not a fork of `spf`
|
|
19
|
+
itself — `.spf/prompt_engineering/<agent>/` resolves before the packaged
|
|
20
|
+
default (see `core/paths.ts`'s `resolvePromptRef`: repo root, then
|
|
21
|
+
`.spf/<ref>`, then `.spf/prompt_engineering/<ref>`, then the packaged asset —
|
|
22
|
+
first hit wins). Copy `reviewer/system.md` into
|
|
23
|
+
`.spf/prompt_engineering/reviewer/system.md`, add the ocr instructions, and
|
|
24
|
+
every chain that names `reviewer` picks it up with no code change.
|
|
25
|
+
|
|
26
|
+
## (a) Credentials — set `env_allowlist` before you add ocr
|
|
27
|
+
|
|
28
|
+
The reviewer's `bash` tool does not run in a sandbox; it inherits whatever
|
|
29
|
+
environment this agent's request carries. Today, unless you configure
|
|
30
|
+
otherwise, that is the FULL operator environment — every key in the shell
|
|
31
|
+
`spf` was launched from, plus anything loaded from the repo's `.env`
|
|
32
|
+
(`AgentConfigSchema.env_allowlist`, `core/agents.ts`'s `agentEnv()`: unset
|
|
33
|
+
means "don't filter at all"). That is fine for a reviewer that only ever
|
|
34
|
+
runs `git diff`/`grep`/`bun test`. It stops being fine the moment `bash` can
|
|
35
|
+
also run `ocr`, because ocr is a THIRD-PARTY BINARY THAT INVOKES ITS OWN LLM
|
|
36
|
+
— it can send anything reachable in that environment (your `ANTHROPIC_API_KEY`,
|
|
37
|
+
`GITHUB_TOKEN`, cloud credentials, database URLs in `.env`) to wherever its
|
|
38
|
+
own model call goes, and nothing in `spf` observes that call to say otherwise.
|
|
39
|
+
|
|
40
|
+
Before pointing the reviewer's prompt at ocr, give that agent an explicit
|
|
41
|
+
allowlist so its `bash` tool — and therefore ocr — only ever sees what ocr
|
|
42
|
+
itself actually needs, plus the baseline `PATH`/`HOME`/`USER`/`LANG`/`TERM`/
|
|
43
|
+
`TMPDIR` every backend keeps regardless.
|
|
44
|
+
|
|
45
|
+
**What `env_allowlist` actually scopes depends on the reviewer's backend.**
|
|
46
|
+
On `coding_agent: claude_code`, it is the environment of the WHOLE `claude`
|
|
47
|
+
subprocess, not just its `bash` tool (`core/agent_cc.ts`'s `spawn(cmd,
|
|
48
|
+
fullArgs, { cwd, env: request.env ?? operatorEnv() })`) — so an allowlist
|
|
49
|
+
scoped only to ocr's own keys strips `ANTHROPIC_API_KEY` from the `claude`
|
|
50
|
+
CLI itself, and the reviewer stops authenticating (unless it relies on
|
|
51
|
+
`claude login`, since `HOME` is in the baseline and that flow's credentials
|
|
52
|
+
live under it). A `claude_code` reviewer must therefore also keep its own
|
|
53
|
+
backend credential in the list. On the default `flue` backend it is safe as
|
|
54
|
+
written below, because `request.env` there only reaches the tool sandbox
|
|
55
|
+
(`local({cwd, env})` in `core/agent_flue.ts`) — Flue's own provider
|
|
56
|
+
credentials are read from the parent's `process.env` regardless of this
|
|
57
|
+
list.
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
# .spf/spf.config.yaml — patches the packaged "reviewer" entry by name
|
|
61
|
+
agents:
|
|
62
|
+
- name: reviewer
|
|
63
|
+
env_allowlist:
|
|
64
|
+
- OCR_API_KEY # whatever credential ocr's own model call needs
|
|
65
|
+
- OCR_BASE_URL # if ocr talks to a self-hosted or proxied endpoint
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Agent config entries merge by name (`core/agents.ts`'s `mergeAgentLists`) and
|
|
69
|
+
patch by field, so this adds `env_allowlist` to the reviewer without having
|
|
70
|
+
to repeat its `model`/`prompt_engineering`/`writes`/`tools`. Do this even if
|
|
71
|
+
you think the reviewer "doesn't have secrets to leak" — the allowlist is
|
|
72
|
+
what makes that true, not an assumption about what happens to be in the
|
|
73
|
+
shell today.
|
|
74
|
+
|
|
75
|
+
## (b) Writes — ocr must not put a file in the repo
|
|
76
|
+
|
|
77
|
+
The packaged reviewer runs `writes: []` — read-only with respect to the
|
|
78
|
+
repo, enforced in code after the call (`core/permissions.ts`), not by asking
|
|
79
|
+
the model nicely. That enforcement has no retry path: it is not a gate a
|
|
80
|
+
violation can re-prompt its way past. A path ocr drops anywhere in the
|
|
81
|
+
working tree becomes a `PermissionBreach`, which `agents.execute()` throws
|
|
82
|
+
straight out of the phase — the run aborts, the same session does not get a
|
|
83
|
+
chance to fix it, and whatever ocr wrote gets rolled back if it can be
|
|
84
|
+
(deleted, if untracked; left alone and reported if it collided with a file
|
|
85
|
+
that was already dirty).
|
|
86
|
+
|
|
87
|
+
Two things to do before wiring ocr in for real, not after the first failed
|
|
88
|
+
run teaches you the hard way:
|
|
89
|
+
|
|
90
|
+
- **Verify ocr's cache/output behavior first**, outside of `spf` — run it
|
|
91
|
+
by hand in a scratch clone and check `git status` afterward. Some
|
|
92
|
+
review/scan tools default to writing a cache directory or a report file
|
|
93
|
+
next to the code they scanned.
|
|
94
|
+
- **Point any output ocr can be configured to write at a path outside the
|
|
95
|
+
repo, or under the run's own data directory** — `context_handoff_dir` is
|
|
96
|
+
always writable regardless of `writes: []` (`core/permissions.ts`'s
|
|
97
|
+
`alwaysWritable()`: the session runtime under `defaults.data_dir` is
|
|
98
|
+
granted to every agent, read-only ones included, because it is where an
|
|
99
|
+
agent hands its own report to the next phase, not repo content). The
|
|
100
|
+
reviewer's own prompt template already receives it as `{{context_handoff_dir}}`
|
|
101
|
+
— reuse that instead of a path ocr would put inside the tracked tree.
|
|
102
|
+
|
|
103
|
+
If ocr has no such flag, prefer piping its output to stdout and letting the
|
|
104
|
+
reviewer's bash call capture it that way — no file, nothing to breach.
|
|
105
|
+
|
|
106
|
+
## (c) Accounting honesty — ocr's spend is invisible to spf
|
|
107
|
+
|
|
108
|
+
`spf` counts tokens and dollars in exactly one place:
|
|
109
|
+
`core/agents.ts`'s `execute()`, around the ONE backend call it makes per
|
|
110
|
+
agent turn (Flue or Claude Code), via `run.addUsage()`. That is the
|
|
111
|
+
reviewer's own LLM call. `ocr review`/`ocr scan` invoked from inside that
|
|
112
|
+
call's `bash` tool is a subprocess the tracer sees as a `tool_call` event —
|
|
113
|
+
arguments, an ok/error flag, and captured output (both clipped at 20,000
|
|
114
|
+
characters) — not an exit status; a non-zero `ocr` exit usually still comes
|
|
115
|
+
back as ordinary successful tool output with the code inside the text, the
|
|
116
|
+
same as any other shell command — never as a second `agent_start`/
|
|
117
|
+
`agent_end` pair, because it isn't one from `spf`'s point of view: it is a
|
|
118
|
+
program the reviewer ran, the same shape as `bun test`. Whatever ocr's own
|
|
119
|
+
model call cost, on whatever credential you scoped in (a), is spent and
|
|
120
|
+
billed entirely outside `spf`'s usage tracking (`spf sessions`, the `cost`
|
|
121
|
+
column, the UI's usage panel — none of them will show it).
|
|
122
|
+
|
|
123
|
+
Be honest with yourself and whoever reads the trace about what this means:
|
|
124
|
+
the session total you see for a `reviewer` phase is a **floor**, not the
|
|
125
|
+
full cost of that review, whenever ocr ran. And whether ocr ran at all on a
|
|
126
|
+
given run is at the MODEL's discretion — it's an instruction in a prompt,
|
|
127
|
+
not a step in a chain, so a reviewer that judges the diff sufficient on its
|
|
128
|
+
own is free to never invoke it. If you need to know it ran, ask the prompt
|
|
129
|
+
to say so in its own `notes_for_next_agent`, and read the phase's
|
|
130
|
+
`tool_call` events for the `ocr` invocation itself — that is the only
|
|
131
|
+
record of it that exists.
|
|
132
|
+
|
|
133
|
+
## (d) `spf doctor` does not check for ocr
|
|
134
|
+
|
|
135
|
+
Deliberately. `spf doctor` checks binaries it has a specific, packaged
|
|
136
|
+
reason to expect: `quality.checks[].argv[0]` (your configured lint/test/build
|
|
137
|
+
commands), the `claude` CLI when `coding_agent: claude_code`, provider env
|
|
138
|
+
keys for whatever `model:` a roster entry names. None of those surfaces ever
|
|
139
|
+
mention ocr, because nothing packaged expects it to exist — it is named only
|
|
140
|
+
inside a prompt file YOU wrote, in a place `doctor` has no reason to read. A
|
|
141
|
+
missing or broken `ocr` binary therefore fails silently from `doctor`'s point
|
|
142
|
+
of view; the reviewer will simply get a failed-command result back from its
|
|
143
|
+
own `bash` tool call, the same as any other missing binary, and has to
|
|
144
|
+
handle that in its judgment like any other tool failure — never a `spf
|
|
145
|
+
doctor` line telling you in advance.
|
|
146
|
+
|
|
147
|
+
## (e) A worked prompt-override snippet
|
|
148
|
+
|
|
149
|
+
`.spf/prompt_engineering/reviewer/system.md` — the packaged file, with one
|
|
150
|
+
instruction added (keep everything else; this only appends):
|
|
151
|
+
|
|
152
|
+
```markdown
|
|
153
|
+
# Reviewer Agent
|
|
154
|
+
|
|
155
|
+
## Purpose
|
|
156
|
+
|
|
157
|
+
Confirm that what was built is what was asked for. This is not testing.
|
|
158
|
+
|
|
159
|
+
## Instructions
|
|
160
|
+
|
|
161
|
+
- Your spec is `<context_handoff_dir>/plan.md` when that file exists — the plan is the refined ask. Otherwise the spec is `prompt`, verbatim.
|
|
162
|
+
- Judge the code on disk, never the builder's summary of it. Start from `previous_envelope.changed_files`, read them, and use `git diff` for anything the envelope did not mention.
|
|
163
|
+
- Break the spec into concrete requirements and rule on each one: met, or not met with the evidence — a `file:line`, or exactly what is missing.
|
|
164
|
+
- Not your job: running tests, style opinions, refactors, or anything the request did not ask for. Work the request never asked for is not blocking on its own; work the request DID ask for and is missing always is.
|
|
165
|
+
- Change nothing. Findings go back to the builder — that is the only repair path.
|
|
166
|
+
- `approved` is true ONLY when every requirement is met and `blocking` is empty. Every blocking item names the specific gap, so the builder can fix it without guessing.
|
|
167
|
+
- You inherit the operator's shell environment, filtered to this agent's `env_allowlist` — call tools by bare name (`bun`, `uv`, `git`, `ocr`); never hunt for a binary or fall back to an absolute `/usr/bin/*` path.
|
|
168
|
+
- Judge any command you run by its exit status, never by scanning its output for words — except tools whose output is the product, such as `git diff`, or a review tool you were asked to consult, which you read as evidence, never as a verdict.
|
|
169
|
+
|
|
170
|
+
## One more evidence source: `ocr`
|
|
171
|
+
|
|
172
|
+
Before writing your findings, run `ocr review --format json` (falls back to
|
|
173
|
+
`ocr scan` if `review` is unavailable in this ocr version) against the
|
|
174
|
+
files in `previous_envelope.changed_files`, piping its output rather than
|
|
175
|
+
writing it to any path in the repo. Treat whatever it reports exactly like a
|
|
176
|
+
`git diff` hunk: evidence to weigh, in your own words, against the spec —
|
|
177
|
+
never a verdict you pass through. Fold anything it found that maps to a real
|
|
178
|
+
requirement into your own `findings`/`blocking`; ignore style opinions it
|
|
179
|
+
raises that the spec never asked about. You alone decide `approved` — ocr
|
|
180
|
+
has no vote. If `ocr` is not on `PATH` or the command errors, note that in
|
|
181
|
+
`notes_for_next_agent` and review from the diff alone; a missing second
|
|
182
|
+
opinion is not a blocking finding about the CODE.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
And the config half, from (a):
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
# .spf/spf.config.yaml
|
|
189
|
+
agents:
|
|
190
|
+
- name: reviewer
|
|
191
|
+
env_allowlist:
|
|
192
|
+
- OCR_API_KEY
|
|
193
|
+
- OCR_BASE_URL
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
That's the whole integration: one prompt file, one config patch, zero code.
|
|
@@ -72,10 +72,13 @@ agents:
|
|
|
72
72
|
tools: [read, edit, bash] # same canonical names either way — see "Retune tools" below
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
`claude_code` shells out to your own installed `claude` CLI
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
`claude_code` shells out to your own installed `claude` CLI — it needs no
|
|
76
|
+
separate npm install, since SPF never depends on it directly. `spf doctor`
|
|
77
|
+
checks whatever `SPF_CLAUDE_CMD`'s first token resolves to on `PATH` (see
|
|
78
|
+
below for routing through a wrapper or launcher), falling back to the
|
|
79
|
+
literal `claude` when `SPF_CLAUDE_CMD` is unset. A missing `ANTHROPIC_API_KEY`
|
|
80
|
+
is informational, not a hard failure: Claude Code also supports its own
|
|
81
|
+
`claude login` flow.
|
|
79
82
|
|
|
80
83
|
**Pointing a `claude_code` agent at Ollama** — local or cloud — needs no
|
|
81
84
|
config at all, just environment variables set before you run `spf` (Claude
|
|
@@ -94,6 +97,14 @@ set `ANTHROPIC_AUTH_TOKEN` to a real Ollama Cloud API key. This is exactly
|
|
|
94
97
|
the same environment-variable pass-through every agent already gets — no
|
|
95
98
|
SPF-specific plumbing, no `provider:` config section to write.
|
|
96
99
|
|
|
100
|
+
There's a second, different way to reach Ollama through `claude_code`: route
|
|
101
|
+
the `claude` command itself through `ollama launch claude` via
|
|
102
|
+
`SPF_CLAUDE_CMD`, instead of pointing `ANTHROPIC_BASE_URL` at Ollama's
|
|
103
|
+
OpenAI-compatible surface. That launcher form needs its own `--model <tag>`
|
|
104
|
+
flag (from `ollama list`) in the command string, since SPF always spawns
|
|
105
|
+
headless — see README.md's "Proxy or wrapper launchers" section for the
|
|
106
|
+
full command and why `--model` is mandatory there, not optional.
|
|
107
|
+
|
|
97
108
|
## Retune tools
|
|
98
109
|
|
|
99
110
|
Known tool names: `read`, `bash`, `edit`, `write`, `grep`, `glob` (`find` is
|
|
@@ -106,6 +106,7 @@ launching anything.
|
|
|
106
106
|
| Launch a chain, watch it, report on it | `cookbooks/run_adw.md` (after `how_to_prompt_for_the_eng.md`) |
|
|
107
107
|
| Retune the roster, add an agent | `cookbooks/roster.md` |
|
|
108
108
|
| Add or extend a chain | `cookbooks/authoring_chains.md` |
|
|
109
|
+
| Fold a third-party review tool's findings into the reviewer's verdict | `cookbooks/ocr_reviewer.md` |
|
|
109
110
|
| Envelope/gate/session contract | `references/handoff.md` |
|
|
110
111
|
| Trace schema, spend vs. context | `references/observability.md` |
|
|
111
112
|
| Full config field reference | `references/config.md` |
|