@valescoagency/runway 0.7.0 → 0.7.1
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/linear.js +80 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -392,7 +392,7 @@ These are tractable, just not v1.
|
|
|
392
392
|
|
|
393
393
|
## Status
|
|
394
394
|
|
|
395
|
-
0.
|
|
395
|
+
0.7.1 — 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/linear.js
CHANGED
|
@@ -26,6 +26,17 @@ const IssueLabelNodeSchema = Schema.Struct({
|
|
|
26
26
|
const ProjectNodeSchema = Schema.Struct({
|
|
27
27
|
id: Schema.String,
|
|
28
28
|
});
|
|
29
|
+
// VA-394: minimal shape needed to ask "is this blocker still active?"
|
|
30
|
+
// — we only read the relation's `type` (filtering for `blocks`) and
|
|
31
|
+
// the related issue's workflow state `type`. Schemas stay narrow so
|
|
32
|
+
// the validation cost per candidate stays small.
|
|
33
|
+
const IssueRelationNodeSchema = Schema.Struct({
|
|
34
|
+
id: Schema.String,
|
|
35
|
+
type: Schema.String,
|
|
36
|
+
});
|
|
37
|
+
const WorkflowStateTypeNodeSchema = Schema.Struct({
|
|
38
|
+
type: Schema.String,
|
|
39
|
+
});
|
|
29
40
|
// VA-383: comments fetched for surfacing prior-attempt review feedback
|
|
30
41
|
// to the implementer. `user` is nullable on Linear's side (system /
|
|
31
42
|
// integration comments can lack an author); we map a missing user to
|
|
@@ -51,6 +62,56 @@ const decodeIssueLabelNode = Schema.decodeUnknownSync(IssueLabelNodeSchema);
|
|
|
51
62
|
const decodeProjectNode = Schema.decodeUnknownSync(ProjectNodeSchema);
|
|
52
63
|
const decodeCommentNode = Schema.decodeUnknownSync(CommentNodeSchema);
|
|
53
64
|
const decodeViewer = Schema.decodeUnknownSync(ViewerSchema);
|
|
65
|
+
const decodeIssueRelationNode = Schema.decodeUnknownSync(IssueRelationNodeSchema);
|
|
66
|
+
const decodeWorkflowStateTypeNode = Schema.decodeUnknownSync(WorkflowStateTypeNodeSchema);
|
|
67
|
+
// VA-394: Linear workflow state `type` values that mean "the blocker
|
|
68
|
+
// has resolved." Anything else (`triage`, `backlog`, `unstarted`,
|
|
69
|
+
// `started`) still gates the blocked issue from runway pickup.
|
|
70
|
+
const TERMINAL_STATE_TYPES = new Set(["completed", "canceled"]);
|
|
71
|
+
const isTerminalStateType = (type) => TERMINAL_STATE_TYPES.has(type);
|
|
72
|
+
/**
|
|
73
|
+
* VA-394: returns true when `issue` carries `hitlLabel`. Runway writes
|
|
74
|
+
* this label on HITL escapes, and triage may apply it to flag
|
|
75
|
+
* human-only work — either way the contract is "do not pick up." Uses
|
|
76
|
+
* the SDK's chained `labels()` call rather than asking for labels
|
|
77
|
+
* inline at the candidate-fetch site so the schema for `IssueNode`
|
|
78
|
+
* stays narrow.
|
|
79
|
+
*/
|
|
80
|
+
async function hasHitlLabel(issue, hitlLabel) {
|
|
81
|
+
const labels = await issue.labels();
|
|
82
|
+
for (const raw of labels.nodes) {
|
|
83
|
+
if (decodeIssueLabelNode(raw).name === hitlLabel)
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* VA-394: returns true when `issue` has at least one `inverseRelations`
|
|
90
|
+
* record of type `blocks` whose blocker is in a non-terminal workflow
|
|
91
|
+
* state. Other relation types (`duplicate`, `related`) do not gate
|
|
92
|
+
* pickup. A blocker with a missing or undecodable state is treated as
|
|
93
|
+
* inactive (best-effort: drift in one relation should not stop the
|
|
94
|
+
* queue), but the decoder still throws `ParseError` on outright
|
|
95
|
+
* malformed relation nodes — caught upstream as `LinearSchemaError`.
|
|
96
|
+
*/
|
|
97
|
+
async function hasActiveBlocker(issue) {
|
|
98
|
+
const relations = await issue.inverseRelations();
|
|
99
|
+
for (const rawRel of relations.nodes) {
|
|
100
|
+
const rel = decodeIssueRelationNode(rawRel);
|
|
101
|
+
if (rel.type !== "blocks")
|
|
102
|
+
continue;
|
|
103
|
+
const blocker = await rawRel.issue;
|
|
104
|
+
if (!blocker)
|
|
105
|
+
continue;
|
|
106
|
+
const blockerState = await blocker.state;
|
|
107
|
+
if (!blockerState)
|
|
108
|
+
continue;
|
|
109
|
+
const stateType = decodeWorkflowStateTypeNode(blockerState).type;
|
|
110
|
+
if (!isTerminalStateType(stateType))
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
54
115
|
export class LinearNotFound extends Data.TaggedError("LinearNotFound") {
|
|
55
116
|
}
|
|
56
117
|
export class LinearUnauthorized extends Data.TaggedError("LinearUnauthorized") {
|
|
@@ -239,15 +300,30 @@ export function createLinearGateway(config, limiter = null) {
|
|
|
239
300
|
// VA-360: validate every issue node through the schema —
|
|
240
301
|
// a single drifted issue surfaces as `LinearSchemaError`
|
|
241
302
|
// instead of a downstream `cannot read property X`.
|
|
242
|
-
|
|
303
|
+
//
|
|
304
|
+
// VA-394: per-candidate eligibility — skip issues carrying
|
|
305
|
+
// `config.hitlLabel` (triage's "human-only" marker, also
|
|
306
|
+
// applied by runway itself on prior HITL escapes), and
|
|
307
|
+
// skip issues with at least one active `blocks` relation
|
|
308
|
+
// pointing at them. The candidate set is the Todo queue,
|
|
309
|
+
// typically a handful of issues; the N+1 SDK calls per
|
|
310
|
+
// candidate go through the rate limiter and the retry
|
|
311
|
+
// policy alongside everything else.
|
|
312
|
+
const eligible = [];
|
|
313
|
+
for (const raw of issues.nodes) {
|
|
243
314
|
const i = decodeIssueNode(raw);
|
|
244
|
-
|
|
315
|
+
if (await hasHitlLabel(raw, config.hitlLabel))
|
|
316
|
+
continue;
|
|
317
|
+
if (await hasActiveBlocker(raw))
|
|
318
|
+
continue;
|
|
319
|
+
eligible.push({
|
|
245
320
|
id: i.id,
|
|
246
321
|
identifier: i.identifier,
|
|
247
322
|
title: i.title,
|
|
248
323
|
description: i.description ?? "",
|
|
249
|
-
};
|
|
250
|
-
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return eligible;
|
|
251
327
|
},
|
|
252
328
|
catch: (err) => classifyLinearError(err, "fetchReady"),
|
|
253
329
|
}), { call: "fetchReady" }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valescoagency/runway",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
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": {
|