@valescoagency/runway 0.11.0 → 0.12.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 +1 -1
- package/dist/dashboard/linear-sync.js +11 -1
- package/dist/dashboard/storage.js +19 -7
- package/dist/dashboard/views.js +13 -1
- package/dist/repo-upgrade.js +71 -27
- package/dist/scaffolder-varlock.js +28 -8
- package/dist/scaffolder-verify.js +21 -3
- package/dist/telemetry.js +10 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -541,7 +541,7 @@ These are tractable, just not v1.
|
|
|
541
541
|
|
|
542
542
|
## Status
|
|
543
543
|
|
|
544
|
-
0.
|
|
544
|
+
0.12.0 — 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.
|
|
@@ -15,12 +15,18 @@ export function createLinearAdapter(opts) {
|
|
|
15
15
|
const teamKey = opts.team ?? "VA";
|
|
16
16
|
const readyLabel = opts.readyLabel ?? "ready-for-agent";
|
|
17
17
|
async function snapshotFromIssue(raw) {
|
|
18
|
-
const [state, labels] = await Promise.all([
|
|
18
|
+
const [state, labels, project] = await Promise.all([
|
|
19
|
+
raw.state,
|
|
20
|
+
raw.labels(),
|
|
21
|
+
raw.project,
|
|
22
|
+
]);
|
|
19
23
|
return {
|
|
20
24
|
identifier: raw.identifier,
|
|
21
25
|
title: raw.title,
|
|
22
26
|
status: state?.name ?? "",
|
|
23
27
|
labels: labels.nodes.map((l) => l.name),
|
|
28
|
+
projectId: project?.id ?? null,
|
|
29
|
+
projectName: project?.name ?? null,
|
|
24
30
|
};
|
|
25
31
|
}
|
|
26
32
|
return {
|
|
@@ -113,6 +119,8 @@ export function startLinearSync(opts) {
|
|
|
113
119
|
title: q.title,
|
|
114
120
|
labels: q.labels,
|
|
115
121
|
queuePosition: i,
|
|
122
|
+
projectId: q.projectId,
|
|
123
|
+
projectName: q.projectName,
|
|
116
124
|
});
|
|
117
125
|
});
|
|
118
126
|
log("info", `[runway dashboard] linear sync: refreshed ready queue (${queue.length} issue${queue.length === 1 ? "" : "s"})`);
|
|
@@ -139,6 +147,8 @@ export function startLinearSync(opts) {
|
|
|
139
147
|
title: r.title,
|
|
140
148
|
labels: r.labels,
|
|
141
149
|
queuePosition: null,
|
|
150
|
+
projectId: r.projectId,
|
|
151
|
+
projectName: r.projectName,
|
|
142
152
|
});
|
|
143
153
|
}
|
|
144
154
|
log("info", `[runway dashboard] linear sync: refreshed status for ${refreshed.length} of ${recent.length} recent issue${recent.length === 1 ? "" : "s"}`);
|
|
@@ -99,7 +99,9 @@ const SCHEMA = `
|
|
|
99
99
|
status TEXT NOT NULL,
|
|
100
100
|
title TEXT NOT NULL,
|
|
101
101
|
labels_json TEXT,
|
|
102
|
-
queue_position INTEGER
|
|
102
|
+
queue_position INTEGER,
|
|
103
|
+
project_id TEXT,
|
|
104
|
+
project_name TEXT
|
|
103
105
|
);
|
|
104
106
|
|
|
105
107
|
CREATE INDEX IF NOT EXISTS idx_linear_snapshots_queue_position
|
|
@@ -289,6 +291,9 @@ export function createStorage(path, opts = {}) {
|
|
|
289
291
|
`ALTER TABLE issue_processes ADD COLUMN issue_labels TEXT`,
|
|
290
292
|
`ALTER TABLE issue_processes ADD COLUMN pr_url TEXT`,
|
|
291
293
|
`ALTER TABLE issue_processes ADD COLUMN hitl_reason TEXT`,
|
|
294
|
+
// VA-450: project Name + UUID for the Todo queue Project column.
|
|
295
|
+
`ALTER TABLE linear_snapshots ADD COLUMN project_id TEXT`,
|
|
296
|
+
`ALTER TABLE linear_snapshots ADD COLUMN project_name TEXT`,
|
|
292
297
|
]) {
|
|
293
298
|
try {
|
|
294
299
|
db.exec(sql);
|
|
@@ -403,24 +408,29 @@ export function createStorage(path, opts = {}) {
|
|
|
403
408
|
const selectAggregates = db.prepare(`SELECT * FROM evaluator_aggregates_v1`);
|
|
404
409
|
const upsertLinearSnapshot = db.prepare(`
|
|
405
410
|
INSERT INTO linear_snapshots (
|
|
406
|
-
issue_identifier, snapshot_at, status, title, labels_json, queue_position
|
|
407
|
-
|
|
411
|
+
issue_identifier, snapshot_at, status, title, labels_json, queue_position,
|
|
412
|
+
project_id, project_name
|
|
413
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
408
414
|
ON CONFLICT (issue_identifier) DO UPDATE SET
|
|
409
415
|
snapshot_at = excluded.snapshot_at,
|
|
410
416
|
status = excluded.status,
|
|
411
417
|
title = excluded.title,
|
|
412
418
|
labels_json = excluded.labels_json,
|
|
413
|
-
queue_position = excluded.queue_position
|
|
419
|
+
queue_position = excluded.queue_position,
|
|
420
|
+
project_id = excluded.project_id,
|
|
421
|
+
project_name = excluded.project_name
|
|
414
422
|
`);
|
|
415
423
|
const clearQueuePositionsStmt = db.prepare(`UPDATE linear_snapshots SET queue_position = NULL`);
|
|
416
424
|
const listTodoQueueStmt = db.prepare(`
|
|
417
|
-
SELECT issue_identifier, snapshot_at, status, title, labels_json, queue_position
|
|
425
|
+
SELECT issue_identifier, snapshot_at, status, title, labels_json, queue_position,
|
|
426
|
+
project_id, project_name
|
|
418
427
|
FROM linear_snapshots
|
|
419
428
|
WHERE queue_position IS NOT NULL
|
|
420
429
|
ORDER BY queue_position ASC
|
|
421
430
|
`);
|
|
422
431
|
const listAllSnapshotsStmt = db.prepare(`
|
|
423
|
-
SELECT issue_identifier, snapshot_at, status, title, labels_json, queue_position
|
|
432
|
+
SELECT issue_identifier, snapshot_at, status, title, labels_json, queue_position,
|
|
433
|
+
project_id, project_name
|
|
424
434
|
FROM linear_snapshots
|
|
425
435
|
`);
|
|
426
436
|
// VA-391: "active drain" = a trace_id with one or more
|
|
@@ -616,7 +626,7 @@ export function createStorage(path, opts = {}) {
|
|
|
616
626
|
};
|
|
617
627
|
const listAggregates = () => selectAggregates.all().map(rowToAggregate);
|
|
618
628
|
const saveLinearSnapshot = (s) => {
|
|
619
|
-
upsertLinearSnapshot.run(s.issueIdentifier, s.snapshotAt, s.status, s.title, s.labels.length === 0 ? null : JSON.stringify(s.labels), s.queuePosition);
|
|
629
|
+
upsertLinearSnapshot.run(s.issueIdentifier, s.snapshotAt, s.status, s.title, s.labels.length === 0 ? null : JSON.stringify(s.labels), s.queuePosition, s.projectId, s.projectName);
|
|
620
630
|
};
|
|
621
631
|
const clearLinearQueuePositions = () => {
|
|
622
632
|
clearQueuePositionsStmt.run();
|
|
@@ -860,6 +870,8 @@ function rowToLinearSnapshot(row) {
|
|
|
860
870
|
title: String(r.title ?? ""),
|
|
861
871
|
labels: parseLabels(r.labels_json),
|
|
862
872
|
queuePosition: nullableNum(r.queue_position),
|
|
873
|
+
projectId: r.project_id == null ? null : String(r.project_id),
|
|
874
|
+
projectName: r.project_name == null ? null : String(r.project_name),
|
|
863
875
|
};
|
|
864
876
|
}
|
|
865
877
|
/**
|
package/dist/dashboard/views.js
CHANGED
|
@@ -33,6 +33,7 @@ const SHARED_STYLE = `
|
|
|
33
33
|
.id { color: #93c5fd; }
|
|
34
34
|
.detail { color: #d4d4d8; }
|
|
35
35
|
.muted { color: #9ca3af; }
|
|
36
|
+
.project-uuid { color: #9ca3af; font-size: 11px; display: block; }
|
|
36
37
|
code { background: #1f2937; padding: 1px 6px; border-radius: 3px; }
|
|
37
38
|
.status-badge { display: inline-block; margin-left: 8px;
|
|
38
39
|
padding: 1px 6px; border-radius: 3px; font-size: 11px;
|
|
@@ -316,7 +317,7 @@ export function filterStateToQueryString(state) {
|
|
|
316
317
|
*/
|
|
317
318
|
function renderTodoQueue(queue) {
|
|
318
319
|
const body = queue.length === 0
|
|
319
|
-
? `<tr><td colspan="
|
|
320
|
+
? `<tr><td colspan="4" class="empty">Linear Todo queue is empty.</td></tr>`
|
|
320
321
|
: queue.map(renderQueueRow).join("");
|
|
321
322
|
return `<h2>Todo queue (next up)</h2>
|
|
322
323
|
<table>
|
|
@@ -325,6 +326,7 @@ function renderTodoQueue(queue) {
|
|
|
325
326
|
<th>Issue</th>
|
|
326
327
|
<th>Title</th>
|
|
327
328
|
<th>Labels</th>
|
|
329
|
+
<th>Project</th>
|
|
328
330
|
</tr>
|
|
329
331
|
</thead>
|
|
330
332
|
<tbody>${body}</tbody>
|
|
@@ -332,10 +334,20 @@ function renderTodoQueue(queue) {
|
|
|
332
334
|
}
|
|
333
335
|
function renderQueueRow(s) {
|
|
334
336
|
const labels = s.labels.length === 0 ? "" : s.labels.join(", ");
|
|
337
|
+
// VA-450: project cell shows the name on top with the UUID muted
|
|
338
|
+
// underneath in the same column. Unprojected issues degrade to an
|
|
339
|
+
// em-dash so the row still renders (the drain WILL pick them up).
|
|
340
|
+
const project = s.projectName == null && s.projectId == null
|
|
341
|
+
? `<span class="muted">—</span>`
|
|
342
|
+
: `<span>${escapeHtml(s.projectName ?? "")}</span>` +
|
|
343
|
+
(s.projectId == null
|
|
344
|
+
? ""
|
|
345
|
+
: `<span class="project-uuid">${escapeHtml(s.projectId)}</span>`);
|
|
335
346
|
return `<tr>
|
|
336
347
|
<td class="id">${escapeHtml(s.issueIdentifier)}</td>
|
|
337
348
|
<td class="detail">${escapeHtml(s.title)}</td>
|
|
338
349
|
<td class="muted">${escapeHtml(labels)}</td>
|
|
350
|
+
<td class="detail">${project}</td>
|
|
339
351
|
</tr>`;
|
|
340
352
|
}
|
|
341
353
|
function renderRow(r, snapshots) {
|
package/dist/repo-upgrade.js
CHANGED
|
@@ -3,6 +3,7 @@ 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";
|
|
5
5
|
import { preflight } from "./scaffolder-preflight.js";
|
|
6
|
+
import { writeShim } from "./scaffolder-varlock.js";
|
|
6
7
|
import { verify } from "./scaffolder-verify.js";
|
|
7
8
|
// ---------------------------------------------------------------------------
|
|
8
9
|
// Tier detection
|
|
@@ -118,6 +119,22 @@ function renderDockerfile(cwd, tier, opts, enforceManualEditGuard) {
|
|
|
118
119
|
return diffStat(dockerfilePath, ".sandcastle/Dockerfile", before, after);
|
|
119
120
|
}
|
|
120
121
|
// ---------------------------------------------------------------------------
|
|
122
|
+
// Render: .sandcastle/runway-claude-shim (tier 2, VA-426)
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// VA-426: the Dockerfile rendered above references
|
|
125
|
+
// `COPY runway-claude-shim …`, so tier-2 upgrades must materialise the
|
|
126
|
+
// shim file alongside the Dockerfile/.env.schema re-render — otherwise
|
|
127
|
+
// the post-render `docker build` verification step fails. `init` writes
|
|
128
|
+
// the shim via `applyVarlockLayer`; `upgrade-repo` historically skipped
|
|
129
|
+
// that step entirely. Reuse the same `writeShim` helper here so the two
|
|
130
|
+
// paths can't drift again.
|
|
131
|
+
function renderShim(cwd) {
|
|
132
|
+
const shimPath = join(cwd, ".sandcastle", "runway-claude-shim");
|
|
133
|
+
const before = existsSync(shimPath) ? readFileSync(shimPath, "utf8") : "";
|
|
134
|
+
const after = readFileSync(join(TEMPLATES_DIR, "claude-shim.sh"), "utf8");
|
|
135
|
+
return diffStat(shimPath, ".sandcastle/runway-claude-shim", before, after);
|
|
136
|
+
}
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
121
138
|
// Render: .env.schema (tier 2)
|
|
122
139
|
// ---------------------------------------------------------------------------
|
|
123
140
|
function renderEnvSchema(cwd, resolved) {
|
|
@@ -191,47 +208,37 @@ function diffStat(path, relPath, before, after) {
|
|
|
191
208
|
// Orchestrator
|
|
192
209
|
// ---------------------------------------------------------------------------
|
|
193
210
|
/**
|
|
194
|
-
*
|
|
195
|
-
* the current vendored templates,
|
|
196
|
-
* the
|
|
197
|
-
*
|
|
211
|
+
* The composition core of `upgradeRepo` — render every scaffold file
|
|
212
|
+
* from the current vendored templates, exit early on `--check` drift,
|
|
213
|
+
* and write the resulting changes to disk. Excludes `preflight`,
|
|
214
|
+
* `buildAgentImage`, and `verify` so tests can drive this against a
|
|
215
|
+
* tmpDir without needing a real git repo / docker daemon / `op` /
|
|
216
|
+
* `gh` (VA-426 regression coverage). Production callers go through
|
|
217
|
+
* `upgradeRepo`, which sandwiches this between those phases.
|
|
198
218
|
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
219
|
+
* Returns the resolved op:// refs on tier 2 (consumed by the outer
|
|
220
|
+
* verify step), or null on tier 1. May call `process.exit(0|1)` —
|
|
221
|
+
* mirrors the existing CLI behaviour.
|
|
201
222
|
*/
|
|
202
|
-
export async function
|
|
203
|
-
const tier = detectTier(cwd);
|
|
204
|
-
console.log(`[runway upgrade-repo] detected tier ${tier}`);
|
|
205
|
-
// --check shouldn't refuse on a dirty tree — drift detection is read-only.
|
|
206
|
-
const allowDirty = opts.check ? true : opts.allowDirty;
|
|
207
|
-
// Preflight uses the scaffolder's helper; satisfy its ScaffolderOptions shape.
|
|
208
|
-
const preflightOpts = {
|
|
209
|
-
tier,
|
|
210
|
-
allowDirty,
|
|
211
|
-
force: opts.force,
|
|
212
|
-
skipBuild: opts.skipBuild,
|
|
213
|
-
opVault: "placeholder",
|
|
214
|
-
anthropicItem: "placeholder",
|
|
215
|
-
ghTokenItem: "placeholder",
|
|
216
|
-
authMode: "api-key",
|
|
217
|
-
};
|
|
218
|
-
await preflight(cwd, preflightOpts);
|
|
223
|
+
export async function composeAndApplyScaffoldChanges(cwd, tier, opts) {
|
|
219
224
|
// Render new file contents in memory.
|
|
220
225
|
// In --check we suppress the manual-edit refusal; drift detection is
|
|
221
226
|
// read-only and the user wants the diff signal, not an exception.
|
|
222
227
|
const dockerfileChange = renderDockerfile(cwd, tier, opts, !opts.check);
|
|
223
228
|
let schemaChange = null;
|
|
229
|
+
let shimChange = null;
|
|
224
230
|
let resolved = null;
|
|
225
231
|
if (tier === 2) {
|
|
226
232
|
resolved = resolveOpRefs(cwd, opts);
|
|
227
233
|
schemaChange = renderEnvSchema(cwd, resolved);
|
|
234
|
+
shimChange = renderShim(cwd);
|
|
228
235
|
}
|
|
229
236
|
// Sandcastle .env warn-once (tier-1 leftover).
|
|
230
237
|
const sandcastleEnv = join(cwd, ".sandcastle", ".env");
|
|
231
238
|
if (existsSync(sandcastleEnv)) {
|
|
232
239
|
console.log(" ⚠ .sandcastle/.env present — runway upgrade-repo leaves it alone (delete manually if you've moved to tier 2)");
|
|
233
240
|
}
|
|
234
|
-
const changes = [dockerfileChange, schemaChange].filter((c) => c !== null && c.before !== c.after);
|
|
241
|
+
const changes = [dockerfileChange, schemaChange, shimChange].filter((c) => c !== null && c.before !== c.after);
|
|
235
242
|
if (changes.length === 0) {
|
|
236
243
|
console.log("[runway upgrade-repo] already up to date");
|
|
237
244
|
process.exit(0);
|
|
@@ -244,11 +251,48 @@ export async function upgradeRepo(cwd, opts) {
|
|
|
244
251
|
console.log("[runway upgrade-repo] drift detected (--check); not writing");
|
|
245
252
|
process.exit(1);
|
|
246
253
|
}
|
|
247
|
-
// Write files.
|
|
254
|
+
// Write files. The shim needs mode 0755 so the docker COPY preserves
|
|
255
|
+
// the execute bit — route it through `writeShim` (the same helper
|
|
256
|
+
// `init` uses) instead of the plain `writeFileSync` path.
|
|
248
257
|
for (const c of changes) {
|
|
249
|
-
|
|
250
|
-
|
|
258
|
+
if (c.relPath === ".sandcastle/runway-claude-shim") {
|
|
259
|
+
writeShim(cwd);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
writeFileSync(c.path, c.after);
|
|
263
|
+
console.log(` ✓ wrote ${c.relPath}`);
|
|
264
|
+
}
|
|
251
265
|
}
|
|
266
|
+
return resolved;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Re-render `.sandcastle/Dockerfile` (and tier-2 `.env.schema` +
|
|
270
|
+
* `.sandcastle/runway-claude-shim`) from the current vendored
|
|
271
|
+
* templates, preserving user-set values like the op:// vault and
|
|
272
|
+
* item names. Composes the scaffolder's preflight/build/verify
|
|
273
|
+
* phases around the template re-render.
|
|
274
|
+
*
|
|
275
|
+
* Exits the process with code 1 if `--check` is set and drift was
|
|
276
|
+
* detected (so CI can gate on it); exits 0 on a clean no-op.
|
|
277
|
+
*/
|
|
278
|
+
export async function upgradeRepo(cwd, opts) {
|
|
279
|
+
const tier = detectTier(cwd);
|
|
280
|
+
console.log(`[runway upgrade-repo] detected tier ${tier}`);
|
|
281
|
+
// --check shouldn't refuse on a dirty tree — drift detection is read-only.
|
|
282
|
+
const allowDirty = opts.check ? true : opts.allowDirty;
|
|
283
|
+
// Preflight uses the scaffolder's helper; satisfy its ScaffolderOptions shape.
|
|
284
|
+
const preflightOpts = {
|
|
285
|
+
tier,
|
|
286
|
+
allowDirty,
|
|
287
|
+
force: opts.force,
|
|
288
|
+
skipBuild: opts.skipBuild,
|
|
289
|
+
opVault: "placeholder",
|
|
290
|
+
anthropicItem: "placeholder",
|
|
291
|
+
ghTokenItem: "placeholder",
|
|
292
|
+
authMode: "api-key",
|
|
293
|
+
};
|
|
294
|
+
await preflight(cwd, preflightOpts);
|
|
295
|
+
const resolved = await composeAndApplyScaffoldChanges(cwd, tier, opts);
|
|
252
296
|
if (!opts.skipBuild) {
|
|
253
297
|
await buildAgentImage(cwd);
|
|
254
298
|
}
|
|
@@ -1,6 +1,31 @@
|
|
|
1
|
-
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { chmodSync, existsSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { AUTH_MODE_ENV_VAR, TEMPLATES_DIR, } from "./scaffolder.js";
|
|
4
|
+
/**
|
|
5
|
+
* Vendor the claude shim into `.sandcastle/runway-claude-shim`.
|
|
6
|
+
*
|
|
7
|
+
* Both `runway init --tier=2` (via `applyVarlockLayer`) and
|
|
8
|
+
* `runway upgrade-repo` (VA-426) must write this file because the
|
|
9
|
+
* rendered Dockerfile contains `COPY runway-claude-shim …` and the
|
|
10
|
+
* `docker build` verification step will otherwise fail to find the
|
|
11
|
+
* source. The shim wraps every `claude` invocation in `varlock run`,
|
|
12
|
+
* and — when the four `RUNWAY_SIGNING_*` env vars are populated —
|
|
13
|
+
* bootstraps SSH-signed agent commits (VA-413).
|
|
14
|
+
*
|
|
15
|
+
* Idempotent: same template content, same on-disk content. Always
|
|
16
|
+
* sets mode 0755 so the docker COPY preserves the execute bit.
|
|
17
|
+
*/
|
|
18
|
+
export function writeShim(cwd) {
|
|
19
|
+
const shimSrc = readFileSync(join(TEMPLATES_DIR, "claude-shim.sh"), "utf8");
|
|
20
|
+
const shimDest = join(cwd, ".sandcastle", "runway-claude-shim");
|
|
21
|
+
writeFileSync(shimDest, shimSrc, { mode: 0o755 });
|
|
22
|
+
// `writeFileSync({mode})` only applies on file creation; on overwrite
|
|
23
|
+
// the existing inode keeps its prior mode (so a stale 0644 shim from
|
|
24
|
+
// a manual `cp` would silently survive). Chmod explicitly to make the
|
|
25
|
+
// execute bit a post-condition.
|
|
26
|
+
chmodSync(shimDest, 0o755);
|
|
27
|
+
console.log(" ✓ wrote .sandcastle/runway-claude-shim (claude wrapper + signing bootstrap)");
|
|
28
|
+
}
|
|
4
29
|
/**
|
|
5
30
|
* Phase 3 (tier 2 only): layer varlock + 1Password CLI on top of
|
|
6
31
|
* the base Dockerfile so the container can resolve `op://` references
|
|
@@ -47,13 +72,8 @@ export async function applyVarlockLayer(cwd, opts) {
|
|
|
47
72
|
}
|
|
48
73
|
// 2b. VA-413: vendor the claude shim into .sandcastle/ so the
|
|
49
74
|
// Dockerfile's `COPY .sandcastle/runway-claude-shim …` resolves
|
|
50
|
-
// during the docker build.
|
|
51
|
-
|
|
52
|
-
// are populated — bootstraps SSH-signed agent commits.
|
|
53
|
-
const shimSrc = readFileSync(join(TEMPLATES_DIR, "claude-shim.sh"), "utf8");
|
|
54
|
-
const shimDest = join(cwd, ".sandcastle", "runway-claude-shim");
|
|
55
|
-
writeFileSync(shimDest, shimSrc, { mode: 0o755 });
|
|
56
|
-
console.log(` ✓ wrote .sandcastle/runway-claude-shim (claude wrapper + signing bootstrap)`);
|
|
75
|
+
// during the docker build.
|
|
76
|
+
writeShim(cwd);
|
|
57
77
|
// 3. Drop .sandcastle/.env and .env.example.
|
|
58
78
|
for (const f of [".sandcastle/.env", ".sandcastle/.env.example"]) {
|
|
59
79
|
const p = join(cwd, f);
|
|
@@ -52,11 +52,29 @@ export async function verify(cwd, opts) {
|
|
|
52
52
|
}
|
|
53
53
|
ok(".env.schema contains no inline secrets");
|
|
54
54
|
const dockerfile = readFileSync(join(cwd, ".sandcastle", "Dockerfile"), "utf8");
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// Post-VA-413 the Dockerfile no longer inlines the shim (`varlock
|
|
56
|
+
// run …` lives inside the vendored `.sandcastle/runway-claude-shim`
|
|
57
|
+
// file). It does still rename the binary and COPY the vendored shim
|
|
58
|
+
// over it, so those are the two on-Dockerfile signals.
|
|
59
|
+
if (!dockerfile.includes("COPY runway-claude-shim"))
|
|
60
|
+
fail("Dockerfile not patched with claude-shim COPY (run `runway upgrade-repo --force`)");
|
|
57
61
|
if (!dockerfile.includes("/home/agent/.local/bin/claude.real"))
|
|
58
62
|
fail("Dockerfile shim not in expected layout");
|
|
59
|
-
ok("Dockerfile patched with
|
|
63
|
+
ok("Dockerfile patched with claude-shim COPY");
|
|
64
|
+
// VA-426 (sibling): the COPY above points at
|
|
65
|
+
// `.sandcastle/runway-claude-shim`, which `runway init` and
|
|
66
|
+
// `runway upgrade-repo` both materialise. Confirm it landed on disk
|
|
67
|
+
// and carries the wrapper's signal lines so a truncated/empty file
|
|
68
|
+
// doesn't slip past verify and trip on `docker build` instead.
|
|
69
|
+
const shimPath = join(cwd, ".sandcastle", "runway-claude-shim");
|
|
70
|
+
if (!existsSync(shimPath))
|
|
71
|
+
fail(".sandcastle/runway-claude-shim missing (run `runway upgrade-repo --force`)");
|
|
72
|
+
const shim = readFileSync(shimPath, "utf8");
|
|
73
|
+
if (!shim.includes("varlock run --env-file"))
|
|
74
|
+
fail(".sandcastle/runway-claude-shim doesn't invoke varlock — stale file?");
|
|
75
|
+
if (!shim.includes("claude.real"))
|
|
76
|
+
fail(".sandcastle/runway-claude-shim doesn't exec claude.real — stale file?");
|
|
77
|
+
ok(".sandcastle/runway-claude-shim present and wraps claude.real via varlock");
|
|
60
78
|
if (existsSync(join(cwd, ".sandcastle", ".env"))) {
|
|
61
79
|
fail(".sandcastle/.env still on disk — tier 2 deletes it");
|
|
62
80
|
}
|
package/dist/telemetry.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NodeSdk } from "@effect/opentelemetry";
|
|
2
2
|
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
3
3
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
4
4
|
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
5
5
|
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
6
|
-
import { Layer } from "effect";
|
|
7
6
|
/**
|
|
8
7
|
* VA-358 + VA-388: OpenTelemetry telemetry layer for the orchestrator.
|
|
9
8
|
*
|
|
@@ -45,11 +44,14 @@ const liveLayer = NodeSdk.layer(() => ({
|
|
|
45
44
|
// paths.
|
|
46
45
|
logRecordProcessor: new BatchLogRecordProcessor(new OTLPLogExporter()),
|
|
47
46
|
}));
|
|
48
|
-
// VA-388:
|
|
49
|
-
//
|
|
50
|
-
// `
|
|
51
|
-
//
|
|
52
|
-
|
|
47
|
+
// VA-388: when `logRecordProcessor` is set above, `NodeSdk.layer`
|
|
48
|
+
// internally composes `Logger.layerLoggerAdd` against an internal
|
|
49
|
+
// `OtelLoggerProvider`, so `Effect.log` calls fan out to OTLP in
|
|
50
|
+
// addition to the default stderr logger. An earlier revision wrapped
|
|
51
|
+
// `Logger.layerLoggerReplace` around `liveLayer` to silence stderr,
|
|
52
|
+
// but `NodeSdk.layer` consumes `OtelLoggerProvider` via `Layer.provide`
|
|
53
|
+
// internally and does not re-export it — that composition raised
|
|
54
|
+
// "Service not found: OtelLoggerProvider" at startup.
|
|
53
55
|
export const TelemetryLive = isTelemetryEnabled()
|
|
54
|
-
?
|
|
56
|
+
? liveLayer
|
|
55
57
|
: NodeSdk.layerEmpty;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valescoagency/runway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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": {
|