@valescoagency/runway 0.11.1 → 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.1 — 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) {
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.1",
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": {