@dahrk/linear 0.1.0 → 0.1.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.
Files changed (62) hide show
  1. package/README.md +24 -12
  2. package/dist/comments.d.ts +36 -0
  3. package/dist/comments.d.ts.map +1 -0
  4. package/dist/comments.js +104 -0
  5. package/dist/comments.js.map +1 -0
  6. package/dist/documents.d.ts +1 -15
  7. package/dist/documents.d.ts.map +1 -1
  8. package/dist/documents.js +37 -27
  9. package/dist/documents.js.map +1 -1
  10. package/dist/format-action.d.ts +25 -0
  11. package/dist/format-action.d.ts.map +1 -0
  12. package/dist/format-action.js +250 -0
  13. package/dist/format-action.js.map +1 -0
  14. package/dist/index.d.ts +113 -11
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +182 -58
  17. package/dist/index.js.map +1 -1
  18. package/dist/issue-graph.d.ts +37 -0
  19. package/dist/issue-graph.d.ts.map +1 -0
  20. package/dist/issue-graph.js +125 -0
  21. package/dist/issue-graph.js.map +1 -0
  22. package/dist/issues.d.ts +3 -2
  23. package/dist/issues.d.ts.map +1 -1
  24. package/dist/issues.js +5 -10
  25. package/dist/issues.js.map +1 -1
  26. package/dist/labels.d.ts +47 -0
  27. package/dist/labels.d.ts.map +1 -1
  28. package/dist/labels.js +71 -23
  29. package/dist/labels.js.map +1 -1
  30. package/dist/linear-client.d.ts +51 -0
  31. package/dist/linear-client.d.ts.map +1 -1
  32. package/dist/linear-client.js +234 -34
  33. package/dist/linear-client.js.map +1 -1
  34. package/dist/oauth.d.ts +11 -10
  35. package/dist/oauth.d.ts.map +1 -1
  36. package/dist/oauth.js +35 -32
  37. package/dist/oauth.js.map +1 -1
  38. package/dist/recording-client.d.ts +20 -2
  39. package/dist/recording-client.d.ts.map +1 -1
  40. package/dist/recording-client.js +39 -1
  41. package/dist/recording-client.js.map +1 -1
  42. package/dist/responding-client.d.ts +49 -0
  43. package/dist/responding-client.d.ts.map +1 -0
  44. package/dist/responding-client.js +47 -0
  45. package/dist/responding-client.js.map +1 -0
  46. package/dist/teams.d.ts +20 -0
  47. package/dist/teams.d.ts.map +1 -0
  48. package/dist/teams.js +32 -0
  49. package/dist/teams.js.map +1 -0
  50. package/package.json +8 -10
  51. package/src/comments.ts +126 -0
  52. package/src/documents.ts +162 -0
  53. package/src/format-action.ts +279 -0
  54. package/src/index.ts +556 -0
  55. package/src/issue-graph.ts +169 -0
  56. package/src/issues.ts +93 -0
  57. package/src/labels.ts +254 -0
  58. package/src/linear-client.ts +449 -0
  59. package/src/oauth.ts +194 -0
  60. package/src/recording-client.ts +141 -0
  61. package/src/responding-client.ts +106 -0
  62. package/src/teams.ts +44 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Fetch the metadata of the issues one hop from a run's issue - parent, children, blockers, blocked,
