@lchase/kiln 0.6.1 → 0.8.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 +47 -18
- 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/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 +2 -1
- package/workflows/feature.yaml +7 -4
package/README.md
CHANGED
|
@@ -22,6 +22,8 @@ If `./.kiln/kiln.config.json` is still in the working directory and the home con
|
|
|
22
22
|
|
|
23
23
|
## Develop from a checkout
|
|
24
24
|
|
|
25
|
+
From `packages/kiln`:
|
|
26
|
+
|
|
25
27
|
```bash
|
|
26
28
|
npm install
|
|
27
29
|
npm run build
|
|
@@ -92,7 +94,9 @@ A linked worktree's `.git` file points at the checkout's common git dir, which i
|
|
|
92
94
|
|
|
93
95
|
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
96
|
|
|
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. It does not print or write the value.
|
|
97
|
+
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.
|
|
98
|
+
|
|
99
|
+
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.
|
|
96
100
|
|
|
97
101
|
```bash
|
|
98
102
|
claude setup-token
|
|
@@ -117,6 +121,8 @@ npm run kiln -- repo list
|
|
|
117
121
|
|
|
118
122
|
`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.
|
|
119
123
|
|
|
124
|
+
`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.
|
|
125
|
+
|
|
120
126
|
```json
|
|
121
127
|
{
|
|
122
128
|
"repos": {
|
|
@@ -138,9 +144,10 @@ A job is one workflow run. The workflow name is a file `workflows/<name>.yaml`.
|
|
|
138
144
|
npm run kiln -- add feature --repo web --spec spec.md --title "Show a Sunday summary"
|
|
139
145
|
npm run kiln -- add feature --repo api --spec api-spec.md --after <job-id>
|
|
140
146
|
npm run kiln -- add feature --repo web --jira PROJ-123
|
|
147
|
+
npm run kiln -- add feature --repo web --spec spec.md --issue ABC-123
|
|
141
148
|
```
|
|
142
149
|
|
|
143
|
-
`--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`:
|
|
150
|
+
`--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`:
|
|
144
151
|
|
|
145
152
|
```json
|
|
146
153
|
{
|
|
@@ -149,17 +156,18 @@ npm run kiln -- add feature --repo web --jira PROJ-123
|
|
|
149
156
|
"workflow": "feature",
|
|
150
157
|
"spec": "specs/j20261025020600ab12.md",
|
|
151
158
|
"title": "Show a Sunday summary",
|
|
159
|
+
"issue": "ABC-123",
|
|
152
160
|
"dependsOn": ["j20261025020500cd34"],
|
|
153
161
|
"source": { "kind": "file", "ref": "/home/dev/specs/sunday.md" },
|
|
154
162
|
"createdAt": "2026-10-25T02:06:00.000Z"
|
|
155
163
|
}
|
|
156
164
|
```
|
|
157
165
|
|
|
158
|
-
`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/`.
|
|
166
|
+
`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/`.
|
|
159
167
|
|
|
160
168
|
## Intake
|
|
161
169
|
|
|
162
|
-
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`.
|
|
170
|
+
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`.
|
|
163
171
|
|
|
164
172
|
### File
|
|
165
173
|
|
|
@@ -185,7 +193,7 @@ npm run kiln -- import ./tickets --repo web
|
|
|
185
193
|
npm run kiln -- import ./tickets --repo web --workflow feature --context spec.md --dry-run
|
|
186
194
|
```
|
|
187
195
|
|
|
188
|
-
`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).
|
|
196
|
+
`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.
|
|
189
197
|
|
|
190
198
|
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.
|
|
191
199
|
|
|
@@ -202,7 +210,7 @@ npm run kiln -- add feature --repo web --jira PROJ-123
|
|
|
202
210
|
npm run kiln -- add feature --repo web --jira PROJ-123 --after <job-id> --dry-run
|
|
203
211
|
```
|
|
204
212
|
|
|
205
|
-
`--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
|
|
213
|
+
`--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.
|
|
206
214
|
|
|
207
215
|
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:
|
|
208
216
|
|
|
@@ -228,17 +236,21 @@ kiln serve --port 4100 --tick 30
|
|
|
228
236
|
kiln serve --no-tick
|
|
229
237
|
```
|
|
230
238
|
|
|
231
|
-
`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
|
|
239
|
+
`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.
|
|
232
240
|
|
|
233
|
-
|
|
241
|
+
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.
|
|
242
|
+
|
|
243
|
+
`/` 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.
|
|
234
244
|
|
|
235
245
|
`GET /jobs/<id>/findings` renders the approved findings markdown as text. HTML in that file is escaped. The page is read-only.
|
|
236
246
|
|
|
237
|
-
`GET /api/jobs` and `GET /api/jobs/<id>` return that view as JSON, including the attention sections. `
|
|
247
|
+
`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.
|
|
248
|
+
|
|
249
|
+
`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).
|
|
238
250
|
|
|
239
251
|
`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.
|
|
240
252
|
|
|
241
|
-
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.
|
|
253
|
+
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.
|
|
242
254
|
|
|
243
255
|
The Jira API token is not rendered. Do not commit checkout paths or job files.
|
|
244
256
|
|
|
@@ -257,13 +269,13 @@ npm run kiln -- reject <job-id>
|
|
|
257
269
|
while true; do npm run kiln -- tick; sleep 30; done
|
|
258
270
|
```
|
|
259
271
|
|
|
260
|
-
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.
|
|
272
|
+
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`.
|
|
261
273
|
|
|
262
274
|
`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.
|
|
263
275
|
|
|
264
276
|
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.
|
|
265
277
|
|
|
266
|
-
`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>`.
|
|
278
|
+
`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.
|
|
267
279
|
|
|
268
280
|
State is derived from the yak journal, the gate files, and (for `deliver=pr`) `gh pr view`:
|
|
269
281
|
|
|
@@ -275,7 +287,7 @@ State is derived from the yak journal, the gate files, and (for `deliver=pr`) `g
|
|
|
275
287
|
| `needs-human` | The run is suspended on an open gate, or pull request feedback is exhausted (`feedback-exhausted`) or ambiguous (`feedback-ambiguous`) |
|
|
276
288
|
| `approved` | The gate says approve and the run has not finished. This still holds a slot |
|
|
277
289
|
| `rejected` | The run finished ok and the gate says reject |
|
|
278
|
-
| `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 |
|
|
290
|
+
| `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 |
|
|
279
291
|
| `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 |
|
|
280
292
|
| `in-review` | `deliver=pr` and the run has landed. The pull request is open, or the next tick still has to open it |
|
|
281
293
|
| `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` |
|
|
@@ -286,7 +298,7 @@ State is derived from the yak journal, the gate files, and (for `deliver=pr`) `g
|
|
|
286
298
|
|
|
287
299
|
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.
|
|
288
300
|
|
|
289
|
-
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`.
|
|
301
|
+
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.
|
|
290
302
|
|
|
291
303
|
The first launch adds `.runs/` and `.yak/` to that checkout's `.git/info/exclude`.
|
|
292
304
|
|
|
@@ -304,15 +316,32 @@ A later job whose `dependsOn` includes that investigation receives the findings
|
|
|
304
316
|
|
|
305
317
|
## Delivery and dependencies
|
|
306
318
|
|
|
307
|
-
`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.
|
|
319
|
+
`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.
|
|
308
320
|
|
|
309
321
|
```bash
|
|
310
322
|
npm run kiln -- repo add web /absolute/path/to/web --test "npm test" --deliver pr --base main
|
|
311
323
|
```
|
|
312
324
|
|
|
313
|
-
`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`.
|
|
325
|
+
`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.
|
|
326
|
+
|
|
327
|
+
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>`.
|
|
328
|
+
|
|
329
|
+
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.
|
|
330
|
+
|
|
331
|
+
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.
|
|
332
|
+
|
|
333
|
+
```json
|
|
334
|
+
"web": {
|
|
335
|
+
"path": "/absolute/path/to/web",
|
|
336
|
+
"test": "npm test",
|
|
337
|
+
"deliver": "pr",
|
|
338
|
+
"base": "main",
|
|
339
|
+
"prTitle": "[{issue}] {title}",
|
|
340
|
+
"branch": "{issue}-{slug}"
|
|
341
|
+
}
|
|
342
|
+
```
|
|
314
343
|
|
|
315
|
-
|
|
344
|
+
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`.
|
|
316
345
|
|
|
317
346
|
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.
|
|
318
347
|
|
|
@@ -322,7 +351,7 @@ If `gh` is missing or not authenticated, that job becomes `failed` and status pr
|
|
|
322
351
|
|
|
323
352
|
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`.
|
|
324
353
|
|
|
325
|
-
`--stack` on `kiln add` or `kiln import` waits for one upstream to be approved and landed, then cuts the worktree from that upstream
|
|
354
|
+
`--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.
|
|
326
355
|
|
|
327
356
|
```bash
|
|
328
357
|
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; }
|