@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,122 @@
1
+ import { createAdapter, captureRecord } from './contract.js';
2
+ import { createJiraAdapter } from './jira.js';
3
+ import { createLinearAdapter } from './linear.js';
4
+ import { providerCredentialStatus } from '../config/load.js';
5
+
6
+ const TRACKERS = new Set(['jira', 'linear']);
7
+ const ENV_NAME = /^[A-Z][A-Z0-9_]{1,127}$/;
8
+
9
+ export class TrackerProviderFactoryError extends Error {
10
+ constructor() {
11
+ super('Tracker provider configuration is missing, ambiguous, or invalid.');
12
+ this.name = 'TrackerProviderFactoryError';
13
+ this.code = 'ERR_TRACKER_PROVIDER_CONFIGURATION';
14
+ this.safeMessage = this.message;
15
+ }
16
+ }
17
+
18
+ function fail() { throw new TrackerProviderFactoryError(); }
19
+
20
+ function snapshotInput(input) {
21
+ try {
22
+ if (!input || typeof input !== 'object' || Array.isArray(input)) fail();
23
+ const allowed = new Set(['config', 'environment', 'transport', 'clock']);
24
+ const keys = Reflect.ownKeys(input);
25
+ if (keys.some(key => typeof key !== 'string' || !allowed.has(key))
26
+ || !['config', 'environment', 'transport'].every(key => keys.includes(key))) fail();
27
+ const output = {};
28
+ for (const key of keys) {
29
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
30
+ if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')) fail();
31
+ output[key] = descriptor.value;
32
+ }
33
+ return output;
34
+ } catch (error) {
35
+ if (error instanceof TrackerProviderFactoryError) throw error;
36
+ fail();
37
+ }
38
+ }
39
+
40
+ function envValue(environment, reference) {
41
+ if (typeof reference !== 'string' || !ENV_NAME.test(reference)) fail();
42
+ let value;
43
+ try { value = environment[reference]; } catch { fail(); }
44
+ if (typeof value !== 'string' || value.length < 1 || value.length > 8192 || /[\u0000\r\n]/.test(value)) fail();
45
+ return value;
46
+ }
47
+
48
+ function selectProvider(providers, tracker, projectId) {
49
+ if (tracker !== undefined && !TRACKERS.has(tracker)) fail();
50
+ const candidates = providers.filter(provider => provider && typeof provider === 'object' && !Array.isArray(provider)
51
+ && (!provider.projectIds?.length || provider.projectIds.includes(projectId))
52
+ && (provider.transport === undefined || provider.transport === 'direct-api')
53
+ && TRACKERS.has(provider.kind) && provider.mode !== 'disabled'
54
+ && Array.isArray(provider.capabilities) && provider.capabilities.includes('issues-read')
55
+ && (tracker === undefined || provider.kind === tracker));
56
+ if (candidates.length !== 1) fail();
57
+ return candidates[0];
58
+ }
59
+
60
+ function credentialsFor(provider, environment) {
61
+ const credentials = provider.credentials;
62
+ if (!credentials || typeof credentials !== 'object' || Array.isArray(credentials)) fail();
63
+ if (provider.kind === 'jira') {
64
+ const username = envValue(environment, credentials.usernameEnv);
65
+ const token = envValue(environment, credentials.apiTokenEnv);
66
+ return Object.freeze({ authorization: `Basic ${Buffer.from(`${username}:${token}`, 'utf8').toString('base64')}`, accept: 'application/json' });
67
+ }
68
+ return Object.freeze({ authorization: envValue(environment, credentials.apiTokenEnv), accept: 'application/json' });
69
+ }
70
+
71
+ export function createTrackerProviderFactory(input) {
72
+ const captured = snapshotInput(input);
73
+ const providers = captured.config?.providers?.providers;
74
+ if (!Array.isArray(providers) || providers.length < 1 || providers.length > 64
75
+ || !captured.environment || typeof captured.environment !== 'object' || Array.isArray(captured.environment)
76
+ || !captured.transport || typeof captured.transport !== 'object'
77
+ || (captured.clock !== undefined && typeof captured.clock !== 'function')) fail();
78
+
79
+ const credentialStatus = () => {
80
+ try {
81
+ return providerCredentialStatus(captured.config, captured.environment)
82
+ .filter(item => providers.some(provider => provider.id === item.provider && TRACKERS.has(provider.kind)));
83
+ } catch { fail(); }
84
+ };
85
+
86
+ return Object.freeze({
87
+ credentialStatus,
88
+ create(options = {}) {
89
+ try {
90
+ if (!options || typeof options !== 'object' || Array.isArray(options)) fail();
91
+ const keys = Reflect.ownKeys(options);
92
+ if (keys.some(key => key !== 'tracker')) fail();
93
+ if (keys.includes('tracker')) {
94
+ const descriptor = Object.getOwnPropertyDescriptor(options, 'tracker');
95
+ if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')) fail();
96
+ }
97
+ const selected = selectProvider(providers, options.tracker, captured.config.project?.id);
98
+ if (typeof selected.endpoint !== 'string') fail();
99
+ const common = {
100
+ transport: captured.transport,
101
+ baseUrl: selected.endpoint,
102
+ headers: credentialsFor(selected, captured.environment),
103
+ ...(captured.clock === undefined ? {} : { clock: captured.clock }),
104
+ };
105
+ const adapter = selected.kind === 'jira' ? createJiraAdapter(common) : createLinearAdapter(common);
106
+ const scope = selected.resourceIds === undefined ? [] : [...selected.resourceIds];
107
+ if (!Array.isArray(selected.resourceIds ?? []) || scope.some(id => typeof id !== 'string' || !id || id.length > 200)) fail();
108
+ if (!scope.length) return adapter;
109
+ return createAdapter({ provider: adapter.provider, fixtureSource: adapter.fixtureSource, capabilities: adapter.capabilities, write: adapter.write,
110
+ async read(input) {
111
+ const request = captureRecord(input, new Set(['kind', 'id', 'signal']), ['kind', 'id']);
112
+ if (!scope.includes(request.id)) fail();
113
+ return adapter.read(request);
114
+ },
115
+ });
116
+ } catch (error) {
117
+ if (error instanceof TrackerProviderFactoryError) throw error;
118
+ fail();
119
+ }
120
+ },
121
+ });
122
+ }
@@ -0,0 +1,178 @@
1
+ import {
2
+ boundedArray,
3
+ boundedString,
4
+ captureRecord,
5
+ createAdapter,
6
+ createSourceEnvelope,
7
+ failProvider,
8
+ immutableRedactedJson,
9
+ providerTimestamp,
10
+ readOnlyWrite,
11
+ sanitizeReferenceUrl,
12
+ snapshotProviderJson,
13
+ } from './contract.js';
14
+ import { createProviderHttpClient } from './http.js';
15
+
16
+ const READ = Object.freeze(['file', 'nodes']);
17
+ const WRITE = Object.freeze([]);
18
+
19
+ function remoteFailure() { failProvider('remote', { provider: 'figma', retryClassification: 'permanent' }); }
20
+
21
+ function validateNamedMap(value, kind) {
22
+ if (!value || typeof value !== 'object' || Array.isArray(value)) remoteFailure();
23
+ for (const entry of Object.values(value)) {
24
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry) || typeof entry.name !== 'string') remoteFailure();
25
+ if ((kind === 'component' || kind === 'style') && typeof entry.key !== 'string') remoteFailure();
26
+ if (kind === 'variable' && (!entry.valuesByMode || typeof entry.valuesByMode !== 'object' || Array.isArray(entry.valuesByMode))) remoteFailure();
27
+ }
28
+ return value;
29
+ }
30
+
31
+ function validateDocument(root) {
32
+ const pending = [root];
33
+ let count = 0;
34
+ while (pending.length > 0) {
35
+ const node = pending.pop();
36
+ count += 1;
37
+ if (count > 10_000 || !node || typeof node !== 'object' || Array.isArray(node)
38
+ || typeof node.id !== 'string' || typeof node.name !== 'string' || typeof node.type !== 'string'
39
+ || (node.children !== undefined && !Array.isArray(node.children))) remoteFailure();
40
+ if (node.children) pending.push(...node.children);
41
+ }
42
+ }
43
+
44
+ function validateFile(value) {
45
+ try {
46
+ if (!value || typeof value !== 'object' || Array.isArray(value) || typeof value.name !== 'string'
47
+ || typeof value.version !== 'string' || !value.document || typeof value.document !== 'object'
48
+ || Array.isArray(value.document)) throw new Error();
49
+ validateDocument(value.document);
50
+ validateNamedMap(value.components === undefined ? {} : value.components, 'component');
51
+ validateNamedMap(value.styles === undefined ? {} : value.styles, 'style');
52
+ if (value.variables !== undefined) validateNamedMap(value.variables, 'variable');
53
+ return value;
54
+ } catch { remoteFailure(); }
55
+ }
56
+
57
+ function validateNodes(value, requestedIds) {
58
+ try {
59
+ if (!value || typeof value !== 'object' || Array.isArray(value)
60
+ || !value.nodes || typeof value.nodes !== 'object' || Array.isArray(value.nodes)) throw new Error();
61
+ const returned = Object.keys(value.nodes).sort();
62
+ const requested = [...requestedIds].sort();
63
+ if (returned.length !== requested.length || returned.some((id, index) => id !== requested[index])) throw new Error();
64
+ for (const id of returned) {
65
+ const item = value.nodes[id];
66
+ if (!item || typeof item !== 'object' || Array.isArray(item)
67
+ || !item.document || typeof item.document !== 'object' || Array.isArray(item.document)
68
+ || item.document.id !== id || typeof item.document.name !== 'string' || typeof item.document.type !== 'string') throw new Error();
69
+ validateDocument(item.document);
70
+ }
71
+ return value;
72
+ } catch { remoteFailure(); }
73
+ }
74
+
75
+ function flattenNodes(root) {
76
+ const output = [];
77
+ const pending = [root];
78
+ let visited = 0;
79
+ while (pending.length > 0) {
80
+ const node = pending.pop();
81
+ if (!node || typeof node !== 'object') continue;
82
+ visited += 1;
83
+ if (visited > 10_000) failProvider('invalid-request', { provider: 'figma' });
84
+ if (typeof node.id === 'string' && node.type !== 'DOCUMENT') {
85
+ output.push({ id: node.id, name: String(node.name ?? ''), type: String(node.type ?? '') });
86
+ }
87
+ if (Array.isArray(node.children)) for (let index = node.children.length - 1; index >= 0; index -= 1) pending.push(node.children[index]);
88
+ }
89
+ return output;
90
+ }
91
+
92
+ export function normalizeFigma(file, rendered = {}, variables) {
93
+ const source = snapshotProviderJson(file);
94
+ const renderedSource = snapshotProviderJson(rendered);
95
+ const variableSource = variables === undefined ? (source?.variables ?? {}) : snapshotProviderJson(variables);
96
+ return immutableRedactedJson({
97
+ metadata: {
98
+ name: String(source?.name ?? ''), version: String(source?.version ?? ''),
99
+ lastModified: String(source?.lastModified ?? ''),
100
+ },
101
+ nodes: flattenNodes(source?.document), components: source?.components ?? {}, styles: source?.styles ?? {},
102
+ variables: variableSource,
103
+ renderedReferences: Object.entries(renderedSource).map(([nodeId, url]) => ({
104
+ nodeId, url: sanitizeReferenceUrl(url),
105
+ })),
106
+ });
107
+ }
108
+
109
+ export function createFigmaAdapter(input) {
110
+ const config = captureRecord(input, new Set([
111
+ 'transport', 'baseUrl', 'headers', 'timeoutMs', 'maxResponseBytes', 'maxPages', 'clock',
112
+ ]), ['transport', 'baseUrl'], 'invalid-config');
113
+ const now = config.clock ?? (() => new Date().toISOString());
114
+ if (typeof now !== 'function') failProvider('invalid-config', { provider: 'figma' });
115
+ const http = createProviderHttpClient({
116
+ provider: 'figma', baseUrl: config.baseUrl, transport: config.transport,
117
+ ...(config.headers === undefined ? {} : { headers: config.headers }),
118
+ ...(config.timeoutMs === undefined ? {} : { timeoutMs: config.timeoutMs }),
119
+ ...(config.maxResponseBytes === undefined ? {} : { maxResponseBytes: config.maxResponseBytes }),
120
+ ...(config.maxPages === undefined ? {} : { maxPages: config.maxPages }),
121
+ });
122
+
123
+ async function read(inputRead) {
124
+ const request = captureRecord(inputRead, new Set(['kind', 'id', 'nodeIds', 'render', 'includeVariables', 'signal']), ['kind', 'id']);
125
+ const kind = boundedString(request.kind, 16, /^(?:file|nodes)$/);
126
+ const id = boundedString(request.id, 128, /^[A-Za-z0-9_-]+$/);
127
+ const nodeIds = request.nodeIds === undefined ? Object.freeze([]) : boundedArray(
128
+ request.nodeIds, 100, child => boundedString(child, 128, /^[A-Za-z0-9:;._-]+$/),
129
+ );
130
+ if (new Set(nodeIds).size !== nodeIds.length || (kind === 'nodes' && nodeIds.length === 0)) failProvider('invalid-request', { provider: 'figma' });
131
+ if (request.render !== undefined && typeof request.render !== 'boolean') failProvider('invalid-request', { provider: 'figma' });
132
+ if (request.includeVariables !== undefined && typeof request.includeVariables !== 'boolean') failProvider('invalid-request', { provider: 'figma' });
133
+ const signal = request.signal === undefined ? {} : { signal: request.signal };
134
+ const path = kind === 'nodes'
135
+ ? `/v1/files/${encodeURIComponent(id)}/nodes?ids=${encodeURIComponent(nodeIds.join(','))}`
136
+ : `/v1/files/${encodeURIComponent(id)}`;
137
+ const fileResponse = await http.request({ method: 'GET', path, ...signal });
138
+ if (kind === 'file') validateFile(fileResponse.data);
139
+ else validateNodes(fileResponse.data, nodeIds);
140
+ const file = kind === 'nodes'
141
+ ? { ...fileResponse.data, document: { id: 'selected', name: 'Selected nodes', type: 'DOCUMENT', children: Object.values(fileResponse.data?.nodes ?? {}).map(item => item?.document).filter(Boolean) } }
142
+ : fileResponse.data;
143
+ let rendered = {};
144
+ if (request.render === true && nodeIds.length > 0) {
145
+ const imageResponse = await http.request({
146
+ method: 'GET', path: `/v1/images/${encodeURIComponent(id)}?ids=${encodeURIComponent(nodeIds.join(','))}`, ...signal,
147
+ });
148
+ rendered = imageResponse.data?.images;
149
+ if (!rendered || typeof rendered !== 'object' || Array.isArray(rendered)
150
+ || Object.values(rendered).some(value => typeof value !== 'string')) failProvider('remote', { provider: 'figma' });
151
+ try { for (const value of Object.values(rendered)) sanitizeReferenceUrl(value); }
152
+ catch { failProvider('remote', { provider: 'figma', retryClassification: 'permanent' }); }
153
+ const imageIds = Object.keys(rendered).sort();
154
+ const requestedImageIds = [...nodeIds].sort();
155
+ if (imageIds.length !== requestedImageIds.length
156
+ || imageIds.some((nodeId, index) => nodeId !== requestedImageIds[index])) remoteFailure();
157
+ }
158
+ let variables = file?.variables ?? {};
159
+ if (request.includeVariables === true) {
160
+ const variableResponse = await http.request({
161
+ method: 'GET', path: `/v1/files/${encodeURIComponent(id)}/variables/local`, ...signal,
162
+ });
163
+ variables = variableResponse.data?.meta?.variables;
164
+ if (!variables || typeof variables !== 'object' || Array.isArray(variables)) failProvider('remote', { provider: 'figma' });
165
+ validateNamedMap(variables, 'variable');
166
+ }
167
+ return createSourceEnvelope({
168
+ provider: 'figma', sourceId: id, sourceUrl: `https://www.figma.com/file/${encodeURIComponent(id)}`,
169
+ fetchedAt: providerTimestamp(now, 'figma'), fixtureSource: false, raw: { file, rendered, variables }, normalized: normalizeFigma(file, rendered, variables),
170
+ retryClassification: 'none', capabilities: { read: READ, write: WRITE },
171
+ });
172
+ }
173
+
174
+ return createAdapter({
175
+ provider: 'figma', fixtureSource: false, capabilities: { read: READ, write: WRITE },
176
+ read, write: readOnlyWrite('figma'),
177
+ });
178
+ }
@@ -0,0 +1,183 @@
1
+ import { constants } from 'node:fs';
2
+ import { lstat, open, realpath } from 'node:fs/promises';
3
+ import { isAbsolute, join, resolve } from 'node:path';
4
+
5
+ import {
6
+ boundedString,
7
+ captureRecord,
8
+ createAdapter,
9
+ createSourceEnvelope,
10
+ failProvider,
11
+ readOnlyWrite,
12
+ } from './contract.js';
13
+ import { normalizeConfluence } from './confluence.js';
14
+ import { normalizeFigma } from './figma.js';
15
+ import { normalizeJira } from './jira.js';
16
+
17
+ const DEFINITIONS = Object.freeze({
18
+ jira: Object.freeze({ file: 'jira.json', read: Object.freeze(['epic', 'issue']), normalize: normalizeJira }),
19
+ confluence: Object.freeze({ file: 'confluence.json', read: Object.freeze(['page']), normalize: normalizeConfluence }),
20
+ figma: Object.freeze({ file: 'figma.json', read: Object.freeze(['file', 'nodes']), normalize: raw => normalizeFigma(raw, Object.fromEntries(
21
+ (raw?.renderedReferences ?? []).map(item => [item.nodeId, item.url]),
22
+ )) }),
23
+ github: Object.freeze({
24
+ file: 'github.json', read: Object.freeze(['repo', 'branch', 'pull', 'checks', 'reviews', 'artifacts', 'delivery']),
25
+ normalize: raw => raw,
26
+ }),
27
+ });
28
+ const trustedPacketSources = new WeakMap();
29
+
30
+ function descriptorRoot(handle) {
31
+ if (process.platform !== 'linux') failProvider('invalid-config');
32
+ return `/proc/self/fd/${handle.fd}`;
33
+ }
34
+
35
+ function sameIdentity(left, right) {
36
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size
37
+ && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
38
+ }
39
+
40
+ function trustedHandle(value, provider) {
41
+ let stat;
42
+ let read;
43
+ try { ({ stat, read } = value); } catch { failProvider('invalid-config', { provider }); }
44
+ if (typeof stat !== 'function' || typeof read !== 'function') failProvider('invalid-config', { provider });
45
+ return Object.freeze({
46
+ stat: options => Reflect.apply(stat, value, [options]),
47
+ read: (...args) => Reflect.apply(read, value, args),
48
+ });
49
+ }
50
+
51
+ export function createTrustedFixturePacketSource(input) {
52
+ const value = captureRecord(input, new Set(['packets']), ['packets'], 'invalid-config');
53
+ const providers = Object.keys(DEFINITIONS);
54
+ const packets = captureRecord(value.packets, new Set(providers), providers, 'invalid-config');
55
+ const handles = {};
56
+ for (const provider of providers) handles[provider] = trustedHandle(packets[provider], provider);
57
+ const source = Object.freeze({ kind: 'trusted-preopened-provider-fixtures' });
58
+ trustedPacketSources.set(source, Object.freeze(handles));
59
+ return source;
60
+ }
61
+
62
+ async function readPacket(handle, provider, validateAnchor = async () => {}) {
63
+ let text;
64
+ let identity;
65
+ try {
66
+ const before = await handle.stat({ bigint: true });
67
+ if (!before.isFile() || before.nlink !== 1n || before.size < 2n || before.size > BigInt(2 * 1024 * 1024)) failProvider('invalid-config', { provider });
68
+ const size = Number(before.size);
69
+ const bytes = Buffer.alloc(size + 1);
70
+ let offset = 0;
71
+ while (offset < bytes.length) {
72
+ const result = await handle.read(bytes, offset, bytes.length - offset, offset);
73
+ if (result.bytesRead === 0) break;
74
+ offset += result.bytesRead;
75
+ }
76
+ const after = await handle.stat({ bigint: true });
77
+ await validateAnchor();
78
+ if (offset !== size || !sameIdentity(before, after)) {
79
+ failProvider('invalid-config', { provider });
80
+ }
81
+ identity = `${before.dev}:${before.ino}`;
82
+ try { text = new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(0, size)); }
83
+ catch { failProvider('invalid-config', { provider }); }
84
+ } catch { failProvider('invalid-config', { provider }); }
85
+ let packet;
86
+ try { packet = JSON.parse(text); } catch { failProvider('invalid-config', { provider }); }
87
+ const value = captureRecord(packet, new Set(['sourceId', 'sourceUrl', 'fetchedAt', 'raw']), ['sourceId', 'sourceUrl', 'fetchedAt', 'raw'], 'invalid-config');
88
+ return Object.freeze({ identity, packet: Object.freeze({
89
+ sourceId: boundedString(value.sourceId, 256), sourceUrl: boundedString(value.sourceUrl, 2048),
90
+ fetchedAt: boundedString(value.fetchedAt, 32), raw: value.raw,
91
+ }) });
92
+ }
93
+
94
+ async function loadPacket(rootHandle, rootIdentity, root, definition, provider) {
95
+ const path = join(descriptorRoot(rootHandle), definition.file);
96
+ const noFollow = constants.O_NOFOLLOW;
97
+ if (!Number.isInteger(noFollow)) failProvider('invalid-config', { provider });
98
+ let fileHandle;
99
+ try { fileHandle = await open(path, constants.O_RDONLY | noFollow); }
100
+ catch { failProvider('invalid-config', { provider }); }
101
+ try {
102
+ return await readPacket(trustedHandle(fileHandle, provider), provider, async () => {
103
+ const rootAfter = await rootHandle.stat({ bigint: true });
104
+ const rootPathAfter = await lstat(root, { bigint: true }).catch(() => null);
105
+ if (rootAfter.dev !== rootIdentity.dev || rootAfter.ino !== rootIdentity.ino
106
+ || !rootPathAfter || rootPathAfter.dev !== rootIdentity.dev || rootPathAfter.ino !== rootIdentity.ino) {
107
+ failProvider('invalid-config', { provider });
108
+ }
109
+ });
110
+ } finally {
111
+ await fileHandle.close().catch(() => {});
112
+ }
113
+ }
114
+
115
+ async function loadPreopenedPackets(source) {
116
+ const handles = trustedPacketSources.get(source);
117
+ if (!handles) failProvider('invalid-config');
118
+ const packets = {};
119
+ const identities = new Set();
120
+ for (const [provider, definition] of Object.entries(DEFINITIONS)) {
121
+ const result = await readPacket(handles[provider], provider);
122
+ if (identities.has(result.identity)) failProvider('invalid-config', { provider });
123
+ identities.add(result.identity);
124
+ packets[provider] = result.packet;
125
+ }
126
+ return packets;
127
+ }
128
+
129
+ async function loadRootPackets(rootInput) {
130
+ if (process.platform !== 'linux') failProvider('invalid-config');
131
+ const root = await realpath(rootInput).catch(() => null);
132
+ if (!root || root !== rootInput) failProvider('invalid-config');
133
+ const pathIdentity = await lstat(root, { bigint: true }).catch(() => null);
134
+ if (!pathIdentity?.isDirectory() || pathIdentity.isSymbolicLink()) failProvider('invalid-config');
135
+ const directoryFlags = constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | (constants.O_NOFOLLOW ?? 0);
136
+ let rootHandle;
137
+ try { rootHandle = await open(root, directoryFlags); } catch { failProvider('invalid-config'); }
138
+ const packets = {};
139
+ const identities = new Set();
140
+ try {
141
+ const identity = await rootHandle.stat({ bigint: true });
142
+ if (!identity.isDirectory() || identity.dev !== pathIdentity.dev || identity.ino !== pathIdentity.ino) failProvider('invalid-config');
143
+ for (const [provider, definition] of Object.entries(DEFINITIONS)) {
144
+ const result = await loadPacket(rootHandle, identity, root, definition, provider);
145
+ if (identities.has(result.identity)) failProvider('invalid-config', { provider });
146
+ identities.add(result.identity);
147
+ packets[provider] = result.packet;
148
+ }
149
+ } finally { await rootHandle.close().catch(() => {}); }
150
+ return packets;
151
+ }
152
+
153
+ export async function createFixtureAdapters(input) {
154
+ const config = captureRecord(input, new Set(['root', 'source']), [], 'invalid-config');
155
+ if ((config.root === undefined) === (config.source === undefined)) failProvider('invalid-config');
156
+ let packets;
157
+ if (config.source !== undefined) packets = await loadPreopenedPackets(config.source);
158
+ else {
159
+ const rootInput = boundedString(config.root, 2048, undefined, 'invalid-config');
160
+ if (!isAbsolute(rootInput) || resolve(rootInput) !== rootInput) failProvider('invalid-config');
161
+ packets = await loadRootPackets(rootInput);
162
+ }
163
+ const output = {};
164
+ for (const [provider, definition] of Object.entries(DEFINITIONS)) {
165
+ const packet = packets[provider];
166
+ const read = async inputRead => {
167
+ const request = captureRecord(inputRead, new Set(['kind']), ['kind']);
168
+ const kind = boundedString(request.kind, 32, /^[a-z][a-z0-9-]*$/);
169
+ if (!definition.read.includes(kind)) failProvider('invalid-request', { provider });
170
+ return createSourceEnvelope({
171
+ provider, sourceId: packet.sourceId, sourceUrl: packet.sourceUrl, fetchedAt: packet.fetchedAt,
172
+ fixtureSource: true, raw: packet.raw,
173
+ normalized: { ...definition.normalize(packet.raw), fixture: Object.freeze({ sourced: true, packet: definition.file }) },
174
+ retryClassification: 'none', capabilities: { read: definition.read, write: [] },
175
+ });
176
+ };
177
+ output[provider] = createAdapter({
178
+ provider, fixtureSource: true, capabilities: { read: definition.read, write: [] }, read,
179
+ write: readOnlyWrite(provider),
180
+ });
181
+ }
182
+ return Object.freeze(output);
183
+ }