@lchase/kiln 0.4.0 → 0.6.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.
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { scheduledRetry } from "./attention.js";
4
4
  import { readBoardFiles, stageOf, stagesFor } from "./board.js";
5
+ import { feedbackRoundLabel } from "./feedback.js";
5
6
  import { INVESTIGATION_WORKFLOW } from "./findings.js";
6
7
  import { packageRoot } from "./paths.js";
7
8
  const REVIEWERS = [
@@ -126,6 +127,7 @@ function card(item, stage) {
126
127
  <div class="branch repo">${escapeHtml(job.repo)}</div>
127
128
  <p class="title">${escapeHtml(job.title || job.jobId)}</p>
128
129
  ${branch ? `<div class="branch">${escapeHtml(branch)}</div>` : ""}
130
+ ${sandboxHtml(job)}
129
131
  ${prLink(job)}
130
132
  ${roundsHtml(job, stage)}
131
133
  ${attemptHtml(job, stage)}
@@ -145,6 +147,7 @@ function sheet(item) {
145
147
  <div class="num">${escapeHtml(num)}</div>
146
148
  <h3>${escapeHtml(job.title || job.jobId)}</h3>
147
149
  ${branch ? `<div class="branch">${escapeHtml(branch)}</div>` : ""}
150
+ ${sandboxHtml(job)}
148
151
  ${prLink(job)}
149
152
  ${actionsHtml(item)}
150
153
  <button type="button" class="close" data-close aria-label="Close">×</button>
@@ -197,11 +200,21 @@ function actionsHtml(item) {
197
200
  return "";
198
201
  return `<div class="d-actions">${forms.join("")}</div>`;
199
202
  }
203
+ function sandboxHtml(job) {
204
+ if (!job.sandboxed)
205
+ return "";
206
+ return `<div class="meta">sandbox · docker</div>`;
207
+ }
200
208
  function roundsHtml(job, stage) {
201
209
  const rounds = job.rounds ?? [];
202
210
  const last = rounds[rounds.length - 1]?.n;
203
211
  const rows = rounds.map((round) => roundRow(round, stage === "review" && job.state === "running" && round.n === last));
204
- if (job.state === "in-review" || (job.state === "done" && safeUrl(job.prUrl))) {
212
+ for (const round of job.feedbackRounds ?? []) {
213
+ const tone = round.outcome ? " PASS" : " CHANGES";
214
+ rows.push(`<div class="round"><span class="r">R${round.round}</span><span class="chip${tone}" style="grid-column: 2 / -1">${escapeHtml(feedbackRoundLabel(round))}</span></div>`);
215
+ }
216
+ const inReview = job.state === "in-review" || (job.state === "done" && safeUrl(job.prUrl));
217
+ if (inReview && (job.feedbackRounds?.length ?? 0) === 0) {
205
218
  const n = (last ?? 0) + 1;
206
219
  const tone = job.state === "done" ? " PASS" : "";
207
220
  rows.push(`<div class="round"><span class="r">R${n}</span><span class="chip${tone}" style="grid-column: 2 / -1">PR review</span></div>`);
package/dist/serve.js CHANGED
@@ -5,6 +5,7 @@ import { answerJob, readStatus, retryJob, runTick, startupRecovery } from "./dri
5
5
  import { defaultHost } from "./host.js";
6
6
  import { INVESTIGATION_WORKFLOW } from "./findings.js";
7
7
  import { packageRoot } from "./paths.js";
8
+ import { SANDBOX_PREFLIGHT_PREFIX } from "./sandbox.js";
8
9
  import { renderConsole, renderMessagePage, renderTextPage } from "./serve-page.js";
9
10
  const BODY_LIMIT = 65_536;
10
11
  const TEXT_LIMIT = 100_000;
@@ -71,7 +72,12 @@ export function loadServeCss() {
71
72
  }
72
73
  function safeTick(config, secrets, startup) {
73
74
  try {
74
- runTick(config, defaultHost(), { startup });
75
+ const report = runTick(config, defaultHost(), { startup });
76
+ if (report.startsWith(SANDBOX_PREFLIGHT_PREFIX)) {
77
+ const tickAt = report.indexOf("\ntick ");
78
+ const head = tickAt === -1 ? report : report.slice(0, tickAt);
79
+ process.stderr.write(`${redact(head.trimEnd(), secrets)}\n`);
80
+ }
75
81
  }
76
82
  catch (error) {
77
83
  const message = error instanceof Error ? error.message : String(error);
package/dist/workflow.js CHANGED
@@ -6,10 +6,12 @@ const FACTORY_DIRECTIVE = /^<!--\s*factory:\s*(prompts\/factory\/[a-z0-9-]+\.md)
6
6
  const WORKFLOW_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
7
7
  const FEATURE_PROMPTS = 7;
8
8
  const INVESTIGATION_PROMPTS = 1;
9
+ const FIX_PROMPTS = 1;
9
10
  /** Prompt files Kiln rewrites before launch. A mismatch refuses the launch. */
10
11
  const PROMPT_COUNTS = {
11
12
  feature: FEATURE_PROMPTS,
12
13
  investigation: INVESTIGATION_PROMPTS,
14
+ fix: FIX_PROMPTS,
13
15
  };
14
16
  /**
15
17
  * Values Kiln can pass into a workflow. A workflow's inputSchema lists the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lchase/kiln",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Run a workflow on any registered repo. A markdown spec is the input.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,17 @@
1
+ <!-- factory: prompts/factory/builder.md -->
2
+
3
+ Kiln runs this factory agent inside yak to answer pull request feedback. The working directory is the worktree (there is no separate worktree path). There is no `.factory/` job folder and no `.factory/repo.md`; read the repo for conventions. `.factory/lessons.md` applies only when that file is really present.
4
+
5
+ `<main branch>` is the base SHA `{{input.baseRef}}` (the pull request head this worktree was branched from, via yak `--from`). Use that SHA wherever the factory instructions say `<main branch>`. Do not guess `main`.
6
+
7
+ Your branch is whatever `git branch --show-current` prints. It is `yak/<run-id>`. That is the branch name in this prompt. Do not BLOCK because it is not `factory/...` or `main`.
8
+
9
+ Commit on that branch. Do not push. Do not force-push. Do not open a pull request. Do not approve one. Do not comment on one. Kiln fast-forwards the existing pull request branch after this run.
10
+
11
+ Do not write `build.md`. Return JSON: `status` is `READY` or `BLOCKED`, `summary` is what you changed. The original spec and the feedback items are the task text below, not files.
12
+
13
+ Task: {{input.title}}
14
+
15
+ {{input.description}}
16
+
17
+ The factory agent instructions follow. Their frontmatter is the original definition; follow the prose, and return the JSON above instead of writing the file they name.
@@ -1,5 +1,10 @@
1
1
  # One Kiln job, run by yak (@lchase/yak).
2
2
  #
3
+ # Sandbox (kiln config `sandbox.mode: docker`, off by default): tick injects
4
+ # `sandbox: docker` under each agent step (spec, build, security, ux, ui,
5
+ # code, approver). verify, the approve gate, and land stay on the host.
6
+ # Pull request open and `gh pr view` run in the kiln process.
7
+ #
3
8
  # Faithful to Lawrence Chase's factory plugin v0.7.0 (MIT):
4
9
  # https://github.com/lchase/coding-agent-plugins/tree/4c84a2ce7fbde5ac298c0de9f0b90141eac4ed98/plugins/factory
5
10
  # skills/factory/SKILL.md (the loop: spec, build, 4 reviews, approver)
@@ -0,0 +1,48 @@
1
+ # One pull-request fix, run by yak (@lchase/yak).
2
+ #
3
+ # Sandbox (kiln config `sandbox.mode: docker`, off by default): tick injects
4
+ # `sandbox: docker` under the build agent. land stays on the host, and Kiln
5
+ # fast-forwards the pull request branch from the host.
6
+ #
7
+ # The same builder as workflows/feature.yaml (factory builder, opus). Kiln
8
+ # launches it with `yak run --isolation worktree --from <pull request head>`
9
+ # on a job whose pull request is already open. Yak cuts a worktree branch
10
+ # `yak/<run-id>` from that commit. The agent commits there. Kiln fast-forwards
11
+ # the open pull request branch. This workflow does not open, approve, merge,
12
+ # or comment on a pull request, and it does not force-push.
13
+ name: kiln-fix
14
+ version: "1"
15
+ inputSchema:
16
+ inline:
17
+ type: object
18
+ additionalProperties: false
19
+ properties:
20
+ title: { type: string }
21
+ description: { type: string }
22
+ acceptance: { type: string }
23
+ runNonce: { type: string }
24
+ baseRef: { type: string }
25
+ required: [title, description, acceptance, runNonce, baseRef]
26
+ steps:
27
+ - id: build
28
+ needs: [input]
29
+ agent:
30
+ model: opus
31
+ prompt:
32
+ file: prompts/kiln/fix.md
33
+ schema:
34
+ inline:
35
+ type: object
36
+ additionalProperties: false
37
+ properties:
38
+ summary: { type: string }
39
+ status: { type: string, enum: [READY, BLOCKED] }
40
+ required: [summary, status]
41
+ tools: [Read, Grep, Glob, Bash, Write, Edit, Skill]
42
+ produces: build
43
+
44
+ - id: land
45
+ needs: [input, build]
46
+ skipIf: 'build.status != "READY"'
47
+ command:
48
+ run: 'git add -A && git diff --cached --quiet || git commit -m "kiln: $KILN_TASK_TITLE"'
@@ -1,5 +1,8 @@
1
1
  # One Kiln investigation, run by yak (@lchase/yak).
2
2
  #
3
+ # Sandbox (kiln config `sandbox.mode: docker`, off by default): tick injects
4
+ # `sandbox: docker` under the explore agent. The approve gate stays on the host.
5
+ #
3
6
  # A spike, probe, or research ticket. The agent explores the worktree and
4
7
  # returns a findings document. A person approves or rejects it. There is no
5
8
  # build, review, verify, or land step, and Kiln does not open a pull request