3
+ * related - so the hub can snapshot a manifest into the run and tell the agent WHAT EXISTS around the
4
+ * ticket it is working.
5
+ *
6
+ * This is deliberately metadata only: identifier, title, relation, state. No descriptions, no comments,
7
+ * no documents. That keeps the manifest cheap enough to send on every run, and it is the piece that
8
+ * makes on-demand context work at all - an agent that cannot see there is a spike or an epic attached
9
+ * will never think to open one.
10
+ *
11
+ * Distinct from `fetchOpenBlockers` in `labels.ts`, which reads the same edges for the deterministic
12
+ * blocked guard: that one answers "may this run proceed" and feeds control flow, this one answers
13
+ * "what surrounds this ticket" and is context only. They are kept apart on purpose so a change to the
14
+ * manifest can never alter a guard.
15
+ *
16
+ * The `IssueGraphSource` seam keeps the assembly pure and unit-testable.
17
+ */
18
+ import { LinearClient } from "@linear/sdk";
19
+ /** How many neighbours ride the manifest. Beyond this the prompt stops being a summary. */
20
+ export const MAX_RELATED_ISSUES = 50;
21
+ /** Stable ordering for the manifest: the structurally important edges first, so a truncated list keeps
22
+ * what matters. Parent and blockers change how the work should be done; `related` is a loose,
23
+ * human-curated edge and is the first thing worth losing. */
24
+ const RELATION_ORDER = {
25
+ parent: 0,
26
+ blocker: 1,
27
+ child: 2,
28
+ blocked: 3,
29
+ related: 4,
30
+ };
31
+ /**
32
+ * Collect the issue's one-hop neighbourhood from a source: drop edges whose issue did not resolve or
33
+ * carries no identifier, de-dupe by identifier (an issue can be reachable by more than one edge -
34
+ * keep the structurally strongest, which is the lowest `RELATION_ORDER`), sort, and cap. Pure of any
35
+ * network.
36
+ */
37
+ export async function collectRelatedIssues(source, issueId) {
38
+ const byKey = new Map();
39
+ for (const edge of await source.issueEdges(issueId)) {
40
+ const issue = edge.issue;
41
+ const key = issue?.identifier?.trim();
42
+ if (!issue || !key)
43
+ continue;
44
+ const candidate = {
45
+ key,
46
+ title: (issue.title ?? "").trim() || "Untitled issue",
47
+ relation: edge.relation,
48
+ stateName: (issue.stateName ?? "").trim() || "unknown",
49
+ stateType: (issue.stateType ?? "").trim() || "unknown",
50
+ url: issue.url ?? "",
51
+ };
52
+ const existing = byKey.get(key);
53
+ if (!existing || RELATION_ORDER[candidate.relation] < RELATION_ORDER[existing.relation]) {
54
+ byKey.set(key, candidate);
55
+ }
56
+ }
57
+ return [...byKey.values()]
58
+ .sort((a, b) => RELATION_ORDER[a.relation] - RELATION_ORDER[b.relation] || a.key.localeCompare(b.key))
59
+ .slice(0, MAX_RELATED_ISSUES);
60
+ }
61
+ /** The live `IssueGraphSource`, backed by a Linear bearer token. Uses the typed SDK. */
62
+ export function linearIssueGraphSource(token) {
63
+ const client = new LinearClient({ accessToken: token });
64
+ return {
65
+ async issueEdges(issueId) {
66
+ const issue = await client.issue(issueId);
67
+ // Resolve one neighbouring issue into the raw shape, including its state. Returns null when the
68
+ // issue or its state does not resolve, which the assembly then skips.
69
+ const shape = async (node) => {
70
+ if (!node)
71
+ return null;
72
+ const state = await node.state;
73
+ return {
74
+ ...(node.identifier ? { identifier: node.identifier } : {}),
75
+ ...(node.title ? { title: node.title } : {}),
76
+ ...(node.url ? { url: node.url } : {}),
77
+ ...(state?.name ? { stateName: state.name } : {}),
78
+ ...(state?.type ? { stateType: state.type } : {}),
79
+ };
80
+ };
81
+ // The four reads run concurrently; each is independently capped. `relations` is what this issue
82
+ // blocks/relates to, `inverseRelations` is what points AT this issue - the direction matters, and
83
+ // getting it backwards is the bug called out on `fetchOpenBlockers` (labels.ts).
84
+ const [parent, children, relations, inverseRelations] = await Promise.all([
85
+ issue.parent,
86
+ issue.children({ first: 25 }),
87
+ issue.relations({ first: 25 }),
88
+ issue.inverseRelations({ first: 25 }),
89
+ ]);
90
+ const edges = [];
91
+ const parentShape = await shape(parent);
92
+ if (parentShape)
93
+ edges.push({ relation: "parent", issue: parentShape });
94
+ for (const child of children.nodes) {
95
+ edges.push({ relation: "child", issue: await shape(child) });
96
+ }
97
+ // Outbound: `A blocks B` stored on A means B is blocked BY us; anything else is a plain relation.
98
+ for (const rel of relations.nodes) {
99
+ const related = await rel.relatedIssue;
100
+ edges.push({
101
+ relation: rel.type === "blocks" ? "blocked" : "related",
102
+ issue: await shape(related),
103
+ });
104
+ }
105
+ // Inbound: a `blocks` relation pointing at us means that issue is OUR blocker.
106
+ for (const rel of inverseRelations.nodes) {
107
+ const other = await rel.issue;
108
+ edges.push({
109
+ relation: rel.type === "blocks" ? "blocker" : "related",
110
+ issue: await shape(other),
111
+ });
112
+ }
113
+ return edges;
114
+ },
115
+ };
116
+ }
117
+ /**
118
+ * Fetch the issue's one-hop neighbourhood as contract `RelatedIssue`s, ready to snapshot into a run.
119
+ * The single entry point the hub calls. A hard failure (auth, network) propagates so the caller can
120
+ * log and proceed with none.
121
+ */
122
+ export async function fetchRelatedIssues(token, issueId) {
123
+ return collectRelatedIssues(linearIssueGraphSource(token), issueId);
124
+ }
125
+ //# sourceMappingURL=issue-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-graph.js","sourceRoot":"","sources":["../src/issue-graph.ts"],"names":[],"mappings":"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"}
package/dist/issues.d.ts CHANGED
@@ -42,7 +42,8 @@ export declare function linearClientAuth(token: string): {
42
42
  accessToken: string;
43
43
  };
