@agilno-tech/rivet 0.1.0-alpha.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 (283) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +59 -0
  4. package/bin/cli.js +42 -0
  5. package/dist/agents/backend-django-agent/SKILL.md +35 -0
  6. package/dist/agents/backend-nestjs-agent/SKILL.md +36 -0
  7. package/dist/agents/boss-agent/SKILL.md +28 -0
  8. package/dist/agents/bounded-worker/SKILL.md +28 -0
  9. package/dist/agents/data-performance-agent/SKILL.md +32 -0
  10. package/dist/agents/delivery-ticketing-agent/SKILL.md +36 -0
  11. package/dist/agents/devops-agent/SKILL.md +34 -0
  12. package/dist/agents/engineering-manager/SKILL.md +28 -0
  13. package/dist/agents/frontend-nextjs-agent/SKILL.md +34 -0
  14. package/dist/agents/frontend-web-agent/SKILL.md +31 -0
  15. package/dist/agents/incident-response-agent/SKILL.md +42 -0
  16. package/dist/agents/jest-agent/SKILL.md +77 -0
  17. package/dist/agents/mobile-agent/SKILL.md +32 -0
  18. package/dist/agents/playwright-agent/SKILL.md +54 -0
  19. package/dist/agents/product-design-manager/SKILL.md +27 -0
  20. package/dist/agents/qa-agent/SKILL.md +29 -0
  21. package/dist/agents/quality-manager/SKILL.md +28 -0
  22. package/dist/agents/vitest-agent/SKILL.md +77 -0
  23. package/dist/governance/pre-push-rules.md +75 -0
  24. package/dist/governance/prompt-hygiene.md +22 -0
  25. package/dist/governance/review-checklist.md +24 -0
  26. package/dist/governance/safety-and-data.md +27 -0
  27. package/dist/governance/usage-rules.md +21 -0
  28. package/dist/mandatory/address-pr-feedback/SKILL.md +196 -0
  29. package/dist/mandatory/agentic-goal/SKILL.md +42 -0
  30. package/dist/mandatory/agentic-status/SKILL.md +74 -0
  31. package/dist/mandatory/apply-design/SKILL.md +228 -0
  32. package/dist/mandatory/check-ac/SKILL.md +124 -0
  33. package/dist/mandatory/create-pr-and-commit/SKILL.md +304 -0
  34. package/dist/mandatory/design/SKILL.md +312 -0
  35. package/dist/mandatory/feature-workflow/SKILL.md +87 -0
  36. package/dist/mandatory/hotfix/SKILL.md +195 -0
  37. package/dist/mandatory/pre-push/SKILL.md +272 -0
  38. package/dist/mandatory/project-context/SKILL.md +180 -0
  39. package/dist/mandatory/release-docs/SKILL.md +191 -0
  40. package/dist/mandatory/review-pr/SKILL.md +323 -0
  41. package/dist/mandatory/security-review/SKILL.md +157 -0
  42. package/dist/skills/api-contract/SKILL.md +77 -0
  43. package/dist/skills/backend-django/SKILL.md +27 -0
  44. package/dist/skills/backend-nestjs/SKILL.md +65 -0
  45. package/dist/skills/bug-ticket-creation/SKILL.md +43 -0
  46. package/dist/skills/cloudwatch-troubleshooting/SKILL.md +73 -0
  47. package/dist/skills/database-migration/SKILL.md +97 -0
  48. package/dist/skills/debugging/SKILL.md +32 -0
  49. package/dist/skills/devops-infra/SKILL.md +26 -0
  50. package/dist/skills/documentation/SKILL.md +24 -0
  51. package/dist/skills/frontend-nextjs/SKILL.md +27 -0
  52. package/dist/skills/incident-postmortem/SKILL.md +38 -0
  53. package/dist/skills/kubernetes-troubleshooting/SKILL.md +71 -0
  54. package/dist/skills/mobile-react-native/SKILL.md +37 -0
  55. package/dist/skills/postgres-analytics/SKILL.md +29 -0
  56. package/dist/skills/product-jira-ticketing/SKILL.md +28 -0
  57. package/dist/skills/qa-bug-analysis/SKILL.md +27 -0
  58. package/dist/skills/refactoring/SKILL.md +32 -0
  59. package/dist/skills/sprint-planning/SKILL.md +32 -0
  60. package/dist/skills/testing-quality/SKILL.md +62 -0
  61. package/dist/v2/protocols/agent-orchestration.md +49 -0
  62. package/dist/v2/protocols/delivery.md +45 -0
  63. package/dist/v2/protocols/design-authority.md +29 -0
  64. package/dist/v2/protocols/goal-graph.md +44 -0
  65. package/dist/v2/protocols/pattern-first-development.md +29 -0
  66. package/dist/v2/protocols/qa-evidence.md +29 -0
  67. package/dist/v2/protocols/security.md +29 -0
  68. package/dist/v2/schemas/event.schema.json +86 -0
  69. package/dist/v2/schemas/evidence.schema.json +94 -0
  70. package/dist/v2/schemas/feature-decomposition.schema.json +48 -0
  71. package/dist/v2/schemas/feature-plan.schema.json +95 -0
  72. package/dist/v2/schemas/goal-graph.schema.json +61 -0
  73. package/dist/v2/schemas/integration.schema.json +119 -0
  74. package/dist/v2/schemas/orchestration.schema.json +69 -0
  75. package/dist/v2/schemas/project.schema.json +209 -0
  76. package/dist/v2/schemas/providers.schema.json +142 -0
  77. package/dist/v2/schemas/quality.schema.json +50 -0
  78. package/dist/v2/schemas/work-action.schema.json +44 -0
  79. package/dist/v2/schemas/work-request.schema.json +253 -0
  80. package/dist/v2/templates/evidence/qa-bundle.json +194 -0
  81. package/dist/v2/templates/github-actions/rivet-deploy.yml +98 -0
  82. package/dist/v2/templates/harness/SKILL.md +90 -0
  83. package/dist/v2/templates/project/.rivet/orchestration.yaml +42 -0
  84. package/dist/v2/templates/project/.rivet/project.yaml +14 -0
  85. package/dist/v2/templates/project/.rivet/providers.yaml +8 -0
  86. package/dist/v2/templates/project/.rivet/quality.yaml +16 -0
  87. package/package.json +58 -0
  88. package/protocols/agent-orchestration.md +49 -0
  89. package/protocols/delivery.md +45 -0
  90. package/protocols/design-authority.md +29 -0
  91. package/protocols/goal-graph.md +44 -0
  92. package/protocols/pattern-first-development.md +29 -0
  93. package/protocols/qa-evidence.md +29 -0
  94. package/protocols/security.md +29 -0
  95. package/schemas/event.schema.json +86 -0
  96. package/schemas/evidence.schema.json +94 -0
  97. package/schemas/feature-decomposition.schema.json +48 -0
  98. package/schemas/feature-plan.schema.json +95 -0
  99. package/schemas/goal-graph.schema.json +61 -0
  100. package/schemas/integration.schema.json +119 -0
  101. package/schemas/orchestration.schema.json +69 -0
  102. package/schemas/project.schema.json +209 -0
  103. package/schemas/providers.schema.json +142 -0
  104. package/schemas/quality.schema.json +50 -0
  105. package/schemas/work-action.schema.json +44 -0
  106. package/schemas/work-request.schema.json +253 -0
  107. package/src/adapters/confluence.js +138 -0
  108. package/src/adapters/contract.js +657 -0
  109. package/src/adapters/factory.js +122 -0
  110. package/src/adapters/figma.js +178 -0
  111. package/src/adapters/fixtures.js +183 -0
  112. package/src/adapters/github.js +314 -0
  113. package/src/adapters/http.js +704 -0
  114. package/src/adapters/jira.js +195 -0
  115. package/src/adapters/linear.js +171 -0
  116. package/src/adapters/node-transport.js +59 -0
  117. package/src/cli/integration-setup-prompt.js +32 -0
  118. package/src/cli/interrupt.js +25 -0
  119. package/src/cli/main.js +596 -0
  120. package/src/cli/output.js +174 -0
  121. package/src/cli/parse-args.js +246 -0
  122. package/src/cli/project-discovery.js +70 -0
  123. package/src/cli/task-confirmation.js +38 -0
  124. package/src/cli/task-presentation.js +22 -0
  125. package/src/clients/claude.js +214 -0
  126. package/src/clients/codex.js +180 -0
  127. package/src/clients/compatibility.js +23 -0
  128. package/src/clients/contract.js +224 -0
  129. package/src/clients/fake.js +170 -0
  130. package/src/clients/process-runner.js +657 -0
  131. package/src/clients/result-contract.js +78 -0
  132. package/src/commands/delivery-publish.js +80 -0
  133. package/src/commands/delivery-remote.js +230 -0
  134. package/src/commands/delivery-review-update.js +39 -0
  135. package/src/commands/delivery-tracker.js +94 -0
  136. package/src/commands/delivery-transition.js +79 -0
  137. package/src/commands/delivery.js +223 -0
  138. package/src/commands/dependency-approval.js +21 -0
  139. package/src/commands/doctor.js +191 -0
  140. package/src/commands/evidence.js +44 -0
  141. package/src/commands/feature.js +212 -0
  142. package/src/commands/goals.js +231 -0
  143. package/src/commands/human-run.js +208 -0
  144. package/src/commands/human-task.js +278 -0
  145. package/src/commands/init.js +848 -0
  146. package/src/commands/install.js +814 -0
  147. package/src/commands/integration-setup.js +65 -0
  148. package/src/commands/integrations.js +54 -0
  149. package/src/commands/models.js +158 -0
  150. package/src/commands/orchestrate.js +61 -0
  151. package/src/commands/preflight.js +125 -0
  152. package/src/commands/protocols.js +228 -0
  153. package/src/commands/repositories.js +77 -0
  154. package/src/commands/setup-remote.js +41 -0
  155. package/src/commands/setup.js +214 -0
  156. package/src/commands/status.js +224 -0
  157. package/src/commands/support.js +44 -0
  158. package/src/commands/task-completion.js +104 -0
  159. package/src/commands/uninstall.js +234 -0
  160. package/src/commands/verify.js +171 -0
  161. package/src/commands/work.js +220 -0
  162. package/src/commands/worktrees.js +52 -0
  163. package/src/config/command-readiness.js +218 -0
  164. package/src/config/commands.js +206 -0
  165. package/src/config/defaults.js +55 -0
  166. package/src/config/load.js +230 -0
  167. package/src/config/validate.js +560 -0
  168. package/src/delivery/bitbucket-review.js +123 -0
  169. package/src/delivery/branch-publication.js +23 -0
  170. package/src/delivery/contract.js +379 -0
  171. package/src/delivery/github-deployment.js +132 -0
  172. package/src/delivery/github-review.js +5 -0
  173. package/src/delivery/github.js +382 -0
  174. package/src/delivery/gitlab-review.js +5 -0
  175. package/src/delivery/gitlab.js +395 -0
  176. package/src/delivery/prepare.js +90 -0
  177. package/src/delivery/publication-process.js +33 -0
  178. package/src/delivery/publication-transport.js +112 -0
  179. package/src/delivery/review-request.js +184 -0
  180. package/src/delivery/review-update.js +74 -0
  181. package/src/delivery/service.js +400 -0
  182. package/src/delivery/store.js +76 -0
  183. package/src/delivery/tracker-target.js +51 -0
  184. package/src/delivery/tracker-transition.js +158 -0
  185. package/src/delivery/tracker.js +164 -0
  186. package/src/discovery/git.js +85 -0
  187. package/src/discovery/portable.js +108 -0
  188. package/src/discovery/project.js +345 -0
  189. package/src/discovery/tools.js +145 -0
  190. package/src/evaluations/approval.js +20 -0
  191. package/src/evaluations/cost-policy.js +20 -0
  192. package/src/evaluations/harness-attempt.js +113 -0
  193. package/src/evaluations/live-runner.js +55 -0
  194. package/src/evaluations/profile.js +28 -0
  195. package/src/evaluations/report.js +11 -0
  196. package/src/evaluations/text-attempt.js +18 -0
  197. package/src/evidence/checksum.js +87 -0
  198. package/src/evidence/collect.js +727 -0
  199. package/src/evidence/validate.js +354 -0
  200. package/src/feature/accepted-integration.js +46 -0
  201. package/src/feature/actions.js +37 -0
  202. package/src/feature/client-profile.js +56 -0
  203. package/src/feature/decomposition-contract.js +58 -0
  204. package/src/feature/host-execution.js +633 -0
  205. package/src/feature/host-lock-recovery.js +96 -0
  206. package/src/feature/host-run-lock.js +32 -0
  207. package/src/feature/local-approval.js +183 -0
  208. package/src/feature/plan-contract.js +215 -0
  209. package/src/feature/planner.js +253 -0
  210. package/src/feature/run-store.js +241 -0
  211. package/src/feature/runtime-bridge.js +840 -0
  212. package/src/feature/verification-report.js +168 -0
  213. package/src/feature/workflow.js +375 -0
  214. package/src/git/client.js +509 -0
  215. package/src/git/integration-worktree.js +153 -0
  216. package/src/git/reconcile.js +222 -0
  217. package/src/git/reservations.js +450 -0
  218. package/src/git/worktrees.js +478 -0
  219. package/src/graph/completion.js +189 -0
  220. package/src/graph/fixtures.js +116 -0
  221. package/src/graph/reducer.js +124 -0
  222. package/src/graph/scheduler.js +252 -0
  223. package/src/graph/validate.js +106 -0
  224. package/src/install/managed.js +579 -0
  225. package/src/install/project-reference.js +33 -0
  226. package/src/install/project-runtime.js +142 -0
  227. package/src/install/runtime-integrity.cjs +141 -0
  228. package/src/integrations/capabilities.js +69 -0
  229. package/src/integrations/host-observation.js +26 -0
  230. package/src/integrations/registry.js +56 -0
  231. package/src/models/delegate.js +102 -0
  232. package/src/models/profiles.js +53 -0
  233. package/src/models/protocols.js +208 -0
  234. package/src/models/registry.js +83 -0
  235. package/src/models/transport.js +59 -0
  236. package/src/policy/approvals.js +251 -0
  237. package/src/policy/authority.js +282 -0
  238. package/src/policy/budget.js +149 -0
  239. package/src/policy/command-bootstrap.js +219 -0
  240. package/src/policy/commands.js +803 -0
  241. package/src/prompts/launch-contract.js +43 -0
  242. package/src/prompts/planning-contract.js +92 -0
  243. package/src/protocols/presentation.js +53 -0
  244. package/src/protocols/project.js +289 -0
  245. package/src/quality/runner.js +497 -0
  246. package/src/quality/traceability.js +215 -0
  247. package/src/repositories/identity.js +59 -0
  248. package/src/repositories/index.js +2 -0
  249. package/src/repositories/provider.js +210 -0
  250. package/src/runtime/application.js +353 -0
  251. package/src/runtime/dependency-directory.js +33 -0
  252. package/src/runtime/harness-discovery.js +121 -0
  253. package/src/runtime/heartbeat.js +47 -0
  254. package/src/runtime/instance-store.js +112 -0
  255. package/src/runtime/orchestrator.js +1133 -0
  256. package/src/runtime/portable-dependencies.js +94 -0
  257. package/src/runtime/recovery.js +190 -0
  258. package/src/runtime/retry.js +54 -0
  259. package/src/runtime/supervisor.js +166 -0
  260. package/src/runtime/worktree-bootstrap.js +169 -0
  261. package/src/state/event-store.js +333 -0
  262. package/src/state/lock.js +266 -0
  263. package/src/state/paths.js +274 -0
  264. package/src/state/redact.js +106 -0
  265. package/src/state/snapshot-store.js +310 -0
  266. package/src/status/public/app.js +96 -0
  267. package/src/status/public/index.html +37 -0
  268. package/src/status/public/styles.css +50 -0
  269. package/src/status/server.js +323 -0
  270. package/src/status/view-model.js +356 -0
  271. package/src/support/bundle.js +195 -0
  272. package/src/support/failure-report.js +96 -0
  273. package/src/work-request/contract.js +250 -0
  274. package/src/work-request/host.js +78 -0
  275. package/src/work-request/local.js +163 -0
  276. package/src/work-request/tracker.js +105 -0
  277. package/templates/evidence/qa-bundle.json +194 -0
  278. package/templates/github-actions/rivet-deploy.yml +98 -0
  279. package/templates/harness/SKILL.md +90 -0
  280. package/templates/project/.rivet/orchestration.yaml +42 -0
  281. package/templates/project/.rivet/project.yaml +14 -0
  282. package/templates/project/.rivet/providers.yaml +8 -0
  283. package/templates/project/.rivet/quality.yaml +16 -0
