@valescoagency/runway 0.14.0 → 0.14.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
@@ -541,7 +541,7 @@ These are tractable, just not v1.
541
541
 
542
542
  ## Status
543
543
 
544
- 0.14.0 — production-shaped and dogfooded against live Linear queues.
544
+ 0.14.2 — production-shaped and dogfooded against live Linear queues.
545
545
  The end-to-end pipeline (init → run → review → PR) is stable; surface
546
546
  may still shift as the orchestrator's policy and iteration mechanics
547
547
  mature. See [CHANGELOG.md](./CHANGELOG.md) for per-release detail.
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { AUTH_MODE_ENV_VAR, TEMPLATES_DIR, } from "./scaffolder.js";
4
4
  import { buildAgentImage } from "./scaffolder-image.js";
@@ -80,7 +80,36 @@ export function parseOpRefs(schema, opts) {
80
80
  // ---------------------------------------------------------------------------
81
81
  // Render: Dockerfile
82
82
  // ---------------------------------------------------------------------------
83
- function renderDockerfile(cwd, tier, opts, enforceManualEditGuard) {
83
+ /**
84
+ * VA-466: read every historical snapshot under `templates/history/`
85
+ * that matches the base Dockerfile naming pattern, flatten them to
86
+ * the set of lines that have ever shipped as canonical content.
87
+ *
88
+ * Files are named `Dockerfile.claude-code.base.v<n>` (where `<n>` is
89
+ * the runway version that owned that exact content). The detector
90
+ * doesn't care about version ordering — it just needs the union of
91
+ * lines so a target Dockerfile rendered at any prior version doesn't
92
+ * trigger false-positive "manual edit" reports.
93
+ *
94
+ * Missing or empty `history/` directory returns an empty list — both
95
+ * are valid states (a fresh checkout that hasn't accumulated history
96
+ * yet, or a tier-1 repo whose template never changed).
97
+ */
98
+ function historicalBaseTemplateLines() {
99
+ const historyDir = join(TEMPLATES_DIR, "history");
100
+ if (!existsSync(historyDir))
101
+ return [];
102
+ const out = [];
103
+ for (const name of readdirSync(historyDir)) {
104
+ if (!name.startsWith("Dockerfile.claude-code.base"))
105
+ continue;
106
+ const contents = readFileSync(join(historyDir, name), "utf8");
107
+ for (const line of contents.split("\n"))
108
+ out.push(line);
109
+ }
110
+ return out;
111
+ }
112
+ export function renderDockerfile(cwd, tier, opts, enforceManualEditGuard) {
84
113
  const dockerfilePath = join(cwd, ".sandcastle", "Dockerfile");
85
114
  const before = readFileSync(dockerfilePath, "utf8");
86
115
  const base = readFileSync(join(TEMPLATES_DIR, "Dockerfile.claude-code.base"), "utf8");
@@ -98,8 +127,22 @@ function renderDockerfile(cwd, tier, opts, enforceManualEditGuard) {
98
127
  }
99
128
  // Detect manual user edits: any line in `before` that isn't in the
100
129
  // expected re-rendered output is foreign. Warn loudly unless --force.
130
+ //
131
+ // VA-466: expectedLines unions the current template with every
132
+ // historical canonical template snapshot under `templates/history/`.
133
+ // Without this, a target repo's Dockerfile rendered by an older
134
+ // runway version flags every cross-version line delta as a "manual
135
+ // edit" — false-positive that forces operators to pass `--force`
136
+ // and lose the detector's real signal. Each release that mutates
137
+ // `Dockerfile.claude-code.base` snapshots the prior version into
138
+ // `templates/history/Dockerfile.claude-code.base.v<n>` BEFORE the
139
+ // edit lands; the detector unions all of them so any line that
140
+ // ever shipped as canonical stays trusted.
101
141
  if (before !== after && !opts.force && enforceManualEditGuard) {
102
142
  const expectedLines = new Set(after.split("\n"));
143
+ for (const line of historicalBaseTemplateLines()) {
144
+ expectedLines.add(line);
145
+ }
103
146
  const foreign = before
104
147
  .split("\n")
105
148
  .filter((l) => l.trim().length && !expectedLines.has(l));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valescoagency/runway",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "Linear-driven orchestrator + scaffolder for coding agents on Sandcastle. `runway init` scaffolds a target repo (sandcastle + varlock + 1Password); `runway run` drains a Linear queue against it; `runway doctor`, `runway upgrade`, `runway upgrade-repo` round out the lifecycle.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -0,0 +1,79 @@
1
+ # Canonical claude-code Dockerfile — vendored from
2
+ # @ai-hero/sandcastle's InitService.ts (CLAUDE_CODE_DOCKERFILE constant).
3
+ # Kept here so `runway init` can write it directly, without invoking
4
+ # `sandcastle init` (which has interactive prompts that hang in
5
+ # non-TTY environments like CI / Mac Mini cron).
6
+ #
7
+ # Drift policy: when sandcastle bumps its claude-code Dockerfile,
8
+ # refresh this file. The diff should be tiny — runway's tier 2 layer
9
+ # patches AFTER this base, so adopters re-run `runway init --force`
10
+ # to roll forward.
11
+
12
+ FROM node:24-bookworm
13
+
14
+ # Install system dependencies
15
+ RUN apt-get update && apt-get install -y \
16
+ git \
17
+ curl \
18
+ jq \
19
+ && rm -rf /var/lib/apt/lists/*
20
+
21
+ # Build-args for UID/GID alignment: defaults match the host user's
22
+ # UID/GID at build time so image-built files and bind-mounted files
23
+ # share an owner without runtime chown.
24
+ ARG AGENT_UID=1000
25
+ ARG AGENT_GID=1000
26
+
27
+ # Rename the base image's "node" user to "agent" and align UID/GID.
28
+ #
29
+ # Divergence from sandcastle's stock Dockerfile: stock runs
30
+ # `groupmod -g $AGENT_GID node` unconditionally, which fails on macOS
31
+ # hosts where the host GID is 20 (`staff`) — Debian's `dialout` group
32
+ # already has GID 20, and `groupmod` refuses to assign a duplicate
33
+ # GID. We guard with `getent group` so groupmod only runs if the
34
+ # target GID is unused; if it's already taken, we point the agent
35
+ # user at the pre-existing group via `usermod -g <gid>` and the
36
+ # image still works (the in-image group name is irrelevant — only the
37
+ # numeric GID matters for bind-mount permissions).
38
+ RUN if ! getent group $AGENT_GID >/dev/null; then \
39
+ groupmod -g $AGENT_GID node; \
40
+ fi \
41
+ && usermod -u $AGENT_UID -g $AGENT_GID -d /home/agent -m -l agent node
42
+
43
+ # VA-351: bake the container env up front so agents don't manually
44
+ # work around host-path leaks, missing pnpm, or unset HOME on every
45
+ # iteration. Without these, every agent run repeats the same
46
+ # corepack/TURBO_CACHE_DIR/HOME setup commands — see VA-312's run log
47
+ # for the receipts.
48
+ ENV HOME=/home/agent
49
+ ENV XDG_CACHE_HOME=/home/agent/.cache
50
+ ENV TURBO_CACHE_DIR=/tmp/turbo-cache
51
+ ENV pnpm_config_cache=/home/agent/.cache/pnpm
52
+
53
+ # Pre-create cache dirs with agent ownership so the first pnpm/turbo
54
+ # run doesn't have to chown them. Both are inside paths the agent owns
55
+ # anyway; this just makes them exist.
56
+ RUN mkdir -p /home/agent/.cache /home/agent/.cache/pnpm /tmp/turbo-cache \
57
+ && chown -R $AGENT_UID:$AGENT_GID /home/agent/.cache /tmp/turbo-cache
58
+
59
+ # Bake pnpm via corepack at build time so `pnpm` is on PATH inside the
60
+ # container before any agent command runs. Pin a default; target repos
61
+ # can override at runtime via `packageManager` in package.json +
62
+ # `corepack use`.
63
+ RUN corepack enable \
64
+ && corepack prepare pnpm@11.1.1 --activate
65
+
66
+ USER ${AGENT_UID}:${AGENT_GID}
67
+
68
+ # Install Claude Code CLI
69
+ RUN curl -fsSL https://claude.ai/install.sh | bash
70
+
71
+ # Add Claude to PATH
72
+ ENV PATH="/home/agent/.local/bin:$PATH"
73
+
74
+ WORKDIR /home/agent
75
+
76
+ # In worktree sandbox mode, Sandcastle bind-mounts the git worktree at
77
+ # the sandbox repo dir and overrides the working directory to that dir
78
+ # at container start.
79
+ ENTRYPOINT ["sleep", "infinity"]