44
44
  /** The live `TriageApi` backed by a Linear token (personal API key or OAuth access token). Resolves
45
- * team (and optional project) by name, paginating like the label provisioner rather than relying on
46
- * SDK filter shapes. */
45
+ * team by key and optional project by name via server-side `filter` (verified on @linear/sdk 86.0.0,
46
+ * DHK-579: `TeamFilter.key`/`ProjectFilter.name` return exactly the expected node - the earlier
47
+ * paginate-then-match workaround is no longer needed). */
47
48
  export declare function linearTriageApi(token: string): TriageApi;
48
49
  //# sourceMappingURL=issues.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../src/issues.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,SAAS;IACxB;;uFAEmF;IACnF,WAAW,CAAC,KAAK,EAAE;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD;;uEAEmE;IACnE,kBAAkB,CAAC,KAAK,EAAE;QACxB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5B;;;sFAGkF;IAClF,YAAY,CAAC,KAAK,EAAE;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,EAAE,MAAM,CAAC;QACvB,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;KAC7B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED;;;2FAG2F;AAC3F,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAE5F;AAED;;yBAEyB;AACzB,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CA8CxD"}
1
+ {"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../src/issues.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,SAAS;IACxB;;uFAEmF;IACnF,WAAW,CAAC,KAAK,EAAE;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD;;uEAEmE;IACnE,kBAAkB,CAAC,KAAK,EAAE;QACxB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5B;;;sFAGkF;IAClF,YAAY,CAAC,KAAK,EAAE;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,EAAE,MAAM,CAAC;QACvB,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;KAC7B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED;;;2FAG2F;AAC3F,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAE5F;AAED;;;2DAG2D;AAC3D,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CA0CxD"}
package/dist/issues.js CHANGED
@@ -13,24 +13,19 @@ export function linearClientAuth(token) {
13
13
  return token.startsWith("lin_api_") ? { apiKey: token } : { accessToken: token };
14
14
  }
