@gr8ful/spf 0.1.0
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/LICENSE +21 -0
- package/README.md +321 -0
- package/assets/defaults/spf.config.yaml +141 -0
- package/assets/prompts/builder/system.md +13 -0
- package/assets/prompts/builder/user.md +34 -0
- package/assets/prompts/documenter/system.md +17 -0
- package/assets/prompts/documenter/user.md +48 -0
- package/assets/prompts/planner/system.md +21 -0
- package/assets/prompts/planner/user.md +45 -0
- package/assets/prompts/reviewer/system.md +16 -0
- package/assets/prompts/reviewer/user.md +44 -0
- package/assets/prompts/scout/system.md +20 -0
- package/assets/prompts/scout/user.md +34 -0
- package/assets/skill/SKILL.md +80 -0
- package/assets/skill/cookbooks/authoring_chains.md +193 -0
- package/assets/skill/cookbooks/how_to_prompt_for_the_eng.md +109 -0
- package/assets/skill/cookbooks/roster.md +197 -0
- package/assets/skill/cookbooks/run_adw.md +92 -0
- package/assets/skill/cookbooks/spf_overview.md +111 -0
- package/assets/skill/references/config.md +188 -0
- package/assets/skill/references/handoff.md +162 -0
- package/assets/skill/references/observability.md +184 -0
- package/dist/chains/adw_build.d.ts +12 -0
- package/dist/chains/adw_build.js +27 -0
- package/dist/chains/adw_build_review.d.ts +21 -0
- package/dist/chains/adw_build_review.js +55 -0
- package/dist/chains/adw_build_test.d.ts +21 -0
- package/dist/chains/adw_build_test.js +67 -0
- package/dist/chains/adw_document.d.ts +23 -0
- package/dist/chains/adw_document.js +59 -0
- package/dist/chains/adw_plan.d.ts +12 -0
- package/dist/chains/adw_plan.js +27 -0
- package/dist/chains/adw_plan_build.d.ts +12 -0
- package/dist/chains/adw_plan_build.js +30 -0
- package/dist/chains/adw_plan_build_test.d.ts +16 -0
- package/dist/chains/adw_plan_build_test.js +65 -0
- package/dist/chains/adw_plan_build_test_quality.d.ts +18 -0
- package/dist/chains/adw_plan_build_test_quality.js +66 -0
- package/dist/chains/adw_prompt.d.ts +12 -0
- package/dist/chains/adw_prompt.js +25 -0
- package/dist/chains/adw_quality.d.ts +12 -0
- package/dist/chains/adw_quality.js +32 -0
- package/dist/chains/adw_scout.d.ts +12 -0
- package/dist/chains/adw_scout.js +27 -0
- package/dist/chains/adw_simple_sdlc.d.ts +43 -0
- package/dist/chains/adw_simple_sdlc.js +147 -0
- package/dist/chains/context.d.ts +19 -0
- package/dist/chains/context.js +1 -0
- package/dist/chains/index.d.ts +19 -0
- package/dist/chains/index.js +116 -0
- package/dist/cli/bin.d.ts +15 -0
- package/dist/cli/bin.js +24 -0
- package/dist/cli/commands/abort.d.ts +1 -0
- package/dist/cli/commands/abort.js +42 -0
- package/dist/cli/commands/doctor.d.ts +1 -0
- package/dist/cli/commands/doctor.js +165 -0
- package/dist/cli/commands/eject.d.ts +1 -0
- package/dist/cli/commands/eject.js +55 -0
- package/dist/cli/commands/events.d.ts +1 -0
- package/dist/cli/commands/events.js +49 -0
- package/dist/cli/commands/init.d.ts +1 -0
- package/dist/cli/commands/init.js +62 -0
- package/dist/cli/commands/install-skill.d.ts +1 -0
- package/dist/cli/commands/install-skill.js +122 -0
- package/dist/cli/commands/list.d.ts +1 -0
- package/dist/cli/commands/list.js +13 -0
- package/dist/cli/commands/migrate.d.ts +1 -0
- package/dist/cli/commands/migrate.js +167 -0
- package/dist/cli/commands/phases.d.ts +1 -0
- package/dist/cli/commands/phases.js +25 -0
- package/dist/cli/commands/run.d.ts +3 -0
- package/dist/cli/commands/run.js +27 -0
- package/dist/cli/commands/sessions.d.ts +1 -0
- package/dist/cli/commands/sessions.js +20 -0
- package/dist/cli/commands/trace.d.ts +8 -0
- package/dist/cli/commands/trace.js +10 -0
- package/dist/cli/commands/ui.d.ts +1 -0
- package/dist/cli/commands/ui.js +35 -0
- package/dist/cli/commands/version.d.ts +1 -0
- package/dist/cli/commands/version.js +8 -0
- package/dist/cli/commands/watch.d.ts +8 -0
- package/dist/cli/commands/watch.js +210 -0
- package/dist/cli/gitignore.d.ts +1 -0
- package/dist/cli/gitignore.js +14 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +156 -0
- package/dist/core/agent_cc.d.ts +75 -0
- package/dist/core/agent_cc.js +322 -0
- package/dist/core/agent_flue.d.ts +66 -0
- package/dist/core/agent_flue.js +321 -0
- package/dist/core/agents.d.ts +64 -0
- package/dist/core/agents.js +456 -0
- package/dist/core/changes.d.ts +35 -0
- package/dist/core/changes.js +98 -0
- package/dist/core/console.d.ts +36 -0
- package/dist/core/console.js +156 -0
- package/dist/core/data_types.d.ts +562 -0
- package/dist/core/data_types.js +382 -0
- package/dist/core/gates.d.ts +33 -0
- package/dist/core/gates.js +144 -0
- package/dist/core/git_helper.d.ts +59 -0
- package/dist/core/git_helper.js +115 -0
- package/dist/core/issues/github_provider.d.ts +52 -0
- package/dist/core/issues/github_provider.js +211 -0
- package/dist/core/issues/provider.d.ts +90 -0
- package/dist/core/issues/provider.js +14 -0
- package/dist/core/paths.d.ts +78 -0
- package/dist/core/paths.js +108 -0
- package/dist/core/permissions.d.ts +78 -0
- package/dist/core/permissions.js +187 -0
- package/dist/core/prompts.d.ts +4 -0
- package/dist/core/prompts.js +17 -0
- package/dist/core/quality.d.ts +65 -0
- package/dist/core/quality.js +194 -0
- package/dist/core/runner.d.ts +72 -0
- package/dist/core/runner.js +168 -0
- package/dist/core/session.d.ts +16 -0
- package/dist/core/session.js +60 -0
- package/dist/core/sqlite.d.ts +55 -0
- package/dist/core/sqlite.js +106 -0
- package/dist/core/tracer.d.ts +56 -0
- package/dist/core/tracer.js +246 -0
- package/dist/core/utils.d.ts +40 -0
- package/dist/core/utils.js +108 -0
- package/dist/core/watch.d.ts +44 -0
- package/dist/core/watch.js +194 -0
- package/dist/test/agent_cc.test.d.ts +1 -0
- package/dist/test/agent_cc.test.js +95 -0
- package/dist/test/agent_flue.test.d.ts +1 -0
- package/dist/test/agent_flue.test.js +83 -0
- package/dist/test/data_types.test.d.ts +10 -0
- package/dist/test/data_types.test.js +49 -0
- package/dist/test/ui_server.test.d.ts +1 -0
- package/dist/test/ui_server.test.js +119 -0
- package/dist/test/watch.test.d.ts +1 -0
- package/dist/test/watch.test.js +227 -0
- package/dist/ui/server/app.d.ts +3 -0
- package/dist/ui/server/app.js +98 -0
- package/dist/ui/server/db.d.ts +82 -0
- package/dist/ui/server/db.js +333 -0
- package/dist/ui/server/serve.d.ts +13 -0
- package/dist/ui/server/serve.js +80 -0
- package/dist/ui/server/static.d.ts +5 -0
- package/dist/ui/server/static.js +55 -0
- package/dist/ui/shared/types.d.ts +263 -0
- package/dist/ui/shared/types.js +8 -0
- package/package.json +52 -0
- package/web/assets/index-C7nF068F.css +1 -0
- package/web/assets/index-mzSArcnQ.js +11 -0
- package/web/assets/play-latin-400-normal-GKW-4YV7.woff2 +0 -0
- package/web/assets/play-latin-700-normal-DyPlLDbb.woff2 +0 -0
- package/web/index.html +14 -0
- package/web/logo.svg +6 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Plan Task
|
|
2
|
+
|
|
3
|
+
## Variables
|
|
4
|
+
|
|
5
|
+
### prompt
|
|
6
|
+
|
|
7
|
+
{{prompt}}
|
|
8
|
+
|
|
9
|
+
### previous_envelope
|
|
10
|
+
|
|
11
|
+
{{previous_envelope}}
|
|
12
|
+
|
|
13
|
+
### context_handoff_dir
|
|
14
|
+
|
|
15
|
+
{{context_handoff_dir}}
|
|
16
|
+
|
|
17
|
+
## Task
|
|
18
|
+
|
|
19
|
+
Plan the work described in `prompt`.
|
|
20
|
+
|
|
21
|
+
1. Write the full plan to `<context_handoff_dir>/plan.md` — this is the copy the builder reads.
|
|
22
|
+
2. Copy that file into the repo under `specs/`:
|
|
23
|
+
- **List `specs/` before you pick the name.** A session that plans more than once reuses its `<adw_id>`, so the obvious name may already be taken.
|
|
24
|
+
- Base name: `specs/<adw_id>_<slug>.md`, where `<adw_id>` is the session directory name inside `context_handoff_dir` (`.../sessions/<adw_id>/context_handoff`) and `<slug>` is two to four kebab-case words naming the work.
|
|
25
|
+
- If a file with that name already exists, use `specs/<adw_id>_<slug>_v2.md`, then `_v3`, and so on until the name is free. **Never overwrite an existing spec** — the earlier plan is the record of what was asked for then.
|
|
26
|
+
- **Copy it, do not retype it.** One bash call does the whole step:
|
|
27
|
+
`mkdir -p specs && cp "<context_handoff_dir>/plan.md" "specs/<adw_id>_<slug>.md"`
|
|
28
|
+
Writing the plan a second time through `write` re-emits every line you already wrote, which costs the whole document again in output tokens and lets the two copies drift.
|
|
29
|
+
3. Emit your `Report` JSON, declaring BOTH paths in `artifacts`.
|
|
30
|
+
|
|
31
|
+
## Report
|
|
32
|
+
|
|
33
|
+
Respond with ONLY valid JSON matching `PlanOutput` — no prose before or after:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"status": "success",
|
|
38
|
+
"summary": "<one sentence describing the plan>",
|
|
39
|
+
"artifacts": ["<context_handoff_dir>/plan.md", "specs/<adw_id>_<slug>.md"],
|
|
40
|
+
"commit_message": "<imperative one-line git subject for committing THIS PLAN DOCUMENT, not the work it describes — e.g. 'Add spec for the /health endpoint'>",
|
|
41
|
+
"notes_for_next_agent": "<what the builder must know>"
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Both `artifacts` entries are the paths you ACTUALLY wrote, `_v2` suffix and all. Gates open these files — a name you meant to use fails them.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Reviewer Agent
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Confirm that what was built is what was asked for. This is not testing.
|
|
6
|
+
|
|
7
|
+
## Instructions
|
|
8
|
+
|
|
9
|
+
- Your spec is `<context_handoff_dir>/plan.md` when that file exists — the plan is the refined ask. Otherwise the spec is `prompt`, verbatim.
|
|
10
|
+
- 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.
|
|
11
|
+
- 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.
|
|
12
|
+
- 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.
|
|
13
|
+
- Change nothing. Findings go back to the builder — that is the only repair path.
|
|
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
|
+
- 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.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Review Task
|
|
2
|
+
|
|
3
|
+
## Variables
|
|
4
|
+
|
|
5
|
+
### prompt
|
|
6
|
+
|
|
7
|
+
{{prompt}}
|
|
8
|
+
|
|
9
|
+
### previous_envelope
|
|
10
|
+
|
|
11
|
+
{{previous_envelope}}
|
|
12
|
+
|
|
13
|
+
### context_handoff_dir
|
|
14
|
+
|
|
15
|
+
{{context_handoff_dir}}
|
|
16
|
+
|
|
17
|
+
## Task
|
|
18
|
+
|
|
19
|
+
Confirm that the work reported in `previous_envelope` is what was asked for.
|
|
20
|
+
|
|
21
|
+
1. Establish the spec: read `<context_handoff_dir>/plan.md` if it exists, else use `prompt`.
|
|
22
|
+
2. Read the code that was actually written, starting from `previous_envelope.changed_files`.
|
|
23
|
+
3. Rule on every requirement in the spec — one `findings` entry each, with evidence.
|
|
24
|
+
4. Write the review to `<context_handoff_dir>/review.md`, then emit your `Report` JSON.
|
|
25
|
+
|
|
26
|
+
## Report
|
|
27
|
+
|
|
28
|
+
Respond with ONLY valid JSON matching `ReviewOutput` — no prose before or after:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"status": "success",
|
|
33
|
+
"approved": false,
|
|
34
|
+
"summary": "<one sentence: N of M requirements met>",
|
|
35
|
+
"findings": [
|
|
36
|
+
{ "requirement": "<the ask, in the requester's words>", "met": true, "evidence": "src/server.ts:42 — handler registered" }
|
|
37
|
+
],
|
|
38
|
+
"blocking": ["<what must change before this can be approved>"],
|
|
39
|
+
"artifacts": ["<context_handoff_dir>/review.md"],
|
|
40
|
+
"notes_for_next_agent": "<what the builder must fix, or how to verify if approved>"
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`status` is `success` when the review itself completed — it is not the verdict. The verdict is `approved`, and it is true only when `findings` has no unmet entry and `blocking` is empty.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Scout Agent
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Find and report where things live. Change nothing.
|
|
6
|
+
|
|
7
|
+
## Instructions
|
|
8
|
+
|
|
9
|
+
- Read-only: search, read, and report — never write to the codebase.
|
|
10
|
+
- Cite exact file paths (with line hints where useful).
|
|
11
|
+
- You inherit the operator's shell environment — their PATH, toolchains and credentials are already live. Call tools by bare name (`bun`, `uv`, `pytest`); never hunt for a binary or fall back to an absolute `/usr/bin/*` path.
|
|
12
|
+
- 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.
|
|
13
|
+
- Write your findings to `<context_handoff_dir>/scout_findings.md` for agents that follow.
|
|
14
|
+
- If you find nothing, say so plainly — an empty finding is a valid finding.
|
|
15
|
+
|
|
16
|
+
## Subagents
|
|
17
|
+
|
|
18
|
+
`subagent_create` / `_continue` / `_list` / `_remove` search several directions at once — one per lead or directory — instead of walking the codebase serially. Give each a self-contained task and hold it to read-only work; omit `model`.
|
|
19
|
+
|
|
20
|
+
They run in the background. **Wait for every one you spawned to report before writing `scout_findings.md` or your Report JSON.** Skip them when a couple of greps would do.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Scout Task
|
|
2
|
+
|
|
3
|
+
## Variables
|
|
4
|
+
|
|
5
|
+
### prompt
|
|
6
|
+
|
|
7
|
+
{{prompt}}
|
|
8
|
+
|
|
9
|
+
### previous_envelope
|
|
10
|
+
|
|
11
|
+
{{previous_envelope}}
|
|
12
|
+
|
|
13
|
+
### context_handoff_dir
|
|
14
|
+
|
|
15
|
+
{{context_handoff_dir}}
|
|
16
|
+
|
|
17
|
+
## Task
|
|
18
|
+
|
|
19
|
+
Find what `prompt` asks about. Write findings into `context_handoff_dir`, then emit your `Report` JSON.
|
|
20
|
+
|
|
21
|
+
## Report
|
|
22
|
+
|
|
23
|
+
Respond with ONLY valid JSON matching `ScoutOutput` — no prose before or after:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"status": "success",
|
|
28
|
+
"summary": "<one sentence on what you found>",
|
|
29
|
+
"findings": [
|
|
30
|
+
{ "file": "src/server.ts", "note": "<why this file matters>" }
|
|
31
|
+
],
|
|
32
|
+
"artifacts": ["<context_handoff_dir>/scout_findings.md"]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spf
|
|
3
|
+
description: Orchestrate SPF (Super Simple Software Factory) ADW chains via the `spf` CLI — deterministic agents-plus-code workflows with typed envelopes, gates, and a sqlite trace. Use when the user wants work planned/built/tested/reviewed/documented through SPF rather than done freehand.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SPF — orchestrator's skill
|
|
7
|
+
|
|
8
|
+
SPF runs **ADWs**: deterministic TypeScript chains that sequence typed agent
|
|
9
|
+
calls and code phases, verify claims with gates, and trace everything to
|
|
10
|
+
sqlite. `spf` is a globally installed CLI, not a template stamped into this
|
|
11
|
+
repo — it runs against this repo's `.spf/` overrides (if any) merged over its
|
|
12
|
+
own packaged defaults.
|
|
13
|
+
|
|
14
|
+
**You are the orchestrator, not the worker.** Launch a chain, watch its
|
|
15
|
+
trace, report to the engineer. Never read the target files an agent was
|
|
16
|
+
asked to change and "help," never fix what an agent was supposed to fix,
|
|
17
|
+
never hand-edit an envelope. A failed run is fixed by a config, prompt, or
|
|
18
|
+
chain change — made deliberately — then a re-run.
|
|
19
|
+
|
|
20
|
+
## Startup
|
|
21
|
+
|
|
22
|
+
Run `spf list` once, at the start of the conversation — it's the real menu;
|
|
23
|
+
chain names elsewhere in this skill are shape, not literal. `spf doctor`
|
|
24
|
+
answers "why did nothing happen" in one shot (every path, agent, quality
|
|
25
|
+
suite, provider key) before you spend a token guessing.
|
|
26
|
+
|
|
27
|
+
Don't volunteer more state than asked — "is it done" wants a status line,
|
|
28
|
+
not a phase-by-phase replay (`how_to_prompt_for_the_eng.md` has the fuller
|
|
29
|
+
rule and the anecdote behind it: an orchestrator once guessed a `runs`
|
|
30
|
+
table and a `payload` column that were never there; `spf sessions`/`spf
|
|
31
|
+
phases`/`spf events` exist so nobody has to guess the schema again).
|
|
32
|
+
|
|
33
|
+
## Orchestrator rules
|
|
34
|
+
|
|
35
|
+
- Never do a chain's work yourself — a missing chain shape is a `spf list`
|
|
36
|
+
gap to report or author (`authoring_chains.md`), not a reason to freehand it.
|
|
37
|
+
- Never hand-edit anything under `.spf/data/sessions/<adw_id>/` — audit trail, not a scratchpad.
|
|
38
|
+
- Observe through `spf sessions`/`spf phases`/`spf events`, never by guessing at `spf.db`'s schema.
|
|
39
|
+
|
|
40
|
+
## Request routing
|
|
41
|
+
|
|
42
|
+
| Request shape | Go to |
|
|
43
|
+
|---|---|
|
|
44
|
+
| "run/build/plan/fix X" | `run_adw.md` — read `how_to_prompt_for_the_eng.md` first, always |
|
|
45
|
+
| "add/retune an agent or model" | `roster.md` |
|
|
46
|
+
| "add a chain / a phase / an output type / a gate" | `authoring_chains.md` |
|
|
47
|
+
| "what is SPF" | `spf_overview.md` |
|
|
48
|
+
| envelope/gate/session contract | `references/handoff.md` |
|
|
49
|
+
| trace schema, spend vs. context | `references/observability.md` |
|
|
50
|
+
| config field reference | `references/config.md` |
|
|
51
|
+
|
|
52
|
+
## Hard rules
|
|
53
|
+
|
|
54
|
+
1. **Validate before running.** `agents.loadConfig()` + `agents.validate()`
|
|
55
|
+
run before any phase opens — a bad roster, missing prompt, unknown tool
|
|
56
|
+
name, or unconfigured quality suite fails loudly there, never mid-run.
|
|
57
|
+
2. **Typed outputs — the synced triad.** Every call's envelope schema
|
|
58
|
+
(Valibot, `data_types.ts`), the JSON in that agent's `user.md`
|
|
59
|
+
`## Report`, and the `output_type:` at the call site are one fact in
|
|
60
|
+
three places. Edit them together.
|
|
61
|
+
3. **Gates verify claims, never predict them.** They run *after* the
|
|
62
|
+
response and check what it claimed. A violation triggers a same-session
|
|
63
|
+
correction, never a cold restart.
|
|
64
|
+
4. **The four-param rule.** >4 parameters becomes one data object
|
|
65
|
+
(`AgentCall`, `PhaseParams`, `RunInit`).
|
|
66
|
+
5. **One agent, one prompt, one purpose.** A roster entry is who an agent
|
|
67
|
+
*is*; output type and per-call prompt are how it's *used*, and live at
|
|
68
|
+
the call site, never in config.
|
|
69
|
+
6. **Chains stay thin.** Sequencing only — business logic belongs in `core/`.
|
|
70
|
+
7. **Every phase needs a real description**, not a restatement of its name — rejected at construction time.
|
|
71
|
+
8. **A known command is code.** Lint/test/typecheck/build are
|
|
72
|
+
`quality.checks` run by a `code` phase, never an agent rediscovering a
|
|
73
|
+
command. An unconfigured suite a chain needs is a hard error, never a placeholder green.
|
|
74
|
+
9. **`writes` is the enforced boundary; `tools` is not.** `tools` grants
|
|
75
|
+
capability without promising restraint. `writes` is checked in code
|
|
76
|
+
after every call against the real diff — a breach is rolled back and
|
|
77
|
+
fails the phase; it is not a gate, because the write already happened.
|
|
78
|
+
10. **`run.finish(accepted, reason)`, called once.** Every phase succeeding
|
|
79
|
+
and the run's own acceptance are separate questions — a red test-suite
|
|
80
|
+
phase that ran correctly still "succeeded."
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Authoring Chains
|
|
2
|
+
|
|
3
|
+
Creating a new chain, extending an existing one, and adding the engine
|
|
4
|
+
primitives a chain needs (an output type, a gate) are one skill with three
|
|
5
|
+
doors. All three live in `src/` inside the SPF package itself — there is no
|
|
6
|
+
per-repo copy to edit. If you need to change engine behavior for a specific
|
|
7
|
+
target repo without forking the package, that's `spf eject` (prints the path
|
|
8
|
+
to the installed package's `src/` for you to copy and load from your own
|
|
9
|
+
`.spf/` — engine-level changes are the one thing `.spf/` config can't express).
|
|
10
|
+
|
|
11
|
+
## Step 1 — design the chain before writing code
|
|
12
|
+
|
|
13
|
+
Lay out the phases as a table: name, kind, owner, output type (if `agent`),
|
|
14
|
+
gates. This is the same table `spf list` will end up describing.
|
|
15
|
+
|
|
16
|
+
| Phase | Kind | Owner | Output type | Gates |
|
|
17
|
+
|---|---|---|---|---|
|
|
18
|
+
| request | engineer | engineer | — | — |
|
|
19
|
+
| scout | agent | scout | `ScoutOutput` | — |
|
|
20
|
+
| plan | agent | planner | `PlanOutput` | `artifactsExist` |
|
|
21
|
+
| build | agent | builder | `BuildOutput` | `diffMatchesClaims` |
|
|
22
|
+
| test | code | quality | — | (suite pass/fail is the phase's own result) |
|
|
23
|
+
| commit | code | git | — | — |
|
|
24
|
+
|
|
25
|
+
**Ownership rules**, non-negotiable:
|
|
26
|
+
|
|
27
|
+
- **`engineer`** phases capture input only — no agent, no code decision.
|
|
28
|
+
- **`agent`** phases: exactly one `ph.call({output_type, prompt, gates})`.
|
|
29
|
+
Pick the narrowest existing envelope type that fits (`GenericOutput`,
|
|
30
|
+
`PlanOutput`, `BuildOutput`, `ScoutOutput`, `ReviewOutput`,
|
|
31
|
+
`DocumentOutput`, or the two code-adapter types, `VerifyOutput` /
|
|
32
|
+
`ChangesOutput`) before adding a new one.
|
|
33
|
+
- **`code`** phases: a known command or a deterministic computation
|
|
34
|
+
(`quality.runSuite`, `changes.capture`, `run.git.commitAll`). If a human
|
|
35
|
+
could write the exact steps down without judgment, it's `code`.
|
|
36
|
+
|
|
37
|
+
## Step 2 — write the chain module
|
|
38
|
+
|
|
39
|
+
A chain is a module exporting `REQUIRED_AGENTS`, `REQUIRED_SUITES`, and
|
|
40
|
+
`main(ctx: ChainContext): Promise<number>`. No shebang, no `import.meta.main`
|
|
41
|
+
block — the CLI's chain registry (`src/chains/index.ts`) calls `main()`
|
|
42
|
+
directly, and `dist/cli/bin.js` is the only entry point that ever runs.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
/**
|
|
46
|
+
* ADW <Name> — <one sentence: what this chain is for and when to pick it>.
|
|
47
|
+
*
|
|
48
|
+
* Phases: engineer(request) -> scout -> planner -> builder -> code(test) -> git(commit)
|
|
49
|
+
*/
|
|
50
|
+
import * as agents from "../core/agents.ts";
|
|
51
|
+
import * as gates from "../core/gates.ts";
|
|
52
|
+
import * as quality from "../core/quality.ts";
|
|
53
|
+
import * as session from "../core/session.ts";
|
|
54
|
+
import { BuildOutput, PlanOutput, makeAgentCall, makePhaseParams } from "../core/data_types.ts";
|
|
55
|
+
import type { ChainContext } from "./context.ts";
|
|
56
|
+
|
|
57
|
+
export const REQUIRED_AGENTS = ["planner", "builder"];
|
|
58
|
+
export const REQUIRED_SUITES: string[] = ["test"]; // [] if this chain runs no quality suite
|
|
59
|
+
|
|
60
|
+
export async function main(ctx: ChainContext): Promise<number> {
|
|
61
|
+
const { prompt, config_paths, adw_id, cwd } = ctx;
|
|
62
|
+
const cfg = agents.loadConfig(config_paths);
|
|
63
|
+
agents.validate(cfg, REQUIRED_AGENTS, REQUIRED_SUITES, cwd);
|
|
64
|
+
const run = session.ensure(cfg, adw_id, cwd);
|
|
65
|
+
|
|
66
|
+
await run.phase(
|
|
67
|
+
makePhaseParams({ name: "request", kind: "engineer", owner: run.engineer, description: "Capture the incoming ask" }),
|
|
68
|
+
async (ph) => { ph.log({ input: prompt }); },
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const plan = await run.phase(
|
|
72
|
+
makePhaseParams({ name: "plan", kind: "agent", owner: "planner", description: "Turn the request into an implementable plan" }),
|
|
73
|
+
(ph) => ph.call(makeAgentCall({ output_type: PlanOutput, prompt, gates: [gates.artifactsExist] })),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const build = await run.phase(
|
|
77
|
+
makePhaseParams({ name: "build", kind: "agent", owner: "builder", description: "Implement the plan" }),
|
|
78
|
+
(ph) => ph.call(makeAgentCall({ output_type: BuildOutput, prompt, previous: plan, gates: [gates.diffMatchesClaims] })),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
await run.phase(
|
|
82
|
+
makePhaseParams({ name: "commit", kind: "code", owner: "git", description: "Commit the implementation" }),
|
|
83
|
+
async () => run.git.commitAll(build.commit_message || `spf: ${prompt.slice(0, 72)}`),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return run.finish();
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Then register it in `src/chains/index.ts`'s `CHAINS` array (name, describe,
|
|
91
|
+
phases string, `requiredAgents`/`requiredSuites`, `run`) — that's what makes
|
|
92
|
+
it show up in `spf list` and dispatchable as `spf <name>`.
|
|
93
|
+
|
|
94
|
+
**Before you ship it:** run it against a scratch repo (`spf <name> "..." --cwd
|
|
95
|
+
/tmp/scratch-repo`), then `spf phases <adw_id>` and `spf events <adw_id>` to
|
|
96
|
+
confirm every phase you designed actually appears with the status you
|
|
97
|
+
expect, and open one `envelope.json` under
|
|
98
|
+
`.spf/data/sessions/<adw_id>/<agent>/` to confirm it matches the type you
|
|
99
|
+
declared.
|
|
100
|
+
|
|
101
|
+
## Step 3 — add a phase to an existing chain
|
|
102
|
+
|
|
103
|
+
Insert a `run.phase(...)` block in sequence; nothing else in the chain needs
|
|
104
|
+
to change unless the new phase's output feeds a later one (thread it through
|
|
105
|
+
as a local variable, the way `plan`/`build` are threaded above).
|
|
106
|
+
|
|
107
|
+
Removing a phase: delete the block; check nothing downstream reads its
|
|
108
|
+
return value, and check `REQUIRED_AGENTS`/`REQUIRED_SUITES` no longer needs
|
|
109
|
+
whatever only that phase used.
|
|
110
|
+
|
|
111
|
+
## Step 4 — add a bounded fix loop
|
|
112
|
+
|
|
113
|
+
The pattern every quality-gated chain uses — run a check, and if it fails,
|
|
114
|
+
loop the builder back in with the failure as `previous`, bounded so a chain
|
|
115
|
+
can't spin forever:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const MAX_FIX_LOOPS = 3;
|
|
119
|
+
let test: QualityResult | null = null;
|
|
120
|
+
for (let i = 1; i <= MAX_FIX_LOOPS; i++) {
|
|
121
|
+
test = await run.phase(
|
|
122
|
+
makePhaseParams({ name: `test_${i}`, kind: "code", owner: "quality", description: "Run the suite" }),
|
|
123
|
+
async (ph) => { const result = quality.runTests(run); ph.log({ passed: result.passed }); return result; },
|
|
124
|
+
);
|
|
125
|
+
if (test.passed) break;
|
|
126
|
+
await run.phase(
|
|
127
|
+
makePhaseParams({ name: `fix_${i}`, kind: "agent", owner: "builder", retries: 1, description: "Repair what the suite reported" }),
|
|
128
|
+
(ph) => ph.call(makeAgentCall({ output_type: BuildOutput, prompt, previous: quality.asEnvelope(test!, "tests"), gates: [gates.diffMatchesClaims] })),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return run.finish(test !== null && test.passed, `the suite still failed after ${MAX_FIX_LOOPS} fix attempt(s)`);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
A failing suite does **not** fail its own phase — the runner did its job,
|
|
135
|
+
the *code under test* is what failed. It's `run.finish()`'s job, at the end,
|
|
136
|
+
to decide whether the whole run is accepted.
|
|
137
|
+
|
|
138
|
+
## Adding an engine primitive
|
|
139
|
+
|
|
140
|
+
These live in `src/core/`, not in a chain. Chains stay thin: sequencing
|
|
141
|
+
only, no business logic.
|
|
142
|
+
|
|
143
|
+
### Adding an output type
|
|
144
|
+
|
|
145
|
+
In `src/core/data_types.ts`, build it with `envelopeType()` over the base
|
|
146
|
+
envelope shape (`status`, `summary`, `artifacts`, `notes_for_next_agent`):
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
export const MyOutput = envelopeType("MyOutput", {
|
|
150
|
+
some_field: v.optional(v.string(), ""),
|
|
151
|
+
some_list: v.optional(v.array(v.string()), () => []),
|
|
152
|
+
});
|
|
153
|
+
export type MyOutputT = v.InferOutput<typeof MyOutput.schema>;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Then the two other legs of the synced triad: the calling chain's
|
|
157
|
+
`output_type: MyOutput`, and the agent's `user.md` `## Report` section
|
|
158
|
+
showing the exact JSON shape. All three must move together — see
|
|
159
|
+
`references/handoff.md`.
|
|
160
|
+
|
|
161
|
+
### Adding a gate
|
|
162
|
+
|
|
163
|
+
A gate is `(envelope, run) -> GateReport`, in `src/core/gates.ts`. Build the
|
|
164
|
+
report with one `.check(item, ok, note)` call per thing looked at; resolve
|
|
165
|
+
any envelope-declared path against `run.repo_root` via `resolveClaim()`
|
|
166
|
+
rather than the process's own cwd — an agent's paths are relative to where
|
|
167
|
+
*it* ran, not to this process.
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
export function myGate(envelope: EnvelopeBase, run: RunContext): GateReport {
|
|
171
|
+
const report = new GateReport();
|
|
172
|
+
// ... report.check(item, ok, note) per thing verified
|
|
173
|
+
return report;
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Never `console.log()` directly
|
|
178
|
+
|
|
179
|
+
Every phase's own logger (`ph.log(payload)`) both prints and writes a `log`
|
|
180
|
+
event to the trace, so the terminal and the trace agree by construction. A
|
|
181
|
+
bare `console.log` inside a phase is a message the trace will never have.
|
|
182
|
+
|
|
183
|
+
### The four-param rule
|
|
184
|
+
|
|
185
|
+
Any function needing more than 4 parameters takes one data object instead —
|
|
186
|
+
`AgentCall`, `PhaseParams`, `RunInit`, `ChangeCapture` are the pattern in
|
|
187
|
+
this codebase; follow it for new primitives too.
|
|
188
|
+
|
|
189
|
+
### Before you finish
|
|
190
|
+
|
|
191
|
+
Run the smallest chain that exercises the new code (`spf prompt "ping"` for
|
|
192
|
+
an engine-level change, or the specific chain for a chain-level one) and
|
|
193
|
+
confirm `spf phases <adw_id>` shows what you expect.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# How to Prompt for the Engineering
|
|
2
|
+
|
|
3
|
+
Read this **before every chain launch**. The prompt you pass is what the whole chain reads: the planner plans from it, the builder builds from it, the reviewer judges against it. Your prompt might run through 10s or 100s of agents. A sloppy prompt is not a small tax; it is paid again by every agent in the chain.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Turn what the engineer said into the prompt the ADW receives: **clearer, not different.** You are a translator, not a redesigner.
|
|
8
|
+
|
|
9
|
+
## The one rule
|
|
10
|
+
|
|
11
|
+
**The intent is theirs. The precision is yours.**
|
|
12
|
+
|
|
13
|
+
| You MAY | You MAY NOT |
|
|
14
|
+
|---|---|
|
|
15
|
+
| Carry every constraint forward, verbatim | Quietly drop a requirement because it looks hard or odd |
|
|
16
|
+
| Fix grammar, cut rambling, order the steps | Soften a strong ask ("rewrite" → "refactor a bit") |
|
|
17
|
+
| Change the language used to better communicate the idea | Research the codebase for exact file names, never go into the app |
|
|
18
|
+
|
|
19
|
+
If you catch yourself improving the *idea* rather than the *sentence*, stop. Raise the concern to the engineer in your own message and launch what they asked for.
|
|
20
|
+
|
|
21
|
+
## You never touch the application, you prompt, monitor, observe, and report.
|
|
22
|
+
|
|
23
|
+
Outside of understanding the ADWs, you never research, touch, or dive into the codebase thats being operated on.
|
|
24
|
+
|
|
25
|
+
Your role is to simply kick off the workflow. There are entire teams of agents inside these ADWs built to do the work.
|
|
26
|
+
|
|
27
|
+
Your job is to kick it off, monitor, observe, report. Not interact with the application layer. You operate only on the agentic layer, the ADWs, the software factory.
|
|
28
|
+
|
|
29
|
+
## The shape
|
|
30
|
+
|
|
31
|
+
Four lines. Nothing else earns its tokens.
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
<the ask — one imperative sentence, their words where they were specific>
|
|
35
|
+
Where: <files or dirs you verified>
|
|
36
|
+
Done means: <the observable result — a response shape, a passing test, a rendered element>
|
|
37
|
+
Out of scope: <what you were tempted to add, named so nobody adds it>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Before** (what the engineer said):
|
|
41
|
+
|
|
42
|
+
> can we get tags on posts, sorted by popularity
|
|
43
|
+
|
|
44
|
+
**After** (what the ADW receives):
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Add a GET /api/tags endpoint returning {tags: [{tag, count}]} — the distinct tags
|
|
48
|
+
across all posts with how many posts carry each, sorted by count descending then
|
|
49
|
+
tag ascending.
|
|
50
|
+
Where: src/server.ts (routes), src/server.test.ts (tests)
|
|
51
|
+
Done means: GET /api/tags returns the counts, and a new test in server.test.ts covers it.
|
|
52
|
+
Out of scope: tag editing UI, tag filtering on the post list.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Same idea, same scope. What changed is that "popularity" became a sort order, the files are named, and nobody has to guess where it stops.
|
|
56
|
+
|
|
57
|
+
## Which chain
|
|
58
|
+
|
|
59
|
+
**If the engineer named one, launch that one.** Their call stands — no second-guessing, no "upgrading" them to a longer chain. If you think another fits better, say so in your own message and launch what they asked for.
|
|
60
|
+
|
|
61
|
+
**If they did not, read what this install actually has and choose from that.**
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
spf list # every chain, its phases, what it needs — the menu
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Chains are the engineer's to add, rename, and rewire, so **`spf list`'s output is the only authority**. Never launch from memory or from a name you saw in a doc; read what it prints, then match by shape:
|
|
68
|
+
|
|
69
|
+
| The work | Look for a chain that |
|
|
70
|
+
|---|---|
|
|
71
|
+
| Changes code, and the shape is not obvious — new behaviour, more than one file, anything you would want a plan for | goes end to end: plans, builds, verifies, reviews, and documents |
|
|
72
|
+
| Changes code, one well-understood edit | plans, builds, and verifies |
|
|
73
|
+
| Implements a plan this session already produced (`--adw-id`) | starts at build and verifies |
|
|
74
|
+
| Confirms built work is what was asked for | ends in a review phase |
|
|
75
|
+
| Writes up work already shipped | captures the diff and documents it |
|
|
76
|
+
| Is a question, and nothing should change | is a single read-only agent — the one case where one phase is right |
|
|
77
|
+
|
|
78
|
+
**Never a single-agent chain when the engineer asked for work to be done.** One-phase chains answer questions and run one-offs; they do not deliver.
|
|
79
|
+
|
|
80
|
+
**The more complex the ask, the more complete the chain.** Complexity means: more than one file, a behaviour you cannot describe in one sentence, anything touching data or an interface others call, or any request where you had to guess. When two chains both fit, take the longer one — a phase you did not need costs cents, while a change nobody planned, verified, reviewed, or wrote up costs an afternoon.
|
|
81
|
+
|
|
82
|
+
If nothing in `spf list` fits the shape you need, say so and offer to compose one (`authoring_chains.md`) rather than forcing the work into a chain that skips the phase it needed.
|
|
83
|
+
|
|
84
|
+
## Workflow
|
|
85
|
+
|
|
86
|
+
1. **Read it twice.** Mark every noun that could point at two things.
|
|
87
|
+
2. **Verify before you write.** Every path, route, and symbol you put in the prompt must exist — check it. A wrong path costs a whole build phase.
|
|
88
|
+
3. **Draft the four lines.**
|
|
89
|
+
4. **Diff against the original.** Every specific thing they said, still there? Anything in your draft they did not say? Delete it.
|
|
90
|
+
5. **Ask at most one question**, only when two readings would produce different code. Otherwise state your assumption in the prompt and say so when you report.
|
|
91
|
+
6. **Launch** the chain from *Which chain* above; `run_adw.md` covers the mechanics and the watching. Inline for a short ask; for anything longer, write `requests/<slug>.md` and pass the path — every chain takes either.
|
|
92
|
+
|
|
93
|
+
## Rules that do not bend here
|
|
94
|
+
|
|
95
|
+
- **Do not write the plan.** Your prompt says WHAT and DONE MEANS. HOW belongs to the planner — unless the engineer specified how, and then you carry it word for word.
|
|
96
|
+
- **Do not address the harness in the prompt.** "Use the reviewer", "retry twice", "then commit" are chain choices, and the chain is chosen by which ADW you launch, not by prose the agents will read.
|
|
97
|
+
- **Do not pad.** No preamble, no restating the repo, no encouragement. Gates check claims, not prose.
|
|
98
|
+
- **Their exact words survive.** When the engineer was specific — a name, a number, a format, a file — quote it rather than paraphrasing.
|
|
99
|
+
|
|
100
|
+
## Report back
|
|
101
|
+
|
|
102
|
+
After launching, show the engineer three things so a bad translation dies in seconds rather than at the commit phase:
|
|
103
|
+
|
|
104
|
+
1. **The prompt you actually sent** — verbatim.
|
|
105
|
+
2. **The chain you chose**, and the one-line reason — or that you used the one they named.
|
|
106
|
+
If they named a roster (a config, a model tier), say which one you ran on; if they did not, you ran the default, and switching that is their call, not yours (`run_adw.md`).
|
|
107
|
+
3. **The `adw_id`**, so they can watch it (`spf phases <adw_id>`).
|
|
108
|
+
|
|
109
|
+
Then observe and report per `run_adw.md`. You run the system; you do not do the work inside it.
|