ghostrail 0.15.0 → 0.15.2

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 CHANGED
@@ -10,7 +10,7 @@ Ghostrail turns a repo into a factory by dropping in one config file and one pro
10
10
 
11
11
  A software factory is thin glue over three primitive layers: where work comes from, what agent does the work, and where the work runs in isolation. Most people rebuild that glue per repo. Ghostrail extracts it once, so the tracker, the agent job, the gate, and the isolation backend are all configuration:
12
12
 
13
- - **Config-driven.** A `ghostrail.toml` plus a prompt file is the whole factory.
13
+ - **Config-driven.** A [`ghostrail.toml`](#configuration-reference) plus a prompt file is the whole factory.
14
14
  - **Pluggable backends.** `local-worktree` and `container-per-run` ship first-class; Firecracker, E2B, and remote sandboxes are adapters behind the same five-verb interface.
15
15
  - **Pluggable agents.** Claude Code headless is the day-one agent, behind an `[agent]` seam so other agents can slot in.
16
16
  - **Local and simple by default.** The quickstart runs on your own hardware with zero infrastructure and no additional cost. Cloud backends and a hosted board are opt-in.
@@ -26,6 +26,208 @@ ghostrail respond # fold new human PR comments back into branch
26
26
  ghostrail board # watch runs at http://127.0.0.1:5050/
27
27
  ```
28
28
 
29
+ ## Configuration reference
30
+
31
+ `ghostrail.toml` is the whole factory: one file, one table per adapter seam.
32
+ Every key below is read and validated in
33
+ [`src/config/load.ts`](./src/config/load.ts); the defaults come from
34
+ [`src/config/schema.ts`](./src/config/schema.ts) — this table is checked
35
+ against that code, not against the illustrative templates under
36
+ [`templates/`](./templates) or [`examples/`](./examples), which drift.
37
+
38
+ A few things apply across every table, worth knowing before you read them:
39
+
40
+ - **Durations are strings.** `timeout` and `claim_ttl` are parsed by a small
41
+ parser that accepts a whole positive number plus one unit: `"30m"`, `"2h"`,
42
+ `"90s"`. A bare number (`30`) is a validation error, not minutes.
43
+ - **An invalid value does not stop the rest of the file from validating.**
44
+ Every key is checked independently: a missing or malformed value is
45
+ recorded as an error and that one key falls back to its default, so one
46
+ typo doesn't hide problems elsewhere in the file. That said, `ghostrail
47
+ run`/`watch` still refuse to start if *any* error was recorded — look for
48
+ `config error at <path>: ...` lines on stderr. A couple of lookups made
49
+ before a run starts (like the factory name in early log output) instead
50
+ treat a config that fails to validate as absent and quietly fall back to
51
+ their own hardcoded default, without printing anything.
52
+ - **An omitted section takes every default in it.** `[guardrails]`,
53
+ `[backend]`, `[gate]`, and `[output]` can all be left out of the file
54
+ entirely.
55
+
56
+ ### `[factory]`
57
+
58
+ | Key | Type | Values | Default | Required |
59
+ | --- | --- | --- | --- | --- |
60
+ | `name` | string | any non-empty string | none | yes |
61
+ | `base_branch` | string | any non-empty string | `"main"` | no |
62
+ | `source` | string | a git URL or local path | none — operates on `--repo` in place | no |
63
+
64
+ ### `[source]`
65
+
66
+ | Key | Type | Values | Default | Required |
67
+ | --- | --- | --- | --- | --- |
68
+ | `kind` | string | `"linear"` \| `"github-issues"` \| `"beads"` | `"linear"`* | yes |
69
+ | `team` | string | tracker team key | `""` | yes for `linear`, no otherwise |
70
+ | `project` | string | project name within the team | none | no |
71
+
72
+ \* A missing or invalid `kind` is recorded as an error, then falls back to
73
+ `"linear"` rather than blocking the rest of the file — see the note above.
74
+
75
+ #### `[source.eligible]`
76
+
77
+ Filters which tracker items are eligible work. The same shape is reused,
78
+ verbatim, by [`[triage.eligible]`](#triage-optional).
79
+
80
+ | Key | Type | Values | Default | Required |
81
+ | --- | --- | --- | --- | --- |
82
+ | `state` | string | a tracker workflow state name | none — not filtered | no |
83
+ | `label` | string or array of strings | one or more label names (AND'd) | `[]` — not filtered | no |
84
+ | `assignee` | string | `"unassigned"` \| `"me"` \| a user email or id | none — not filtered | no |
85
+
86
+ ### `[agent]`
87
+
88
+ | Key | Type | Values | Default | Required |
89
+ | --- | --- | --- | --- | --- |
90
+ | `kind` | string | `"claude-code"` today | `"claude-code"`* | yes |
91
+ | `prompt` | string | path to a prompt file, relative to the config | none | yes |
92
+ | `allowed_tools` | array of strings | tool names the agent may call | `["Read", "Edit", "Write", "Bash", "Grep", "Glob"]` | no |
93
+
94
+ \* Same fallback as `[source].kind`: missing or invalid is an error, and the
95
+ field still resolves to `"claude-code"`.
96
+
97
+ ### `[backend]`
98
+
99
+ | Key | Type | Values | Default | Required |
100
+ | --- | --- | --- | --- | --- |
101
+ | `kind` | string | `"local-worktree"` \| `"container"` \| `"firecracker"` \| `"e2b"` \| `"remote"` | `"local-worktree"` | no |
102
+ | `image` | string | a container image reference | none | no — needed once `kind = "container"` |
103
+ | `managed_checkout` | boolean | `true` \| `false` | `true` | no |
104
+
105
+ `managed_checkout` only affects `local-worktree`: `true` makes worktrees from
106
+ a bare clone ghostrail owns under its own state dir; `false` borrows your
107
+ working checkout instead. See [choosing a backend](docs/backends.md).
108
+
109
+ ### `[gate]`
110
+
111
+ | Key | Type | Values | Default | Required |
112
+ | --- | --- | --- | --- | --- |
113
+ | `commands` | array of strings | shell commands, run in order in the workspace | `[]` | no |
114
+
115
+ ### `[guardrails]`
116
+
117
+ | Key | Type | Values | Default | Required |
118
+ | --- | --- | --- | --- | --- |
119
+ | `max_items_per_run` | number | > 0 | `3` | no |
120
+ | `budget_usd_per_item` | number | > 0 | `3` | no |
121
+ | `max_cost_usd_per_run` | number | > 0 | `5` | no |
122
+ | `timeout` | duration string | e.g. `"30m"` | `"30m"` | no |
123
+ | `claim_ttl` | duration string | e.g. `"2h"` | `"2h"` | no |
124
+ | `concurrency` | number | > 0 | `1` | no |
125
+
126
+ - `budget_usd_per_item` does two jobs: it's the hard per-item cost ceiling,
127
+ *and* it's the cost ghostrail assumes for an item that finishes without
128
+ reporting any cost at all. Raising it to give one item more headroom also
129
+ raises what an unreported-cost item gets charged.
130
+ - Neither budget key can express "unlimited" today
131
+ ([TJ-1581](https://linear.app/timothyjordan/issue/TJ-1581/featguardrails-allow-an-unlimited-budget-for-budget-usd-per-item-and)
132
+ tracks adding one); update this row once it ships.
133
+ - This table says what each key accepts. What each one bounds inside a tick,
134
+ and how they interact with `concurrency`, is the subject of the tick
135
+ documentation (tracked in
136
+ [TJ-1580](https://linear.app/timothyjordan/issue/TJ-1580/docs-explain-how-a-tick-works-and-how-every-guardrail-lever-changes-it));
137
+ until it lands, see "How it works" in [`docs/index.md`](docs/index.md).
138
+
139
+ ### `[output]`
140
+
141
+ | Key | Type | Values | Default | Required |
142
+ | --- | --- | --- | --- | --- |
143
+ | `open_pr` | boolean | `true` \| `false` | `true` | no |
144
+ | `draft` | boolean | `true` \| `false` | `false` | no |
145
+ | `auto_merge` | boolean | `true` \| `false` | `false` | no |
146
+ | `branch_prefix` | string | any non-empty string | `"ghostrail"` | no |
147
+ | `commit_type` | string | a conventional-commit type, e.g. `"feat"`, `"fix"`, `"chore"` | `"chore"` | no |
148
+
149
+ `auto_merge` is policy, not just a default: it hard-defaults to `false`
150
+ because a human owns every merge. No combination of keys makes ghostrail
151
+ merge its own PR.
152
+
153
+ ### Top level
154
+
155
+ | Key | Type | Values | Default | Required |
156
+ | --- | --- | --- | --- | --- |
157
+ | `artifacts` | array of strings | names of global artifacts under `~/.config/ghostrail/artifacts/` | `[]` | no |
158
+
159
+ ### `[respond]` (optional)
160
+
161
+ Enables `ghostrail respond`. Omit the whole section to leave it disabled.
162
+
163
+ | Key | Type | Values | Default | Required |
164
+ | --- | --- | --- | --- | --- |
165
+ | `prompt` | string | path to a prompt file, relative to the config | none | yes, if `[respond]` is present |
166
+
167
+ ### `[triage]` (optional)
168
+
169
+ Required by `ghostrail triage`; without it, that command has nothing to run.
170
+
171
+ | Key | Type | Values | Default | Required |
172
+ | --- | --- | --- | --- | --- |
173
+ | `prompt` | string | path to a prompt file, relative to the config | none | yes, if `[triage]` is present |
174
+ | `eligible` | inline table | same shape as [`[source.eligible]`](#sourceeligible) | `{ labels: [] }` | no |
175
+
176
+ ### A complete example
177
+
178
+ ```toml
179
+ [factory]
180
+ name = "fix-bugs"
181
+ base_branch = "main" # default; shown for clarity
182
+
183
+ [source]
184
+ kind = "linear" # linear | github-issues | beads
185
+ team = "TJ" # required for linear: its team key
186
+ project = "myproject" # optional: scope to one project
187
+ eligible = { state = "Todo", label = "factory", assignee = "unassigned" }
188
+
189
+ [agent]
190
+ kind = "claude-code"
191
+ prompt = "prompts/fix-bug.md"
192
+ # allowed_tools defaults to ["Read", "Edit", "Write", "Bash", "Grep", "Glob"]
193
+
194
+ [backend]
195
+ kind = "local-worktree" # default; container | firecracker | e2b | remote also declared
196
+ # managed_checkout = true # default: local-worktree owns its clone
197
+
198
+ [gate]
199
+ commands = [
200
+ "pnpm install --frozen-lockfile",
201
+ "pnpm lint",
202
+ "pnpm typecheck",
203
+ "pnpm test:unit",
204
+ ]
205
+
206
+ [guardrails]
207
+ max_items_per_run = 3 # default
208
+ budget_usd_per_item = 3 # default; also the assumed cost of an unreported-cost item
209
+ max_cost_usd_per_run = 5 # default
210
+ timeout = "30m" # default
211
+ claim_ttl = "2h" # default
212
+ concurrency = 1 # default: one item at a time
213
+
214
+ [output]
215
+ open_pr = true # default
216
+ draft = false # default
217
+ auto_merge = false # hard default: a human owns every merge
218
+ branch_prefix = "ghostrail" # default
219
+ commit_type = "chore" # default
220
+
221
+ artifacts = ["style-guide"] # optional: shared references staged into every run
222
+
223
+ [respond] # optional: enables `ghostrail respond`
224
+ prompt = "prompts/respond.md"
225
+
226
+ [triage] # optional: required by `ghostrail triage`
227
+ prompt = "prompts/triage.md"
228
+ eligible = { state = "Todo", label = "triage", assignee = "unassigned" }
229
+ ```
230
+
29
231
  ## Credentials
30
232
 
31
233
  Ghostrail reads its credentials from two dotenv-style files before every `run`,
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
@@ -10,10 +10,10 @@ import { createBoardServer, isLoopback, normalizeToken } from "../board/server.j
10
10
  import { buildProposal, writeProfile } from "../claude-profile/index.js";
11
11
  import { parseDuration } from "../config/duration.js";
12
12
  import { parseConfig, parseMergedConfig } from "../config/load.js";
13
- import { createDashboardServer } from "../dashboard/index.js";
13
+ import { createDashboardServer, defaultStateDir } from "../dashboard/index.js";
14
14
  import { buildLoopDeps, buildRespondDeps, buildTriageDeps, repoGhRunner, resolveRepoSlug, } from "../factory/build.js";
15
15
  import { describeMigration, migrateLegacyStore, resolveWorkRepo, stateDirFor, } from "../factory/state.js";
16
- import { entryKey, findByKey, findByPath, upsert } from "../global/registry.js";
16
+ import { entryKey, findByPath, findForRepoResolving, replaceEntry, upsert, } from "../global/registry.js";
17
17
  import { canonicalRepoId, localRepoId } from "../global/repo-id.js";
18
18
  import { GlobalStore, resolveGlobalDir } from "../global/store.js";
19
19
  import { DEFAULT_FACTORY_IMAGE, dockerBuildArgs, dockerDir, parseImageFlags, } from "../image/build.js";
@@ -34,6 +34,7 @@ import { FileStore } from "../store/file-store.js";
34
34
  import { triageTick } from "../triage/engine.js";
35
35
  import { VERSION } from "../version.js";
36
36
  import { askOnTerminal, parseInstallOffers } from "./prompt.js";
37
+ import { describeStalledUpdate } from "./update-diagnosis.js";
37
38
  /**
38
39
  * Parse the flags shared by `run` and `respond`: `--config`/`-c <path>` and
39
40
  * `--repo`/`-C <path>`, starting from the given defaults. Unknown tokens are
@@ -436,6 +437,7 @@ function dashboardDeps(env, manager) {
436
437
  readRegistry: () => store.readRegistry(),
437
438
  writeRegistry: (entries) => store.writeRegistry(entries),
438
439
  health: ghostrailHealth,
440
+ stateDir: defaultStateDir,
439
441
  states: async (entries) => {
440
442
  if ((await manager.kind()) !== "systemd")
441
443
  return new Map();
@@ -1068,16 +1070,27 @@ async function registerGhostrail(repo, env, streams) {
1068
1070
  return;
1069
1071
  }
1070
1072
  const repoId = await resolveRepoId(path);
1071
- const existing = findByKey(entries, repoId);
1073
+ const existing = await findForRepoResolving(entries, repoId, path, resolveRepoId);
1072
1074
  if (existing !== undefined) {
1073
- // A second checkout of a repo that already has a ghostrail. Adopt this
1074
- // checkout as the hint and say so, rather than adding a second ghostrail
1075
- // that would poll the same tracker query and race the first for its work.
1076
- if (existing.path === path)
1075
+ // Everything the entry already carries survives, `watch` above all:
1076
+ // re-registering must never switch a ghostrail back on that the operator
1077
+ // deliberately turned off.
1078
+ const adopted = { ...existing, repo: repoId, path };
1079
+ if (existing.repo === repoId && existing.path === path)
1077
1080
  return;
1078
- await store.writeRegistry(upsert(entries, { ...existing, repo: repoId, path }));
1079
- streams.out(`this repo already has the ghostrail "${existing.name}"; pointed it at this checkout\n` +
1080
- ` was: ${existing.path}\n now: ${path}\n`);
1081
+ // By the entry's *old* key: adopting an identity re-keys it, so an
1082
+ // ordinary upsert would append beside it rather than replace it.
1083
+ await store.writeRegistry(replaceEntry(entries, entryKey(existing), adopted));
1084
+ if (existing.repo === undefined) {
1085
+ // Registered before repo identities existed. Adopting it here is the
1086
+ // migration; appending instead would give one repo two entries and two
1087
+ // service units, which is the failure this all exists to remove.
1088
+ streams.out(`adopted the existing ghostrail "${existing.name}" for ${repoId}\n`);
1089
+ }
1090
+ else {
1091
+ streams.out(`this repo already has the ghostrail "${existing.name}"; pointed it at this checkout\n` +
1092
+ ` was: ${existing.path}\n now: ${path}\n`);
1093
+ }
1081
1094
  return;
1082
1095
  }
1083
1096
  const entry = {
@@ -1309,10 +1322,29 @@ export async function cmdInit(argv, streams, cwd = process.cwd(), env = process.
1309
1322
  }
1310
1323
  streams.out("\n");
1311
1324
  await registerGhostrail(repo, env, streams);
1312
- streams.out("next: `ghostrail skill` installs the /ghostrail skill for your coding agent,\n");
1313
- streams.out(" then run `/ghostrail customize` there to tailor the prompts to this repo\n");
1325
+ streams.out(await initNextStepsHint(env, cwd));
1314
1326
  return 0;
1315
1327
  }
1328
+ /**
1329
+ * The `next:` hint at the end of a plain `ghostrail init`. Skips telling the
1330
+ * user to run `ghostrail skill` when it is already installed and current, so
1331
+ * a repeat `init` does not repeat advice already followed. Detection reuses
1332
+ * the same plan `ghostrail skill --check` reports on; any failure there is
1333
+ * best-effort only and falls back to the original two-line hint rather than
1334
+ * failing `init`, which already succeeded at scaffolding.
1335
+ */
1336
+ async function initNextStepsHint(env, cwd) {
1337
+ const customizeOnly = "next: run `/ghostrail customize` in your coding agent to tailor the prompts to this repo\n";
1338
+ const installAndCustomize = "next: `ghostrail skill` installs the /ghostrail skill for your coding agent,\n" +
1339
+ " then run `/ghostrail customize` there to tailor the prompts to this repo\n";
1340
+ try {
1341
+ const plan = await planSkillInstall(pathCtx(env, cwd), { scope: "global" });
1342
+ return plan.upToDate ? customizeOnly : installAndCustomize;
1343
+ }
1344
+ catch {
1345
+ return installAndCustomize;
1346
+ }
1347
+ }
1316
1348
  /**
1317
1349
  * Parse `ghostrail skill` options. Default scope is `global` (the user's own
1318
1350
  * agent config), because the skill drives ghostrail wherever you are; `--project`
@@ -1511,17 +1543,20 @@ export async function cmdSelfUpdate(argv, streams, deps = {}) {
1511
1543
  // after "updated X -> Y" (the CLI's status) with nothing marking which one
1512
1544
  // it described.
1513
1545
  streams.out("ghostrail:\n");
1514
- // Resolve `latest` BEFORE installing. This is what makes the "version did not
1515
- // change" branch below meaningful: with the already-current case ruled out
1516
- // first, an unchanged version can only mean the install went somewhere PATH
1517
- // does not resolve. Without this check the two are indistinguishable, and we
1518
- // used to blame a split install for what was simply nothing to do.
1546
+ // Resolve `latest` BEFORE installing, so "nothing newer existed" is ruled out
1547
+ // before anything else is inferred from an unchanged version.
1519
1548
  const latest = await readLatest();
1520
1549
  if (latest !== undefined && latest === current) {
1521
1550
  streams.out(` already on the latest version (${current})\n`);
1522
1551
  return finishWithSkill(argv, streams, runSkill);
1523
1552
  }
1524
- const installResult = await install("latest");
1553
+ // The exact version, not the tag. `npm view` reads the registry while
1554
+ // `npm install` may resolve `latest` from a cached packument, so for a few
1555
+ // minutes after a release the two disagree and npm cheerfully reinstalls the
1556
+ // version already present, exits 0, and leaves nothing to distinguish that
1557
+ // from a failed install. Naming the version closes the window: a stale cache
1558
+ // now produces a real npm error instead of a silent no-op.
1559
+ const installResult = await install(latest ?? "latest");
1525
1560
  if (installResult.exitCode !== 0) {
1526
1561
  streams.err(installResult.stderr || installResult.stdout);
1527
1562
  streams.err(` update failed (npm exited ${installResult.exitCode})\n`);
@@ -1537,16 +1572,18 @@ export async function cmdSelfUpdate(argv, streams, deps = {}) {
1537
1572
  return 1;
1538
1573
  }
1539
1574
  if (installed === current) {
1540
- // We only get here having already ruled out "nothing newer existed", so
1541
- // blaming a split install is sound unless we never learned what `latest`
1542
- // is, in which case say that instead of asserting a newer version exists.
1543
- if (latest === undefined) {
1544
- streams.err(` could not determine the latest version; ghostrail is still ${current}\n`);
1545
- }
1546
- else {
1547
- streams.err(` still on ${current} after updating; the new version went somewhere else\n`);
1575
+ // Which explanation fits is decided by the machine, not assumed. Ruling out
1576
+ // "nothing newer existed" does not leave a split install as the only
1577
+ // possibility, and asserting it while printing two agreeing paths is what
1578
+ // sent an operator looking for a second node install that did not exist.
1579
+ const locations = await (deps.locations ?? probeInstallLocations)();
1580
+ for (const line of describeStalledUpdate({
1581
+ current,
1582
+ ...(latest === undefined ? {} : { wanted: latest }),
1583
+ locations,
1584
+ })) {
1585
+ streams.err(` ${line}\n`);
1548
1586
  }
1549
- await reportInstallLocations(streams, deps);
1550
1587
  return 1;
1551
1588
  }
1552
1589
  streams.out(` updated ${current} -> ${installed}\n`);
@@ -1594,19 +1631,6 @@ async function installedVersion() {
1594
1631
  const version = result.stdout.trim();
1595
1632
  return version.length === 0 ? undefined : version;
1596
1633
  }
1597
- /**
1598
- * Print the two facts that identify a split install: where npm puts global
1599
- * packages, and which `ghostrail` the shell actually runs. When they disagree,
1600
- * that is the whole answer.
1601
- */
1602
- async function reportInstallLocations(streams, deps = {}) {
1603
- const { npmRoot, which } = await (deps.locations ?? probeInstallLocations)();
1604
- if (npmRoot !== undefined)
1605
- streams.err(` npm installs into: ${npmRoot}\n`);
1606
- if (which !== undefined)
1607
- streams.err(` your shell runs: ${which}\n`);
1608
- streams.err(" If those disagree, you have more than one node version and the update landed under a different one.\n");
1609
- }
1610
1634
  /** Ask npm where it installs, and the shell which ghostrail it runs. */
1611
1635
  async function probeInstallLocations() {
1612
1636
  const npm = process.platform === "win32" ? "npm.cmd" : "npm";