@lchase/kiln 0.0.0-stage → 0.3.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 +263 -2
- package/assets/serve.css +233 -0
- package/dist/cli.js +409 -0
- package/dist/config.js +255 -0
- package/dist/delivery.js +245 -0
- package/dist/driver.js +825 -0
- package/dist/errors.js +6 -0
- package/dist/findings.js +79 -0
- package/dist/host.js +223 -0
- package/dist/intake/file.js +29 -0
- package/dist/intake/jira.js +585 -0
- package/dist/intake/prompt.js +47 -0
- package/dist/intake/tickets.js +448 -0
- package/dist/jobs.js +154 -0
- package/dist/paths.js +34 -0
- package/dist/reconcile.js +141 -0
- package/dist/report.js +254 -0
- package/dist/schemas.js +95 -0
- package/dist/serve-page.js +216 -0
- package/dist/serve.js +351 -0
- package/dist/truncate-output.js +122 -0
- package/dist/workflow.js +131 -0
- package/package.json +41 -4
- package/prompts/factory/approver.md +49 -0
- package/prompts/factory/builder.md +76 -0
- package/prompts/factory/review-code.md +55 -0
- package/prompts/factory/review-security.md +50 -0
- package/prompts/factory/review-ui.md +50 -0
- package/prompts/factory/review-ux.md +48 -0
- package/prompts/factory/spec-writer.md +67 -0
- package/prompts/kiln/approver.md +33 -0
- package/prompts/kiln/build.md +38 -0
- package/prompts/kiln/code.md +26 -0
- package/prompts/kiln/investigation.md +31 -0
- package/prompts/kiln/security.md +26 -0
- package/prompts/kiln/spec.md +18 -0
- package/prompts/kiln/ui.md +26 -0
- package/prompts/kiln/ux.md +26 -0
- package/workflows/feature.yaml +239 -0
- package/workflows/investigation.yaml +67 -0
package/README.md
CHANGED
|
@@ -1,3 +1,264 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Kiln
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Kiln runs a workflow against a git checkout. Register any repo, add a job from a markdown spec, a prompt, a directory of tickets, or a Jira Cloud issue, and tick. Tick launches ready jobs in yak worktrees. Status shows each job. Approve or reject answers a human gate. `kiln serve` opens that same derived state in a browser on this machine.
|
|
4
|
+
|
|
5
|
+
A ready spec is the input. Kiln does not build or iterate requirements.
|
|
6
|
+
|
|
7
|
+
This repository is public. Checkout paths and jobs stay in `~/.kiln/` (or `$KILN_HOME` when that is set). Do not commit them.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g @lchase/kiln
|
|
13
|
+
kiln repo add web /absolute/path/to/web --test "npm test"
|
|
14
|
+
kiln repo list
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Run `kiln repo add <name> <path>` from any directory. The first run creates `~/.kiln/kiln.config.json`. Jobs, logs, and deliveries live in that same directory, whichever directory you run `kiln` from. Set `KILN_HOME` or pass `--config <file>` to use a different kiln home.
|
|
18
|
+
|
|
19
|
+
If `./.kiln/kiln.config.json` is still in the working directory and the home config does not exist, kiln stops. Move it with `mv ./.kiln ~/.kiln`, or pass `--config` / set `KILN_HOME`. Kiln does not move it for you.
|
|
20
|
+
|
|
21
|
+
`kiln` is the command. The subcommands in the rest of this file are the same ones. Examples that say `npm run kiln --` are the in-checkout form.
|
|
22
|
+
|
|
23
|
+
## Develop from a checkout
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install
|
|
27
|
+
npm run build
|
|
28
|
+
npm link
|
|
29
|
+
kiln repo list
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`npm link` puts this checkout's `kiln` on your PATH. The bin is `dist/cli.js`, so build before linking, and rebuild after source changes. `npm run kiln --` builds and runs without a global link.
|
|
33
|
+
|
|
34
|
+
Workflows (`workflows/*.yaml`) and prompts (`prompts/`) are read from the package that contains the running file, next to its `package.json`. A global install and `npm link` both resolve them from that package, not from the directory you ran `kiln` in.
|
|
35
|
+
|
|
36
|
+
## Prerequisites
|
|
37
|
+
|
|
38
|
+
- Node.js >= 22 (the `engines` field in `package.json`)
|
|
39
|
+
- git
|
|
40
|
+
- Claude Code, installed and logged in, for a real run. `adapter: "mock"` does not call a model.
|
|
41
|
+
- `gh`, installed and logged in, only when a repo uses `deliver: "pr"`.
|
|
42
|
+
|
|
43
|
+
Each checkout is a git repo with a commit at `HEAD`. `tick` adds a worktree and leaves uncommitted files in the checkout as they are. The job edits the worktree, on branch `yak/<run-id>`. With the default `deliver: "local"`, Kiln does not push. `deliver: "pr"` pushes that branch and opens a draft pull request. See [Delivery and dependencies](#delivery-and-dependencies).
|
|
44
|
+
|
|
45
|
+
## Register repos
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install
|
|
49
|
+
npm run kiln -- repo add web /absolute/path/to/web --test "npm test"
|
|
50
|
+
npm run kiln -- repo add api /absolute/path/to/api --test "npm test"
|
|
51
|
+
npm run kiln -- repo list
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`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.
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"repos": {
|
|
59
|
+
"web": { "path": "/absolute/path/to/web", "test": "npm test" },
|
|
60
|
+
"api": { "path": "~/src/api", "test": "npm test" }
|
|
61
|
+
},
|
|
62
|
+
"maxConcurrent": 2,
|
|
63
|
+
"adapter": "claude-code"
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`repos` is an open map. Any name is allowed. `maxConcurrent` caps how many jobs may be in flight (default 2). `adapter` is `claude-code` (default) or `mock`.
|
|
68
|
+
|
|
69
|
+
## Add a job
|
|
70
|
+
|
|
71
|
+
A job is one workflow run. The workflow name is a file `workflows/<name>.yaml`. `feature` is the factory loop. `investigation` answers a spike and stops for a person to approve the findings. Bug and security workflows are next. A later `pr-review` workflow is not in this repo yet.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm run kiln -- add feature --repo web --spec spec.md --title "Show a Sunday summary"
|
|
75
|
+
npm run kiln -- add feature --repo api --spec api-spec.md --after <job-id>
|
|
76
|
+
npm run kiln -- add feature --repo web --jira PROJ-123
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`--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`:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"id": "j20261025020600ab12",
|
|
84
|
+
"repo": "web",
|
|
85
|
+
"workflow": "feature",
|
|
86
|
+
"spec": "specs/j20261025020600ab12.md",
|
|
87
|
+
"title": "Show a Sunday summary",
|
|
88
|
+
"dependsOn": ["j20261025020500cd34"],
|
|
89
|
+
"source": { "kind": "file", "ref": "/home/dev/specs/sunday.md" },
|
|
90
|
+
"createdAt": "2026-10-25T02:06:00.000Z"
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`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/`.
|
|
95
|
+
|
|
96
|
+
## Intake
|
|
97
|
+
|
|
98
|
+
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`.
|
|
99
|
+
|
|
100
|
+
### File
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm run kiln -- add feature --repo web --spec spec.md --title "Show a Sunday summary"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This copies the markdown file into the kiln directory. `source.kind` is `file`. `source.ref` is the absolute path of the file that was copied. `--title` defaults to the first `#` heading, then the file name. `--after` may be repeated, and those jobs must already exist.
|
|
107
|
+
|
|
108
|
+
### Prompt
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npm run kiln -- add feature --repo web --prompt "Show a quiet empty state"
|
|
112
|
+
npm run kiln -- add feature --repo api --prompt - < notes.md
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`--prompt -` reads stdin. The text is stored as that job's spec. The title defaults to the first non-empty line, truncated to 80 characters. `source.kind` is `prompt`. `source.ref` is `inline` for an argument and `stdin` when the prompt is read from stdin. `--title` and `--after` work the same way as file intake. Pass one of `--spec`, `--prompt`, or `--jira`.
|
|
116
|
+
|
|
117
|
+
### Tickets
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm run kiln -- import ./tickets --repo web
|
|
121
|
+
npm run kiln -- import ./tickets --repo web --workflow feature --context spec.md --dry-run
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`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).
|
|
125
|
+
|
|
126
|
+
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.
|
|
127
|
+
|
|
128
|
+
`--workflow` defaults to `feature`. Kiln switches a ticket to `investigation` when the ticket is a spike: front matter `type` is `spike`, `investigation`, or `research` (the word after a colon counts, as in `wayfinder:research`), the title contains spike or probe, or the text says "no product code". A body that only mentions an upstream spike stays on the requested workflow.
|
|
129
|
+
|
|
130
|
+
`--context` defaults to `spec.md` in the parent of the ticket directory when that file is present. The text is appended to every job spec as a `## Background` section. A context file larger than 40KB is not copied in. The spec names its path instead.
|
|
131
|
+
|
|
132
|
+
Import is idempotent. The key is the ticket directory plus the filename, stored as `source.ref` (an absolute path) with `source.kind` `tickets`. Running import again on the same directory does not create a second job for a file already imported. The report says how many jobs are new and how many are unchanged. `--dry-run` prints each ticket, its workflow, and its dependencies, and writes nothing.
|
|
133
|
+
|
|
134
|
+
### Jira
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npm run kiln -- add feature --repo web --jira PROJ-123
|
|
138
|
+
npm run kiln -- add feature --repo web --jira PROJ-123 --after <job-id> --dry-run
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`--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>`. The spec is that heading, a `Source:` line with the issue's browse URL, then the description converted from Atlassian document format to markdown.
|
|
142
|
+
|
|
143
|
+
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:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
"jira": {
|
|
147
|
+
"baseUrl": "https://acme.atlassian.net",
|
|
148
|
+
"email": "dev@example.com",
|
|
149
|
+
"apiToken": "<token>"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Create a token at <https://id.atlassian.com/manage-profile/security/api-tokens>. Kiln sends it as Basic auth and does not write it into the job, the spec, or error text. A missing value is an error that names `JIRA_BASE_URL`, `JIRA_EMAIL`, or `JIRA_API_TOKEN`.
|
|
154
|
+
|
|
155
|
+
A Blocks link where this issue is blocked by another key becomes `dependsOn` when a kiln job with `source` `jira` and that key already exists. Otherwise Kiln prints a warning naming the key and still creates the job. `--after` is merged with those ids. Issue type Spike or Research uses the same rule as a spike ticket and is imported as `investigation`. Running add again for the same key prints the existing job id and creates nothing. `--dry-run` prints the job and writes nothing.
|
|
156
|
+
|
|
157
|
+
`examples/spec.md` is a fictional spec you can copy.
|
|
158
|
+
|
|
159
|
+
## Serve
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
kiln serve
|
|
163
|
+
kiln serve --port 4100 --tick 30
|
|
164
|
+
kiln serve --no-tick
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`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 and reject functions. Nothing new is stored.
|
|
168
|
+
|
|
169
|
+
`/` is the inbox: jobs in `needs-human`, with the gate prompt, the artifacts and review output yak wrote, links to the spec and the log, and Approve / Reject. An investigation job also links to its findings. `/board` groups every job by state, shows `dependsOn` (including blocked-by), the stacked flag, the pull request for `in-review` and `done`, and the failure or launch error for `failed`. Both pages refresh every 3 seconds.
|
|
170
|
+
|
|
171
|
+
`GET /jobs/<id>/findings` renders the approved findings markdown as text. HTML in that file is escaped. The page is read-only.
|
|
172
|
+
|
|
173
|
+
`GET /api/jobs` and `GET /api/jobs/<id>` return that view as JSON. `POST /api/jobs/<id>/approve` and `POST /api/jobs/<id>/reject` answer the gate. A browser POST from another origin is rejected. The page and the stylesheet ship inside the package (`assets/serve.css`, read from the package root).
|
|
174
|
+
|
|
175
|
+
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. There is no intake form in this UI yet.
|
|
176
|
+
|
|
177
|
+
The Jira API token is not rendered. Do not commit checkout paths or job files.
|
|
178
|
+
|
|
179
|
+
## Tick, status, approve
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npm run kiln -- tick
|
|
183
|
+
npm run kiln -- status
|
|
184
|
+
npm run kiln -- approve <job-id>
|
|
185
|
+
npm run kiln -- reject <job-id>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`tick` launches at most one ready job and returns. Run it again until nothing is left to launch:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
while true; do npm run kiln -- tick; sleep 30; done
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
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.
|
|
195
|
+
|
|
196
|
+
`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>`. For `deliver=pr` it also prints the pull request url once one exists. 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 inbox and board link 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`.
|
|
197
|
+
|
|
198
|
+
State is derived from the yak journal, the gate files, and (for `deliver=pr`) `gh pr view`:
|
|
199
|
+
|
|
200
|
+
| State | Meaning |
|
|
201
|
+
| --- | --- |
|
|
202
|
+
| `queued` | Dependencies are met, and the job has not started |
|
|
203
|
+
| `blocked` | An after-merged dependency is not `done`, or a stacked dependency has not landed. A missing job id is named |
|
|
204
|
+
| `running` | Yak is alive, or the launch process is alive and has not written a journal |
|
|
205
|
+
| `needs-human` | The run is suspended on an open gate |
|
|
206
|
+
| `approved` | The gate says approve and the run has not finished. This still holds a slot |
|
|
207
|
+
| `rejected` | The run finished ok and the gate says reject |
|
|
208
|
+
| `failed` | The run failed, the launch died before a journal, the run finished ok with no readable approval, or a pull request could not be opened |
|
|
209
|
+
| `dead` | The journal's process is gone. Status prints the cancel command |
|
|
210
|
+
| `in-review` | `deliver=pr` and the run has landed. The pull request is open, or the next tick still has to open it |
|
|
211
|
+
| `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` |
|
|
212
|
+
|
|
213
|
+
## What a feature job runs
|
|
214
|
+
|
|
215
|
+
`npm run kiln -- tick` starts `yak run --isolation worktree --from <sha>` in the repo, tagged `kiln:<jobId>`, with logs in `<kiln-dir>/logs/<jobId>.log`. Kiln passes `title`, the spec text as `description` and `acceptance`, `baseRef` (the commit the worktree is cut from), and `runNonce`. For `deliver=local` that commit is checkout `HEAD`. For `deliver=pr` it is `origin/<base>` after `git fetch`, or the upstream branch tip when the job is stacked. The test command is `KILN_TEST`. `--from` is that same commit. Yak cuts the worktree from it and leaves the checkout's `HEAD` and files where they are.
|
|
216
|
+
|
|
217
|
+
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.
|
|
218
|
+
|
|
219
|
+
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`. Yak warns that those four Bash steps share one working tree and may race. That warning is expected: the reviewer preambles say to use the verify output and not re-run tests, builds, or anything else that writes. Git diff and file reads stay allowed. 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.
|
|
220
|
+
|
|
221
|
+
The first launch adds `.runs/` and `.yak/` to that checkout's `.git/info/exclude`.
|
|
222
|
+
|
|
223
|
+
## What an investigation job runs
|
|
224
|
+
|
|
225
|
+
A spike, probe, or research ticket runs `workflows/investigation.yaml`. One agent explores the worktree. It may run commands and scripts. It must not change product code: the step has no Write or Edit tool, and the prompt forbids creating, editing, deleting, committing, and pushing. The agent returns a markdown document with four sections: Questions, Answers (each answer includes evidence: file paths and command output), Unconfirmed, and Implications for dependent tickets.
|
|
226
|
+
|
|
227
|
+
The run then suspends on a human gate. There is no build, review, verify, or land step. `npm run kiln -- approve <job-id>` stores that markdown at `<kiln-dir>/findings/<job-id>.md` and the job is `done`. Writing the same text again does not change the file. `npm run kiln -- reject <job-id>` is `rejected` and writes no findings file. A rejected investigation does not unblock a dependent.
|
|
228
|
+
|
|
229
|
+
`deliver=pr` does not change this. Kiln does not push the investigation branch and does not run `gh pr create`.
|
|
230
|
+
|
|
231
|
+
A later job whose `dependsOn` includes that investigation receives the findings in the spec yak sees. Kiln appends a `## Findings from <title>` section for each investigation dependency. The copy under `<kiln-dir>/specs/` is left as it was. After merged still waits until the investigation is `done`. `--stack` onto an investigation is an error: there is no branch to target.
|
|
232
|
+
|
|
233
|
+
`adapter: "mock"` reads `test/fixtures/kiln-investigation/explore.json` from the checkout. The prompt is `prompts/kiln/investigation.md`. The published package includes `prompts` and `workflows`.
|
|
234
|
+
|
|
235
|
+
## Delivery and dependencies
|
|
236
|
+
|
|
237
|
+
`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.
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
npm run kiln -- repo add web /absolute/path/to/web --test "npm test" --deliver pr --base main
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`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`.
|
|
244
|
+
|
|
245
|
+
With `deliver=pr`, the tick after an approved land pushes `yak/<run-id>` and runs `gh pr create --draft`. The title is the job title. 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 branch and does not push when origin already has the same commit. 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`.
|
|
246
|
+
|
|
247
|
+
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.
|
|
248
|
+
|
|
249
|
+
`done` for `deliver=pr` is `gh pr view --json state,mergedAt`. A squash merge counts. Kiln does not ask git whether the landed commit is an ancestor of the base.
|
|
250
|
+
|
|
251
|
+
### After merged and stacked
|
|
252
|
+
|
|
253
|
+
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`.
|
|
254
|
+
|
|
255
|
+
`--stack` on `kiln add` or `kiln import` waits for one upstream to be approved and landed, then cuts the worktree from that upstream branch (`yak/<run-id>`). The pull request targets that branch. `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.
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
npm run kiln -- add feature --repo web --spec spec.md --after <job-id> --stack
|
|
259
|
+
npm run kiln -- import ./tickets --repo web --stack
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Not included
|
|
263
|
+
|
|
264
|
+
No requirement refinement. Bug, security, and pr-review workflows are not in this repo yet. Kiln does not merge pull requests and does not push the base branch.
|
package/assets/serve.css
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/* kiln-serve */
|
|
2
|
+
:root {
|
|
3
|
+
color-scheme: light;
|
|
4
|
+
--bg: #efe6d6;
|
|
5
|
+
--bg-2: #e7dcc8;
|
|
6
|
+
--card: #fffaf3;
|
|
7
|
+
--ink: #241c16;
|
|
8
|
+
--muted: #6f6256;
|
|
9
|
+
--line: #e0d3c2;
|
|
10
|
+
--accent: #c2410c;
|
|
11
|
+
--accent-ink: #fff7f2;
|
|
12
|
+
--good: #1f6b45;
|
|
13
|
+
--bad: #9b2c2c;
|
|
14
|
+
--chip: #f3e7d6;
|
|
15
|
+
--shadow: 0 1px 0 rgba(36, 28, 22, 0.04);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
* { box-sizing: border-box; }
|
|
19
|
+
|
|
20
|
+
body {
|
|
21
|
+
margin: 0;
|
|
22
|
+
background:
|
|
23
|
+
radial-gradient(1200px 400px at 10% -10%, #f7efe2, transparent 50%),
|
|
24
|
+
var(--bg);
|
|
25
|
+
color: var(--ink);
|
|
26
|
+
font: 15px/1.45 ui-sans-serif, system-ui, sans-serif;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
a { color: inherit; }
|
|
30
|
+
|
|
31
|
+
.top {
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: baseline;
|
|
34
|
+
gap: 1.25rem;
|
|
35
|
+
padding: 1rem 1.5rem;
|
|
36
|
+
border-bottom: 1px solid var(--line);
|
|
37
|
+
background: rgba(255, 250, 243, 0.9);
|
|
38
|
+
position: sticky;
|
|
39
|
+
top: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.kiln-mark {
|
|
43
|
+
font-weight: 700;
|
|
44
|
+
letter-spacing: 0.08em;
|
|
45
|
+
text-transform: uppercase;
|
|
46
|
+
text-decoration: none;
|
|
47
|
+
color: var(--accent);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
nav { display: flex; gap: 0.9rem; }
|
|
51
|
+
|
|
52
|
+
nav a {
|
|
53
|
+
text-decoration: none;
|
|
54
|
+
color: var(--muted);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
nav a[aria-current="page"] {
|
|
58
|
+
color: var(--ink);
|
|
59
|
+
font-weight: 650;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.refresh {
|
|
63
|
+
margin: 0 0 0 auto;
|
|
64
|
+
color: var(--muted);
|
|
65
|
+
font-size: 0.85rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
main {
|
|
69
|
+
width: min(980px, calc(100% - 2rem));
|
|
70
|
+
margin: 1.25rem auto 3rem;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
h1 {
|
|
74
|
+
font-size: 1.35rem;
|
|
75
|
+
font-weight: 650;
|
|
76
|
+
margin: 0 0 0.35rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.lede {
|
|
80
|
+
margin: 0 0 1.25rem;
|
|
81
|
+
color: var(--muted);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.empty {
|
|
85
|
+
padding: 1.25rem;
|
|
86
|
+
background: var(--card);
|
|
87
|
+
border: 1px solid var(--line);
|
|
88
|
+
border-radius: 12px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.card, .job {
|
|
92
|
+
background: var(--card);
|
|
93
|
+
border: 1px solid var(--line);
|
|
94
|
+
border-radius: 12px;
|
|
95
|
+
box-shadow: var(--shadow);
|
|
96
|
+
padding: 1rem 1.1rem 1.05rem;
|
|
97
|
+
margin: 0 0 0.85rem;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.card h2, .job h3 {
|
|
101
|
+
margin: 0;
|
|
102
|
+
font-size: 1.15rem;
|
|
103
|
+
font-weight: 650;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.meta, .deps, .links, .why {
|
|
107
|
+
color: var(--muted);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.meta { margin: 0.25rem 0 0.75rem; }
|
|
111
|
+
|
|
112
|
+
.meta code, .deps a {
|
|
113
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
114
|
+
font-size: 0.86em;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.prompt {
|
|
118
|
+
margin: 0 0 0.75rem;
|
|
119
|
+
padding: 0.7rem 0.8rem;
|
|
120
|
+
background: #fff;
|
|
121
|
+
border-left: 3px solid var(--accent);
|
|
122
|
+
white-space: pre-wrap;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.facts {
|
|
126
|
+
list-style: none;
|
|
127
|
+
margin: 0 0 0.75rem;
|
|
128
|
+
padding: 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.facts li { margin: 0.2rem 0; }
|
|
132
|
+
|
|
133
|
+
.facts .name {
|
|
134
|
+
display: inline-block;
|
|
135
|
+
min-width: 5.5rem;
|
|
136
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
137
|
+
font-size: 0.86em;
|
|
138
|
+
color: var(--muted);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
h4 {
|
|
142
|
+
margin: 0.4rem 0 0.2rem;
|
|
143
|
+
font-size: 0.78rem;
|
|
144
|
+
letter-spacing: 0.06em;
|
|
145
|
+
text-transform: uppercase;
|
|
146
|
+
color: var(--muted);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.links a { margin-right: 0.9rem; }
|
|
150
|
+
|
|
151
|
+
.actions { display: flex; gap: 0.5rem; margin-top: 0.85rem; }
|
|
152
|
+
|
|
153
|
+
button {
|
|
154
|
+
font: inherit;
|
|
155
|
+
border-radius: 8px;
|
|
156
|
+
padding: 0.4rem 0.85rem;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
button.approve {
|
|
161
|
+
background: var(--accent);
|
|
162
|
+
color: var(--accent-ink);
|
|
163
|
+
border: 1px solid var(--accent);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
button.reject {
|
|
167
|
+
background: transparent;
|
|
168
|
+
color: var(--ink);
|
|
169
|
+
border: 1px solid var(--line);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.group { margin: 0 0 1.4rem; }
|
|
173
|
+
|
|
174
|
+
.group h2 {
|
|
175
|
+
font-size: 0.95rem;
|
|
176
|
+
margin: 0 0 0.45rem;
|
|
177
|
+
display: flex;
|
|
178
|
+
gap: 0.5rem;
|
|
179
|
+
align-items: baseline;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.count {
|
|
183
|
+
color: var(--muted);
|
|
184
|
+
font-weight: 450;
|
|
185
|
+
font-size: 0.85rem;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.chips { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0.45rem 0; }
|
|
189
|
+
|
|
190
|
+
.chip {
|
|
191
|
+
background: var(--chip);
|
|
192
|
+
border-radius: 999px;
|
|
193
|
+
padding: 0.1rem 0.5rem;
|
|
194
|
+
font-size: 0.78rem;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.chip.state-needs-human,
|
|
198
|
+
.chip.state-blocked { background: #f6d7b8; }
|
|
199
|
+
|
|
200
|
+
.chip.state-failed,
|
|
201
|
+
.chip.state-rejected,
|
|
202
|
+
.chip.state-dead,
|
|
203
|
+
.chip.state-invalid { background: #f3d0d0; color: var(--bad); }
|
|
204
|
+
|
|
205
|
+
.chip.state-approved,
|
|
206
|
+
.chip.state-done,
|
|
207
|
+
.chip.state-in-review { background: #d7eadf; color: var(--good); }
|
|
208
|
+
|
|
209
|
+
.chip.state-running,
|
|
210
|
+
.chip.state-queued { background: #d9e4f2; }
|
|
211
|
+
|
|
212
|
+
.deps { margin: 0.35rem 0; }
|
|
213
|
+
|
|
214
|
+
.details {
|
|
215
|
+
margin: 0.4rem 0 0;
|
|
216
|
+
padding: 0;
|
|
217
|
+
list-style: none;
|
|
218
|
+
color: var(--muted);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.details li.bad { color: var(--bad); }
|
|
222
|
+
|
|
223
|
+
pre.text {
|
|
224
|
+
white-space: pre-wrap;
|
|
225
|
+
word-break: break-word;
|
|
226
|
+
background: var(--card);
|
|
227
|
+
border: 1px solid var(--line);
|
|
228
|
+
border-radius: 12px;
|
|
229
|
+
padding: 1rem;
|
|
230
|
+
margin: 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.back { display: inline-block; margin-bottom: 0.75rem; }
|