@@ -0,0 +1,195 @@
1
+ import {
2
+ boundedString,
3
+ captureRecord,
4
+ createAdapter,
5
+ createProviderWriteGovernance,
6
+ createProviderWireBody,
7
+ createSourceEnvelope,
8
+ executeGovernedWrite,
9
+ executeTrustedConditionalMutation,
10
+ failProvider,
11
+ immutableRedactedJson,
12
+ providerTimestamp,
13
+ snapshotProviderJson,
14
+ validateTrustedConditionalMutationCapability,
15
+ } from './contract.js';
16
+ import { createProviderHttpClient } from './http.js';
17
+
18
+ const READ = Object.freeze(['epic', 'issue']);
19
+ const WRITE = Object.freeze(['comment', 'status']);
20
+ const ID = /^[A-Z][A-Z0-9]{0,31}-[1-9][0-9]{0,15}$/;
21
+
22
+ function remoteFailure() { failProvider('remote', { provider: 'jira', retryClassification: 'permanent' }); }
23
+
24
+ function validateIssue(value, expectedId, full = false, expectedKind) {
25
+ try {
26
+ if (!value || typeof value !== 'object' || Array.isArray(value) || value.key !== expectedId
27
+ || !value.fields || typeof value.fields !== 'object' || Array.isArray(value.fields)
28
+ || !value.fields.status || typeof value.fields.status.id !== 'string') remoteFailure();
29
+ if (full && (typeof value.id !== 'string' || !/^[1-9][0-9]*$/.test(value.id)
30
+ || typeof value.fields.summary !== 'string' || typeof value.fields.issuetype?.name !== 'string'
31
+ || typeof value.fields.status.name !== 'string'
32
+ || (value.fields.issuelinks !== undefined && !Array.isArray(value.fields.issuelinks))
33
+ || (value.fields.comment !== undefined && (!value.fields.comment || typeof value.fields.comment !== 'object'
34
+ || !Array.isArray(value.fields.comment.comments))))) remoteFailure();
35
+ if (full && value.fields.issuelinks?.some(link => {
36
+ const keys = [link?.outwardIssue?.key, link?.inwardIssue?.key].filter(key => typeof key === 'string');
37
+ return !link || typeof link !== 'object' || Array.isArray(link)
38
+ || typeof link.id !== 'string' || !/^[1-9][0-9]*$/.test(link.id) || typeof link.type?.name !== 'string'
39
+ || keys.length !== 1 || !ID.test(keys[0]);
40
+ })) remoteFailure();
41
+ if (expectedKind === 'epic' && value.fields.issuetype.name !== 'Epic') remoteFailure();
42
+ return value;
43
+ } catch (error) { if (error instanceof Error && error.code === 'ERR_PROVIDER_REMOTE') throw error; remoteFailure(); }
44
+ }
45
+
46
+ function validateComments(value) {
47
+ try {
48
+ if (!Array.isArray(value)) remoteFailure();
49
+ for (const comment of value) {
50
+ if (!comment || typeof comment !== 'object' || Array.isArray(comment)
51
+ || typeof comment.id !== 'string' || !/^[1-9][0-9]*$/.test(comment.id)
52
+ || (typeof comment.body !== 'string' && (!comment.body || typeof comment.body !== 'object' || Array.isArray(comment.body)))
53
+ || !comment.author || typeof comment.author !== 'object' || Array.isArray(comment.author)
54
+ || (typeof comment.author.displayName !== 'string' && typeof comment.author.accountId !== 'string')) remoteFailure();
55
+ }
56
+ return value;
57
+ } catch (error) { if (error instanceof Error && error.code === 'ERR_PROVIDER_REMOTE') throw error; remoteFailure(); }
58
+ }
59
+
60
+ function textFromDocument(value) {
61
+ if (typeof value === 'string') return value;
62
+ if (!value || typeof value !== 'object') return '';
63
+ if (typeof value.text === 'string') return value.text;
64
+ if (Array.isArray(value.content)) return value.content.map(textFromDocument).filter(Boolean).join('\n');
65
+ return '';
66
+ }
67
+
68
+ export function normalizeJira(raw) {
69
+ const source = snapshotProviderJson(raw);
70
+ const fields = source?.fields ?? {};
71
+ const description = textFromDocument(fields.description);
72
+ const namedCriteria = Object.entries(source?.names ?? {})
73
+ .filter(([, name]) => /acceptance criteria/i.test(String(name)))
74
+ .flatMap(([key]) => textFromDocument(fields[key]).split(/\r?\n/));
75
+ const acceptanceCriteria = [...description.split(/\r?\n/), ...namedCriteria].map(line => line.trim())
76
+ .filter(line => /^(?:AC\s*\d*|acceptance criteria)\s*[:.-]/i.test(line));
77
+ const links = Array.isArray(fields.issuelinks) ? fields.issuelinks.map(link => ({
78
+ id: String(link?.id ?? ''), type: String(link?.type?.name ?? ''),
79
+ issueId: String(link?.outwardIssue?.key ?? link?.inwardIssue?.key ?? ''),
80
+ })) : [];
81
+ const comments = Array.isArray(fields.comment?.comments) ? fields.comment.comments.map(comment => ({
82
+ id: String(comment?.id ?? ''), body: comment?.body ?? null,
83
+ author: String(comment?.author?.displayName ?? comment?.author?.accountId ?? ''),
84
+ })) : [];
85
+ return immutableRedactedJson({
86
+ id: String(source?.key ?? source?.id ?? ''), internalId: String(source?.id ?? ''),
87
+ kind: String(fields.issuetype?.name ?? ''), summary: String(fields.summary ?? ''),
88
+ epicId: String(fields.parent?.key ?? fields.epic?.key ?? ''),
89
+ status: { id: String(fields.status?.id ?? ''), name: String(fields.status?.name ?? '') },
90
+ description, acceptanceCriteria, links, comments, revision: String(fields.updated ?? ''),
91
+ });
92
+ }
93
+
94
+ function clock(configured) {
95
+ const read = configured ?? (() => new Date().toISOString());
96
+ if (typeof read !== 'function') failProvider('invalid-config', { provider: 'jira' });
97
+ return () => {
98
+ let value;
99
+ try { value = read(); } catch { failProvider('invalid-config', { provider: 'jira' }); }
100
+ return value;
101
+ };
102
+ }
103
+
104
+ export function createJiraAdapter(input) {
105
+ const config = captureRecord(input, new Set([
106
+ 'transport', 'baseUrl', 'headers', 'timeoutMs', 'maxResponseBytes', 'maxPages', 'maxItems', 'clock', 'governance', 'conditionalMutation',
107
+ ]), ['transport', 'baseUrl'], 'invalid-config');
108
+ const now = clock(config.clock);
109
+ const governance = config.governance === undefined ? null : createProviderWriteGovernance(config.governance, 'jira');
110
+ const conditionalMutation = config.conditionalMutation;
111
+ if (governance && !validateTrustedConditionalMutationCapability(conditionalMutation, 'jira', WRITE)) failProvider('invalid-config', { provider: 'jira' });
112
+ const http = createProviderHttpClient({
113
+ provider: 'jira', baseUrl: config.baseUrl, transport: config.transport,
114
+ ...(config.headers === undefined ? {} : { headers: config.headers }),
115
+ ...(config.timeoutMs === undefined ? {} : { timeoutMs: config.timeoutMs }),
116
+ ...(config.maxResponseBytes === undefined ? {} : { maxResponseBytes: config.maxResponseBytes }),
117
+ ...(config.maxPages === undefined ? {} : { maxPages: config.maxPages }),
118
+ ...(config.maxItems === undefined ? {} : { maxItems: config.maxItems }),
119
+ });
120
+
121
+ async function read(inputRead) {
122
+ const request = captureRecord(inputRead, new Set(['kind', 'id', 'signal']), ['kind', 'id']);
123
+ const kind = boundedString(request.kind, 16, /^(?:epic|issue)$/);
124
+ const id = boundedString(request.id, 64, ID);
125
+ const response = await http.request({
126
+ method: 'GET', path: `/rest/api/3/issue/${encodeURIComponent(id)}?fields=*all&expand=names`,
127
+ ...(request.signal === undefined ? {} : { signal: request.signal }),
128
+ });
129
+ validateIssue(response.data, id, true, kind);
130
+ const comments = await http.paginate({
131
+ path: `/rest/api/3/issue/${encodeURIComponent(id)}/comment?startAt=0&maxResults=100`,
132
+ itemsKey: 'comments', identityKey: 'id', mode: 'jira-offset', ...(request.signal === undefined ? {} : { signal: request.signal }),
133
+ });
134
+ validateComments(comments);
135
+ const raw = immutableRedactedJson({
136
+ ...response.data,
137
+ fields: { ...response.data.fields, comment: { comments } },
138
+ });
139
+ return createSourceEnvelope({
140
+ provider: 'jira', sourceId: id, sourceUrl: http.urlFor(`/browse/${encodeURIComponent(id)}`),
141
+ fetchedAt: providerTimestamp(now, 'jira'), fixtureSource: false, raw,
142
+ normalized: { ...normalizeJira(raw), requestedKind: kind },
143
+ retryClassification: 'none', capabilities: { read: READ, write: WRITE },
144
+ });
145
+ }
146
+
147
+ function write(inputWrite) {
148
+ if (!governance) failProvider('approval-required', { provider: 'jira' });
149
+ return executeGovernedWrite(inputWrite, {
150
+ provider: 'jira', actions: new Set(WRITE), governance,
151
+ async preflight(writeRequest) {
152
+ const id = boundedString(writeRequest.resourceId, 64, ID);
153
+ const response = await http.request({ method: 'GET', path: `/rest/api/3/issue/${encodeURIComponent(id)}?fields=status` });
154
+ validateIssue(response.data, id);
155
+ return Object.freeze({
156
+ state: String(response.data?.fields?.status?.id ?? ''),
157
+ version: String(response.headers.etag ?? response.data?.version ?? ''),
158
+ });
159
+ },
160
+ prepare(writeRequest) {
161
+ const id = boundedString(writeRequest.resourceId, 64, ID);
162
+ if (writeRequest.action === 'comment') {
163
+ const payload = captureRecord(writeRequest.payload, new Set(['body']), ['body']);
164
+ const body = typeof payload.body === 'string'
165
+ ? boundedString(payload.body, 32_768)
166
+ : payload.body;
167
+ return Object.freeze({ method: 'POST', path: `/rest/api/3/issue/${encodeURIComponent(id)}/comment`, wireBody: createProviderWireBody(writeRequest) });
168
+ }
169
+ const payload = captureRecord(writeRequest.payload, new Set(['transitionId']), ['transitionId']);
170
+ const transitionId = boundedString(payload.transitionId, 64, /^[A-Za-z0-9._-]+$/);
171
+ return Object.freeze({
172
+ method: 'POST', path: `/rest/api/3/issue/${encodeURIComponent(id)}/transitions`,
173
+ wireBody: createProviderWireBody(writeRequest),
174
+ });
175
+ },
176
+ async mutate(writeRequest, _state, prepared) {
177
+ return executeTrustedConditionalMutation(conditionalMutation, {
178
+ provider: 'jira', action: writeRequest.action, resourceId: writeRequest.resourceId,
179
+ expectedState: writeRequest.expectedState, expectedVersion: writeRequest.expectedVersion,
180
+ idempotencyKey: writeRequest.idempotencyKey, wireBody: prepared.wireBody,
181
+ dispatch: async () => {
182
+ const data = (await http.request({ ...prepared, headers: { 'idempotency-key': writeRequest.idempotencyKey } })).data;
183
+ if (writeRequest.action === 'comment' && (!data || typeof data.id !== 'string' || !/^[1-9][0-9]*$/.test(data.id))) remoteFailure();
184
+ if (writeRequest.action === 'status' && data !== null) remoteFailure();
185
+ return data;
186
+ },
187
+ });
188
+ },
189
+ });
190
+ }
191
+
192
+ return createAdapter({
193
+ provider: 'jira', fixtureSource: false, capabilities: { read: READ, write: WRITE }, read, write,
194
+ });
195
+ }
@@ -0,0 +1,171 @@
1
+ import { markdownAcceptanceCriteria } from '../work-request/local.js';
2
+ import {
3
+ boundedString,
4
+ captureRecord,
5
+ createAdapter,
6
+ createProviderReadBody,
7
+ createSourceEnvelope,
8
+ failProvider,
9
+ immutableRedactedJson,
10
+ providerTimestamp,
11
+ readOnlyWrite,
12
+ snapshotProviderJson,
13
+ } from './contract.js';
14
+ import { createProviderHttpClient } from './http.js';
15
+
16
+ const READ = Object.freeze(['issue']);
17
+ const WRITE = Object.freeze([]);
18
+ const ID = /^[A-Z][A-Z0-9]{0,31}-[1-9][0-9]{0,15}$/;
19
+ const SAFE_REMOTE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
20
+ const QUERY = `query AgilnoFeatureIssue($identifier: String!) {
21
+ issue(id: $identifier) {
22
+ id identifier title description updatedAt url
23
+ state { id name }
24
+ team { id key }
25
+ labels(first: 100) { nodes { id name } pageInfo { hasNextPage endCursor } }
26
+ relations(first: 100) { nodes { id type relatedIssue { identifier } } pageInfo { hasNextPage endCursor } }
27
+ comments(first: 100) { nodes { id body user { id name } } pageInfo { hasNextPage endCursor } }
28
+ }
29
+ }`;
30
+
31
+ function remoteFailure() { failProvider('remote', { provider: 'linear', retryClassification: 'permanent' }); }
32
+
33
+ function array(value, maximum) {
34
+ if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype || value.length > maximum) remoteFailure();
35
+ return value;
36
+ }
37
+
38
+ function safeId(value) {
39
+ if (typeof value !== 'string' || !SAFE_REMOTE_ID.test(value)) remoteFailure();
40
+ return value;
41
+ }
42
+
43
+ function connection(value, maximum) {
44
+ if (!value || typeof value !== 'object' || Array.isArray(value)
45
+ || !value.pageInfo || typeof value.pageInfo !== 'object' || Array.isArray(value.pageInfo)
46
+ || value.pageInfo.hasNextPage !== false) remoteFailure();
47
+ return array(value.nodes, maximum);
48
+ }
49
+
50
+ function validateIssue(raw, expectedId) {
51
+ try {
52
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)
53
+ || raw.identifier !== expectedId || typeof raw.title !== 'string'
54
+ || (raw.description !== null && typeof raw.description !== 'string')
55
+ || typeof raw.updatedAt !== 'string' || typeof raw.url !== 'string'
56
+ || !raw.state || typeof raw.state !== 'object' || Array.isArray(raw.state)
57
+ || typeof raw.state.name !== 'string'
58
+ || !raw.team || typeof raw.team !== 'object' || Array.isArray(raw.team)
59
+ || typeof raw.team.key !== 'string') remoteFailure();
60
+ safeId(raw.id); safeId(raw.state.id); safeId(raw.team.id);
61
+ for (const label of connection(raw.labels, 100)) {
62
+ if (!label || typeof label !== 'object' || Array.isArray(label) || typeof label.name !== 'string') remoteFailure();
63
+ safeId(label.id);
64
+ }
65
+ for (const relation of connection(raw.relations, 100)) {
66
+ if (!relation || typeof relation !== 'object' || Array.isArray(relation)
67
+ || typeof relation.type !== 'string' || !relation.relatedIssue || typeof relation.relatedIssue !== 'object'
68
+ || Array.isArray(relation.relatedIssue) || !ID.test(relation.relatedIssue.identifier)) remoteFailure();
69
+ safeId(relation.id);
70
+ }
71
+ for (const comment of connection(raw.comments, 100)) {
72
+ if (!comment || typeof comment !== 'object' || Array.isArray(comment) || typeof comment.body !== 'string'
73
+ || !comment.user || typeof comment.user !== 'object' || Array.isArray(comment.user)
74
+ || typeof comment.user.name !== 'string') remoteFailure();
75
+ safeId(comment.id); safeId(comment.user.id);
76
+ }
77
+ return raw;
78
+ } catch (error) {
79
+ if (error instanceof Error && error.code === 'ERR_PROVIDER_REMOTE') throw error;
80
+ remoteFailure();
81
+ }
82
+ }
83
+
84
+ export function normalizeLinear(raw) {
85
+ const source = snapshotProviderJson(raw);
86
+ validateIssue(source, source.identifier);
87
+ const description = source.description ?? '';
88
+ const labeledCriteria = description.split(/\r?\n/).map(line => line.trim())
89
+ .filter(line => /^(?:AC\s*\d*|acceptance criteria)\s*[:.-]/i.test(line));
90
+ return immutableRedactedJson({
91
+ id: source.identifier,
92
+ internalId: source.id,
93
+ kind: 'Issue',
94
+ summary: source.title,
95
+ description,
96
+ acceptanceCriteria: labeledCriteria.length ? labeledCriteria : markdownAcceptanceCriteria(description),
97
+ status: { id: source.state.id, name: source.state.name },
98
+ team: { id: source.team.id, key: source.team.key },
99
+ labels: source.labels.nodes.map(label => ({ id: label.id, name: label.name })),
100
+ links: source.relations.nodes.map(relation => ({ id: relation.id, type: relation.type, issueId: relation.relatedIssue.identifier })),
101
+ comments: source.comments.nodes.map(comment => ({ id: comment.id, body: comment.body, author: comment.user.name })),
102
+ revision: source.updatedAt,
103
+ url: source.url,
104
+ });
105
+ }
106
+
107
+ function clock(configured) {
108
+ const read = configured ?? (() => new Date().toISOString());
109
+ if (typeof read !== 'function') failProvider('invalid-config', { provider: 'linear' });
110
+ return () => {
111
+ let value;
112
+ try { value = read(); } catch { failProvider('invalid-config', { provider: 'linear' }); }
113
+ return value;
114
+ };
115
+ }
116
+
117
+ export function createLinearAdapter(input) {
118
+ const config = captureRecord(input, new Set([
119
+ 'transport', 'baseUrl', 'headers', 'timeoutMs', 'maxResponseBytes', 'clock',
120
+ ]), ['transport', 'baseUrl'], 'invalid-config');
121
+ const now = clock(config.clock);
122
+ const http = createProviderHttpClient({
123
+ provider: 'linear',
124
+ baseUrl: config.baseUrl,
125
+ transport: config.transport,
126
+ ...(config.headers === undefined ? {} : { headers: config.headers }),
127
+ ...(config.timeoutMs === undefined ? {} : { timeoutMs: config.timeoutMs }),
128
+ ...(config.maxResponseBytes === undefined ? {} : { maxResponseBytes: config.maxResponseBytes }),
129
+ });
130
+
131
+ async function read(inputRead) {
132
+ const request = captureRecord(inputRead, new Set(['kind', 'id', 'signal']), ['kind', 'id']);
133
+ const kind = boundedString(request.kind, 16, /^issue$/);
134
+ const id = boundedString(request.id, 64, ID);
135
+ const wireBody = createProviderReadBody({
136
+ provider: 'linear',
137
+ payload: { query: QUERY, variables: { identifier: id } },
138
+ });
139
+ const response = await http.request({
140
+ method: 'POST',
141
+ path: '/graphql',
142
+ wireBody,
143
+ ...(request.signal === undefined ? {} : { signal: request.signal }),
144
+ });
145
+ const rawResponse = snapshotProviderJson(response.data);
146
+ if (!rawResponse || typeof rawResponse !== 'object' || Array.isArray(rawResponse)
147
+ || Object.hasOwn(rawResponse, 'errors') || !rawResponse.data || typeof rawResponse.data !== 'object'
148
+ || !rawResponse.data.issue || typeof rawResponse.data.issue !== 'object') remoteFailure();
149
+ const raw = validateIssue(rawResponse.data.issue, id);
150
+ const normalized = normalizeLinear(raw);
151
+ return createSourceEnvelope({
152
+ provider: 'linear',
153
+ sourceId: id,
154
+ sourceUrl: normalized.url,
155
+ fetchedAt: providerTimestamp(now, 'linear'),
156
+ fixtureSource: false,
157
+ raw,
158
+ normalized: { ...normalized, requestedKind: kind },
159
+ retryClassification: 'none',
160
+ capabilities: { read: READ, write: WRITE },
161
+ });
162
+ }
163
+
164
+ return createAdapter({
165
+ provider: 'linear',
166
+ fixtureSource: false,
167
+ capabilities: { read: READ, write: WRITE },
168
+ read,
169
+ write: readOnlyWrite('linear'),
170
+ });
171
+ }
@@ -0,0 +1,59 @@
1
+ import { lookup as dnsLookup } from 'node:dns/promises';
2
+ import { request as httpsRequest } from 'node:https';
3
+ import { isIP } from 'node:net';
4
+ import { Readable } from 'node:stream';
5
+
6
+ import { createTrustedProviderTransport } from './http.js';
7
+
8
+ // The HTTP policy validates DNS answers before supplying these exact pins.
9
+ // Retain the URL hostname for TLS certificate validation and SNI.
10
+ export function createNodeProviderTransport({ lookup = dnsLookup, request = httpsRequest } = {}) {
11
+ return createTrustedProviderTransport({
12
+ async resolve(hostname) {
13
+ return (await lookup(hostname, { all: true })).map(item => item.address);
14
+ },
15
+ fetchPinned(url, init, pins) {
16
+ return new Promise((resolve, reject) => {
17
+ const target = new URL(url);
18
+ if (target.protocol !== 'https:' || target.hostname !== pins.hostname || !pins.addresses.length) {
19
+ reject(new Error('Invalid pinned destination.'));
20
+ return;
21
+ }
22
+ const addresses = pins.addresses.map(address => ({ address, family: isIP(address) }));
23
+ const outgoing = request(target, {
24
+ method: init.method,
25
+ headers: init.headers,
26
+ signal: init.signal,
27
+ agent: false,
28
+ servername: pins.hostname,
29
+ rejectUnauthorized: true,
30
+ lookup(hostname, options, callback) {
31
+ if (hostname !== pins.hostname) return callback(new Error('Unexpected hostname.'));
32
+ if (options?.all) return callback(null, addresses);
33
+ const selected = addresses.find(item => !options?.family || item.family === options.family);
34
+ if (!selected) return callback(new Error('No matching pinned address.'));
35
+ callback(null, selected.address, selected.family);
36
+ },
37
+ }, incoming => {
38
+ const status = incoming.statusCode;
39
+ if (!Number.isInteger(status) || (status >= 300 && status < 400)) {
40
+ incoming.destroy();
41
+ reject(new Error('Provider redirect or invalid response.'));
42
+ return;
43
+ }
44
+ try {
45
+ const headers = new Headers();
46
+ for (const [name, value] of Object.entries(incoming.headers)) {
47
+ if (value !== undefined) headers.set(name, Array.isArray(value) ? value.join(', ') : value);
48
+ }
49
+ const noBody = status === 204 || status === 205;
50
+ if (noBody) incoming.resume();
51
+ resolve(new Response(noBody ? null : Readable.toWeb(incoming), { status, headers }));
52
+ } catch (error) { incoming.destroy(); reject(error); }
53
+ });
54
+ outgoing.on('error', reject);
55
+ outgoing.end(init.body);
56
+ });
57
+ },
58
+ });
59
+ }
@@ -0,0 +1,32 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+
3
+ // The guide asks only for local metadata and environment variable names. It has
4
+ // no secret-input mode and never reads credentials or contacts a provider.
5
+ export async function defaultIntegrationSetupPrompt(question) {
6
+ const readline = createInterface({ input: process.stdin, output: process.stdout });
7
+ let timer;
8
+ try {
9
+ let prompt = question.message;
10
+ if (question.type === 'select' || question.type === 'multi') {
11
+ prompt += '\n' + question.choices.map((choice, index) => ` ${index + 1}. ${choice.label}`).join('\n');
12
+ prompt += question.type === 'multi' ? '\nNumbers separated by commas (blank to cancel): ' : '\nNumber (blank to cancel): ';
13
+ } else if (question.type === 'confirm') prompt += ' [y/N] ';
14
+ else prompt += question.defaultValue ? ` [${question.defaultValue}] (q to cancel): ` : ' (blank to cancel): ';
15
+ const answer = String(await Promise.race([
16
+ readline.question(prompt, { signal: question.signal }),
17
+ new Promise(resolve => { timer = setTimeout(() => resolve('q'), 120000); }),
18
+ ])).trim();
19
+ if (answer.toLowerCase() === 'q') return null;
20
+ if (question.type === 'confirm') return /^(?:y|yes)$/i.test(answer);
21
+ if (question.type === 'input') return answer || question.defaultValue || null;
22
+ if (!answer) return null;
23
+ const parts = answer.split(',').map(value => value.trim());
24
+ if (parts.some(value => !/^[1-9][0-9]*$/.test(value))) return null;
25
+ const indexes = parts.map(value => Number(value) - 1);
26
+ if (indexes.some(index => !Number.isSafeInteger(index) || index >= question.choices.length)
27
+ || new Set(indexes).size !== indexes.length || (question.type === 'select' && indexes.length !== 1)) return null;
28
+ const selected = indexes.map(index => question.choices[index].value);
29
+ return question.type === 'select' ? selected[0] : selected;
30
+ } catch { return null; }
31
+ finally { clearTimeout(timer); readline.close(); }
32
+ }
@@ -0,0 +1,25 @@
1
+ import { CliError } from './output.js';
2
+
3
+ function interrupted() {
4
+ return new CliError('Task interrupted. Inspect it with rivet task status before continuing.', 'REPOSITORY_CONFLICT');
5
+ }
6
+
7
+ export async function withTerminalInterruption(operation) {
8
+ const controller = new AbortController();
9
+ const abort = () => controller.abort();
10
+ process.on('SIGINT', abort);
11
+ process.on('SIGTERM', abort);
12
+ try {
13
+ let result;
14
+ try { result = await operation(controller.signal); }
15
+ catch (error) {
16
+ if (controller.signal.aborted) throw interrupted();
17
+ throw error;
18
+ }
19
+ if (controller.signal.aborted) throw interrupted();
20
+ return result;
21
+ } finally {
22
+ process.off('SIGINT', abort);
23
+ process.off('SIGTERM', abort);
24
+ }
25
+ }