@lchase/kiln 0.6.0 → 0.7.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 +58 -21
- package/assets/serve.css +93 -0
- package/assets/serve.js +844 -0
- package/dist/attention.js +5 -0
- package/dist/auth.js +48 -0
- package/dist/board.js +10 -5
- package/dist/cli.js +31 -7
- package/dist/config.js +33 -10
- package/dist/delivery.js +53 -15
- package/dist/driver.js +29 -5
- package/dist/errors.js +9 -0
- package/dist/fs-browse.js +113 -0
- package/dist/host.js +7 -1
- package/dist/intake/file.js +1 -0
- package/dist/intake/jira.js +19 -4
- package/dist/intake/prompt.js +1 -0
- package/dist/intake/tickets.js +41 -1
- package/dist/intake-http.js +201 -0
- package/dist/jobs.js +5 -1
- package/dist/pr-names.js +116 -0
- package/dist/reconcile.js +5 -2
- package/dist/reliability.js +83 -22
- package/dist/report.js +7 -2
- package/dist/sandbox.js +18 -10
- package/dist/schemas.js +76 -16
- package/dist/serve-page.js +157 -13
- package/dist/serve.js +254 -16
- package/dist/settings.js +204 -0
- package/dist/workflow.js +17 -1
- package/package.json +3 -2
- package/workflows/feature.yaml +7 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lawrence Chase
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -82,17 +82,29 @@ That is the Anthropic API, the npm registry, and the GitHub hosts a fetch or ins
|
|
|
82
82
|
`kiln sandbox` prints the resolved mode, the allowlist, and anything still missing. `kiln sandbox build` builds the images yak ships but does not publish, from the tag that matches the installed `@lchase/yak`:
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
|
-
docker build -t yak-agent-sandbox:latest https://github.com/lchase/yak.git#yak-v0.
|
|
86
|
-
docker build -t yak-agent-sandbox-proxy:latest https://github.com/lchase/yak.git#yak-v0.
|
|
85
|
+
docker build -t yak-agent-sandbox:latest https://github.com/lchase/yak.git#yak-v0.8.0:docker/agent-sandbox
|
|
86
|
+
docker build -t yak-agent-sandbox-proxy:latest https://github.com/lchase/yak.git#yak-v0.8.0:docker/agent-proxy
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
The agent image is `node:22-slim` plus the Claude CLI at `/app/claude-cli`. It has no git. `kiln sandbox build` adds `git` and `ca-certificates` and tags that result `yak-agent-sandbox:latest` as well. The proxy image is `yak-agent-sandbox-proxy:latest` (squid on port 3128). Yak mounts the worktree at `/workspace` and puts the container on an `--internal` network whose only egress is that proxy.
|
|
90
90
|
|
|
91
91
|
A linked worktree's `.git` file points at the checkout's common git dir, which is outside that mount. `git` inside the container cannot see the repository until yak mounts that dir too. Land stays on the host, where the worktree link works.
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
Podman works when `docker` is aliased to `podman`. Yak 0.8.0 falls back from the `bridge` egress network to `podman` when `bridge` is absent. `YAK_SANDBOX_EGRESS_NETWORK` overrides that name.
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
On macOS, Claude subscription auth lives in the keychain. Yak forwards only `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN` into the container, and never a `~/.claude` directory. Kiln checks that one of those variables is set before a sandboxed run. It does not print or write the value.
|
|
96
|
+
|
|
97
|
+
Outside the sandbox, an unset `CLAUDE_CODE_OAUTH_TOKEN` is allowed: Claude Code can use the keychain. A variable that is set but empty, or a `CLAUDE_CODE_OAUTH_TOKEN` that does not start with `sk-ant-oat`, makes `kiln tick` and `kiln serve` refuse to launch. The message names the variable and does not include the value. A later 401, `Invalid bearer token`, or `Failed to authenticate` in the yak failure or the kiln log is `auth-failed`. That attempt is not retried. The board says `Claude login rejected (401) — check CLAUDE_CODE_OAUTH_TOKEN / claude login, then Retry`. `kiln retry` is the way back after the token is fixed.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
claude setup-token
|
|
101
|
+
export CLAUDE_CODE_OAUTH_TOKEN
|
|
102
|
+
kiln sandbox build
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Then set the kiln config to `"sandbox": { "mode": "docker" }`.
|
|
106
|
+
|
|
107
|
+
`kiln tick` and `kiln serve` name what is missing when sandbox mode is on: the docker binary, the docker daemon, each image, and the credential. They also refuse an installed `@lchase/yak` that does not pass `allowedDomains` from the agent step into the sandbox proxy. `@lchase/yak` 0.8.0 does. An older package drops `sandbox`, `image`, and `allowedDomains` while loading YAML and opens the proxy with only `api.anthropic.com`, so the job would run on the host with the open internet. Kiln leaves that job failed until `kiln retry`, after the installed package is 0.8.0 or newer.
|
|
96
108
|
|
|
97
109
|
A step that fails with yak reason `sandbox-error` goes to Needs human. The card shows the failure detail. Kiln does not schedule an automatic retry for it. The card of a sandboxed job shows `sandbox · docker` in the same monospace metadata as the rest of the board.
|
|
98
110
|
|
|
@@ -107,6 +119,8 @@ npm run kiln -- repo list
|
|
|
107
119
|
|
|
108
120
|
`npm run kiln` builds first. `kiln repo add <name> <path>` writes `~/.kiln/kiln.config.json`. When `KILN_HOME` is set it writes `$KILN_HOME/kiln.config.json`. `--config <file>` writes that file instead, and jobs live in the file's directory. A relative path is stored absolute, resolved from the working directory at add time. `~` is expanded from the home directory. `test` is optional on the entry and required before tick can launch a job in that repo.
|
|
109
121
|
|
|
122
|
+
`repo add` prints the deliver mode in effect and the command that changes it. The default is `local`, and that default is not written into the file until you pass `--deliver`. `kiln repo add <existing-name> <path> --deliver pr` updates that repo in place: a flag you omit keeps the stored test command, deliver mode, base, `prTitle`, and `branch`. `kiln repo list` prints `deliver local` or `deliver pr` for every repo.
|
|
123
|
+
|
|
110
124
|
```json
|
|
111
125
|
{
|
|
112
126
|
"repos": {
|
|
@@ -128,9 +142,10 @@ A job is one workflow run. The workflow name is a file `workflows/<name>.yaml`.
|
|
|
128
142
|
npm run kiln -- add feature --repo web --spec spec.md --title "Show a Sunday summary"
|
|
129
143
|
npm run kiln -- add feature --repo api --spec api-spec.md --after <job-id>
|
|
130
144
|
npm run kiln -- add feature --repo web --jira PROJ-123
|
|
145
|
+
npm run kiln -- add feature --repo web --spec spec.md --issue ABC-123
|
|
131
146
|
```
|
|
132
147
|
|
|
133
|
-
`--title` defaults to the first `#` heading in the spec, then the file name. `--after` may be repeated. Those jobs must already exist. Kiln copies the spec to `~/.kiln/specs/<id>.md` and writes `~/.kiln/jobs/<id>.json`:
|
|
148
|
+
`--title` defaults to the first `#` heading in the spec, then the file name. `--issue KEY` stores an issue id matching `KEY-123` (uppercased). `--after` may be repeated. Those jobs must already exist. Kiln copies the spec to `~/.kiln/specs/<id>.md` and writes `~/.kiln/jobs/<id>.json`:
|
|
134
149
|
|
|
135
150
|
```json
|
|
136
151
|
{
|
|
@@ -139,17 +154,18 @@ npm run kiln -- add feature --repo web --jira PROJ-123
|
|
|
139
154
|
"workflow": "feature",
|
|
140
155
|
"spec": "specs/j20261025020600ab12.md",
|
|
141
156
|
"title": "Show a Sunday summary",
|
|
157
|
+
"issue": "ABC-123",
|
|
142
158
|
"dependsOn": ["j20261025020500cd34"],
|
|
143
159
|
"source": { "kind": "file", "ref": "/home/dev/specs/sunday.md" },
|
|
144
160
|
"createdAt": "2026-10-25T02:06:00.000Z"
|
|
145
161
|
}
|
|
146
162
|
```
|
|
147
163
|
|
|
148
|
-
`dependsOn` is omitted when the job has no `--after`. `stack: true` is set by `--stack` and is omitted otherwise. `source` records which intake created the job. Jobs written before intake omit `source` and still run. State is not stored. The pull request url is not stored on the job; Kiln writes it under `~/.kiln/deliveries/`.
|
|
164
|
+
`dependsOn` is omitted when the job has no `--after`. `issue` is omitted when intake had no key. `stack: true` is set by `--stack` and is omitted otherwise. `source` records which intake created the job. Jobs written before intake omit `source` and still run. State is not stored. The pull request url is not stored on the job; Kiln writes it under `~/.kiln/deliveries/`.
|
|
149
165
|
|
|
150
166
|
## Intake
|
|
151
167
|
|
|
152
|
-
Every intake writes the same job: `id`, `repo`, `workflow`, `spec`, `title`, optional `dependsOn`, and `source`. `source` is `{ "kind": "file" | "prompt" | "tickets" | "jira", "ref": "..." }`. Status prints `source <kind> <ref>` when the job has one. Tick, reconcile, and status do not special-case an intake. The adapters are `src/intake/file.ts`, `src/intake/prompt.ts`, `src/intake/tickets.ts`, and `src/intake/jira.ts`.
|
|
168
|
+
Every intake writes the same job: `id`, `repo`, `workflow`, `spec`, `title`, optional `issue`, optional `dependsOn`, and `source`. `source` is `{ "kind": "file" | "prompt" | "tickets" | "jira", "ref": "..." }`. Status prints `source <kind> <ref>` when the job has one. Tick, reconcile, and status do not special-case an intake. The adapters are `src/intake/file.ts`, `src/intake/prompt.ts`, `src/intake/tickets.ts`, and `src/intake/jira.ts`.
|
|
153
169
|
|
|
154
170
|
### File
|
|
155
171
|
|
|
@@ -175,7 +191,7 @@ npm run kiln -- import ./tickets --repo web
|
|
|
175
191
|
npm run kiln -- import ./tickets --repo web --workflow feature --context spec.md --dry-run
|
|
176
192
|
```
|
|
177
193
|
|
|
178
|
-
`import` reads a directory of `NN-slug.md` files, the shape a Matt Pocock-style `/to-tickets` pass writes. Each file starts with `# NN: Title`, a `**What to build:**` paragraph, and a `**Blocked by:**` line. That line is followed by `None…` or by bullets such as `- 01: Probe the unconfirmed platform facts`. Kiln matches the `NN` number to a filename in the same directory (`01` and `1` are the same number). YAML front matter is also accepted, with `title`, `type`, and `blocked-by` (`blocked-by:` as a list, a flow list, or a single number).
|
|
194
|
+
`import` reads a directory of `NN-slug.md` files, the shape a Matt Pocock-style `/to-tickets` pass writes. Each file starts with `# NN: Title`, a `**What to build:**` paragraph, and a `**Blocked by:**` line. That line is followed by `None…` or by bullets such as `- 01: Probe the unconfirmed platform facts`. Kiln matches the `NN` number to a filename in the same directory (`01` and `1` are the same number). YAML front matter is also accepted, with `title`, `type`, `issue`, and `blocked-by` (`blocked-by:` as a list, a flow list, or a single number). `issue` must be a `KEY-123` id. A heading or title that already starts with `ABC-123:`, `ABC-123 `, or `[ABC-123]` sets the same field. A numbered heading such as `# 01: Title` does not. The stored title keeps that prefix; the pull request template strips it.
|
|
179
195
|
|
|
180
196
|
One file becomes one job. Blocked-by becomes `dependsOn` on the imported job ids. An unknown number is an error that names the ticket file, and no new job is written.
|
|
181
197
|
|
|
@@ -192,7 +208,7 @@ npm run kiln -- add feature --repo web --jira PROJ-123
|
|
|
192
208
|
npm run kiln -- add feature --repo web --jira PROJ-123 --after <job-id> --dry-run
|
|
193
209
|
```
|
|
194
210
|
|
|
195
|
-
`--jira` reads one Jira Cloud issue over REST API v3 and writes the same job as the other intakes. `source.kind` is `jira`. `source.ref` is the issue key (`PROJ-123`). The title is `PROJ-123: <summary
|
|
211
|
+
`--jira` reads one Jira Cloud issue over REST API v3 and writes the same job as the other intakes. `source.kind` is `jira`. `source.ref` is the issue key (`PROJ-123`). The job's `issue` is that key. The title is the issue summary, or `--title` when you pass one. The spec heading stays `# PROJ-123: <summary>`, then a `Source:` line with the issue's browse URL, then the description converted from Atlassian document format to markdown. `--issue` on the same command must be that key.
|
|
196
212
|
|
|
197
213
|
Set `JIRA_BASE_URL` (for example `https://acme.atlassian.net`), `JIRA_EMAIL`, and `JIRA_API_TOKEN`. Each value falls back to the optional `jira` block in the kiln config (`~/.kiln/kiln.config.json`, or `$KILN_HOME/kiln.config.json` when `KILN_HOME` is set, or the file passed with `--config`). Environment variables override the block:
|
|
198
214
|
|
|
@@ -218,17 +234,21 @@ kiln serve --port 4100 --tick 30
|
|
|
218
234
|
kiln serve --no-tick
|
|
219
235
|
```
|
|
220
236
|
|
|
221
|
-
`kiln serve` listens on `127.0.0.1` only (port 4100 unless `--port` is set). It reads the same derived state as `kiln status` and calls the same approve, reject, and
|
|
237
|
+
`kiln serve` listens on `127.0.0.1` only (port 4100 unless `--port` is set). It reads the same derived state as `kiln status` and calls the same approve, reject, retry, add, and import functions. Nothing new is stored.
|
|
238
|
+
|
|
239
|
+
Before each request and each tick, serve stats the kiln config and reloads it when the mtime or size changed. `kiln repo add`, a sandbox mode change, and `maxAttempts` apply on the next request or tick. Leave serve running. When the file does not parse, serve keeps the last good config and shows one monospace line on the board with the config path and the validation message. That line goes away when the file is valid again. Secret redaction uses the reloaded config.
|
|
222
240
|
|
|
223
|
-
`/` is the operator board, using the factory dashboard's dark charcoal layout. Columns are workflow stages taken from the job's yaml steps and the current yak step. Queued is first: jobs whose derived state is queued or blocked, with the subtitle `waiting · deps` and dimmer text. A blocked card names the jobs it is waiting on. Spec holds a job only while the spec step is the one running. Then Build (`builder · opus`), Review (`sec · ux · ui · code`), Approve (`approver · opus`), Land, PR review, Merged, and Needs human. An investigation adds Explore. Needs human stays at the right and holds open gates, dead runs, failures that are not waiting on an automatic retry, invalid job files, and rejections. A failed job with `nextRetryAt` still set stays in the column of the step it was on (Build, Review, and so on), dimmer, with one line `retrying 7:35 PM · attempt 1/3 · stalled` in the browser's local time. It is not in the needs-you count. When that retry starts, the card is a normal running card again. An empty column is a narrow strip with the name written down it. The header clock and any timestamps in the drawer use the browser's local time; stored values stay ISO UTC. The bottom toolbar switches between that and always-expanded columns; the choice is stored in `localStorage`. Repo chips across the top filter the cards. A card shows the job id, repo, title, branch, pull request, and one row per review round (security, ux, ui, code). A card in PR review also shows each feedback round as its own row: `CI failed`, `changes requested`, then `pushed` or `fixed` once that round lands. A fix that is still running is a normal running card. Only `feedback-exhausted` (and feedback Kiln could not classify) moves to Needs human, with your call. The drawer lists each feedback round's checks and comments, with links. A blocked card names what it is waiting on. Clicking a card opens a drawer. Approve, Reject, and Retry sit in the drawer header when the job is gated or failed. The drawer lists the spec, build, decision, each review, and investigation findings, and the selected file is rendered on the right. `/board` is the same page. The document title is `(N) Kiln`, where N is the needs-you count from the attention view (gates, dead runs, failures with no automatic retry left, and open pull requests). A job waiting out its backoff is not included, and it does not fire the desktop notification. The page polls every 3 seconds. After you opt in from the toolbar, a desktop notification fires when that count goes up. Kiln does not record factory quality averages, so the header does not invent round or human-fix numbers.
|
|
241
|
+
`/` is the operator board, using the factory dashboard's dark charcoal layout. Columns are workflow stages taken from the job's yaml steps and the current yak step. Queued is first: jobs whose derived state is queued or blocked, with the subtitle `waiting · deps` and dimmer text. A blocked card names the jobs it is waiting on. Spec holds a job only while the spec step is the one running. Then Build (`builder · opus`), Review (`sec · ux · ui · code`), Approve (`approver · opus`), Land, PR review, Done, Merged, and Needs human. Done (`local`) is a landed job with no pull request: `deliver=local`, or an investigation. The card says `landed locally · yak/<run-id>`. Merged (`pr`) is a done job whose pull request exists. The card says `merged · PR #N`. An empty board shows Done and does not label that column Merged. Both columns appear when the kiln has one of each. A card's repo line includes the deliver mode, `web · local` or `web · pr`. An investigation adds Explore. Needs human stays at the right and holds open gates, dead runs, failures that are not waiting on an automatic retry, invalid job files, and rejections. A failed job with `nextRetryAt` still set stays in the column of the step it was on (Build, Review, and so on), dimmer, with one line `retrying 7:35 PM · attempt 1/3 · stalled` in the browser's local time. It is not in the needs-you count. When that retry starts, the card is a normal running card again. An empty column is a narrow strip with the name written down it. The header clock and any timestamps in the drawer use the browser's local time; stored values stay ISO UTC. The bottom toolbar switches between that and always-expanded columns; the choice is stored in `localStorage`. Add job sits in that toolbar and opens a drawer, not a second page: repo chips, a workflow from `workflows/*.yaml`, then Prompt, Spec file, Tickets folder, or Jira. An optional issue id (`ABC-123`) is the same field as `kiln add --issue`. A Jira source fills it from the issue key. The title starts as the spec's first heading or the prompt's first line, and stays editable. After lists jobs that are not done. Submit closes the drawer and highlights the new card in Queued. A tickets folder can be previewed first; the drawer shows the jobs and the dependency graph and writes nothing until Add. The file list starts at the selected repo when that path is under the home directory, and remembers the last directory in `localStorage`. Repo chips across the top filter the cards. A card shows the job id, repo, title, branch, pull request, and one row per review round (security, ux, ui, code). A card in PR review also shows each feedback round as its own row: `CI failed`, `changes requested`, then `pushed` or `fixed` once that round lands. A fix that is still running is a normal running card. Only `feedback-exhausted` (and feedback Kiln could not classify) moves to Needs human, with your call. The drawer lists each feedback round's checks and comments, with links. A blocked card names what it is waiting on. Clicking a card opens a drawer. Approve, Reject, and Retry sit in the drawer header when the job is gated or failed. The drawer lists the spec, build, decision, each review, and investigation findings, and the selected file is rendered on the right. `/board` is the same page. The document title is `(N) Kiln`, where N is the needs-you count from the attention view (gates, dead runs, failures with no automatic retry left, and open pull requests). A job waiting out its backoff is not included, and it does not fire the desktop notification. The page polls every 3 seconds. After you opt in from the toolbar, a desktop notification fires when that count goes up. Kiln does not record factory quality averages, so the header does not invent round or human-fix numbers.
|
|
224
242
|
|
|
225
243
|
`GET /jobs/<id>/findings` renders the approved findings markdown as text. HTML in that file is escaped. The page is read-only.
|
|
226
244
|
|
|
227
|
-
`GET /api/jobs` and `GET /api/jobs/<id>` return that view as JSON, including the attention sections. `
|
|
245
|
+
`GET /api/jobs` and `GET /api/jobs/<id>` return that view as JSON, including the attention sections. `GET /api/status` is that same list plus `repos` (name and path) and `configError` (`null`, or the validation message). `GET /api/jobs` carries those two fields too, and the page polls it every 3 seconds, so a repo added from the CLI shows up on the Add job chips without a separate reload. `POST /api/jobs` adds work through the intake adapters. The body is `{ repo, workflow, title?, issue?, after?, stack?, dryRun?, source }` where `source` is `{ kind: "prompt", text }`, `{ kind: "spec", path }`, `{ kind: "tickets", dir, context? }`, or `{ kind: "jira", key }`. `issue` is an optional `KEY-123` id, the same field `kiln add --issue` stores. With `deliver=pr` the draft title is `[ABC-123] Title` and the remote branch is `ABC-123-slug`. A Jira source stores the issue key when `issue` is omitted, and rejects a different key. A tickets import does not take `issue`; each file has its own. `dryRun: true` previews a tickets import and writes nothing. The response is `{ ids: string[] }`. Spec and ticket paths must be absolute and under the home directory.
|
|
246
|
+
|
|
247
|
+
`POST /api/jobs/<id>/approve`, `POST /api/jobs/<id>/reject`, and `POST /api/jobs/<id>/retry` call the same functions as the CLI. Every POST requires the CSRF token embedded in the page (`X-Kiln-CSRF`, or a `csrf` field on the approve forms). A request whose Origin or Host is not this localhost port is rejected. That check also covers `GET /api/fs`. `GET /api/fs?path=` lists directories and `.md` files under the home directory. A chosen `.md` file returns its first 40 lines. Paths outside the home directory, hidden directories, and other files are refused. The page and the stylesheet ship inside the package (`assets/serve.css`, `assets/serve.js`, read from the package root).
|
|
228
248
|
|
|
229
249
|
`kiln retry <jobId>` relaunches a failed or dead job with the same launch tick uses, and clears `<kiln-dir>/attempts/<job-id>.json`. A landed job that only failed to open its pull request is delivered again instead. The board's Retry button calls that same function.
|
|
230
250
|
|
|
231
|
-
By default, serve runs `kiln tick` once when it starts and then every 30 seconds. `--tick <seconds>` changes the interval. `--no-tick` leaves the factory where it is. The tick uses the same lock as `kiln tick`, so a second tick exits with "another kiln tick is running" instead of launching twice.
|
|
251
|
+
By default, serve runs `kiln tick` once when it starts and then every 30 seconds. `--tick <seconds>` changes the interval. `--no-tick` leaves the factory where it is. The tick uses the same lock as `kiln tick`, so a second tick exits with "another kiln tick is running" instead of launching twice. The tick re-reads the config first, so a repo registered while serve is open is launchable on the next tick.
|
|
232
252
|
|
|
233
253
|
The Jira API token is not rendered. Do not commit checkout paths or job files.
|
|
234
254
|
|
|
@@ -247,13 +267,13 @@ npm run kiln -- reject <job-id>
|
|
|
247
267
|
while true; do npm run kiln -- tick; sleep 30; done
|
|
248
268
|
```
|
|
249
269
|
|
|
250
|
-
A job is ready when its dependencies are met and the number of jobs in flight is under `maxConcurrent`. The default is after merged: every job in `dependsOn` is `done`. `--stack` is met when that one upstream has landed. A rejected gate does not unblock the next job. An ok run with no readable approval does not either.
|
|
270
|
+
A job is ready when its dependencies are met and the number of jobs in flight is under `maxConcurrent`. The default is after merged: every job in `dependsOn` is `done`. `--stack` is met when that one upstream has landed. A rejected gate does not unblock the next job. An ok run with no readable approval does not either. An authentication failure does not either: reason `auth-failed` stays in Needs human until `kiln retry`.
|
|
251
271
|
|
|
252
272
|
`stallTimeoutMs` (default `900000`, 15 minutes), `maxAttempts` (default `3`), and `maxFixRounds` (default `2`) live in the kiln config. `stallTimeoutMs` of `0` or below turns stall detection off. A running job with no yak journal event and no log output for that long is killed, process tree included, and marked failed with reason `stalled`. A process that is already gone when `kiln tick` or `kiln serve` starts is `orphaned`. An agent or launch crash is `crashed`. A non-zero exit that is not a gate rejection is `failed`. Those four retry on their own after `10s × 2^(n-1)`, capped at 5 minutes. A rejected gate, a cancelled run, and an ok run with no readable approval do not. After `maxAttempts` the job is `dead`. Each attempt is a file under `<kiln-dir>/attempts/<job-id>.json`, so state stays derived. Status prints `attempts`, `reason`, and `next retry` from the same view `readStatus` returns. On the board, a dead card in Needs human shows `attempt N/M` and the reason. A failure that still has a scheduled retry stays in its stage column instead, with `retrying` and the local time.
|
|
253
273
|
|
|
254
274
|
While a `deliver=pr` job is `in-review`, tick reads that pull request with `gh`: check runs for the head, reviews, and unresolved review comments. The operator's inline comments and unresolved threads count; a top-level note counts only when it starts with `/kiln`, and that prefix is left out of the fix prompt. Comments from `kiln` or a `[bot]` login are ignored, and a resolved thread does not start a round. Pending checks are ignored. A completed failing check on a head that no round has recorded, or review feedback newer than the last round, starts `workflows/fix.yaml` with `yak run --from` that head. The prompt is the original spec plus the failing check names (with a short log excerpt) and the review comment bodies, including file and line. The agent commits on `yak/<run-id>` and does not push. Kiln fast-forwards the open pull request branch. It does not open a second pull request, force-push, merge, approve, or comment. The same head and the same feedback do not start two runs. Each round is a file under `<kiln-dir>/feedback/<job-id>.json`. A stall or crash during a fix uses the attempt ledger above. After `maxFixRounds` the job is `needs-human` with reason `feedback-exhausted`. Feedback Kiln cannot classify is `needs-human` with reason `feedback-ambiguous`. The person merges the pull request.
|
|
255
275
|
|
|
256
|
-
`npm run kiln -- status` lists jobs across repos. When a job has `source`, status prints `source <kind> <ref>`. For `needs-human` it prints `approve: kiln approve <id>` and `reject: kiln reject <id>`. Run those as `npm run kiln -- approve <id>` and `npm run kiln -- reject <id>`. For `done` and `in-review` it prints `branch yak/<run-id>`.
|
|
276
|
+
`npm run kiln -- status` lists jobs across repos. When a job has `source`, status prints `source <kind> <ref>`. For `needs-human` it prints `approve: kiln approve <id>` and `reject: kiln reject <id>`. Run those as `npm run kiln -- approve <id>` and `npm run kiln -- reject <id>`. For `done` and `in-review` it prints the branch. `deliver=local` prints `branch yak/<run-id>`. `deliver=pr` prints the remote branch name recorded on the delivery, and the pull request url once one exists. `auth-failed` prints `Claude login rejected (401) — check CLAUDE_CODE_OAUTH_TOKEN / claude login, then Retry`. For an investigation whose findings file exists, it prints `findings <path>`. That path is `findingsPath` on the same status view `kiln serve` renders, and the console links it. For `blocked` on a dependency id that has no job file, it prints `blocked on missing job <id>`. While an upstream pull request is open, it prints `waiting for PR #N to merge`. `npm run kiln -- retry <id>` relaunches a failed or dead job, or delivers again when the only failure is opening the pull request, and clears the attempt ledger.
|
|
257
277
|
|
|
258
278
|
State is derived from the yak journal, the gate files, and (for `deliver=pr`) `gh pr view`:
|
|
259
279
|
|
|
@@ -265,7 +285,7 @@ State is derived from the yak journal, the gate files, and (for `deliver=pr`) `g
|
|
|
265
285
|
| `needs-human` | The run is suspended on an open gate, or pull request feedback is exhausted (`feedback-exhausted`) or ambiguous (`feedback-ambiguous`) |
|
|
266
286
|
| `approved` | The gate says approve and the run has not finished. This still holds a slot |
|
|
267
287
|
| `rejected` | The run finished ok and the gate says reject |
|
|
268
|
-
| `failed` | The run failed, the launch died before a journal, the run finished ok with no readable approval, a pull request could not be opened, or a transient failure is waiting to retry |
|
|
288
|
+
| `failed` | The run failed, the launch died before a journal, the run finished ok with no readable approval, a pull request could not be opened, a Claude login was rejected (`auth-failed`, not retried), or a transient failure is waiting to retry |
|
|
269
289
|
| `dead` | The attempt budget is used up. Before the next tick, a journal whose process is already gone is also `dead`. Status prints the cancel command when a process is gone, and `kiln retry <id>` once attempts are exhausted |
|
|
270
290
|
| `in-review` | `deliver=pr` and the run has landed. The pull request is open, or the next tick still has to open it |
|
|
271
291
|
| `done` | `deliver=local`: the run finished ok and the gate says approve. `deliver=pr`: that pull request has merged. An investigation is `done` when the gate says approve, including when the repo is `deliver=pr`. It does not enter `in-review` |
|
|
@@ -276,7 +296,7 @@ State is derived from the yak journal, the gate files, and (for `deliver=pr`) `g
|
|
|
276
296
|
|
|
277
297
|
The job runs the factory loop from Lawrence Chase's factory plugin v0.7.0 (MIT), copied from commit `4c84a2ce7fbde5ac298c0de9f0b90141eac4ed98`: spec, then up to two rounds of build, verify, and the security, ux, ui, and code reviewers, then the approver, a human gate, and a commit. The prompts in `prompts/factory/` are those agent files, copied verbatim. `prompts/kiln/` maps the factory's job-folder files onto the artifacts yak passes in. `npm run kiln -- tick` writes a per-run copy of the workflow under the kiln directory, with absolute prompt paths, before it starts yak.
|
|
278
298
|
|
|
279
|
-
Verify runs the repo's `test` command with `failOn: never`. Stdout and stderr stored from verify are cut to the last 200 lines or 20KB. One round is build, then verify, then the four reviewers. `concurrency: 4` starts security, ux, ui, and code together after verify. Build stays first. The approver runs after the loop. Each reviewer needs `spec`, `build`, and `verify`, and inherits `input` so the preamble can still read `baseRef`.
|
|
299
|
+
Verify runs the repo's `test` command with `failOn: never`. Stdout and stderr stored from verify are cut to the last 200 lines or 20KB. One round is build, then verify, then the four reviewers. `concurrency: 4` starts security, ux, ui, and code together after verify. Build stays first. The approver runs after the loop. Each reviewer needs `spec`, `build`, and `verify`, and inherits `input` so the preamble can still read `baseRef`. The reviewer prompts run `git diff <main branch>...HEAD`, so Bash stays. The kiln preambles tell them to use the verify output and not re-run tests, builds, or anything else that writes. Yak 0.8.0 still warns: it treats Bash as write-capable (`WRITE_CAPABLE_TOOLS` in `src/ir/validate.ts`) and has no way to mark a shell step read-only. The only fixes yak offers are `concurrency: 1` or dropping Bash. Neither is right here. Parallel reviews stay, and silencing that warning needs a yak change. A `CHANGES` verdict, a `BLOCKED` build, or a non-zero verify starts another build, at most twice. The loop then continues to the approver. After an approved gate, the run commits on `yak/<run-id>` with message `kiln: <title>`. A reject skips that commit. `adapter: "mock"` reads fixtures at `test/fixtures/kiln-feature/<step>.json` and does not call a model.
|
|
280
300
|
|
|
281
301
|
The first launch adds `.runs/` and `.yak/` to that checkout's `.git/info/exclude`.
|
|
282
302
|
|
|
@@ -294,15 +314,32 @@ A later job whose `dependsOn` includes that investigation receives the findings
|
|
|
294
314
|
|
|
295
315
|
## Delivery and dependencies
|
|
296
316
|
|
|
297
|
-
`deliver` is set on the repo. The default is `local`: the job is `done` when the run lands on `yak/<run-id>`, and Kiln does not push. Existing repos and tests stay on that path.
|
|
317
|
+
`deliver` is set on the repo. The default is `local`: the job is `done` when the run lands on `yak/<run-id>`, and Kiln does not push. Existing repos and tests stay on that path. `kiln repo add` prints that mode. `kiln repo list` shows it. The board card does too.
|
|
298
318
|
|
|
299
319
|
```bash
|
|
300
320
|
npm run kiln -- repo add web /absolute/path/to/web --test "npm test" --deliver pr --base main
|
|
301
321
|
```
|
|
302
322
|
|
|
303
|
-
`deliver` is `local` or `pr`. `base` is the pull request base branch. When `base` is omitted, Kiln uses origin's default branch, from `refs/remotes/origin/HEAD` after `git fetch`.
|
|
323
|
+
`deliver` is `local` or `pr`. `base` is the pull request base branch. When `base` is omitted, Kiln uses origin's default branch, from `refs/remotes/origin/HEAD` after `git fetch`. Running `repo add` again for the same name updates the entry in place.
|
|
324
|
+
|
|
325
|
+
With `deliver=pr`, the tick after an approved land pushes the local `yak/<run-id>` ref to a readable remote branch and runs `gh pr create --draft --head <that name>`. Yak keeps the local branch. The delivery record stores the remote name, and later ticks, including a fix round that fast-forwards the open pull request, use that name. A name already recorded for this run is reused, including an older record that stored `yak/<run-id>`.
|
|
326
|
+
|
|
327
|
+
The default remote name is `<issue>-<slug>` when the job has an issue, and `kiln/<slug>-<short id>` otherwise. `slug` is the title in kebab case, at most 50 characters, with a leading issue prefix removed. `short id` is the four hex digits at the end of a kiln job id. A remote branch that already points at a different commit gets `-<short id>` appended, or the full job id when the short id is already on the name. A branch that already points at this commit is ours.
|
|
328
|
+
|
|
329
|
+
The default pull request title is `[ABC-123] Title` when `issue` is set: template `[{issue}] {title}`. Before that template is applied, a leading `ABC-123:`, `ABC-123 `, or `[ABC-123]` is stripped, so a Jira `KEY: summary` title is not written twice. A job with no issue keeps its title, and a repo `prTitle` template is ignored in that case. Set `prTitle` on the repo (`{issue}` and `{title}`) for another convention. Set `branch` (`{issue}`, `{slug}`, `{jobId}`) for another remote name.
|
|
330
|
+
|
|
331
|
+
```json
|
|
332
|
+
"web": {
|
|
333
|
+
"path": "/absolute/path/to/web",
|
|
334
|
+
"test": "npm test",
|
|
335
|
+
"deliver": "pr",
|
|
336
|
+
"base": "main",
|
|
337
|
+
"prTitle": "[{issue}] {title}",
|
|
338
|
+
"branch": "{issue}-{slug}"
|
|
339
|
+
}
|
|
340
|
+
```
|
|
304
341
|
|
|
305
|
-
|
|
342
|
+
The body is the spec's first paragraph, the line `Kiln job: <id>`, and the security, ux, ui, code, and approver verdicts. The branch, pull request number, and url are written to `~/.kiln/deliveries/<job-id>.json`, not the job file. A later tick looks up an existing pull request for that remote branch and does not push when origin already has the same commit. Deleting the delivery file and ticking again finds that pull request by the remote name. Kiln does not merge, and it does not push the base branch. An investigation is not delivered this way: approve stores findings and the job is `done`.
|
|
306
343
|
|
|
307
344
|
If `gh` is missing or not authenticated, that job becomes `failed` and status prints what to run (`gh auth login`, then `kiln tick`). The rest of the tick still runs. The next tick tries again.
|
|
308
345
|
|
|
@@ -312,7 +349,7 @@ If `gh` is missing or not authenticated, that job becomes `failed` and status pr
|
|
|
312
349
|
|
|
313
350
|
The default is after merged. A job stays `blocked` until every upstream is `done`. Status says `waiting for PR #N to merge` while an upstream pull request is open. A `deliver=pr` worktree is cut from `origin/<base>` after `git fetch`, not from checkout `HEAD`.
|
|
314
351
|
|
|
315
|
-
`--stack` on `kiln add` or `kiln import` waits for one upstream to be approved and landed, then cuts the worktree from that upstream
|
|
352
|
+
`--stack` on `kiln add` or `kiln import` waits for one upstream to be approved and landed, then cuts the worktree from that upstream's local `yak/<run-id>` branch. The pull request targets the upstream's remote branch name. `stack` with any count other than one dependency is an error at add time, and the upstream job has to be in the same repo. Stacking onto an investigation is an error, because that job does not land a branch. Stacked work does not wait for the upstream pull request to merge.
|
|
316
353
|
|
|
317
354
|
```bash
|
|
318
355
|
npm run kiln -- add feature --repo web --spec spec.md --after <job-id> --stack
|
package/assets/serve.css
CHANGED
|
@@ -190,6 +190,7 @@ h1 { margin: 0; font-size: 18px; line-height: 24px; font-weight: 500; }
|
|
|
190
190
|
.card.retrying .meta { color: var(--faint); }
|
|
191
191
|
.card.queued:hover,
|
|
192
192
|
.card.retrying:hover { border-color: var(--line-strong); }
|
|
193
|
+
.card.fresh { border-color: var(--live); box-shadow: 0 0 0 1px var(--live); }
|
|
193
194
|
.card-top { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
|
194
195
|
.num {
|
|
195
196
|
font-family: var(--mono);
|
|
@@ -431,6 +432,98 @@ body.open .drawer { transform: none; }
|
|
|
431
432
|
.verdict.warn { color: var(--changes); background: color-mix(in srgb, var(--changes) 18%, transparent); }
|
|
432
433
|
.verdict.stop { color: var(--human); background: color-mix(in srgb, var(--human) 18%, transparent); }
|
|
433
434
|
|
|
435
|
+
.config-error { margin: 0; padding: 8px 24px 0; color: var(--human); }
|
|
436
|
+
.sheet.intake { height: 100%; display: flex; flex-direction: column; }
|
|
437
|
+
.intake-form {
|
|
438
|
+
flex: 1;
|
|
439
|
+
min-height: 0;
|
|
440
|
+
overflow: auto;
|
|
441
|
+
display: flex;
|
|
442
|
+
flex-direction: column;
|
|
443
|
+
gap: 14px;
|
|
444
|
+
padding: 16px 24px 48px;
|
|
445
|
+
max-width: 560px;
|
|
446
|
+
}
|
|
447
|
+
.intake-form .field { display: flex; flex-direction: column; gap: 6px; margin: 0; border: 0; padding: 0; min-width: 0; }
|
|
448
|
+
.intake-form .k, .intake-form legend.k {
|
|
449
|
+
font-family: var(--mono);
|
|
450
|
+
font-size: 12px;
|
|
451
|
+
line-height: 16px;
|
|
452
|
+
color: var(--muted);
|
|
453
|
+
padding: 0;
|
|
454
|
+
}
|
|
455
|
+
.intake-form select,
|
|
456
|
+
.intake-form input[type="text"],
|
|
457
|
+
.intake-form input[type="number"],
|
|
458
|
+
.intake-form textarea {
|
|
459
|
+
width: 100%;
|
|
460
|
+
font: 14px/20px Roboto, sans-serif;
|
|
461
|
+
color: var(--ink);
|
|
462
|
+
background: var(--bg);
|
|
463
|
+
border: 1px solid var(--line-strong);
|
|
464
|
+
border-radius: 4px;
|
|
465
|
+
padding: 8px 10px;
|
|
466
|
+
}
|
|
467
|
+
.intake-form textarea { resize: vertical; min-height: 88px; }
|
|
468
|
+
.picks, .seg { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
469
|
+
.pick, .seg button, .fs-up, .fs-use {
|
|
470
|
+
height: 28px;
|
|
471
|
+
padding: 0 12px;
|
|
472
|
+
border: 1px solid var(--line-strong);
|
|
473
|
+
border-radius: 4px;
|
|
474
|
+
background: none;
|
|
475
|
+
color: var(--muted);
|
|
476
|
+
font: 12px/16px var(--mono);
|
|
477
|
+
cursor: pointer;
|
|
478
|
+
}
|
|
479
|
+
.pick[aria-pressed="true"], .seg button[aria-pressed="true"] {
|
|
480
|
+
border-color: transparent;
|
|
481
|
+
background: color-mix(in srgb, var(--merged) 22%, transparent);
|
|
482
|
+
color: var(--ink);
|
|
483
|
+
}
|
|
484
|
+
.pick:hover, .seg button:hover, .fs-up:hover, .fs-use:hover, .fs-item:hover { background: var(--card-hover); color: var(--ink); }
|
|
485
|
+
.fs { display: flex; flex-direction: column; gap: 8px; }
|
|
486
|
+
.fs-list { display: flex; flex-direction: column; max-height: 220px; overflow: auto; border: 1px solid var(--line); border-radius: 4px; }
|
|
487
|
+
.fs-item {
|
|
488
|
+
text-align: left;
|
|
489
|
+
background: none;
|
|
490
|
+
border: 0;
|
|
491
|
+
border-bottom: 1px solid var(--line);
|
|
492
|
+
color: var(--ink);
|
|
493
|
+
font: 12px/16px var(--mono);
|
|
494
|
+
padding: 6px 10px;
|
|
495
|
+
cursor: pointer;
|
|
496
|
+
}
|
|
497
|
+
.fs-item:last-child { border-bottom: 0; }
|
|
498
|
+
#after-list { max-height: 140px; overflow: auto; }
|
|
499
|
+
.after { display: flex; gap: 8px; align-items: baseline; margin: 4px 0; font-size: 13px; cursor: pointer; }
|
|
500
|
+
.after .num { font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
|
501
|
+
.form-error { margin: 0; color: var(--human); font-size: 12px; line-height: 16px; }
|
|
502
|
+
.intake-form .preview { margin: 0; max-height: 200px; overflow: auto; }
|
|
503
|
+
.intake-form .d-actions button[type="submit"] {
|
|
504
|
+
color: var(--ink);
|
|
505
|
+
background: var(--primary);
|
|
506
|
+
border-color: transparent;
|
|
507
|
+
}
|
|
508
|
+
#settings-form { max-width: 640px; }
|
|
509
|
+
#settings-path { margin: 0; }
|
|
510
|
+
.saved { margin: 0; color: var(--pass); font-family: var(--mono); font-size: 12px; line-height: 16px; }
|
|
511
|
+
.hint { margin: 0; color: var(--faint); font-size: 12px; line-height: 16px; }
|
|
512
|
+
.field-error { margin: 0; color: var(--human); font-size: 12px; line-height: 16px; }
|
|
513
|
+
.path-row { display: flex; gap: 8px; align-items: center; }
|
|
514
|
+
.path-row input { flex: 1; min-width: 0; }
|
|
515
|
+
.repo-row {
|
|
516
|
+
display: flex;
|
|
517
|
+
justify-content: space-between;
|
|
518
|
+
gap: 12px;
|
|
519
|
+
align-items: flex-start;
|
|
520
|
+
padding: 10px 0;
|
|
521
|
+
border-bottom: 1px solid var(--line);
|
|
522
|
+
}
|
|
523
|
+
.repo-row .num { font-family: var(--mono); font-size: 13px; }
|
|
524
|
+
.repo-row .d-actions { margin-top: 0; flex: none; }
|
|
525
|
+
#repo-editor { padding-top: 8px; }
|
|
526
|
+
#settings-error { white-space: pre-wrap; }
|
|
434
527
|
.back { display: inline-block; margin: 24px 24px 0; }
|
|
435
528
|
.page { padding: 0 24px 48px; }
|
|
436
529
|
.text { font-family: var(--mono); font-size: 13px; white-space: pre-wrap; overflow-wrap: anywhere; }
|