@pome-sh/cli 0.15.0 → 0.17.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.
Files changed (110) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/src/cli/checks-add.d.ts +2 -7
  3. package/dist/src/cli/checks-add.js +10 -24
  4. package/dist/src/cli/checks.d.ts +3 -2
  5. package/dist/src/cli/checks.js +22 -9
  6. package/dist/src/cli/vocabulary-skew.d.ts +64 -0
  7. package/dist/src/cli/vocabulary-skew.js +187 -0
  8. package/dist/src/twin/githubCloneAdapter.d.ts +1 -0
  9. package/node_modules/@pome-sh/twin-github/FIDELITY.md +14 -0
  10. package/node_modules/@pome-sh/twin-github/dist/src/check-pulls.d.ts +4 -0
  11. package/node_modules/@pome-sh/twin-github/dist/src/check-pulls.js +93 -0
  12. package/node_modules/@pome-sh/twin-github/dist/src/check-pulls.js.map +1 -1
  13. package/node_modules/@pome-sh/twin-github/dist/src/check-state.d.ts +1 -0
  14. package/node_modules/@pome-sh/twin-github/dist/src/check-state.js.map +1 -1
  15. package/node_modules/@pome-sh/twin-github/dist/src/checks.d.ts +3 -0
  16. package/node_modules/@pome-sh/twin-github/dist/src/checks.js +2 -1
  17. package/node_modules/@pome-sh/twin-github/dist/src/checks.js.map +1 -1
  18. package/node_modules/@pome-sh/twin-github/dist/src/db.js +46 -4
  19. package/node_modules/@pome-sh/twin-github/dist/src/db.js.map +1 -1
  20. package/node_modules/@pome-sh/twin-github/dist/src/domain/github-domain.d.ts +3 -0
  21. package/node_modules/@pome-sh/twin-github/dist/src/domain/github-domain.js +49 -1
  22. package/node_modules/@pome-sh/twin-github/dist/src/domain/github-domain.js.map +1 -1
  23. package/node_modules/@pome-sh/twin-github/dist/src/domain/issues.js +15 -5
  24. package/node_modules/@pome-sh/twin-github/dist/src/domain/issues.js.map +1 -1
  25. package/node_modules/@pome-sh/twin-github/dist/src/serializers.d.ts +1 -1
  26. package/node_modules/@pome-sh/twin-github/dist/src/serializers.js +8 -2
  27. package/node_modules/@pome-sh/twin-github/dist/src/serializers.js.map +1 -1
  28. package/node_modules/@pome-sh/twin-github/package.json +1 -1
  29. package/node_modules/@pome-sh/twin-linear/CHANGELOG.md +142 -0
  30. package/node_modules/@pome-sh/twin-linear/dist/src/check-comments.d.ts +10 -0
  31. package/node_modules/@pome-sh/twin-linear/dist/src/check-comments.js +117 -0
  32. package/node_modules/@pome-sh/twin-linear/dist/src/check-comments.js.map +1 -0
  33. package/node_modules/@pome-sh/twin-linear/dist/src/check-issues.d.ts +25 -0
  34. package/node_modules/@pome-sh/twin-linear/dist/src/check-issues.js +211 -0
  35. package/node_modules/@pome-sh/twin-linear/dist/src/check-issues.js.map +1 -0
  36. package/node_modules/@pome-sh/twin-linear/dist/src/check-kind.d.ts +3 -0
  37. package/node_modules/@pome-sh/twin-linear/dist/src/check-kind.js +9 -0
  38. package/node_modules/@pome-sh/twin-linear/dist/src/check-kind.js.map +1 -0
  39. package/node_modules/@pome-sh/twin-linear/dist/src/check-params.d.ts +8 -0
  40. package/node_modules/@pome-sh/twin-linear/dist/src/check-params.js +94 -0
  41. package/node_modules/@pome-sh/twin-linear/dist/src/check-params.js.map +1 -0
  42. package/node_modules/@pome-sh/twin-linear/dist/src/check-state.d.ts +98 -0
  43. package/node_modules/@pome-sh/twin-linear/dist/src/check-state.js +131 -0
  44. package/node_modules/@pome-sh/twin-linear/dist/src/check-state.js.map +1 -0
  45. package/node_modules/@pome-sh/twin-linear/dist/src/check-tape.d.ts +2 -0
  46. package/node_modules/@pome-sh/twin-linear/dist/src/check-tape.js +100 -0
  47. package/node_modules/@pome-sh/twin-linear/dist/src/check-tape.js.map +1 -0
  48. package/node_modules/@pome-sh/twin-linear/dist/src/check-worlds.d.ts +44 -0
  49. package/node_modules/@pome-sh/twin-linear/dist/src/check-worlds.js +93 -0
  50. package/node_modules/@pome-sh/twin-linear/dist/src/check-worlds.js.map +1 -0
  51. package/node_modules/@pome-sh/twin-linear/dist/src/checks.d.ts +29 -0
  52. package/node_modules/@pome-sh/twin-linear/dist/src/checks.js +52 -0
  53. package/node_modules/@pome-sh/twin-linear/dist/src/checks.js.map +1 -0
  54. package/node_modules/@pome-sh/twin-linear/dist/src/db.d.ts +6 -0
  55. package/node_modules/@pome-sh/twin-linear/dist/src/db.js +53 -4
  56. package/node_modules/@pome-sh/twin-linear/dist/src/db.js.map +1 -1
  57. package/node_modules/@pome-sh/twin-linear/dist/src/domain/agents.d.ts +16 -13
  58. package/node_modules/@pome-sh/twin-linear/dist/src/domain/agents.js +16 -12
  59. package/node_modules/@pome-sh/twin-linear/dist/src/domain/agents.js.map +1 -1
  60. package/node_modules/@pome-sh/twin-linear/dist/src/domain/comments.js +1 -1
  61. package/node_modules/@pome-sh/twin-linear/dist/src/domain/comments.js.map +1 -1
  62. package/node_modules/@pome-sh/twin-linear/dist/src/domain/documents.js +8 -8
  63. package/node_modules/@pome-sh/twin-linear/dist/src/domain/documents.js.map +1 -1
  64. package/node_modules/@pome-sh/twin-linear/dist/src/domain/issues.js +20 -17
  65. package/node_modules/@pome-sh/twin-linear/dist/src/domain/issues.js.map +1 -1
  66. package/node_modules/@pome-sh/twin-linear/dist/src/domain/labels.js +1 -1
  67. package/node_modules/@pome-sh/twin-linear/dist/src/domain/labels.js.map +1 -1
  68. package/node_modules/@pome-sh/twin-linear/dist/src/domain/linear-domain.d.ts +4 -17
  69. package/node_modules/@pome-sh/twin-linear/dist/src/domain/linear-domain.js.map +1 -1
  70. package/node_modules/@pome-sh/twin-linear/dist/src/domain/normalize.d.ts +12 -2
  71. package/node_modules/@pome-sh/twin-linear/dist/src/domain/normalize.js +28 -5
  72. package/node_modules/@pome-sh/twin-linear/dist/src/domain/normalize.js.map +1 -1
  73. package/node_modules/@pome-sh/twin-linear/dist/src/domain/projects-cycles.js +1 -1
  74. package/node_modules/@pome-sh/twin-linear/dist/src/domain/projects-cycles.js.map +1 -1
  75. package/node_modules/@pome-sh/twin-linear/dist/src/domain/rows.d.ts +3 -3
  76. package/node_modules/@pome-sh/twin-linear/dist/src/domain/rows.js +4 -3
  77. package/node_modules/@pome-sh/twin-linear/dist/src/domain/rows.js.map +1 -1
  78. package/node_modules/@pome-sh/twin-linear/dist/src/graphql/formatters.js +3 -3
  79. package/node_modules/@pome-sh/twin-linear/dist/src/graphql/formatters.js.map +1 -1
  80. package/node_modules/@pome-sh/twin-linear/dist/src/graphql/mutation-inputs.d.ts +30 -12
  81. package/node_modules/@pome-sh/twin-linear/dist/src/graphql/mutation-inputs.js +33 -28
  82. package/node_modules/@pome-sh/twin-linear/dist/src/graphql/mutation-inputs.js.map +1 -1
  83. package/node_modules/@pome-sh/twin-linear/dist/src/graphql/resolvers.js +2 -2
  84. package/node_modules/@pome-sh/twin-linear/dist/src/graphql/resolvers.js.map +1 -1
  85. package/node_modules/@pome-sh/twin-linear/dist/src/graphql/schema.js +31 -13
  86. package/node_modules/@pome-sh/twin-linear/dist/src/graphql/schema.js.map +1 -1
  87. package/node_modules/@pome-sh/twin-linear/dist/src/index.d.ts +2 -0
  88. package/node_modules/@pome-sh/twin-linear/dist/src/index.js +7 -0
  89. package/node_modules/@pome-sh/twin-linear/dist/src/index.js.map +1 -1
  90. package/node_modules/@pome-sh/twin-linear/dist/src/state.js +4 -4
  91. package/node_modules/@pome-sh/twin-linear/dist/src/state.js.map +1 -1
  92. package/node_modules/@pome-sh/twin-linear/dist/src/types.d.ts +10 -4
  93. package/node_modules/@pome-sh/twin-linear/dist/src/types.js.map +1 -1
  94. package/node_modules/@pome-sh/twin-linear/fixtures/README.md +1 -0
  95. package/node_modules/@pome-sh/twin-linear/fixtures/linear-introspection.json +66 -0
  96. package/node_modules/@pome-sh/twin-linear/package.json +2 -1
  97. package/node_modules/@pome-sh/twin-stripe/FIDELITY.md +8 -0
  98. package/node_modules/@pome-sh/twin-stripe/dist/src/idempotency.d.ts +23 -1
  99. package/node_modules/@pome-sh/twin-stripe/dist/src/idempotency.js +40 -6
  100. package/node_modules/@pome-sh/twin-stripe/dist/src/idempotency.js.map +1 -1
  101. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/_helpers.js +5 -0
  102. package/node_modules/@pome-sh/twin-stripe/dist/src/routes/_helpers.js.map +1 -1
  103. package/node_modules/@pome-sh/twin-stripe/dist/src/twin.js +7 -1
  104. package/node_modules/@pome-sh/twin-stripe/dist/src/twin.js.map +1 -1
  105. package/node_modules/@pome-sh/twin-stripe/package.json +1 -1
  106. package/package.json +4 -4
  107. package/tasks/14-stripe-refund-retry.md +17 -14
  108. package/tasks/24-linear-issue-triage.md +2 -2
  109. package/tasks/25-linear-comment-label-triage.md +3 -3
  110. package/tasks/26-github-linear-handoff.md +3 -4
