@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,65 @@
1
+ import { CliError, EXIT_CODES } from '../cli/output.js';
2
+ import { resolveConfiguredProject } from '../cli/project-discovery.js';
3
+ import { prepareProviderAppend } from './init.js';
4
+ import { createIntegrationRegistry } from '../integrations/registry.js';
5
+
6
+ const KINDS=['jira','linear','figma','confluence'];
7
+ const CAPS={jira:'issues-read',linear:'issues-read',figma:'files-read',confluence:'pages-read'};
8
+ class Cancelled extends Error {}
9
+ function fail(message,code='INVALID_INPUT'){throw new CliError(message,code);}
10
+ function text(value){if(typeof value!=='string'||!value.length||value.length>2048||/[\u0000-\u001f\u007f]/.test(value))fail('Enter a bounded configuration value. Secret values must never be entered.');return value.trim();}
11
+ function envName(value){const name=text(value);if(!/^[A-Z][A-Z0-9_]{1,127}$/.test(name))fail('Enter an environment variable NAME such as JIRA_API_TOKEN, never a credential value.');return name;}
12
+ function csv(value,pattern){const values=text(value).split(',').map(item=>item.trim());if(values.length>64||values.some(item=>!pattern.test(item))||new Set(values).size!==values.length)fail('Enter unique comma-separated values in the requested format.');return values;}
13
+ function endpoint(kind,value){
14
+ const result=text(value);let url;try{url=new URL(result);}catch{fail('Enter the HTTPS source origin only.');}
15
+ const host=kind==='linear'?url.hostname==='linear.app':kind==='figma'?['figma.com','www.figma.com'].includes(url.hostname):/^[a-z0-9][a-z0-9-]*\.atlassian\.net$/.test(url.hostname);
16
+ if(!host||url.protocol!=='https:'||url.username||url.password||url.port||url.pathname!=='/'||url.search||url.hash)fail('Use the supported HTTPS source origin with no credentials, path or query.');return url.origin;
17
+ }
18
+ export async function integrationSetup(parsed,dependencies){
19
+ if(parsed.operands.length||Object.keys(parsed.flags).some(key=>key!=='project'))fail('Use rivet integrations setup [--project=<path>] in an interactive terminal.');
20
+ if(dependencies.terminalIsInteractive?.()!==true||typeof dependencies.integrationSetupPrompt!=='function')fail('Run rivet integrations setup in an interactive terminal to preview and approve local settings.');
21
+ const project=await resolveConfiguredProject(dependencies.cwd(),parsed.flags.project,{env:dependencies.env,runner:dependencies.runGit});
22
+ let captured;try{captured=await prepareProviderAppend(project.root,{fs:dependencies.fs});}catch{fail('Existing project configuration is missing, invalid or unsafe. Run rivet setup first; existing files were preserved.','REPOSITORY_CONFLICT');}
23
+ const ask=async question=>{let answer;try{answer=await dependencies.integrationSetupPrompt(question);}catch{throw new Cancelled();}if(answer===null||answer===undefined)throw new Cancelled();return answer;};
24
+ const select=async(id,message,choices)=>{const answer=await ask({id,type:'select',message,choices});if(!choices.some(choice=>choice.value===answer))fail('Choose one of the displayed options.');return answer;};
25
+ const input=(id,message,defaultValue)=>ask({id,type:'input',message,...(defaultValue?{defaultValue}:{})});
26
+ const existing=captured.config.providers.providers,projectId=captured.config.project.id;
27
+ dependencies.output.log(`Project: ${projectId}\nExisting integrations preserved: ${existing.map(p=>p.id).join(', ')}`);
28
+ dependencies.output.log('This guide appends read-only local settings. It never requests credential values, authenticates tools or calls providers. Figma and Confluence are harness-MCP context sources in this workflow.');
29
+ try{
30
+ const kinds=await ask({id:'providers',type:'multi',message:'Which integrations would you like to add?',choices:KINDS.map(value=>({value,label:value==='confluence'?'Confluence context':value==='figma'?'Figma context':value==='jira'?'Jira tracker':'Linear tracker'}))});
31
+ if(!Array.isArray(kinds)||kinds.length>4||new Set(kinds).size!==kinds.length||kinds.some(kind=>!KINDS.includes(kind)))fail('Select the displayed integrations.');
32
+ if(!kinds.length)throw new Cancelled();
33
+ const added=[],ids=new Set(existing.map(p=>p.id));
34
+ for(const kind of kinds){
35
+ const transport=await select(kind+'.transport',`How will ${kind} be read?`,[
36
+ {value:'harness-mcp',label:'Connected harness tools (MCP)'},...(['jira','linear'].includes(kind)?[{value:'direct-api',label:'Direct API using environment references'}]:[])]);
37
+ if(transport==='direct-api'&&existing.some(p=>p.kind===kind&&(p.transport??'direct-api')==='direct-api'&&p.mode!=='disabled'&&p.capabilities.includes('issues-read')&&(!p.projectIds?.length||p.projectIds.includes(projectId))))fail(`An enabled direct ${kind} issue reader already matches this project. Adding another would make ticket intake ambiguous. Review the existing descriptor; the guide will not replace it.`,'REPOSITORY_CONFLICT');
38
+ const prefix=kind+(transport==='direct-api'?'-direct':'-mcp');let id=prefix,index=2;while(ids.has(id))id=prefix+'-'+index++;ids.add(id);
39
+ const descriptor={id,kind,mode:'read-only',transport,capabilities:[CAPS[kind]],projectIds:[projectId]};
40
+ if(kind==='linear'&&transport==='direct-api')descriptor.endpoint='https://api.linear.app';
41
+ else descriptor.endpoint=endpoint(kind,await input(kind+'.endpoint',`${kind} HTTPS source origin (no path, query or credentials)`,kind==='linear'?'https://linear.app':kind==='figma'?'https://www.figma.com':undefined));
42
+ const scope=await select(kind+'.scope',`Resource access for ${id} within project ${projectId}`,[{value:'selected',label:'Specific resource IDs'},{value:'all',label:'All provider-accessible resources for this project'}]);
43
+ descriptor.resourceIds=scope==='all'?[]:csv(await input(kind+'.resources',kind==='jira'||kind==='linear'?'Issue keys, comma-separated (for example DEMO-1, DEMO-2)':kind==='confluence'?'Confluence page IDs, comma-separated':'Figma file keys, comma-separated'),['jira','linear'].includes(kind)?/^[A-Z][A-Z0-9]{0,31}-[1-9][0-9]{0,15}$/:kind==='confluence'?/^[1-9][0-9]{0,19}$/:/^[A-Za-z0-9_-]{1,200}$/);
44
+ if(transport==='harness-mcp')descriptor.tools=csv(await input(kind+'.tools','Exact read tool names exposed by your harness, comma-separated; tool availability is not inferred'),/^[A-Za-z0-9_.:-]{1,128}$/);
45
+ else descriptor.credentials=kind==='jira'?{
46
+ usernameEnv:envName(await input(kind+'.usernameEnv','Environment variable NAME for your Jira email (never the email value)','JIRA_USERNAME')),
47
+ apiTokenEnv:envName(await input(kind+'.apiTokenEnv','Environment variable NAME for the Jira API token (never the token value)','JIRA_API_TOKEN')),
48
+ }:{apiTokenEnv:envName(await input(kind+'.apiTokenEnv','Environment variable NAME for the Linear API key (never the key value)','LINEAR_API_KEY'))};
49
+ added.push(descriptor);
50
+ }
51
+ let proposal;try{
52
+ const config={...captured.config,providers:{...captured.config.providers,providers:[...existing,...added]}};
53
+ createIntegrationRegistry({config,projectId});proposal=captured.propose(added);
54
+ }catch{fail('The proposed provider settings failed configuration validation. No files were written.');}
55
+ dependencies.output.log('Exact providers.yaml preview (existing descriptor values are preserved):\n'+proposal.providersYaml);
56
+ for(const provider of added)dependencies.output.log(`${provider.id}: project ${projectId}; ${provider.resourceIds.length?'only resource IDs: '+provider.resourceIds.join(', '):'all provider-accessible resources in this project; no resource ID filter'}. ${provider.transport==='harness-mcp'?'Tool names configured; current authenticated host inventory is still required.':'Credential references configured; environment values were not inspected.'}`);
57
+ dependencies.output.log('No network access or authentication was checked. Only .rivet/providers.yaml content changes; the other configuration files retain their exact bytes.');
58
+ const confirmed=await ask({id:'confirm',type:'confirm',message:'Write these local provider settings?'});
59
+ if(confirmed!==true)throw new Cancelled();
60
+ let cleanup;try{cleanup=await proposal.commit();}catch(error){throw new CliError('Provider settings were not committed safely. Inspect concurrent edits, initialization locks or transaction recovery; do not force an overwrite.','REPOSITORY_CONFLICT',{cause:error});}
61
+ dependencies.output.log('Local provider settings written. Review and commit the configuration, then run rivet integrations check. Connected tool inventory and credentials remain separate.');
62
+ if(cleanup.residueCount)dependencies.output.log(cleanup.warning??cleanup.remediation);
63
+ return EXIT_CODES.SUCCESS;
64
+ }catch(error){if(error instanceof Cancelled){dependencies.output.log('Integration setup cancelled. No configuration changes were written.');return EXIT_CODES.SUCCESS;}throw error;}
65
+ }
@@ -0,0 +1,54 @@
1
+ import { resolve } from 'node:path';
2
+ import { loadProjectConfig } from '../config/load.js';
3
+ import { createIntegrationRegistry } from '../integrations/registry.js';
4
+ import { CliError, EXIT_CODES } from '../cli/output.js';
5
+
6
+ function readHostInventory(flags, dependencies) {
7
+ const inline = flags['host-inventory-json'];
8
+ if (inline === undefined) return dependencies.integrationHost;
9
+ if (dependencies.integrationHost !== undefined) {
10
+ throw new CliError('Supply host inventory once, through the CLI or host adapter.', 'INVALID_INPUT');
11
+ }
12
+ if (typeof inline !== 'string' || Buffer.byteLength(inline, 'utf8') > 65536) {
13
+ throw new CliError('Host inventory must be JSON of at most 64 KiB.', 'INVALID_INPUT');
14
+ }
15
+ try {
16
+ return JSON.parse(inline);
17
+ } catch {
18
+ throw new CliError('Host inventory must be valid JSON.', 'INVALID_INPUT');
19
+ }
20
+ }
21
+
22
+ export async function integrationsCommand(parsed, dependencies) {
23
+ const { subcommand, operands, flags } = parsed;
24
+ if (subcommand === 'setup') return (await import('./integration-setup.js')).integrationSetup(parsed, dependencies);
25
+ if (!['list', 'check'].includes(subcommand) || operands.length
26
+ || Object.keys(flags).some(key => !['project', 'json', 'host-inventory-json'].includes(key))) {
27
+ throw new CliError('Use rivet integrations list or check [--project=<path>] [--host-inventory-json=<json>] [--json].', 'INVALID_INPUT');
28
+ }
29
+ const host = readHostInventory(flags, dependencies);
30
+ const projectRoot = resolve(flags.project ?? dependencies.cwd?.() ?? process.cwd());
31
+ let config;
32
+ try {
33
+ config = await (dependencies.configLoader ?? loadProjectConfig)(projectRoot);
34
+ } catch {
35
+ throw new CliError('Project configuration is missing or invalid.', 'MISSING_CONFIGURATION');
36
+ }
37
+ let result;
38
+ try {
39
+ const registry = createIntegrationRegistry({
40
+ config,
41
+ projectId: config.project.id,
42
+ environment: dependencies.env ?? process.env,
43
+ host,
44
+ });
45
+ result = { integrations: registry[subcommand](), networkChecked: false };
46
+ } catch {
47
+ throw new CliError('Integration configuration or supplied host inventory is invalid.', 'INVALID_INPUT');
48
+ }
49
+ if (flags.json) dependencies.output.json({ ok: true, result });
50
+ else dependencies.output.log(result.integrations.map(item =>
51
+ `${item.id} (${item.transport}): ${item.readiness}. ${item.remedy}`).join('\n'));
52
+ // Diagnostic completion is success even when an optional integration is unavailable.
53
+ return EXIT_CODES.SUCCESS;
54
+ }
@@ -0,0 +1,158 @@
1
+ import { resolveRoleProfile } from '../models/profiles.js';
2
+ import { humanRunCommand } from './human-run.js';
3
+ import { resolve } from 'node:path';
4
+ import { isDeepStrictEqual } from 'node:util';
5
+ import { delegateText, ModelDelegationError } from '../models/delegate.js';
6
+ import { buildModelRequest } from '../models/protocols.js';
7
+ import { containsSecretMaterial } from '../clients/contract.js';
8
+ import { open } from 'node:fs/promises';
9
+ import { constants } from 'node:fs';
10
+ import { createModelRegistry } from '../models/registry.js';
11
+ import { CliError, EXIT_CODES } from '../cli/output.js';
12
+
13
+ async function readProfile(path) {
14
+ let handle;
15
+ try {
16
+ handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
17
+ const metadata = await handle.stat();
18
+ if (!metadata.isFile() || metadata.size > 64 * 1024) throw new Error();
19
+ const buffer = Buffer.alloc(64 * 1024 + 1);
20
+ let offset = 0;
21
+ while (offset < buffer.length) {
22
+ const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, null);
23
+ if (bytesRead === 0) break;
24
+ offset += bytesRead;
25
+ }
26
+ if (offset > 64 * 1024) throw new Error();
27
+ return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(buffer.subarray(0, offset)));
28
+ } catch { throw new CliError('Model profile must be a valid JSON file of at most 64 KiB.', 'INVALID_INPUT'); }
29
+ finally { await handle?.close(); }
30
+ }
31
+
32
+ export async function modelsCommand(parsed, dependencies) {
33
+ const { subcommand, operands, flags } = parsed;
34
+ if (subcommand === 'role') {
35
+ if (operands.length || typeof flags.roles !== 'string' || typeof flags.role !== 'string'
36
+ || Object.keys(flags).some(key => !['roles', 'role', 'json'].includes(key))) throw new CliError('Use rivet models role --roles=<file> --role=<name> [--json].', 'INVALID_INPUT');
37
+ const result = await readRole(flags, dependencies);
38
+ if (flags.json) dependencies.output.json({ ok: true, result });
39
+ else dependencies.output.log(`Role ${result.role}: ${result.target.kind}. This inspection does not execute work.`);
40
+ return EXIT_CODES.SUCCESS;
41
+ }
42
+ if (subcommand === 'delegate') return delegateCommand(parsed, dependencies);
43
+ if (operands.length || !['list', 'check'].includes(subcommand)
44
+ || Object.keys(flags).some(key => !['json', 'profile'].includes(key))
45
+ || (subcommand === 'list' && flags.profile !== undefined)
46
+ || (subcommand === 'check' && typeof flags.profile !== 'string')) {
47
+ throw new CliError('Use rivet models list or rivet models check --profile=<file>.', 'INVALID_INPUT');
48
+ }
49
+ const registry = createModelRegistry();
50
+ let result;
51
+ if (subcommand === 'list') result = { providers: registry.list() };
52
+ else {
53
+ const input = await readProfile(resolve(dependencies.cwd(), flags.profile));
54
+ try { result = registry.resolve(input); }
55
+ catch { throw new CliError('Invalid model profile. Check the provider, model, limits, endpoint and credential reference.', 'INVALID_INPUT'); }
56
+ }
57
+ if (flags.json) dependencies.output.json({ ok: true, result });
58
+ else if (subcommand === 'list') {
59
+ dependencies.output.log(result.providers.map(provider => `${provider.id}: ${provider.label} (${provider.kind}; ${provider.execution}; not live verified)`).join('\n'));
60
+ } else {
61
+ dependencies.output.log(`Profile valid: ${result.profile.provider} / ${result.profile.model}. Execution is not ready: ${result.reasons.join(', ')}. No model was called.`);
62
+ }
63
+ return EXIT_CODES.SUCCESS;
64
+ }
65
+
66
+ const UNSAFE_DISPLAY = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u;
67
+ async function delegateCommand({ operands, flags }, dependencies) {
68
+ const invalid = message => { throw new CliError(message, 'INVALID_INPUT'); };
69
+ const roleMode = typeof flags.roles === 'string' && typeof flags.role === 'string' && flags.profile === undefined;
70
+ const profileMode = typeof flags.profile === 'string' && flags.roles === undefined && flags.role === undefined;
71
+ if (operands.length !== 1 || (!roleMode && !profileMode)
72
+ || Object.keys(flags).some(key => !(roleMode ? ['roles', 'role', 'project'] : ['profile']).includes(key))
73
+ || !dependencies.terminalIsInteractive()) {
74
+ invalid('Use rivet models delegate "prompt" with --profile=<file> or --roles=<file> --role=<name> in an interactive terminal.');
75
+ }
76
+ const prompt = operands[0];
77
+ if (typeof prompt !== 'string' || !prompt.trim() || Buffer.byteLength(prompt) > 64 * 1024
78
+ || UNSAFE_DISPLAY.test(prompt) || containsSecretMaterial(prompt)) invalid('Use a prompt of at most 64 KiB without secrets or terminal control characters.');
79
+ const selection = roleMode ? await readRole(flags, dependencies) : undefined;
80
+ if (roleMode) dependencies.output.log(`Role ${selection.role}: ${selection.target.kind}`);
81
+ if (selection?.target.kind === 'active-harness') {
82
+ if (flags.project !== undefined) invalid('--project applies only to a delegated harness task.');
83
+ dependencies.output.log('Continue this request in your active harness through the Rivet workflow. No model or process was started; the task has not been performed.');
84
+ return EXIT_CODES.SUCCESS;
85
+ }
86
+ if (selection?.target.kind === 'harness') {
87
+ const unchanged = async () => {
88
+ if (!isDeepStrictEqual(selection, await readRole(flags, dependencies))) invalid('Role selection changed. Run the command again.');
89
+ };
90
+ await unchanged();
91
+ return humanRunCommand({ command: 'run', subcommand: null, operands, flags: {
92
+ harness: selection.target.harness, ...(flags.project === undefined ? {} : { project: flags.project }),
93
+ } }, { ...dependencies, confirmFeatureActivation: async (proposal, options) => {
94
+ await unchanged();
95
+ const approved = await dependencies.confirmFeatureActivation(proposal, options);
96
+ await unchanged();
97
+ return approved;
98
+ } });
99
+ }
100
+ if (flags.project !== undefined) invalid('--project applies only to a delegated harness task.');
101
+ const path = profileMode ? resolve(dependencies.cwd(), flags.profile) : undefined;
102
+ const resolveProfile = async () => {
103
+ if (roleMode) {
104
+ const current = await readRole(flags, dependencies);
105
+ if (!isDeepStrictEqual(selection, current)) invalid('Role selection changed. Run the command again.');
106
+ return current.target.profile;
107
+ }
108
+ const input = await readProfile(path);
109
+ try { return createModelRegistry().resolve(input, { requiredCapabilities: ['text'] }).profile; }
110
+ catch { invalid('Invalid model profile.'); }
111
+ };
112
+ const profile = await resolveProfile();
113
+ if (profile.maxCostUsd !== undefined) invalid('Monetary caps cannot be enforced for direct text delegation.');
114
+ if (profile.timeoutMs > 120000) invalid('Direct text delegation requires timeoutMs at most 120000.');
115
+ let url;
116
+ try { url = buildModelRequest(profile, prompt, 'preview-only').url; }
117
+ catch { invalid('This profile does not support direct text delegation. Check its provider and endpoint.'); }
118
+ if (UNSAFE_DISPLAY.test(profile.model)) invalid('Model names must not contain terminal control characters.');
119
+ const controller = new AbortController();
120
+ const abort = () => controller.abort();
121
+ const external = dependencies.models?.signal;
122
+ if (external?.aborted) controller.abort();
123
+ external?.addEventListener('abort', abort, { once: true });
124
+ process.on('SIGINT', abort); process.on('SIGTERM', abort);
125
+ let timer;
126
+ try {
127
+ if (controller.signal.aborted) invalid('Text delegation cancelled.');
128
+ const preview = Object.freeze({ profile, prompt, url });
129
+ dependencies.output.log(`Text delegation: ${profile.provider} / ${profile.model}\nDestination: ${url}\nLimits: ${profile.timeoutMs} ms, ${profile.maxOutputTokens} output tokens\nExact prompt:\n${prompt}`);
130
+ const cancelled = new Promise(resolvePromise => {
131
+ controller.signal.addEventListener('abort', () => resolvePromise(false), { once: true });
132
+ timer = setTimeout(() => { controller.abort(); resolvePromise(false); }, 30000);
133
+ });
134
+ const approved = await Promise.race([dependencies.confirmModelDelegation(preview, { signal: controller.signal }), cancelled]);
135
+ clearTimeout(timer);
136
+ if (approved !== true || controller.signal.aborted) invalid('Text delegation cancelled.');
137
+ if (!isDeepStrictEqual(profile, await resolveProfile())) invalid('Model profile changed after approval. Run the command again.');
138
+ if (controller.signal.aborted) invalid('Text delegation cancelled.');
139
+ const result = await (dependencies.models?.delegateText ?? delegateText)({ profile, prompt,
140
+ environment: dependencies.env, signal: controller.signal });
141
+ const text = result.text.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/gu, '');
142
+ const model = result.model.replace(/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/gu, '');
143
+ dependencies.output.log(`Unverified advisory output (${model}; input ${result.usage.inputTokens}, output ${result.usage.outputTokens} tokens):\n${text}`);
144
+ return EXIT_CODES.SUCCESS;
145
+ } catch (error) {
146
+ if (error instanceof ModelDelegationError) throw new CliError(error.safeMessage, 'INVALID_INPUT');
147
+ throw error;
148
+ } finally {
149
+ clearTimeout(timer); external?.removeEventListener('abort', abort);
150
+ process.removeListener('SIGINT', abort); process.removeListener('SIGTERM', abort);
151
+ }
152
+ }
153
+
154
+ async function readRole(flags, dependencies) {
155
+ const input = await readProfile(resolve(dependencies.cwd(), flags.roles));
156
+ try { return resolveRoleProfile(input, flags.role); }
157
+ catch { throw new CliError('Invalid model role configuration or role name.', 'INVALID_INPUT'); }
158
+ }
@@ -0,0 +1,61 @@
1
+ import { watchOrchestration } from '../runtime/supervisor.js';
2
+ import { CliError, EXIT_CODES } from '../cli/output.js';
3
+
4
+ export async function orchestrateRun(runtime, instance, options) { return runtime.tick(instance, options); }
5
+ export async function orchestrateWatch(runtime, instance, options) { return watchOrchestration(runtime, instance, options); }
6
+
7
+ function cliFailure(message, code = 'INVALID_INPUT') { throw new CliError(message, code); }
8
+ function cliInteger(value, name, { minimum = 0, maximum = Number.MAX_SAFE_INTEGER } = {}) {
9
+ const parsed = typeof value === 'string' && /^(?:0|[1-9][0-9]*)$/.test(value) ? Number(value) : value;
10
+ if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) cliFailure(`Orchestrate option '${name}' is invalid.`);
11
+ return parsed;
12
+ }
13
+ function cliInput(parsed, dependencies) {
14
+ if (!parsed || parsed.command !== 'orchestrate' || !['run', 'watch'].includes(parsed.subcommand)
15
+ || !Array.isArray(parsed.operands) || parsed.operands.length !== 1
16
+ || typeof parsed.operands[0] !== 'string' || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(parsed.operands[0])
17
+ || parsed.operands[0].length > 64) cliFailure('Orchestrate commands require one explicit private instance ID.');
18
+ const allowed = parsed.subcommand === 'run'
19
+ ? ['json', 'expected-version', 'max-active-nodes']
20
+ : ['json', 'expected-version', 'interval-ms', 'max-ticks', 'deadline-ms'];
21
+ if (!parsed.flags || Object.keys(parsed.flags).some(key => !allowed.includes(key))) cliFailure('Orchestrate command options are invalid.');
22
+ const context = dependencies?.orchestration;
23
+ if (!context || typeof context.resolveInstance !== 'function' || !context.runtime || typeof context.runtime.tick !== 'function') cliFailure('Private orchestration is not configured.', 'MISSING_CONFIGURATION');
24
+ return { context, instanceId: parsed.operands[0] };
25
+ }
26
+ function emitCliResult(parsed, dependencies, result) {
27
+ const payload = { ok: true, command: 'orchestrate', subcommand: parsed.subcommand, result };
28
+ if (parsed.flags.json) dependencies.output.json(payload);
29
+ else dependencies.output.log(`Orchestration ${parsed.subcommand}: ${result.terminal ?? 'running'}.`);
30
+ return EXIT_CODES.SUCCESS;
31
+ }
32
+
33
+ export async function orchestrateCommand(parsed, dependencies) {
34
+ const { context, instanceId } = cliInput(parsed, dependencies);
35
+ const expectedVersion = cliInteger(parsed.flags['expected-version'], 'expected-version');
36
+ let options;
37
+ if (parsed.subcommand === 'run') {
38
+ options = { expectedVersion };
39
+ if (parsed.flags['max-active-nodes'] !== undefined) options.maxActiveNodes = cliInteger(parsed.flags['max-active-nodes'], 'max-active-nodes', { minimum: 1, maximum: 64 });
40
+ } else {
41
+ if (typeof context.now !== 'function' || typeof context.wait !== 'function') cliFailure('Orchestration watch dependencies are not configured.', 'MISSING_CONFIGURATION');
42
+ options = {
43
+ expectedVersion,
44
+ intervalMs: cliInteger(parsed.flags['interval-ms'], 'interval-ms', { minimum: 1, maximum: 60_000 }),
45
+ maxTicks: cliInteger(parsed.flags['max-ticks'], 'max-ticks', { minimum: 1, maximum: 10_000 }),
46
+ deadlineMs: cliInteger(parsed.flags['deadline-ms'], 'deadline-ms'),
47
+ now: context.now,
48
+ wait: context.wait,
49
+ ...(context.signal === undefined ? {} : { signal: context.signal }),
50
+ };
51
+ }
52
+ const instance = await context.resolveInstance(instanceId);
53
+ if (!instance || instance.id !== instanceId) cliFailure('Private orchestration instance is unavailable.', 'MISSING_CONFIGURATION');
54
+ let result;
55
+ if (parsed.subcommand === 'run') {
56
+ result = await orchestrateRun(context.runtime, instance, options);
57
+ } else {
58
+ result = await orchestrateWatch(context.runtime, instance, options);
59
+ }
60
+ return emitCliResult(parsed, dependencies, result);
61
+ }
@@ -0,0 +1,125 @@
1
+ import { join, resolve } from 'node:path';
2
+
3
+ import { EXIT_CODES } from '../cli/output.js';
4
+ import { loadProjectConfig } from '../config/load.js';
5
+ import { discoverGit } from '../discovery/git.js';
6
+ import { discoverTools } from '../discovery/tools.js';
7
+ import { diagnoseDoctor } from './doctor.js';
8
+
9
+ function check(id, passed, remediation, details = {}) {
10
+ return { id, status: passed ? 'pass' : 'fail', ...details, ...(passed ? {} : { remediation }) };
11
+ }
12
+
13
+ function emit(output, json, payload, exitCode) {
14
+ if (json) {
15
+ output.json(payload, exitCode === EXIT_CODES.SUCCESS ? 'stdout' : 'stderr');
16
+ } else if (exitCode === EXIT_CODES.SUCCESS) {
17
+ output.log(`Preflight: ${payload.status}.${payload.dependencyPreparation==='required'?' Dependencies require separate approval and installation in the isolated task checkout before checks can run.':''}`);
18
+ } else {
19
+ output.error(`Preflight: ${payload.status}. ${payload.remediations.join(' ')}`);
20
+ }
21
+ return exitCode;
22
+ }
23
+
24
+ function defaultGoalState() {
25
+ return { status: 'not_initialized' };
26
+ }
27
+
28
+ export async function preflight(parsed, dependencies = {}) {
29
+ const json = parsed.flags.json === true;
30
+ const mode = parsed.flags.mode ?? 'orchestration';
31
+ if (!['host', 'orchestration'].includes(mode)) {
32
+ return emit(dependencies.output, json, {
33
+ ok: false, status: 'fail', checks: [],
34
+ remediations: ['Use --mode=host or --mode=orchestration.'],
35
+ error: { code: 'INVALID_INPUT', exitCode: EXIT_CODES.INVALID_INPUT, message: 'Preflight mode is invalid.' },
36
+ }, EXIT_CODES.INVALID_INPUT);
37
+ }
38
+ const projectRoot = resolve(parsed.flags.project ?? dependencies.cwd?.() ?? process.cwd());
39
+ let config;
40
+ try {
41
+ config = await (dependencies.configLoader ?? loadProjectConfig)(projectRoot, { fs: dependencies.fs });
42
+ } catch {
43
+ return emit(dependencies.output, json, {
44
+ ok: false,
45
+ status: 'fail',
46
+ checks: [],
47
+ remediations: ['Create or repair the project configuration before preflight.'],
48
+ error: {
49
+ code: 'MISSING_CONFIGURATION',
50
+ exitCode: EXIT_CODES.MISSING_CONFIGURATION,
51
+ message: 'Project configuration is missing or invalid.',
52
+ },
53
+ }, EXIT_CODES.MISSING_CONFIGURATION);
54
+ }
55
+ try {
56
+ const packageManager = config.project.stack.packageManager;
57
+ const direct = config.project.schemaVersion === 3;
58
+ const doctor = await diagnoseDoctor(projectRoot, {
59
+ ...dependencies,
60
+ ...(mode === 'host' ? { hostReadiness: true } : {}),
61
+ });
62
+ const [git, tools, goalState] = await Promise.all([
63
+ (dependencies.gitDiscovery ?? discoverGit)(projectRoot, {
64
+ runner: dependencies.runner,
65
+ ...(mode === 'host' ? { defaultBranch: config.project.repository.defaultBranch } : {}),
66
+ candidatePaths: dependencies.candidatePaths ?? [join(projectRoot, '.worktrees', 'next')],
67
+ }),
68
+ (dependencies.toolDiscovery ?? discoverTools)(direct ? { runtimeOnly: true } : { packageManager }, {
69
+ cwd: projectRoot,
70
+ runner: dependencies.runner,
71
+ }),
72
+ mode === 'host' ? Promise.resolve(null) : (dependencies.goalStateReader ?? defaultGoalState)(projectRoot),
73
+ ]);
74
+ const capacity = dependencies.runtimeCapacity ?? { available: 1, required: 1 };
75
+ const qualityCommands = doctor.checks?.commands ?? { ready: false, steps: [] };
76
+ const checks = [
77
+ check('doctor', doctor.exitCode === EXIT_CODES.SUCCESS, 'Run doctor and resolve failed readiness checks.'),
78
+ check('repository', git.repository === true, 'Run preflight inside a Git repository.'),
79
+ check('worktree-clean', git.dirty === false, 'Commit or stash worktree changes.'),
80
+ check('head-attached', git.detached === false, 'Switch to a local branch.'),
81
+ ...(mode === 'host' ? [check('configured-branch', git.currentBranch === config.project.repository.defaultBranch
82
+ && git.defaultBranch === config.project.repository.defaultBranch,
83
+ 'Review repository.defaultBranch in .rivet/project.yaml and switch to that local branch before starting a task.')] : []),
84
+ check('base-freshness', git.baseFreshness === 'fresh' || git.baseFreshness === 'ahead', 'Update the local default branch from its existing remote-tracking ref.'),
85
+ check('worktree-discovery', git.worktreeCheck?.checked === true, 'Resolve local Git worktree discovery before retrying.'),
86
+ ...(mode === 'host' ? [] : [check('worktree-paths', (git.occupiedCandidatePaths ?? []).length === 0, 'Select an unoccupied worktree path.', {
87
+ occupiedCount: (git.occupiedCandidatePaths ?? []).length,
88
+ })]),
89
+ ...(mode === 'host' ? [] : [check('runtime-capacity', Number(capacity.available) >= Number(capacity.required), 'Increase available runtime capacity.', {
90
+ available: Number(capacity.available), required: Number(capacity.required),
91
+ })]),
92
+ ...(mode === 'host' ? [] : [check('goal-state', goalState?.status === 'ready', 'Initialize and approve the private goal instance.')]),
93
+ check('toolchain', tools.node?.supported === true && (direct || tools[packageManager]?.supported === true) && tools.git?.supported === true,
94
+ direct ? 'Install a supported local Node runtime and Git.' : 'Install a supported local Node, package manager, and Git toolchain.'),
95
+ check('quality-commands', qualityCommands.ready || (direct && qualityCommands.preparationReady === true), direct
96
+ ? 'Resolve the configured command executables on PATH and verify their project working directories. Run rivet doctor for the failing command.'
97
+ : 'Define every required quality command as an effective bounded package script.', {
98
+ commands: qualityCommands.steps,
99
+ }),
100
+ ];
101
+ const failed = checks.filter(item => item.status === 'fail');
102
+ const payload = {
103
+ ok: failed.length === 0,
104
+ status: failed.length === 0 ? 'pass' : 'fail',
105
+ ...(mode === 'host' ? { mode } : {}),
106
+ ...(doctor.status === 'preparation-required' ? { dependencyPreparation: 'required' } : {}),
107
+ checks,
108
+ remediations: [...new Set(failed.map(item => item.remediation))],
109
+ };
110
+ return emit(dependencies.output, json, payload,
111
+ failed.length === 0 ? EXIT_CODES.SUCCESS : EXIT_CODES.FAILED_GATE);
112
+ } catch {
113
+ return emit(dependencies.output, json, {
114
+ ok: false,
115
+ status: 'fail',
116
+ checks: [],
117
+ remediations: ['Retry after resolving the local discovery failure.'],
118
+ error: {
119
+ code: 'INTERNAL_ERROR',
120
+ exitCode: EXIT_CODES.INTERNAL_ERROR,
121
+ message: 'Preflight could not complete safely.',
122
+ },
123
+ }, EXIT_CODES.INTERNAL_ERROR);
124
+ }
125
+ }