15
15
  /** The live `TriageApi` backed by a Linear token (personal API key or OAuth access token). Resolves
16
- * team (and optional project) by name, paginating like the label provisioner rather than relying on
17
- * SDK filter shapes. */
16
+ * team by key and optional project by name via server-side `filter` (verified on @linear/sdk 86.0.0,
17
+ * DHK-579: `TeamFilter.key`/`ProjectFilter.name` return exactly the expected node - the earlier
18
+ * paginate-then-match workaround is no longer needed). */
18
19
  export function linearTriageApi(token) {
19
20
  const client = new LinearClient(linearClientAuth(token));
20
21
  return {
21
22
  async createIssue(input) {
22
- const teams = await client.teams();
23
- while (teams.pageInfo.hasNextPage)
24
- await teams.fetchNext();
25
- const team = teams.nodes.find((t) => t.key === input.teamKey);
23
+ const team = (await client.teams({ filter: { key: { eq: input.teamKey } } })).nodes[0];
26
24
  if (!team)
27
25
  throw new Error(`triage team not found: ${input.teamKey}`);
28
26
  let projectId;
29
27
  if (input.projectName) {
30
- const projects = await client.projects();
31
- while (projects.pageInfo.hasNextPage)
32
- await projects.fetchNext();
33
- projectId = projects.nodes.find((p) => p.name === input.projectName)?.id;
28
+ projectId = (await client.projects({ filter: { name: { eq: input.projectName } } })).nodes[0]?.id;
34
29
  }
35
30
  const payload = await client.createIssue({
36
31
  teamId: team.id,
@@ -1 +1 @@
1
- {"version":3,"file":"issues.js","sourceRoot":"","sources":["../src/issues.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"issues.js","sourceRoot":"","sources":["../src/issues.ts"],"names":[],"mappings":"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"}
package/dist/labels.d.ts CHANGED
@@ -53,6 +53,53 @@ export declare function provisionRepoLabels(api: LabelApi, repoNames: Iterable<s
53
53
  * feeds deterministic routing and is snapshotted into the run.
54
54
  */
55
55
  export declare function fetchIssueProjectRepoLabels(token: string, issueId: string): Promise<string[]>;
56
+ /**
57
+ * Count an issue's child issues (DHK-412), so the hub's epic guard can decline a parent-of-children.
58
+ * The webhook payload carries an issue's `<parent-issue>` but never a listing of its children, so
59
+ * "has children" must be resolved with a read against the connection token. Returns 0 when the issue
60
+ * has none (or the field is empty). Capped at `first:1` - the guard only needs "any", not an exact
61
+ * total - so it stays a cheap intake read. Data assembly, not control flow: the count feeds the
62
+ * deterministic, no-LLM epic guard.
63
+ */
64
+ export declare function fetchIssueChildCount(token: string, issueId: string): Promise<number>;
65
+ /** One issue that blocks another and has not settled yet. `identifier` is the human key (e.g.
66
+ * `DHK-777`) the decline note names; `stateName` is the team's own label for the state it sits in,
67
+ * carried for the note only - never matched on (see `fetchOpenBlockers`). */
68
+ export interface OpenBlocker {
69
+ identifier: string;
70
+ stateName: string;
71
+ }
72
+ /**
73
+ * Has a blocker finished, so it no longer blocks?
74
+ *
75
+ * Decided on the workflow state's `type` - Linear's own fixed vocabulary (`backlog`, `unstarted`,
76
+ * `started`, `completed`, `canceled`) - and NEVER on its name. Teams rename states freely: "Done"
77
+ * becomes "Shipped", "Released", "Merged". A name match would stop guarding for every one of those
78
+ * teams with nothing to notice, because the guard would simply never fire again.
79
+ *
80
+ * An absent/unknown type is treated as NOT settled: a blocker we cannot prove is finished is one we
81
+ * must assume is not. Pure, so the rule is pinned by a test rather than by a live Linear read.
82
+ */
83
+ export declare function isBlockerSettled(stateType: string | undefined): boolean;
84
+ /**
85
+ * The issues blocking `issueId` that are not finished yet, so the hub's blocked guard can decline a
86
+ * run that cannot possibly succeed. The webhook payload carries no relation data at all, so this must
87
+ * be resolved with a read against the connection token, exactly as `fetchIssueChildCount` resolves the
88
+ * epic signal. Returns an empty list when nothing blocks the issue, or when every blocker has settled.
89
+ *
90
+ * The direction matters: `A blocks B` is stored on A, so B reads it through `inverseRelations`. Reading
91
+ * `relations` instead would return what this issue blocks - the opposite question, and one that would
92
+ * silently never guard anything.
93
+ *
94
+ * "Settled" is decided on the state's `type` (`completed` / `canceled`), NEVER on its name. Teams rename
95
+ * workflow states freely - "Done" becomes "Shipped", "Released", "Merged" - and a name match would stop
96
+ * guarding for those teams without any error to notice. The type is Linear's own fixed vocabulary.
97
+ *
98
+ * Capped, like the child-count read: a ticket with more open blockers than the cap is still blocked, and
99
+ * the note naming the first few is enough to act on. Data assembly, not control flow: the list feeds the
100
+ * deterministic, no-LLM blocked guard.
101
+ */
102
+ export declare function fetchOpenBlockers(token: string, issueId: string): Promise<OpenBlocker[]>;
56
103
  /** The live `LabelApi` backed by a Linear bearer token (workspace-scoped labels). */
57
104
  export declare function linearLabelApi(token: string): LabelApi;
58
105
  /** The live `LabelApi` backed by a Linear bearer token, for **project** labels (a separate namespace
@@ -1 +1 @@
1
- {"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../src/labels.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,QAAQ;IACvB,qDAAqD;IACrD,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,yDAAyD;IACzD,WAAW,CAAC,KAAK,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACrC;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EACvB,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,MAAM,EAAE,CAAC,CA0BnB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EACvB,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,MAAM,EAAE,CAAC,CAMnB;AAED;;mGAEmG;AACnG,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,QAAQ,EACb,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC3B,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5B,OAAO,CAAC,MAAM,EAAE,CAAC,CAOnB;AAID;;;;;;;GAOG;AACH,wBAAsB,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAwBnG;AAED,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAsBtD;AAED;;mDAEmD;AACnD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAsBvD"}
1
+ {"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../src/labels.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,QAAQ;IACvB,qDAAqD;IACrD,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,yDAAyD;IACzD,WAAW,CAAC,KAAK,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACrC;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EACvB,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,MAAM,EAAE,CAAC,CA0BnB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EACvB,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,MAAM,EAAE,CAAC,CAMnB;AAED;;mGAEmG;AACnG,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,QAAQ,EACb,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC3B,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5B,OAAO,CAAC,MAAM,EAAE,CAAC,CAOnB;AAED;;;;;;;GAOG;AACH,wBAAsB,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOnG;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK1F;AAED;;8EAE8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEvE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAc9F;AAED,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAsBtD;AAED;;mDAEmD;AACnD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAsBvD"}
package/dist/labels.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Trigger-label auto-provisioning (build spec section 14; plan item 7). A repository declares its
3
- * workflows as `.skakel/workflows/*.yaml`, each with a `select.label`; for those labels to be
3
+ * workflows as `.dahrk/workflows/*.yaml`, each with a `select.label`; for those labels to be
4
4
  * assignable in Linear they must exist in the workspace. Rather than make operators create them by
5
5
  * hand, the onboarding path reads the workflow label set and creates any that are missing - once,
6
6
  * idempotently - optionally grouped under a single "Dahrk workflow" label group.
@@ -70,7 +70,6 @@ export async function provisionRepoLabels(api, repoNames, opts = {}) {
70
70
  describe: (name) => `Dahrk repository selector: ${name}`,
71
71
  });
72
72
  }
73
- const GRAPHQL_URL = "https://api.linear.app/graphql";
74
73
  /**
75
74
  * Fetch the child names of the issue's **project** `repo` label group (project labels are a separate
76
75
  * Linear namespace from issue labels, `Project.labels`). Returns the names of project labels whose
@@ -80,28 +79,77 @@ const GRAPHQL_URL = "https://api.linear.app/graphql";
80
79
  * feeds deterministic routing and is snapshotted into the run.
81
80
  */
82
81
  export async function fetchIssueProjectRepoLabels(token, issueId) {
83
- const res = await fetch(GRAPHQL_URL, {
84
- method: "POST",
85
- headers: { "content-type": "application/json", authorization: token },
86
- body: JSON.stringify({
87
- query: `query($id:String!){ issue(id:$id){ project{ labels(first:100){ nodes{ name parent{ name } } } } } }`,
88
- variables: { id: issueId },
89
- }),
90
- });
91
- if (!res.ok)
92
- throw new Error(`linear graphql ${res.status}`);
93
- const json = (await res.json().catch(() => ({})));
94
- if (json.errors?.length)
95
- throw new Error(json.errors.map((e) => e.message).join("; "));
96
- const nodes = json.data?.issue?.project?.labels?.nodes ?? [];
97
- const out = [];
98
- for (const n of nodes) {
99
- const group = n.parent?.name;
100
- if (typeof n.name === "string" && typeof group === "string" && group.trim().toLowerCase() === "repo") {
101
- out.push(n.name.trim());
102
- }
82
+ const client = new LinearClient({ accessToken: token });
83
+ const issue = await client.issue(issueId);
84
+ const project = await issue.project;
85
+ if (!project)
86
+ return [];
87
+ const conn = await project.labels({ first: 100, filter: { parent: { name: { eqIgnoreCase: REPO_LABEL_GROUP } } } });
88
+ return conn.nodes.map((n) => n.name.trim());
89
+ }
90
+ /**
91
+ * Count an issue's child issues (DHK-412), so the hub's epic guard can decline a parent-of-children.
92
+ * The webhook payload carries an issue's `<parent-issue>` but never a listing of its children, so
93
+ * "has children" must be resolved with a read against the connection token. Returns 0 when the issue
94
+ * has none (or the field is empty). Capped at `first:1` - the guard only needs "any", not an exact
95
+ * total - so it stays a cheap intake read. Data assembly, not control flow: the count feeds the
96
+ * deterministic, no-LLM epic guard.
97
+ */
98
+ export async function fetchIssueChildCount(token, issueId) {
99
+ const client = new LinearClient({ accessToken: token });
100
+ const issue = await client.issue(issueId);
101
+ const conn = await issue.children({ first: 1 });
102
+ return conn.nodes.length;
103
+ }
104
+ /**
105
+ * Has a blocker finished, so it no longer blocks?
106
+ *
107
+ * Decided on the workflow state's `type` - Linear's own fixed vocabulary (`backlog`, `unstarted`,
108
+ * `started`, `completed`, `canceled`) - and NEVER on its name. Teams rename states freely: "Done"
109
+ * becomes "Shipped", "Released", "Merged". A name match would stop guarding for every one of those
110
+ * teams with nothing to notice, because the guard would simply never fire again.
111
+ *
112
+ * An absent/unknown type is treated as NOT settled: a blocker we cannot prove is finished is one we
113
+ * must assume is not. Pure, so the rule is pinned by a test rather than by a live Linear read.
114
+ */
115
+ export function isBlockerSettled(stateType) {
116
+ return stateType === "completed" || stateType === "canceled";
117
+ }
118
+ /**
119
+ * The issues blocking `issueId` that are not finished yet, so the hub's blocked guard can decline a
120
+ * run that cannot possibly succeed. The webhook payload carries no relation data at all, so this must
121
+ * be resolved with a read against the connection token, exactly as `fetchIssueChildCount` resolves the
122
+ * epic signal. Returns an empty list when nothing blocks the issue, or when every blocker has settled.
123
+ *
124
+ * The direction matters: `A blocks B` is stored on A, so B reads it through `inverseRelations`. Reading
125
+ * `relations` instead would return what this issue blocks - the opposite question, and one that would
126
+ * silently never guard anything.
127
+ *
128
+ * "Settled" is decided on the state's `type` (`completed` / `canceled`), NEVER on its name. Teams rename
129
+ * workflow states freely - "Done" becomes "Shipped", "Released", "Merged" - and a name match would stop
130
+ * guarding for those teams without any error to notice. The type is Linear's own fixed vocabulary.
131
+ *
132
+ * Capped, like the child-count read: a ticket with more open blockers than the cap is still blocked, and
133
+ * the note naming the first few is enough to act on. Data assembly, not control flow: the list feeds the
134
+ * deterministic, no-LLM blocked guard.
135
+ */
136
+ export async function fetchOpenBlockers(token, issueId) {
137
+ const client = new LinearClient({ accessToken: token });
138
+ const issue = await client.issue(issueId);
139
+ const conn = await issue.inverseRelations({ first: 25 });
140
+ const open = [];
141
+ for (const relation of conn.nodes) {
142
+ if (relation.type !== "blocks")
143
+ continue;
144
+ const blocker = await relation.issue;
145
+ if (!blocker)
146
+ continue;
147
+ const state = await blocker.state;
148
+ if (isBlockerSettled(state?.type))
149
+ continue;
150
+ open.push({ identifier: blocker.identifier, stateName: state?.name ?? "unknown" });
103
151
  }
104
- return out;
152
+ return open;
105
153
  }
106
154
  /** The live `LabelApi` backed by a Linear bearer token (workspace-scoped labels). */
107
155
  export function linearLabelApi(token) {
@@ -1 +1 @@
1
- {"version":3,"file":"labels.js","sourceRoot":"","sources":["../src/labels.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"labels.js","sourceRoot":"","sources":["../src/labels.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,54 @@
1
1
  import type { AgentSessionClient } from "./index.js";
2
+ /** The subset of a Linear workflow state that state-selection needs. Kept structural so the live
3
+ * SDK's `WorkflowState` nodes satisfy it and the pure selectors are unit-testable without the SDK. */
4
+ export interface StateLike {
5
+ id: string;
6
+ name: string;
7
+ position: number;
8
+ type: string;
9
+ }
10
+ /** Find the review-target state the way `moveIssueToReview` does, so `startIssue` can EXCLUDE it.
11
+ * Linear has no "review" state type, so it is matched by name: an explicit configured name
12
+ * (`DAHRK_REVIEW_STATE_NAME`) when set, else the first review-ish name. */
13
+ export declare function findReviewState<T extends StateLike>(states: readonly T[], reviewName?: string): T | undefined;
14
+ /**
15
+ * Pick the team's working ("In Progress") state for a just-started run. A team can have MORE THAN
16
+ * ONE `started`-typed state (e.g. both "In Progress" and "In Review" are typed `started`), so the
17
+ * naive "lowest-position started state" is ambiguous and can wrongly land the ticket in In Review.
18
+ * Prefer an explicit working-name match (`/in progress|doing|started/i`), else the lowest-position
19
+ * `started` state that is NOT the review state. Returns `undefined` when there is no usable working
20
+ * state (so the caller reports a no-op rather than moving the ticket to review).
21
+ */
22
+ export declare function selectStartState<T extends StateLike>(states: readonly T[], reviewName?: string): T | undefined;
23
+ /** Returns true when `delegateId` should be written on the issue.
24
+ * Skipping when already equal avoids the "assigned to you" notification on every re-run (DHK-263). */
25
+ export declare function shouldSetDelegate(issueDelegateId: string | undefined, viewerId: string): boolean;
26
+ /** A document as the issue-documents lookup sees it. Structural, so `findDocumentByTitle` is unit
27
+ * testable without standing up the SDK. */
28
+ export interface DocumentLike {
29
+ id: string;
30
+ /** Optional so the live SDK's non-null `title` is assignable and a null from the API cannot throw. */
31
+ title?: string;
32
+ url?: string;
33
+ }
34
+ /** The slice of a Linear issue the document lookup needs. */
35
+ export interface IssueDocuments {
36
+ documents(args: {
37
+ first: number;
38
+ }): Promise<{
39
+ nodes: DocumentLike[];
40
+ }>;
41
+ }
42
+ /**
43
+ * The document already attached to this issue under `title`, or null when there is none. This is
44
+ * what gives `createDocument` its upsert: Linear keys documents by id only, so a title match on the
45
+ * issue is our stand-in.
46
+ *
47
+ * Titles are compared trimmed and case-insensitively, so a human who lightly re-cases the title does
48
+ * not cause the next run to fork a second copy. The read is bounded and unpaginated at 50, matching
49
+ * `linearDocumentSource.issueDocuments` in `documents.ts`: an issue carrying more than 50 documents
50
+ * silently drops the overflow, which is a deliberate limit rather than a bug.
51
+ */
52
+ export declare function findDocumentByTitle(issue: IssueDocuments, title: string): Promise<DocumentLike | null>;
2
53
  export declare function createLinearClient(token: string): AgentSessionClient;
3
54
  //# sourceMappingURL=linear-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"linear-client.d.ts","sourceRoot":"","sources":["../src/linear-client.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAEV,kBAAkB,EAMnB,MAAM,YAAY,CAAC;AAqBpB,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,CA4JpE"}
1
+ {"version":3,"file":"linear-client.d.ts","sourceRoot":"","sources":["../src/linear-client.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAEV,kBAAkB,EASnB,MAAM,YAAY,CAAC;AA6CpB;uGACuG;AACvG,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AASD;;4EAE4E;AAC5E,wBAAgB,eAAe,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAK7G;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAO9G;AAED;uGACuG;AACvG,wBAAgB,iBAAiB,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEhG;AAED;4CAC4C;AAC5C,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,sGAAsG;IACtG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC,CAAC;CACxE;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAK5G;AA6BD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,CA6QpE"}