@@ -0,0 +1,211 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // What Linear's declared checks can assert about an ISSUE ROW (F-1129).
4
+ //
5
+ // Migrated from pome-cloud's `services/evaluators/deterministic/linear.ts`,
6
+ // where they were hand-written regexes over a cloud-side mirror of the state
7
+ // shape. Three things changed in the move, and each is load-bearing:
8
+ //
9
+ // 1. THE SUBJECT IS A PARAMETER, AND IT IS THE TITLE. The legacy patterns
10
+ // keyed on `identifier` ("ENG-123") or `number`, and NOT ONE of the nine
11
+ // shipped criteria uses either — every one names a title. The registries
12
+ // and the tasks were built for each other and never once met. Title-keyed
13
+ // is what authors actually wrote, and task 26's issue does not exist in the
14
+ // seed at all, so it has no id to key on.
15
+ // 2. EVERY CHECK NAMES ITS TEAM. twin-github's repo rule, inherited rather
16
+ // than argued away: `seed.ts:319-325` validates issue-title uniqueness PER
17
+ // TEAM, so a title-keyed selector over a two-team world is exactly the
18
+ // ambiguity that rule closes. twin-slack could argue the absence; Linear
19
+ // cannot.
20
+ // 3. A MISSING SUBJECT FAILS. Measured on task 26: with a skip, three
21
+ // unresolved criteria leave the denominator and the surviving github
22
+ // criterion is a negative the seed already satisfies, so a do-nothing agent
23
+ // scores 1/1 = 100%. See `docs/grading/linear-vocabulary.md` §3. The one
24
+ // miss that skips is a TRUNCATED export, which the twin reports itself.
25
+ //
26
+ // `linear.issue-lifecycle` is NOT migrated. A declared `... is {lifecycle}`
27
+ // would near-miss every `is in state "..."` sentence and report a corrupted
28
+ // state criterion under the lifecycle check — pointing an author at a check
29
+ // they never picked, which is the exact hazard `github.issue-state`'s wording
30
+ // comment says its phrasing exists to avoid. It has zero corpus users, and
31
+ // `is in state "Done"` / `"Canceled"` re-expresses the cases that matter.
32
+ // Recorded as this migration's one narrowing rather than absorbed.
33
+ import { defineCheck, VACUITY_SENTINEL, VACUITY_SENTINEL_NUMBER } from "@pome-sh/sdk/checks";
34
+ import { estimatePoints, issueTitle, labelName, teamKey, userRef, workflowStateName, } from "./check-params.js";
35
+ import { resolveIssue, resolveLabelNames, resolveUserLabels, resolveWorkflowStateName, unresolved, } from "./check-state.js";
36
+ import { finalWorld, issueRow, linearState } from "./check-worlds.js";
37
+ export const issueExists = defineCheck({
38
+ id: "linear.issue-exists",
39
+ description: "Asserts an unarchived issue with this exact title exists in the named team. Declared and " +
40
+ "unused by the shipped corpus on purpose: `linear.issue-state` FAILS on a missing issue and " +
41
+ "therefore subsumes this one, so task 26 carries the state criterion alone — twin-github " +
42
+ "ships the same pair for the same reason. A vocabulary is what an author may pick from, not " +
43
+ "what the corpus happens to exercise. Title matching is EXACT and archived issues do not " +
44
+ "count, so an examinee that renames or archives the issue fails this.",
45
+ template: 'An issue titled "{title}" exists in `{team}`',
46
+ params: { title: issueTitle, team: teamKey },
47
+ substrate: "final",
48
+ polarity: () => "positive",
49
+ // The only check where the title IS the assertion rather than the selector,
50
+ // which is why it is the only one that declares the title as its subject.
51
+ subject: ({ title }) => title,
52
+ vacuityMutant: (args) => ({ ...args, title: VACUITY_SENTINEL }),
53
+ discriminatingWorlds: ({ title }) => ({
54
+ // The team is present in both worlds; only the issue moves. `linearState`
55
+ // always fills teams, so the failing world's reason is "no issue with that
56
+ // title in `ENG`" rather than the empty world's "team not found" — which is
57
+ // what arm 3 rejects.
58
+ passing: finalWorld(linearState([issueRow(title)])),
59
+ failing: finalWorld(linearState([issueRow("a different issue entirely")])),
60
+ }),
61
+ evaluate({ title, team }, { final }) {
62
+ const issue = resolveIssue(final, team, title);
63
+ if ("missing" in issue)
64
+ return unresolved(issue);
65
+ // Echoing the title is safe HERE and only here: this check declares it as
66
+ // its subject, so the engine skipped the criterion as `subject_redacted`
67
+ // before reaching this line if a redactor would have destroyed it.
68
+ return { passed: true, reason: `an issue titled "${title}" exists in \`${team}\`` };
69
+ },
70
+ });
71
+ export const issueState = defineCheck({
72
+ id: "linear.issue-state",
73
+ description: "Resolves the issue by title within the named team, follows `stateId` to that team's " +
74
+ "workflow-state row, and compares its NAME to the one given — case-insensitively, because a " +
75
+ "workflow state name is prose an author retypes. An issue that is absent, archived, or " +
76
+ "ambiguous FAILS, which is what makes this check subsume the existence assertion. A miss " +
77
+ "inside a TRUNCATED export skips instead, because the twin reported that rows were dropped. " +
78
+ "Workflow state names are user-defined per team, so the slot is free text rather than a " +
79
+ "closed set.",
80
+ // "is in state X", not "is X". Two reasons, both of which twin-github's
81
+ // `issue-state` comment states while crediting this very idiom to the Linear
82
+ // tasks: one reading habit spans twins, and the longer literal tail keeps
83
+ // this template from near-missing `... is assigned to \`{user}\``.
84
+ template: 'Issue "{title}" in `{team}` is in state "{state}"',
85
+ params: { title: issueTitle, team: teamKey, state: workflowStateName },
86
+ substrate: "final",
87
+ // Positive on every shipped use. Unlike `github.issue-state` there is no
88
+ // canonical "open" member to read a prohibition off, because the state names
89
+ // belong to the workspace rather than to the API.
90
+ polarity: () => "positive",
91
+ // The state name is the value COMPARED against the state; title and team only
92
+ // select.
93
+ subject: ({ state }) => state,
94
+ vacuityMutant: (args) => ({ ...args, state: VACUITY_SENTINEL }),
95
+ discriminatingWorlds: ({ title, state }) => ({
96
+ passing: finalWorld(linearState([issueRow(title, { stateName: state })])),
97
+ // The team and the issue are PRESENT in both worlds; only the state moves.
98
+ failing: finalWorld(linearState([issueRow(title, { stateName: "Backlog" })])),
99
+ }),
100
+ evaluate({ title, team, state }, { final }) {
101
+ const issue = resolveIssue(final, team, title);
102
+ if ("missing" in issue)
103
+ return unresolved(issue);
104
+ const name = resolveWorkflowStateName(final, issue.found);
105
+ if ("missing" in name)
106
+ return unresolved(name);
107
+ return {
108
+ passed: name.found.toLowerCase() === state.toLowerCase(),
109
+ reason: `issue state is "${name.found}" (wanted "${state}")`,
110
+ };
111
+ },
112
+ });
113
+ export const issueHasLabel = defineCheck({
114
+ id: "linear.issue-has-label",
115
+ description: "Resolves the issue, joins its `labelIds` to the workspace label catalog, and asserts the " +
116
+ "named label is among them — case-insensitively, as the legacy rule's comparison was. The " +
117
+ "join is the point: this export carries label IDS where the seed writes names and " +
118
+ "twin-github writes objects, so one concept has three shapes and only this one is exported. " +
119
+ "A label id with no catalog row is a partial export and SKIPS rather than failing.",
120
+ template: 'Issue "{title}" in `{team}` has label "{label}"',
121
+ params: { title: issueTitle, team: teamKey, label: labelName },
122
+ substrate: "final",
123
+ polarity: () => "positive",
124
+ subject: ({ label }) => label,
125
+ vacuityMutant: (args) => ({ ...args, label: VACUITY_SENTINEL }),
126
+ discriminatingWorlds: ({ title, label }) => ({
127
+ passing: finalWorld(linearState([issueRow(title, { labelNames: [label] })], [], [label])),
128
+ // The issue is present and resolvable in both; only its labels move.
129
+ failing: finalWorld(linearState([issueRow(title, { labelNames: [] })], [], [label])),
130
+ }),
131
+ evaluate({ title, team, label }, { final }) {
132
+ const issue = resolveIssue(final, team, title);
133
+ if ("missing" in issue)
134
+ return unresolved(issue);
135
+ const names = resolveLabelNames(final, issue.found);
136
+ if ("missing" in names)
137
+ return unresolved(names);
138
+ return {
139
+ passed: names.found.has(label.toLowerCase()),
140
+ reason: `issue carries ${names.found.size} label(s) (wanted "${label}")`,
141
+ };
142
+ },
143
+ });
144
+ export const issueEstimate = defineCheck({
145
+ id: "linear.issue-estimate",
146
+ description: "Resolves the issue and compares its `estimate` column to the number given. An UNSET " +
147
+ "estimate is a real FAIL, not a skip: an unestimated issue is exactly the state this " +
148
+ "assertion exists to rule out.",
149
+ template: 'Issue "{title}" in `{team}` has estimate {estimate}',
150
+ params: { title: issueTitle, team: teamKey, estimate: estimatePoints },
151
+ substrate: "final",
152
+ polarity: () => "positive",
153
+ // The single null in the subject column, and for twin-slack's emoji-name
154
+ // reason rather than an exemption: a bare integer of at most three digits
155
+ // matches no pattern in `redactSecrets` (key prefixes, 13-19-digit runs) or
156
+ // in a team's `PII_PATTERNS` (emails, phones). A value no redactor can eat is
157
+ // not a subject, and declaring one would only narrow the corpus gate's
158
+ // whole-phrase fallback for nothing.
159
+ subject: () => null,
160
+ // D10's second allowlist entry ever, and the argument is stripe's unchanged:
161
+ // here the number IS the scanned value, and the title is the selector.
162
+ vacuityMutant: (args) => ({ ...args, estimate: String(VACUITY_SENTINEL_NUMBER) }),
163
+ discriminatingWorlds: ({ title, estimate }) => ({
164
+ passing: finalWorld(linearState([issueRow(title, { estimate: Number(estimate) })])),
165
+ failing: finalWorld(linearState([issueRow(title, { estimate: null })])),
166
+ }),
167
+ evaluate({ title, team, estimate }, { final }) {
168
+ const issue = resolveIssue(final, team, title);
169
+ if ("missing" in issue)
170
+ return unresolved(issue);
171
+ const actual = issue.found.estimate ?? null;
172
+ return {
173
+ passed: actual === Number(estimate),
174
+ reason: `issue estimate is ${actual === null ? "unset" : actual} (wanted ${estimate})`,
175
+ };
176
+ },
177
+ });
178
+ export const issueAssignee = defineCheck({
179
+ id: "linear.issue-assignee",
180
+ description: "Resolves the issue, then its assignee, and matches the given reference against that user's " +
181
+ "email, name OR displayName — every spelling the legacy rule accepted. An UNASSIGNED issue " +
182
+ "is a real FAIL. Declared with no shipped corpus user, carrying a legacy capability forward. " +
183
+ "This is the check whose `subject` earns its keep: an email reference is destroyed by a " +
184
+ "team's `PII_PATTERNS`, which the twin's own redactor has no equivalent of, so without the " +
185
+ "declaration the criterion would silently be unable to fire.",
186
+ template: 'Issue "{title}" in `{team}` is assigned to `{user}`',
187
+ params: { title: issueTitle, team: teamKey, user: userRef },
188
+ substrate: "final",
189
+ polarity: () => "positive",
190
+ subject: ({ user }) => user,
191
+ vacuityMutant: (args) => ({ ...args, user: VACUITY_SENTINEL }),
192
+ discriminatingWorlds: ({ title }) => ({
193
+ // The issue resolves in both worlds; only the assignee moves.
194
+ passing: finalWorld(linearState([issueRow(title, { assigneeId: "user_dev" })])),
195
+ failing: finalWorld(linearState([issueRow(title)])),
196
+ }),
197
+ evaluate({ title, team, user }, { final }) {
198
+ const issue = resolveIssue(final, team, title);
199
+ if ("missing" in issue)
200
+ return unresolved(issue);
201
+ const labels = resolveUserLabels(final, issue.found.assigneeId);
202
+ if (labels.length === 0)
203
+ return { passed: false, reason: "issue has no assignee" };
204
+ return {
205
+ passed: labels.some((l) => l.toLowerCase() === user.toLowerCase()),
206
+ // Safe to quote: `user` is this check's declared subject.
207
+ reason: `issue is assigned to \`${labels[0]}\` (wanted \`${user}\`)`,
208
+ };
209
+ },
210
+ });
211
+ //# sourceMappingURL=check-issues.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-issues.js","sourceRoot":"","sources":["../../src/check-issues.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CheckDefinition } from "@pome-sh/sdk/checks";
2
+ import type { LinearCheckState } from "./check-state.js";
3
+ export type Check<TArgs extends Record<string, string>> = CheckDefinition<LinearCheckState, TArgs>;
@@ -0,0 +1,9 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // The one type every Linear check declaration is written against, mirroring
4
+ // twin-github's and twin-slack's. It exists so the declaration files can split
5
+ // by what they assert about without each re-deriving the binding to the state
6
+ // tree — and so a declaration that forgets to name `LinearCheckState` cannot
7
+ // compile.
8
+ export {};
9
+ //# sourceMappingURL=check-kind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-kind.js","sourceRoot":"","sources":["../../src/check-kind.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAC7E,WAAW"}
@@ -0,0 +1,8 @@
1
+ import type { CheckParamType } from "@pome-sh/sdk/checks";
2
+ export declare const issueTitle: CheckParamType;
3
+ export declare const teamKey: CheckParamType;
4
+ export declare const workflowStateName: CheckParamType;
5
+ export declare const labelName: CheckParamType;
6
+ export declare const estimatePoints: CheckParamType;
7
+ export declare const userRef: CheckParamType;
8
+ export declare const commentNeedle: CheckParamType;
@@ -0,0 +1,94 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // The typed slots Linear's declared checks fill (F-1129).
4
+ //
5
+ // They live in the twin, not the sdk, for the same reason the declarations do:
6
+ // the twin owns what a Linear team key or workflow state name may look like.
7
+ // Every pattern is capture-group-free — `defineCheck` throws otherwise, because
8
+ // each consumer reads capture group i+1 as slot i and one smuggled group hands
9
+ // every predicate its neighbour's argument.
10
+ //
11
+ // NO SLOT MAY SIT AT THE END OF A TEMPLATE UNBOUNDED. The contract suite
12
+ // requires that `"${rendered} and also something else"` does not match, and an
13
+ // anchored `[^"\n]+` in terminal position would swallow the suffix. Every
14
+ // free-text slot below is delimited by a quote or a backtick in the templates
15
+ // that use it — which is also why Linear's state slot is quoted where
16
+ // `github.issue-state`'s is bare.
17
+ // Double-quote delimited. Linear puts no constraint on an issue title beyond
18
+ // non-empty, so the quote is the only exclusion.
19
+ export const issueTitle = {
20
+ name: "title",
21
+ pattern: '[^"\\n]+',
22
+ example: "Orders 500 after deploy",
23
+ render: (value) => value,
24
+ parse: (raw) => raw,
25
+ };
26
+ // Linear's own team-key constraint, from `seedSchema` (`seed.ts`):
27
+ // /^[A-Z][A-Z0-9]*$/.
28
+ //
29
+ // This is the SCOPE slot twin-github's repo rule requires of every state check,
30
+ // and Linear inherits that rule rather than arguing it away as twin-slack did:
31
+ // `seed.ts:319-325` validates issue-title uniqueness PER TEAM, so a title-keyed
32
+ // selector over a two-team world is exactly the ambiguity the rule closes.
33
+ //
34
+ // Its shape is also why a reason string may quote it — an uppercase
35
+ // alphanumeric run matches no pattern in either redactor.
36
+ export const teamKey = {
37
+ name: "team",
38
+ pattern: "[A-Z][A-Z0-9]*",
39
+ example: "ENG",
40
+ render: (value) => value,
41
+ parse: (raw) => raw,
42
+ };
43
+ // NOT a closed set, where `github.issue-state`'s is. GitHub has exactly two
44
+ // issue states; Linear workflow state names are user-defined per team
45
+ // (`seed.ts` `teams[].states[].name` is free text), so a closed alternation
46
+ // would silently exclude every custom workflow. The template quotes it, which
47
+ // is what keeps a free-text slot safe in terminal position.
48
+ export const workflowStateName = {
49
+ name: "state",
50
+ pattern: '[^"\\n]+',
51
+ example: "In Progress",
52
+ render: (value) => value,
53
+ parse: (raw) => raw,
54
+ };
55
+ export const labelName = {
56
+ name: "label",
57
+ pattern: '[^"\\n]+',
58
+ example: "Agent",
59
+ render: (value) => value,
60
+ parse: (raw) => raw,
61
+ };
62
+ // A bare integer, no leading zeros, so `render(parse(x))` round-trips.
63
+ // `VACUITY_SENTINEL_NUMBER` (987654321) satisfies this pattern, which is what
64
+ // lets this slot carry a real mutant rather than an admitted null.
65
+ export const estimatePoints = {
66
+ name: "estimate",
67
+ pattern: "0|[1-9][0-9]*",
68
+ example: "2",
69
+ render: (value) => value,
70
+ parse: (raw) => raw,
71
+ };
72
+ // Backtick-delimited, matching `github.issue-assignee`'s `{login}`. The legacy
73
+ // rule matched a user by email, name OR displayName and this keeps all three —
74
+ // which is exactly why its check declares this as its `subject`: the team's
75
+ // `PII_PATTERNS` eats an email that the twin's own `SCRUB_STEPS` leaves alone,
76
+ // so without the declaration the criterion could never fire.
77
+ export const userRef = {
78
+ name: "user",
79
+ pattern: "[^`\\n]+",
80
+ example: "dev@pome-twin.test",
81
+ render: (value) => value,
82
+ parse: (raw) => raw,
83
+ };
84
+ // The value SCANNED inside free prose rather than compared to a field, which is
85
+ // why its check declares it as `subject` — twin-slack's `messageNeedle`
86
+ // precedent.
87
+ export const commentNeedle = {
88
+ name: "needle",
89
+ pattern: '[^"\\n]+',
90
+ example: "triage",
91
+ render: (value) => value,
92
+ parse: (raw) => raw,
93
+ };
94
+ //# sourceMappingURL=check-params.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-params.js","sourceRoot":"","sources":["../../src/check-params.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,0DAA0D;AAC1D,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,gFAAgF;AAChF,+EAA+E;AAC/E,4CAA4C;AAC5C,EAAE;AACF,yEAAyE;AACzE,+EAA+E;AAC/E,0EAA0E;AAC1E,8EAA8E;AAC9E,sEAAsE;AACtE,kCAAkC;AAIlC,6EAA6E;AAC7E,iDAAiD;AACjD,MAAM,CAAC,MAAM,UAAU,GAAmB;IACxC,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,UAAU;IACnB,OAAO,EAAE,yBAAyB;IAClC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK;IACxB,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG;CACpB,CAAC;AAEF,mEAAmE;AACnE,sBAAsB;AACtB,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,gFAAgF;AAChF,2EAA2E;AAC3E,EAAE;AACF,oEAAoE;AACpE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,OAAO,GAAmB;IACrC,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,gBAAgB;IACzB,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK;IACxB,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG;CACpB,CAAC;AAEF,4EAA4E;AAC5E,sEAAsE;AACtE,4EAA4E;AAC5E,8EAA8E;AAC9E,4DAA4D;AAC5D,MAAM,CAAC,MAAM,iBAAiB,GAAmB;IAC/C,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,UAAU;IACnB,OAAO,EAAE,aAAa;IACtB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK;IACxB,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG;CACpB,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAmB;IACvC,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,UAAU;IACnB,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK;IACxB,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG;CACpB,CAAC;AAEF,uEAAuE;AACvE,8EAA8E;AAC9E,mEAAmE;AACnE,MAAM,CAAC,MAAM,cAAc,GAAmB;IAC5C,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,eAAe;IACxB,OAAO,EAAE,GAAG;IACZ,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK;IACxB,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG;CACpB,CAAC;AAEF,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,+EAA+E;AAC/E,6DAA6D;AAC7D,MAAM,CAAC,MAAM,OAAO,GAAmB;IACrC,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,UAAU;IACnB,OAAO,EAAE,oBAAoB;IAC7B,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK;IACxB,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG;CACpB,CAAC;AAEF,gFAAgF;AAChF,wEAAwE;AACxE,aAAa;AACb,MAAM,CAAC,MAAM,aAAa,GAAmB;IAC3C,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,UAAU;IACnB,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK;IACxB,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG;CACpB,CAAC"}
@@ -0,0 +1,98 @@
1
+ import type { CheckOutcome } from "@pome-sh/sdk/checks";
2
+ export interface LinearCheckStateTeam {
3
+ id?: string;
4
+ key?: string;
5
+ name?: string;
6
+ }
7
+ export interface LinearCheckStateWorkflowState {
8
+ id?: string;
9
+ teamId?: string;
10
+ name?: string;
11
+ type?: string;
12
+ }
13
+ export interface LinearCheckStateLabel {
14
+ id?: string;
15
+ teamId?: string;
16
+ name?: string;
17
+ }
18
+ export interface LinearCheckStateIssue {
19
+ id?: string;
20
+ identifier?: string;
21
+ number?: number;
22
+ teamId?: string;
23
+ title?: string;
24
+ estimate?: number | null;
25
+ stateId?: string;
26
+ assigneeId?: string;
27
+ archivedAt?: string | null;
28
+ labelIds?: string[];
29
+ }
30
+ export interface LinearCheckStateComment {
31
+ id?: string;
32
+ issueId?: string;
33
+ parentId?: string | null;
34
+ userId?: string;
35
+ body?: string;
36
+ }
37
+ export interface LinearCheckStateUser {
38
+ id?: string;
39
+ email?: string;
40
+ name?: string;
41
+ displayName?: string;
42
+ }
43
+ export interface LinearCheckState {
44
+ teams?: LinearCheckStateTeam[] | null;
45
+ workflowStates?: LinearCheckStateWorkflowState[] | null;
46
+ labels?: LinearCheckStateLabel[] | null;
47
+ issues?: LinearCheckStateIssue[] | null;
48
+ comments?: LinearCheckStateComment[] | null;
49
+ users?: LinearCheckStateUser[] | null;
50
+ exportBounds?: {
51
+ truncatedCollections?: string[];
52
+ } | null;
53
+ }
54
+ /**
55
+ * twin-github's `Resolved<T>` with one field added.
56
+ *
57
+ * github's resolvers always FAIL on a miss and twin-slack's always SKIP. Linear
58
+ * needs both from the same resolver, because a miss inside a truncated
59
+ * collection is evidence of a partial export while a miss in a complete one is
60
+ * evidence about the examinee. Carrying the disposition on the value is what
61
+ * stops every call site from re-deciding it — and re-deciding it wrongly is how
62
+ * a do-nothing agent scores 100% on task 26.
63
+ */
64
+ export type Resolved<T> = {
65
+ found: T;
66
+ } | {
67
+ missing: string;
68
+ skip: boolean;
69
+ };
70
+ /** The one place an unresolved selector becomes an outcome. */
71
+ export declare function unresolved(r: {
72
+ missing: string;
73
+ skip: boolean;
74
+ }): CheckOutcome;
75
+ export declare function isTruncated(state: LinearCheckState, collection: string): boolean;
76
+ /**
77
+ * The issue an assertion is about, by exact title, scoped to one team.
78
+ *
79
+ * Exact rather than case-insensitive: the task names the title it seeded or
80
+ * asked for, and a case-folding match would let an examinee half-comply.
81
+ */
82
+ export declare function resolveIssue(state: LinearCheckState, teamKey: string, title: string): Resolved<LinearCheckStateIssue>;
83
+ /** Trap 1: `stateId` → the team's own workflow-state row. */
84
+ export declare function resolveWorkflowStateName(state: LinearCheckState, issue: LinearCheckStateIssue): Resolved<string>;
85
+ /**
86
+ * Trap 2: `labelIds` → catalog names, lowercased.
87
+ *
88
+ * Case-insensitive because the legacy rule's `eqi` was, and because a label an
89
+ * examinee creates is prose it retyped.
90
+ */
91
+ export declare function resolveLabelNames(state: LinearCheckState, issue: LinearCheckStateIssue): Resolved<Set<string>>;
92
+ export declare function resolveComments(state: LinearCheckState, issue: LinearCheckStateIssue): Resolved<LinearCheckStateComment[]>;
93
+ /**
94
+ * Every spelling of a user the legacy `linear.issue-assignee` rule accepted —
95
+ * email, name, displayName — in that order, so a reason string names the most
96
+ * specific one first.
97
+ */
98
+ export declare function resolveUserLabels(state: LinearCheckState, id: string | undefined): string[];
@@ -0,0 +1,131 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // How a declared check READS the exported Linear workspace (F-1129).
4
+ //
5
+ // `LinearStateExport` (`state.ts:4-27`) names the collections but types every
6
+ // row `unknown[]`, so the row shapes still have to be modelled by hand here, as
7
+ // twin-slack's `check-state.ts` does. Every field is optional and every list
8
+ // nullable: an older snapshot, a partial upload, or a schema that gained a
9
+ // column all arrive as absent fields. A predicate that assumes presence throws
10
+ // inside the evaluator; one that reads this model returns a NAMED verdict. That
11
+ // is the D4 bargain — a criterion may leave the denominator, but it may never
12
+ // fabricate one.
13
+ //
14
+ // Five shapes are counter-intuitive and each has a wrong-verdict story:
15
+ //
16
+ // 1. AN ISSUE HAS NO STATE STRING. `issue.stateId` references a
17
+ // `workflowStates` row, and workflow states are scoped by `teamId` — so a
18
+ // join that is not team-scoped resolves the wrong row in a two-team world.
19
+ // 2. LABELS ARE A THIRD SHAPE. The SEED writes label names; twin-github's
20
+ // export writes label objects; this export writes `issue.labelIds:
21
+ // string[]`, which must be joined to `labels[].name`. One concept, three
22
+ // shapes, and only this one is exported.
23
+ // 3. `archivedAt` IS EXPORTED. An archived issue is still a row in `issues`.
24
+ // Resolution treats archived as absent, so an existence assertion cannot
25
+ // be satisfied by an issue the examinee archived.
26
+ // 4. `exportBounds.truncatedCollections` NAMES WHAT WAS CAPPED.
27
+ // `STATE_EXPORT_CAP = 2000` (`types.ts:394`) drops rows past the cap, and
28
+ // this is the ONLY place in the vocabulary where "not found" and "absent"
29
+ // are different facts. Neither GitHubCheckState nor SlackCheckState
30
+ // carries an analogue, which is why this contract is Linear's alone.
31
+ // 5. TITLE UNIQUENESS IS SEED-TIME ONLY. `seed.ts:319-325` validates
32
+ // `issueTitlesByTeam` at parse; nothing enforces it at runtime, so an
33
+ // examinee can create a duplicate. Two matches FAIL naming the count —
34
+ // silently grading the first is the wrong-match hazard twin-github's repo
35
+ // rule exists to close, one level down.
36
+ //
37
+ // REASON STRINGS NEVER ECHO A TITLE. Only a check's declared `subject` is
38
+ // guaranteed to have survived the redaction pipeline by the time `evaluate`
39
+ // runs (`evaluate.ts:141-144`), and `title` is a selector on every check except
40
+ // `linear.issue-exists`. The team key is safe to quote: `[A-Z][A-Z0-9]*`
41
+ // matches no pattern in either redactor.
42
+ /** The one place an unresolved selector becomes an outcome. */
43
+ export function unresolved(r) {
44
+ return r.skip
45
+ ? { passed: false, status: "skipped", reason: r.missing }
46
+ : { passed: false, reason: r.missing };
47
+ }
48
+ export function isTruncated(state, collection) {
49
+ return (state.exportBounds?.truncatedCollections ?? []).includes(collection);
50
+ }
51
+ /**
52
+ * The issue an assertion is about, by exact title, scoped to one team.
53
+ *
54
+ * Exact rather than case-insensitive: the task names the title it seeded or
55
+ * asked for, and a case-folding match would let an examinee half-comply.
56
+ */
57
+ export function resolveIssue(state, teamKey, title) {
58
+ if (state.teams == null)
59
+ return { missing: "state_incomplete", skip: true };
60
+ const team = state.teams.find((t) => t.key === teamKey);
61
+ if (team?.id == null) {
62
+ return { missing: `team \`${teamKey}\` not found in state_final`, skip: false };
63
+ }
64
+ if (state.issues == null)
65
+ return { missing: "state_incomplete", skip: true };
66
+ const matches = state.issues.filter((issue) => issue.teamId === team.id &&
67
+ issue.title === title &&
68
+ (issue.archivedAt ?? null) === null);
69
+ if (matches.length === 1)
70
+ return { found: matches[0] };
71
+ if (matches.length > 1) {
72
+ return {
73
+ missing: `${matches.length} issues in \`${teamKey}\` share that title`,
74
+ skip: false,
75
+ };
76
+ }
77
+ // The only skip a miss can earn, and it is earned by evidence rather than by
78
+ // taste: the twin itself reported that `issues` lost rows to the export cap.
79
+ if (isTruncated(state, "issues"))
80
+ return { missing: "state_truncated", skip: true };
81
+ return { missing: `no issue with that title in \`${teamKey}\``, skip: false };
82
+ }
83
+ /** Trap 1: `stateId` → the team's own workflow-state row. */
84
+ export function resolveWorkflowStateName(state, issue) {
85
+ if (state.workflowStates == null)
86
+ return { missing: "state_incomplete", skip: true };
87
+ const row = state.workflowStates.find((s) => s.id === issue.stateId && s.teamId === issue.teamId);
88
+ if (row?.name == null)
89
+ return { missing: "workflow_state_unresolved", skip: true };
90
+ return { found: row.name };
91
+ }
92
+ /**
93
+ * Trap 2: `labelIds` → catalog names, lowercased.
94
+ *
95
+ * Case-insensitive because the legacy rule's `eqi` was, and because a label an
96
+ * examinee creates is prose it retyped.
97
+ */
98
+ export function resolveLabelNames(state, issue) {
99
+ if (state.labels == null)
100
+ return { missing: "state_incomplete", skip: true };
101
+ const byId = new Map(state.labels.filter((l) => l.id != null).map((l) => [l.id, (l.name ?? "").toLowerCase()]));
102
+ const names = new Set();
103
+ for (const id of issue.labelIds ?? []) {
104
+ const name = byId.get(id);
105
+ // A link to a label the export did not carry is a partial export, not a
106
+ // verdict about the examinee.
107
+ if (name === undefined)
108
+ return { missing: "label_unresolved", skip: true };
109
+ names.add(name);
110
+ }
111
+ return { found: names };
112
+ }
113
+ export function resolveComments(state, issue) {
114
+ if (state.comments == null)
115
+ return { missing: "state_incomplete", skip: true };
116
+ return { found: state.comments.filter((c) => c.issueId === issue.id) };
117
+ }
118
+ /**
119
+ * Every spelling of a user the legacy `linear.issue-assignee` rule accepted —
120
+ * email, name, displayName — in that order, so a reason string names the most
121
+ * specific one first.
122
+ */
123
+ export function resolveUserLabels(state, id) {
124
+ if (id == null || state.users == null)
125
+ return [];
126
+ const user = state.users.find((u) => u.id === id);
127
+ if (user == null)
128
+ return [];
129
+ return [user.email, user.name, user.displayName].filter((v) => typeof v === "string" && v.length > 0);
130
+ }
131
+ //# sourceMappingURL=check-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-state.js","sourceRoot":"","sources":["../../src/check-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Check } from "./check-kind.js";
2
+ export declare const noUnsupportedEndpoint: Check<Record<string, never>>;