@gr8ful/spf 0.3.0 → 0.5.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/README.md +177 -9
- package/assets/defaults/spf.config.yaml +22 -0
- package/assets/prompts/refiner/system.md +53 -0
- package/assets/prompts/refiner/user.md +70 -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 +104 -3
- package/assets/skill/references/observability.md +11 -2
- package/assets/templates/ts-cc.spf.config.yaml +3 -3
- package/assets/templates/ts-flue-ollama.spf.config.yaml +67 -0
- package/assets/templates/ts.spf.config.yaml +15 -2
- package/dist/chains/context.d.ts +39 -0
- package/dist/chains/index.d.ts +94 -10
- package/dist/chains/index.js +75 -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 +237 -18
- package/dist/chains/steps.js +477 -58
- 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 +324 -10
- package/dist/cli/commands/init.d.ts +12 -0
- package/dist/cli/commands/init.js +108 -4
- package/dist/cli/commands/install-skill.js +5 -2
- package/dist/cli/commands/list.js +43 -5
- package/dist/cli/commands/run.js +29 -2
- package/dist/cli/commands/watch.d.ts +18 -0
- package/dist/cli/commands/watch.js +214 -16
- package/dist/cli/index.js +63 -6
- package/dist/cli/interview.js +81 -9
- 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 +182 -4
- package/dist/core/data_types.js +141 -2
- package/dist/core/gates.d.ts +13 -0
- package/dist/core/gates.js +103 -0
- package/dist/core/git_helper.d.ts +29 -0
- package/dist/core/git_helper.js +41 -1
- package/dist/core/issues/github_provider.d.ts +35 -9
- package/dist/core/issues/github_provider.js +76 -28
- package/dist/core/issues/jira_provider.d.ts +14 -1
- package/dist/core/issues/jira_provider.js +9 -7
- package/dist/core/issues/provider.d.ts +77 -15
- package/dist/core/issues/provider.js +7 -4
- package/dist/core/notify/channel.d.ts +1 -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/providers.js +4 -0
- package/dist/core/refine.d.ts +39 -0
- package/dist/core/refine.js +152 -0
- 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 +67 -1
- package/dist/core/watch.js +217 -13
- package/dist/test/chains.test.js +9 -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 +71 -1
- package/dist/test/interview.test.d.ts +15 -1
- package/dist/test/interview.test.js +131 -3
- 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/refine.test.d.ts +1 -0
- package/dist/test/refine.test.js +189 -0
- 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 +297 -6
- package/package.json +5 -5
|
@@ -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` |
|
|
@@ -12,7 +12,7 @@ always shows the resolved, merged result for the repo you're in.
|
|
|
12
12
|
1. The packaged built-in default (`assets/defaults/spf.config.yaml` inside
|
|
13
13
|
the installed CLI).
|
|
14
14
|
2. `.spf/spf.config.yaml` in the target repo, if present — merged on top,
|
|
15
|
-
field by field (`defaults`/`observability`/`quality`/`watch`/`notifications`
|
|
15
|
+
field by field (`defaults`/`observability`/`quality`/`watch`/`notifications`/`review`
|
|
16
16
|
merge key-by-key — `notifications.channels` replaces wholesale, same as
|
|
17
17
|
`quality.checks`; `agents` merges by `name`: a matching name patches that
|
|
18
18
|
entry, a new name appends).
|
|
@@ -72,6 +72,8 @@ agents:
|
|
|
72
72
|
| `color` | hex string | Lane color fallback for agents that don't set their own. |
|
|
73
73
|
| `harness_engineering` | string[] | **Must stay `[]`** — no analogue on any current backend; a non-empty entry fails validate(). |
|
|
74
74
|
| `tools` | string[] \| null | Roster-wide allowlist. Unset/null = every built-in tool usable. |
|
|
75
|
+
| `writes` | string[] \| null | Roster-wide write allowlist, back-filled onto any agent that doesn't set its own — see `writes` under `agents[]` below for the three-state semantics. |
|
|
76
|
+
| `env_allowlist` | string[] \| null | Roster-wide env allowlist, back-filled the same way as `writes`. Unset/null = every agent gets the full operator environment (see `env_allowlist` under `agents[]`). |
|
|
75
77
|
| `protected_files` | string[] | Paths no agent may touch unless named in its own `writes`. Default `[".spf/", "spf.config.yaml"]`. |
|
|
76
78
|
| `data_dir` | path | Runtime home, repo-relative. Default `.spf/data`. |
|
|
77
79
|
|
|
@@ -81,6 +83,11 @@ agents:
|
|
|
81
83
|
|---|---|---|
|
|
82
84
|
| `db` | path | The trace sqlite db. Default `.spf/data/spf.db`. |
|
|
83
85
|
| `poll_ms` | int | UI live-poll cadence. Default `500`. |
|
|
86
|
+
| `otel.endpoint` | string | OTLP/HTTP collector endpoint (e.g., `https://your-host/v1/traces`). Omit to disable OTel export. |
|
|
87
|
+
| `otel.headers` | object | Optional HTTP headers (e.g., auth tokens). Each value is a string. |
|
|
88
|
+
| `otel.service_name` | string | Optional service name in exported spans. Default `spf`. |
|
|
89
|
+
|
|
90
|
+
**No ambient env activation**: OTEL export requires explicit `observability.otel` config — the `OTEL_EXPORTER_OTLP_ENDPOINT` shell variable is never consulted. An unrelated shell env variable must not become a data-egress switch.
|
|
84
91
|
|
|
85
92
|
### `quality`
|
|
86
93
|
|
|
@@ -108,6 +115,45 @@ sections: `assets/templates/ts.spf.config.yaml` in the spf package (or
|
|
|
108
115
|
`spf init --template ts` to write it straight into `.spf/spf.config.yaml`).
|
|
109
116
|
`spf init` with no `--template` prints every packaged template's name.
|
|
110
117
|
|
|
118
|
+
### `watch`
|
|
119
|
+
|
|
120
|
+
Full mechanism: the main README's "`spf watch`" section. Field reference:
|
|
121
|
+
|
|
122
|
+
| Field | Type | Meaning |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `issue_provider` | `"github"` \| `"jira"` | The tracker `spf watch` polls. Default `github`. |
|
|
125
|
+
| `code_host` | `"github"` \| `"bitbucket"` | Where PRs open — independent of `issue_provider` (Jira issues against a Bitbucket repo is a real setup). Default `github`. |
|
|
126
|
+
| `repo` | string | The **code host's** repo — required once watch is actually run (not schema-validated — fails loudly at `spf watch` startup instead). `"owner/name"` for `code_host: github`, `"workspace/repo_slug"` for `code_host: bitbucket`. |
|
|
127
|
+
| `issue_repo` | string | Overrides `repo` for the **issue tracker** side, only meaningful (and only needed) for `issue_provider: github` + `code_host: bitbucket` — the one combination where the tracker and the code host are genuinely different repos in different systems. Unset (the default) falls back to `repo`, which is exactly right for `github`+`github` (one repo) and for `issue_provider: jira` (which never reads `repo` at all). |
|
|
128
|
+
| `label_prefix` | string | State-machine label prefix — polls/writes `<prefix>:ready`, `<prefix>:working`, etc. Default `spf`. |
|
|
129
|
+
| `chain` | string | Which registered chain runs per claimed `<prefix>:ready` issue. Default `plan-build-test`. |
|
|
130
|
+
| `base_branch` | string | Branch worktrees fork from and PRs target. Default `main`. |
|
|
131
|
+
| `poll_ms` | int | Tick interval. Default `60000`. |
|
|
132
|
+
| `concurrency` | int ≥1 | Max issues claimed and run at once, the build lane's own budget (independent of `refine.concurrency`). Default `2`. |
|
|
133
|
+
| `jira.base_url` / `jira.project_key` | string | Only consulted when `issue_provider: jira`. |
|
|
134
|
+
| `refine.enabled` | bool | Turns on the second lane: decompose a `<prefix>:spec-ready` product spec into a feature/story-or-bug tree of real issues, instead of running `chain` against it directly (a spec isn't individually workable). Default `false` — off by default, so an existing `watch:` config is unaffected by upgrading. Needs `issue_provider: github` — `spf watch` fails loudly at startup otherwise, since issue authoring (create + link a hierarchy) isn't implemented for Jira yet. |
|
|
135
|
+
| `refine.chain` | string | Which registered chain runs per claimed spec. Default `refine`. |
|
|
136
|
+
| `refine.concurrency` | int ≥1 | The refine lane's own budget, separate from `concurrency`. Default `1`. |
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
watch:
|
|
140
|
+
repo: owner/name
|
|
141
|
+
label_prefix: spf
|
|
142
|
+
chain: plan-build-test
|
|
143
|
+
refine:
|
|
144
|
+
enabled: true # decompose spf:spec-ready specs into a feature/story tree
|
|
145
|
+
chain: refine
|
|
146
|
+
concurrency: 1
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Generated issues carry a second, independent label vocabulary —
|
|
150
|
+
`<prefix>:type:epic|feature|story|bug|task` — seeded by `spf watch init`
|
|
151
|
+
alongside the state labels. A feature/epic (a container: some other node
|
|
152
|
+
names it as `parent`) gets only its type label; a leaf (story/bug/task)
|
|
153
|
+
additionally gets `<prefix>:refined`, so a human can review and promote it to
|
|
154
|
+
`<prefix>:ready` when it's worth building — the refine lane never
|
|
155
|
+
auto-promotes anything.
|
|
156
|
+
|
|
111
157
|
### `notifications`
|
|
112
158
|
|
|
113
159
|
Optional outbound push for unattended work — `spf watch`'s daemon lifecycle,
|
|
@@ -151,6 +197,33 @@ run's exit code. See the main README's "Notifications" section for how to
|
|
|
151
197
|
get each channel's webhook URL, and `spf init`'s interview, which asks for
|
|
152
198
|
this section and collects the URL straight into `.env`.
|
|
153
199
|
|
|
200
|
+
### `review`
|
|
201
|
+
|
|
202
|
+
The human sign-off gate in front of `simple-sdlc`'s `commit_build` phase —
|
|
203
|
+
the one place in this codebase an AI reviewer's `approved` flag alone would
|
|
204
|
+
otherwise gate a commit (`build-review` has no commit step, and no other
|
|
205
|
+
chain runs a reviewer at all). See the main README's "Isolation" section for
|
|
206
|
+
the full behavior; this is just the two knobs.
|
|
207
|
+
|
|
208
|
+
| Field | Type | Meaning |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| `require_human_signoff` | bool | `false` (default, this release): an unattended run (`spf watch`, CI, no TTY) proceeds on the reviewer's verdict alone, with a loud warning printed and traced every time. `true`: an unattended run instead fails the phase closed — rerun attended, or route the work through `spf watch`, whose own human gate is the PR merge. Never affects an attended run's prompt, which is always shown either way. |
|
|
211
|
+
| `signoff_timeout_seconds` | number ≥1 | Bounds the attended confirm prompt. Default `300`. Not required to be an integer — `1.5` parses and becomes a 1500ms timer. Expiry resolves to the prompt's own default — **not accepted**, same as if the answer had been "no" — never an unbounded `stdin` read inside `run.phase()`. |
|
|
212
|
+
|
|
213
|
+
```yaml
|
|
214
|
+
review:
|
|
215
|
+
require_human_signoff: false # this release's default — see above
|
|
216
|
+
signoff_timeout_seconds: 300
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The confirm prompt's own default is always `false` — never `review.approved`,
|
|
220
|
+
so an AI's own verdict can never auto-approve itself by way of an unanswered
|
|
221
|
+
default. A `Signed-off-by:` trailer is appended to the commit only on a
|
|
222
|
+
recorded explicit "yes," built from `git config user.name`/`user.email` at
|
|
223
|
+
the repo (never `ENGINEER_NAME`/`$USER`, which are spoofable and fall back to
|
|
224
|
+
the literal string `"engineer"`) — an unattended run's AI-only commit never
|
|
225
|
+
carries one, because nobody said yes to attest to.
|
|
226
|
+
|
|
154
227
|
### `agents[]`
|
|
155
228
|
|
|
156
229
|
| Field | Required | Meaning |
|
|
@@ -161,6 +234,7 @@ this section and collects the URL straight into `.env`.
|
|
|
161
234
|
| `coding_agent`, `model`, `thinking`, `color`, `harness_engineering` | no | Override the matching `defaults` key. |
|
|
162
235
|
| `tools` | no | Allowlist. Omitting it means all tools usable. A capability list, not a boundary — see `writes`. |
|
|
163
236
|
| `writes` | no | What this agent may modify **in the repo**, enforced after every call. `undefined`/`null` = unrestricted (still barred from `protected_files`); `[]` = no repo writes; a list = only those paths (trailing `/` = directory prefix, `*` = one path segment, `**` = crosses segments, anything else = exact path). |
|
|
237
|
+
| `env_allowlist` | no | Opt-in filter on the environment handed to this agent's subprocess/sandbox. `undefined` (default) = the full operator environment, unchanged. A list = only those keys, plus the baseline (`PATH`, `HOME`, `USER`, `LANG`, `TERM`, `TMPDIR`) either backend keeps regardless. |
|
|
164
238
|
|
|
165
239
|
Output types are deliberately absent from config: an entry defines who an
|
|
166
240
|
agent *is*; the call site defines how it's *used*.
|
|
@@ -174,7 +248,15 @@ no public model-registry API) — `agents.ts` checks only the static shape at
|
|
|
174
248
|
first real dispatch instead. Provider credentials come from the
|
|
175
249
|
environment, matching the provider you named (`GEMINI_API_KEY`/
|
|
176
250
|
`GOOGLE_API_KEY` for `google/...`, `ANTHROPIC_API_KEY` for `anthropic/...`,
|
|
177
|
-
etc.) — `spf doctor` checks the common ones are set.
|
|
251
|
+
etc.) — `spf doctor` checks the common ones are set. `ollama/...` is the one
|
|
252
|
+
keyless provider (`PROVIDER_ENV_KEYS.ollama` is `[]`) — instead of a key, set
|
|
253
|
+
`OLLAMA_BASE_URL` (default `http://localhost:11434/v1` if unset) to point at
|
|
254
|
+
your server; the `spf init` interview asks for this instead of a secret when
|
|
255
|
+
you pick `ollama`, and `spf doctor` probes it (informational — a down server
|
|
256
|
+
is reported, never a hard failure). See README.md's "flue + local Ollama"
|
|
257
|
+
section for the full walkthrough and
|
|
258
|
+
[`assets/templates/ts-flue-ollama.spf.config.yaml`](../../templates/ts-flue-ollama.spf.config.yaml)
|
|
259
|
+
for a ready-to-run starting config.
|
|
178
260
|
|
|
179
261
|
**For `coding_agent: claude_code`:** write `model` in Claude Code's own
|
|
180
262
|
vocabulary — a bare alias (`sonnet`, `opus`) or a full model name — never
|
|
@@ -233,4 +315,23 @@ No config section for this — it's an environment-variable recipe, since
|
|
|
233
315
|
`claude` subprocess, exactly like every other env var. Set
|
|
234
316
|
`ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` (local or cloud Ollama) before
|
|
235
317
|
running `spf`; see `roster.md`'s "Coding agent backends" section for the
|
|
236
|
-
exact commands.
|
|
318
|
+
exact commands. `spf doctor` probes `ANTHROPIC_BASE_URL` (informational — a
|
|
319
|
+
down endpoint or wrong path is reported, never a hard failure) and flags a
|
|
320
|
+
base URL that already ends in `/v1` as a likely double-path mistake, since
|
|
321
|
+
the `claude` CLI appends `/v1/messages` itself.
|
|
322
|
+
|
|
323
|
+
Routing through `SPF_CLAUDE_CMD="ollama launch claude --model granite4.1:8b"`
|
|
324
|
+
instead needs an explicit `--` before `claude`'s own flags (cobra flag
|
|
325
|
+
parsing otherwise consumes them as `ollama launch`'s own) — `agent_cc.ts`
|
|
326
|
+
detects this exact `ollama launch ...` token shape and inserts that
|
|
327
|
+
separator automatically, so you never add it by hand. The `--model` before
|
|
328
|
+
that separator is NOT optional, though: it's `ollama launch`'s own flag, and
|
|
329
|
+
it's mandatory in headless mode (SPF always pipes stdio, so the interactive
|
|
330
|
+
model picker `ollama launch` falls back to without it can never run) — a
|
|
331
|
+
`--model` typed after the `--` belongs to `claude`, not to `ollama launch`,
|
|
332
|
+
and doesn't help. `spf doctor` hard-fails a `SPF_CLAUDE_CMD` missing it. See
|
|
333
|
+
README.md's "Proxy or wrapper launchers" section for the full explanation.
|
|
334
|
+
|
|
335
|
+
For `flue` (the default backend) pointed at Ollama instead of `claude_code`
|
|
336
|
+
— i.e. `model: ollama/<tag>` — see "Model resolution" above and README.md's
|
|
337
|
+
"flue + local Ollama" section.
|
|
@@ -31,8 +31,17 @@ Location comes from `observability.db` in `spf.config.yaml`, default
|
|
|
31
31
|
| `phase_end` | the block exits; carries the resolved status |
|
|
32
32
|
| `error` | a throw inside a phase block |
|
|
33
33
|
|
|
34
|
-
`parent_id`
|
|
35
|
-
|
|
34
|
+
`parent_id` is reserved and structurally empty today — SPF's phases are flat
|
|
35
|
+
siblings, and nothing writes nesting into it. The UI reconstructs phase/
|
|
36
|
+
tool-call nesting from `phase_id` plus agent-call bracketing instead of
|
|
37
|
+
reading `parent_id`.
|
|
38
|
+
|
|
39
|
+
**Optional OTel export.** When `observability.otel.endpoint` is set in
|
|
40
|
+
`spf.config.yaml`, a lossy, allowlisted projection of phase/agent/tool spans
|
|
41
|
+
(status, model, token/cost counts, gate results — never prompts, envelopes,
|
|
42
|
+
tool arguments, or source code) is also pushed to an OTLP/HTTP collector,
|
|
43
|
+
fire-and-forget. SQLite remains the source of truth regardless; see
|
|
44
|
+
`config.md`'s `observability.otel.*` rows for the field reference.
|
|
36
45
|
|
|
37
46
|
**Spend is itemized per phase.** `agent_end.usage` carries tokens *and*
|
|
38
47
|
dollars for each component Flue reports (matching pi-ai's field names
|
|
@@ -47,9 +47,9 @@ defaults:
|
|
|
47
47
|
# provider/model-id strings, which an agent's own model always wins over
|
|
48
48
|
# defaults.model above — switching coding_agent globally does NOT reset
|
|
49
49
|
# those three, so they'd run on Claude Code (redirected at Ollama) with a
|
|
50
|
-
# model id Ollama has never heard of, and fail outright. builder/scout
|
|
51
|
-
# have no model of their own in the packaged roster, so they
|
|
52
|
-
# inherit defaults.model above and need no override here.
|
|
50
|
+
# model id Ollama has never heard of, and fail outright. builder/scout/
|
|
51
|
+
# refiner have no model of their own in the packaged roster, so they
|
|
52
|
+
# correctly inherit defaults.model above and need no override here.
|
|
53
53
|
agents:
|
|
54
54
|
- name: planner
|
|
55
55
|
model: qwen3-coder:30b
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# .spf/spf.config.yaml — Flue backend (the default), routed at a local
|
|
2
|
+
# Ollama server instead of a hosted provider. `spf init --template
|
|
3
|
+
# ts-flue-ollama` writes this file as-is.
|
|
4
|
+
#
|
|
5
|
+
# model: is ALWAYS provider/model-id for Flue — "ollama/<tag>", where <tag>
|
|
6
|
+
# is whatever `ollama list` shows on the machine actually running the model
|
|
7
|
+
# (agent_flue.ts registers each distinct "ollama/<tag>" it sees with Flue's
|
|
8
|
+
# own provider registry the first time it's dispatched — no separate model
|
|
9
|
+
# catalog to keep in sync here). agents.validate() only checks the STRING
|
|
10
|
+
# SHAPE (provider/id, via agent_flue.ts's resolveModel()) — never that the
|
|
11
|
+
# tag actually exists on the server. A wrong or not-yet-pulled tag only
|
|
12
|
+
# surfaces at the first real dispatch, as an "Unknown model" error from
|
|
13
|
+
# Ollama itself, not at validate() time.
|
|
14
|
+
#
|
|
15
|
+
# The two model ids below (qwen3.8:27b-mlx, granite4.1:8b) are EXAMPLES from
|
|
16
|
+
# one spike machine's own `ollama list` output, not a stable public catalog —
|
|
17
|
+
# replace both with whatever `ollama list` shows on YOUR machine before
|
|
18
|
+
# running spf. Nothing here pins a specific model family; the split is
|
|
19
|
+
# purely "heavier reasoning agent" vs. "lighter/faster agent", same as any
|
|
20
|
+
# other template's planner-vs-builder split.
|
|
21
|
+
#
|
|
22
|
+
# ollama is a KEYLESS provider (providers.ts's PROVIDER_ENV_KEYS.ollama is
|
|
23
|
+
# `[]`) — no API key to export. What IS required:
|
|
24
|
+
# export OLLAMA_BASE_URL=http://localhost:11434/v1 # default if unset
|
|
25
|
+
# `spf doctor` probes OLLAMA_BASE_URL/models (informational, never a hard
|
|
26
|
+
# failure) so a server that isn't running shows up before your first real
|
|
27
|
+
# run does, not during it.
|
|
28
|
+
#
|
|
29
|
+
# Context-window occupancy reporting is disabled for every ollama/* model:
|
|
30
|
+
# agent_flue.ts registers these models with contextWindow: 0 (spike-verified
|
|
31
|
+
# safe — the same convention agent_flue.ts already uses for
|
|
32
|
+
# context_window: 0 elsewhere), which turns off threshold-based compaction
|
|
33
|
+
# rather than reporting a fabricated number Ollama's OpenAI-compatible API
|
|
34
|
+
# doesn't actually provide per-model.
|
|
35
|
+
quality:
|
|
36
|
+
checks:
|
|
37
|
+
- { name: typecheck, operation: typecheck, argv: ["npm", "run", "typecheck"], timeout_seconds: 60 }
|
|
38
|
+
- { name: lint, operation: lint, argv: ["npm", "run", "lint"], timeout_seconds: 60 }
|
|
39
|
+
- { name: build, operation: build, argv: ["npm", "run", "build"], timeout_seconds: 300 }
|
|
40
|
+
- { name: test, operation: build, argv: ["npm", "test"], timeout_seconds: 300 }
|
|
41
|
+
suites:
|
|
42
|
+
test: [test]
|
|
43
|
+
all: [typecheck, lint, build, test]
|
|
44
|
+
|
|
45
|
+
defaults:
|
|
46
|
+
coding_agent: flue
|
|
47
|
+
# EXAMPLE tag — replace with a heavier model from your own `ollama list`.
|
|
48
|
+
model: ollama/qwen3.8:27b-mlx
|
|
49
|
+
|
|
50
|
+
# Overridden here (unlike the OpenRouter template's rationale, which applies
|
|
51
|
+
# equally): the packaged roster's planner/reviewer/documenter pin their own
|
|
52
|
+
# Flue-style provider/model-id strings (fireworks/openai models), unrelated
|
|
53
|
+
# to a local Ollama server — left alone, those three would still resolve and
|
|
54
|
+
# run fine, just against a hosted provider's key this template never asks
|
|
55
|
+
# for. Overridden so every agent actually demonstrates local-only routing.
|
|
56
|
+
agents:
|
|
57
|
+
- name: planner
|
|
58
|
+
model: ollama/qwen3.8:27b-mlx
|
|
59
|
+
- name: reviewer
|
|
60
|
+
model: ollama/qwen3.8:27b-mlx
|
|
61
|
+
- name: documenter
|
|
62
|
+
# EXAMPLE tag — replace with a lighter/faster model from your own
|
|
63
|
+
# `ollama list`. Tool-calling (the injected sf_report contract every
|
|
64
|
+
# agent's structured output rides on) was spike-verified to work down to
|
|
65
|
+
# a 3B model — a lighter tag here is a real, not merely theoretical,
|
|
66
|
+
# option.
|
|
67
|
+
model: ollama/granite4.1:8b
|
|
@@ -39,8 +39,8 @@ defaults:
|
|
|
39
39
|
# always wins over defaults.model — switching coding_agent globally does
|
|
40
40
|
# NOT reset those three, so they'd run on Claude Code with a model id it
|
|
41
41
|
# can't resolve at all ("There's an issue with the selected model...").
|
|
42
|
-
# builder/scout have no model of their own in the packaged roster,
|
|
43
|
-
# correctly inherit defaults.model above and need no override here.
|
|
42
|
+
# builder/scout/refiner have no model of their own in the packaged roster,
|
|
43
|
+
# so they correctly inherit defaults.model above and need no override here.
|
|
44
44
|
agents:
|
|
45
45
|
- name: planner
|
|
46
46
|
model: sonnet
|
|
@@ -61,9 +61,22 @@ agents:
|
|
|
61
61
|
# Needs a GITHUB_TOKEN env var (classic PAT, repo scope) for the defaults below.
|
|
62
62
|
# watch:
|
|
63
63
|
# repo: owner/name
|
|
64
|
+
# # issue_repo: owner/name # ONLY for issue_provider: github + code_host: bitbucket,
|
|
65
|
+
# # # where the issue tracker and code host are genuinely
|
|
66
|
+
# # # different repos — repo above is then the BITBUCKET
|
|
67
|
+
# # # repo, and issue_repo is the GitHub one. Unused/unset
|
|
68
|
+
# # # for every other combination.
|
|
64
69
|
# label_prefix: spf
|
|
65
70
|
# chain: plan-build-test
|
|
66
71
|
# base_branch: main
|
|
72
|
+
# # Optional second lane: decompose a spf:spec-ready product spec into a
|
|
73
|
+
# # feature/story-or-bug tree of real issues instead of building it
|
|
74
|
+
# # directly. Off by default; needs issue_provider: github (the default
|
|
75
|
+
# # above) — issue authoring isn't implemented for Jira yet.
|
|
76
|
+
# refine:
|
|
77
|
+
# enabled: true
|
|
78
|
+
# chain: refine
|
|
79
|
+
# concurrency: 1
|
|
67
80
|
|
|
68
81
|
# Optional: push notifications for unattended work — spf watch's daemon
|
|
69
82
|
# lifecycle, and every chain run (including watch's own per-issue runs).
|
package/dist/chains/context.d.ts
CHANGED
|
@@ -18,4 +18,43 @@ export interface ChainContext {
|
|
|
18
18
|
cwd: string;
|
|
19
19
|
/** The CLI name (`"plan-build-test"`), for session.ensure()'s trace record — see core/session.ts. */
|
|
20
20
|
chain_name: string;
|
|
21
|
+
/**
|
|
22
|
+
* The originating tracker issue's id, only meaningful to the `refine`
|
|
23
|
+
* chain — `steps.publishIssues()` renders it as a `## Parent: #<id>`
|
|
24
|
+
* back-reference on every issue it creates (`core/refine.ts`'s
|
|
25
|
+
* `renderBody`). `null`/omitted for a manual run with no source issue
|
|
26
|
+
* (a bare `spf refine "<spec text>"`, no `--issue`). Every other chain
|
|
27
|
+
* ignores this field.
|
|
28
|
+
*/
|
|
29
|
+
issue_id?: string | null;
|
|
30
|
+
/**
|
|
31
|
+
* True when no human is at the keyboard for this run — `spf watch`'s
|
|
32
|
+
* daemon lane, a cron/CI invocation — false for an interactive
|
|
33
|
+
* `spf <chain> "..."`.
|
|
34
|
+
*
|
|
35
|
+
* REQUIRED, deliberately: it is not a flag a call site may forget. Every
|
|
36
|
+
* ChainContext construction site has to state which lane it is, because
|
|
37
|
+
* "unattended" is what makes several safety rules non-negotiable rather
|
|
38
|
+
* than merely advisable — most concretely, a repo-local chain
|
|
39
|
+
* (`.spf/chains/*.yaml`) may only ADD gates, never drop a built-in one
|
|
40
|
+
* (see `steps.ts`'s GATE_ALLOWLIST comment): there is nobody watching the
|
|
41
|
+
* console to notice that `diffMatchesClaims` was quietly turned off. A
|
|
42
|
+
* boolean that defaults to `false` would make the dangerous lane the one
|
|
43
|
+
* you get by accident.
|
|
44
|
+
*/
|
|
45
|
+
unattended: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Absolute path of the YAML file this chain was loaded from, when it is a
|
|
48
|
+
* repo-local chain (`.spf/chains/<name>.yaml` — see
|
|
49
|
+
* `chains/repo_chains.ts`). `undefined` for a built-in chain, which has no
|
|
50
|
+
* file: it IS spf's own code.
|
|
51
|
+
*
|
|
52
|
+
* `steps.startRun()` writes this into the trace as a single
|
|
53
|
+
* `chain_source` log event, so a session in the UI or in `spf trace` can
|
|
54
|
+
* always answer "whose chain definition ran this?" — the answer stops
|
|
55
|
+
* being obvious the moment a target repo can ship its own chains, and the
|
|
56
|
+
* trace is the only durable record (the YAML file itself may have been
|
|
57
|
+
* edited by the time anyone reads the run back).
|
|
58
|
+
*/
|
|
59
|
+
chain_source?: string;
|
|
21
60
|
}
|