@valescoagency/runway 0.8.0 → 0.9.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 +2 -2
- package/dist/implement.js +4 -0
- package/dist/linear.js +39 -10
- package/dist/prompts.js +4 -0
- package/package.json +2 -2
- package/prompts/implement.md +9 -2
- package/templates/Dockerfile.claude-code.base +4 -4
- package/templates/dockerfile-varlock.snippet +1 -1
package/README.md
CHANGED
|
@@ -156,7 +156,7 @@ sandcastle reads `.sandcastle/.env` per its docs.
|
|
|
156
156
|
## Install
|
|
157
157
|
|
|
158
158
|
```bash
|
|
159
|
-
pnpm
|
|
159
|
+
pnpm add -g @valescoagency/runway # or npm i -g, yarn global add
|
|
160
160
|
```
|
|
161
161
|
|
|
162
162
|
Export runway's own env (in your shell rc, or wherever you keep API keys):
|
|
@@ -392,7 +392,7 @@ These are tractable, just not v1.
|
|
|
392
392
|
|
|
393
393
|
## Status
|
|
394
394
|
|
|
395
|
-
0.
|
|
395
|
+
0.9.0 — production-shaped and dogfooded against live Linear queues.
|
|
396
396
|
The end-to-end pipeline (init → run → review → PR) is stable; surface
|
|
397
397
|
may still shift as the orchestrator's policy and iteration mechanics
|
|
398
398
|
mature. See [CHANGELOG.md](./CHANGELOG.md) for per-release detail.
|
package/dist/implement.js
CHANGED
|
@@ -60,6 +60,10 @@ export const runImplementLoop = (issue, deps, branch) => Effect.gen(function* ()
|
|
|
60
60
|
// and iteration N+1, since the implementer keeps drifting
|
|
61
61
|
// toward the same code paths until corrected.
|
|
62
62
|
priorReviewFeedback: priorFeedback,
|
|
63
|
+
// VA-417: pass the actual base branch so the prompt
|
|
64
|
+
// renders the real name (`main`, `master`, etc.) rather
|
|
65
|
+
// than a hardcoded default.
|
|
66
|
+
baseBranch,
|
|
63
67
|
}));
|
|
64
68
|
const sandcastleRunId = `impl-${issue.identifier}-iter-${iter}`;
|
|
65
69
|
// VA-389: parse the verdict inside the span scope so the
|
package/dist/linear.js
CHANGED
|
@@ -42,14 +42,20 @@ const WorkflowStateTypeNodeSchema = Schema.Struct({
|
|
|
42
42
|
// integration comments can lack an author); we map a missing user to
|
|
43
43
|
// the empty string so downstream filters can match by name without a
|
|
44
44
|
// null-guard at every call site.
|
|
45
|
+
//
|
|
46
|
+
// VA-415: `user` can also be *absent from the payload entirely* (vs.
|
|
47
|
+
// present-and-null) on some system / deleted-user comments. The
|
|
48
|
+
// optional + nullable shape below accepts all three forms: missing,
|
|
49
|
+
// null, and full struct. Downstream code already null-coalesces via
|
|
50
|
+
// `comment.user?.name ?? ""`.
|
|
45
51
|
const CommentNodeSchema = Schema.Struct({
|
|
46
52
|
id: Schema.String,
|
|
47
53
|
body: Schema.String,
|
|
48
54
|
createdAt: Schema.Union(Schema.String, Schema.DateFromSelf),
|
|
49
|
-
user: Schema.NullOr(Schema.Struct({
|
|
55
|
+
user: Schema.optional(Schema.NullOr(Schema.Struct({
|
|
50
56
|
id: Schema.String,
|
|
51
57
|
name: Schema.String,
|
|
52
|
-
})),
|
|
58
|
+
}))),
|
|
53
59
|
});
|
|
54
60
|
const ViewerSchema = Schema.Struct({
|
|
55
61
|
id: Schema.String,
|
|
@@ -358,21 +364,44 @@ export function createLinearGateway(config, limiter = null) {
|
|
|
358
364
|
message: `Issue ${issueId} has no team`,
|
|
359
365
|
});
|
|
360
366
|
}
|
|
367
|
+
// VA-416: Linear labels can be team-scoped OR
|
|
368
|
+
// workspace-scoped (no team association). Earlier code
|
|
369
|
+
// filtered by team only, which silently excluded
|
|
370
|
+
// workspace-scoped labels like the default
|
|
371
|
+
// `ready-for-human`. Query by name alone, then prefer a
|
|
372
|
+
// team-scoped match when one exists so the team-vs-
|
|
373
|
+
// workspace name-collision edge resolves the obvious way.
|
|
361
374
|
const labels = await client.issueLabels({
|
|
362
|
-
filter: {
|
|
363
|
-
team: { id: { eq: team.id } },
|
|
364
|
-
name: { eq: labelName },
|
|
365
|
-
},
|
|
375
|
+
filter: { name: { eq: labelName } },
|
|
366
376
|
});
|
|
367
|
-
const
|
|
368
|
-
|
|
377
|
+
const decoded = labels.nodes.map((l) => ({
|
|
378
|
+
raw: l,
|
|
379
|
+
decoded: decodeIssueLabelNode(l),
|
|
380
|
+
}));
|
|
381
|
+
const teamScoped = await (async () => {
|
|
382
|
+
for (const { raw, decoded: d } of decoded) {
|
|
383
|
+
const labelTeam = await raw.team;
|
|
384
|
+
if (labelTeam?.id === team.id)
|
|
385
|
+
return d;
|
|
386
|
+
}
|
|
387
|
+
return undefined;
|
|
388
|
+
})();
|
|
389
|
+
const workspaceScoped = await (async () => {
|
|
390
|
+
for (const { raw, decoded: d } of decoded) {
|
|
391
|
+
const labelTeam = await raw.team;
|
|
392
|
+
if (!labelTeam)
|
|
393
|
+
return d;
|
|
394
|
+
}
|
|
395
|
+
return undefined;
|
|
396
|
+
})();
|
|
397
|
+
const label = teamScoped ?? workspaceScoped;
|
|
398
|
+
if (!label) {
|
|
369
399
|
throw new LinearNotFound({
|
|
370
400
|
resource: "label",
|
|
371
401
|
identifier: labelName,
|
|
372
|
-
message: `Linear label "${labelName}" not found on team ${team.id}`,
|
|
402
|
+
message: `Linear label "${labelName}" not found at workspace scope or on team ${team.id}`,
|
|
373
403
|
});
|
|
374
404
|
}
|
|
375
|
-
const label = decodeIssueLabelNode(rawLabel);
|
|
376
405
|
const existing = await issue.labels();
|
|
377
406
|
const existingIds = existing.nodes.map((l) => decodeIssueLabelNode(l).id);
|
|
378
407
|
const labelIds = [...existingIds, label.id];
|
package/dist/prompts.js
CHANGED
|
@@ -56,6 +56,10 @@ function implementVars(args) {
|
|
|
56
56
|
PREVIOUS_ITERATIONS: args.previousIterations,
|
|
57
57
|
PRIOR_REVIEW_FEEDBACK: args.priorReviewFeedback,
|
|
58
58
|
POLICY_FORBIDDEN_BULLET: renderForbiddenPathsBullet(args.policy),
|
|
59
|
+
// VA-417: fall back to "main" if the caller didn't pass one, so
|
|
60
|
+
// older test fixtures keep working. Production always passes the
|
|
61
|
+
// resolved base branch from config / orchestrator detection.
|
|
62
|
+
BASE_BRANCH: args.baseBranch ?? "main",
|
|
59
63
|
};
|
|
60
64
|
}
|
|
61
65
|
function reviewVars(args) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valescoagency/runway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"vitest": "^4.1.6"
|
|
64
64
|
},
|
|
65
65
|
"engines": {
|
|
66
|
-
"node": ">=
|
|
66
|
+
"node": ">=24"
|
|
67
67
|
},
|
|
68
68
|
"publishConfig": {
|
|
69
69
|
"access": "public"
|
package/prompts/implement.md
CHANGED
|
@@ -12,8 +12,15 @@ You are an autonomous coding agent working on a single Linear issue.
|
|
|
12
12
|
|
|
13
13
|
# Repository context
|
|
14
14
|
|
|
15
|
-
You are operating
|
|
16
|
-
|
|
15
|
+
You are operating on the branch `agent/{{ISSUE_IDENTIFIER}}` based off
|
|
16
|
+
`{{BASE_BRANCH}}` (this repo's default branch — usually `main` or
|
|
17
|
+
`master`). The branch is clean on a first attempt. **On a retry it
|
|
18
|
+
may already carry commits from a prior attempt that review rejected**
|
|
19
|
+
— the rejection reasons then appear in the *Review feedback from
|
|
20
|
+
prior attempts* section above. Your job is to produce a final state
|
|
21
|
+
that addresses every blocker in that feedback, replacing the existing
|
|
22
|
+
commits if they would re-introduce a rejected design. Do not treat
|
|
23
|
+
pre-existing commits as authoritative.
|
|
17
24
|
|
|
18
25
|
# What done looks like
|
|
19
26
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
# patches AFTER this base, so adopters re-run `runway init --force`
|
|
10
10
|
# to roll forward.
|
|
11
11
|
|
|
12
|
-
FROM node:
|
|
12
|
+
FROM node:24-bookworm
|
|
13
13
|
|
|
14
14
|
# Install system dependencies
|
|
15
15
|
RUN apt-get update && apt-get install -y \
|
|
@@ -48,12 +48,12 @@ RUN if ! getent group $AGENT_GID >/dev/null; then \
|
|
|
48
48
|
ENV HOME=/home/agent
|
|
49
49
|
ENV XDG_CACHE_HOME=/home/agent/.cache
|
|
50
50
|
ENV TURBO_CACHE_DIR=/tmp/turbo-cache
|
|
51
|
-
ENV
|
|
51
|
+
ENV pnpm_config_cache=/home/agent/.cache/pnpm
|
|
52
52
|
|
|
53
53
|
# Pre-create cache dirs with agent ownership so the first pnpm/turbo
|
|
54
54
|
# run doesn't have to chown them. Both are inside paths the agent owns
|
|
55
55
|
# anyway; this just makes them exist.
|
|
56
|
-
RUN mkdir -p /home/agent/.cache /home/agent/.cache/
|
|
56
|
+
RUN mkdir -p /home/agent/.cache /home/agent/.cache/pnpm /tmp/turbo-cache \
|
|
57
57
|
&& chown -R $AGENT_UID:$AGENT_GID /home/agent/.cache /tmp/turbo-cache
|
|
58
58
|
|
|
59
59
|
# Bake pnpm via corepack at build time so `pnpm` is on PATH inside the
|
|
@@ -61,7 +61,7 @@ RUN mkdir -p /home/agent/.cache /home/agent/.cache/npm /tmp/turbo-cache \
|
|
|
61
61
|
# can override at runtime via `packageManager` in package.json +
|
|
62
62
|
# `corepack use`.
|
|
63
63
|
RUN corepack enable \
|
|
64
|
-
&& corepack prepare pnpm@
|
|
64
|
+
&& corepack prepare pnpm@11.1.1 --activate
|
|
65
65
|
|
|
66
66
|
USER ${AGENT_UID}:${AGENT_GID}
|
|
67
67
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# Install varlock via npm. The official `ghcr.io/dmno-dev/varlock`
|
|
6
6
|
# image is musl/Alpine — copying its binary into a glibc base
|
|
7
|
-
# (node:
|
|
7
|
+
# (node:24-bookworm) produces an ELF that the loader can't resolve
|
|
8
8
|
# ("not found" on exec). npm install gets the right binary for the
|
|
9
9
|
# image's libc.
|
|
10
10
|
USER root
|