@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 CHANGED
@@ -541,7 +541,7 @@ These are tractable, just not v1.
541
541
 
542
542
  ## Status
543
543
 
544
- 0.11.0 — production-shaped and dogfooded against live Linear queues.
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([raw.state, raw.labels()]);
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
- ) VALUES (?, ?, ?, ?, ?, ?)
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
  /**
@@ -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="3" class="empty">Linear Todo queue is empty.</td></tr>`
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) {
@@ -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
- * Re-render `.sandcastle/Dockerfile` (and tier-2 `.env.schema`) from
195
- * the current vendored templates, preserving user-set values like
196
- * the op:// vault and item names. Composes the scaffolder's
197
- * preflight/build/verify phases around the template re-render.
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
- * Exits the process with code 1 if `--check` is set and drift was
200
- * detected (so CI can gate on it); exits 0 on a clean no-op.
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 upgradeRepo(cwd, opts) {
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
- writeFileSync(c.path, c.after);
250
- console.log(` ✓ wrote ${c.relPath}`);
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. The shim wraps each `claude` invocation
51
- // in `varlock run`, and — when the four RUNWAY_SIGNING_* env vars
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
- if (!dockerfile.includes("varlock run --env-file"))
56
- fail("Dockerfile not patched with varlock shim");
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 varlock + claude shim");
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 { Logger as OtelLogger, NodeSdk } from "@effect/opentelemetry";
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: replace Effect's default logger with one that fans out to
49
- // the OtelLoggerProvider built by `NodeSdk.layer`. Provided ON TOP of
50
- // `liveLayer` so the OtelLoggerProvider context is satisfied; without
51
- // the SDK behind it, this layer would fail to construct.
52
- const loggerLayer = Layer.provide(OtelLogger.layerLoggerReplace, liveLayer);
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
- ? Layer.merge(liveLayer, loggerLayer)
56
+ ? liveLayer
55
57
  : NodeSdk.layerEmpty;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valescoagency/runway",
3
- "version": "0.11.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": {