@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,174 @@
1
+ import { Buffer } from 'node:buffer';
2
+
3
+ /**
4
+ * Stable process exit codes. These numeric values are part of the CLI contract.
5
+ */
6
+ export const EXIT_CODES = Object.freeze({
7
+ SUCCESS: 0,
8
+ INVALID_INPUT: 1,
9
+ MISSING_CONFIGURATION: 2,
10
+ BLOCKED_AUTHORITY: 3,
11
+ FAILED_GATE: 4,
12
+ PROVIDER_UNAVAILABLE: 5,
13
+ REPOSITORY_CONFLICT: 6,
14
+ INTERNAL_ERROR: 7,
15
+ });
16
+
17
+ export const MAX_JSON_OUTPUT_BYTES = 64 * 1024;
18
+ const MIN_JSON_OUTPUT_BYTES = 128;
19
+ const outputErrorSubscriptions = new WeakMap();
20
+
21
+ export class CliError extends Error {
22
+ constructor(safeMessage, code = 'INTERNAL_ERROR', { cause } = {}) {
23
+ super(safeMessage, { cause });
24
+ this.name = 'CliError';
25
+ this.safeMessage = safeMessage;
26
+ this.code = Object.hasOwn(EXIT_CODES, code) ? code : 'INTERNAL_ERROR';
27
+ this.exitCode = EXIT_CODES[this.code];
28
+ }
29
+ }
30
+
31
+ /** Buffers machine output so the process boundary can commit exactly one object. */
32
+ export function createJsonOutputBoundary(maxBytes = MAX_JSON_OUTPUT_BYTES) {
33
+ const outputBudget = Number.isFinite(maxBytes)
34
+ ? Math.min(MAX_JSON_OUTPUT_BYTES, Math.max(MIN_JSON_OUTPUT_BYTES, maxBytes))
35
+ : MAX_JSON_OUTPUT_BYTES;
36
+ let bufferedWrite = null;
37
+ let attempted = false;
38
+ let violated = false;
39
+
40
+ const rejectWrite = () => {
41
+ violated = true;
42
+ throw new Error('Invalid output in JSON mode');
43
+ };
44
+ const output = Object.freeze({
45
+ log: rejectWrite,
46
+ warn: rejectWrite,
47
+ error: rejectWrite,
48
+ json(value, stream = 'stdout') {
49
+ if (attempted || (stream !== 'stdout' && stream !== 'stderr')) {
50
+ rejectWrite();
51
+ }
52
+ attempted = true;
53
+ let serializedValue;
54
+ try {
55
+ const serialized = JSON.stringify(value);
56
+ if (Buffer.byteLength(serialized, 'utf8') + 1 > outputBudget) rejectWrite();
57
+ serializedValue = JSON.parse(serialized);
58
+ } catch {
59
+ rejectWrite();
60
+ }
61
+ if (
62
+ serializedValue === null
63
+ || Array.isArray(serializedValue)
64
+ || typeof serializedValue !== 'object'
65
+ ) {
66
+ rejectWrite();
67
+ }
68
+ bufferedWrite = { value: serializedValue, stream };
69
+ },
70
+ });
71
+
72
+ return Object.freeze({
73
+ output,
74
+ commit(target, exitCode) {
75
+ const expectedStream = exitCode === EXIT_CODES.SUCCESS ? 'stdout' : 'stderr';
76
+ if (violated || !bufferedWrite || bufferedWrite.stream !== expectedStream) {
77
+ throw new Error('Invalid output in JSON mode');
78
+ }
79
+ target.json(bufferedWrite.value, bufferedWrite.stream);
80
+ },
81
+ });
82
+ }
83
+
84
+ export function createOutput({ stdout = process.stdout, stderr = process.stderr } = {}) {
85
+ const listeners = new Set();
86
+ let detachStreams = null;
87
+ const notifyOutputError = () => {
88
+ for (const listener of listeners) {
89
+ try { listener(); } catch { /* output failure observers cannot affect publication */ }
90
+ }
91
+ };
92
+ const attachStream = stream => {
93
+ if (!stream || typeof stream.on !== 'function' || typeof stream.off !== 'function') return () => {};
94
+ const onError = () => notifyOutputError();
95
+ stream.on('error', onError);
96
+ return () => {
97
+ try { stream.off('error', onError); } catch { /* stream cleanup is best effort */ }
98
+ };
99
+ };
100
+ const subscribe = listener => {
101
+ listeners.add(listener);
102
+ if (listeners.size === 1) {
103
+ let detachStdout = () => {};
104
+ let detachStderr = () => {};
105
+ try {
106
+ detachStdout = attachStream(stdout);
107
+ if (stderr !== stdout) detachStderr = attachStream(stderr);
108
+ } catch {
109
+ listeners.delete(listener);
110
+ detachStdout();
111
+ detachStderr();
112
+ return null;
113
+ }
114
+ detachStreams = () => { detachStdout(); detachStderr(); };
115
+ }
116
+ let active = true;
117
+ return () => {
118
+ if (!active) return;
119
+ active = false;
120
+ listeners.delete(listener);
121
+ if (listeners.size === 0 && detachStreams) {
122
+ detachStreams();
123
+ detachStreams = null;
124
+ }
125
+ };
126
+ };
127
+ const writeLine = (stream, value = '') => {
128
+ try { stream.write(`${String(value)}\n`); }
129
+ catch {
130
+ notifyOutputError();
131
+ throw new Error('Output publication failed.');
132
+ }
133
+ };
134
+
135
+ const output = Object.freeze({
136
+ log: value => writeLine(stdout, value),
137
+ warn: value => writeLine(stderr, value),
138
+ error: value => writeLine(stderr, value),
139
+ json(value, stream = 'stdout') {
140
+ writeLine(stream === 'stderr' ? stderr : stdout, JSON.stringify(value));
141
+ },
142
+ });
143
+ outputErrorSubscriptions.set(output, subscribe);
144
+ return output;
145
+ }
146
+
147
+ /** Subscribes only outputs created by createOutput; listener receives no raw stream error. */
148
+ export function observeOutputErrors(output, listener) {
149
+ if (typeof listener !== 'function') return null;
150
+ const subscribe = outputErrorSubscriptions.get(output);
151
+ if (!subscribe) return null;
152
+ try { return subscribe(listener); } catch { return null; }
153
+ }
154
+
155
+ /** JSON-mode errors are one object on stderr; human-readable errors use stderr text. */
156
+ export function emitCliError(output, error, { json = false } = {}) {
157
+ const cliError = error instanceof CliError
158
+ ? error
159
+ : new CliError('Unexpected rivet failure.', 'INTERNAL_ERROR');
160
+
161
+ if (json) {
162
+ output.json({
163
+ ok: false,
164
+ error: {
165
+ code: cliError.code,
166
+ exitCode: cliError.exitCode,
167
+ message: cliError.safeMessage,
168
+ },
169
+ }, 'stderr');
170
+ } else {
171
+ output.error(`ERROR: ${cliError.safeMessage}`);
172
+ }
173
+ return cliError.exitCode;
174
+ }
@@ -0,0 +1,246 @@
1
+ const COMMANDS = new Set([
2
+ 'doctor',
3
+ 'support',
4
+ 'delivery',
5
+ 'evidence',
6
+ 'feature',
7
+ 'goals',
8
+ 'init',
9
+ 'install',
10
+ 'models',
11
+ 'integrations',
12
+ 'repositories',
13
+ 'orchestrate',
14
+ 'preflight',
15
+ 'protocols',
16
+ 'run',
17
+ 'status',
18
+ 'setup',
19
+ 'uninstall',
20
+ 'verify',
21
+ 'work',
22
+ 'task',
23
+ 'worktrees',
24
+ ]);
25
+
26
+ const LEGACY_COMMANDS = new Set(['init', 'install', 'uninstall']);
27
+ const NESTED_COMMANDS = new Set(['delivery', 'feature', 'goals', 'integrations', 'repositories', 'models', 'orchestrate', 'protocols', 'task', 'work', 'worktrees']);
28
+ const FLAG_NAME = /^[a-z][a-z0-9-]*$/;
29
+ const LEGACY_OPTIONS = {
30
+ init: {
31
+ boolean: new Set(['json', 'overwrite', 'write']),
32
+ valued: new Set(['project', 'remote', 'checks-json', 'dependencies-json']),
33
+ },
34
+ install: {
35
+ boolean: new Set(['all', 'claude', 'codex', 'global', 'json', 'minimal', 'project-runtime']),
36
+ valued: new Set(['target', 'project']),
37
+ },
38
+ uninstall: {
39
+ boolean: new Set(['all', 'claude', 'codex', 'global', 'json', 'minimal', 'project-runtime']),
40
+ valued: new Set(['target', 'project']),
41
+ },
42
+ };
43
+ const STRICT_OPTIONS = {
44
+ delivery: { boolean: new Set(['json']), valued: new Set(['project', 'run', 'remote', 'provider', 'method', 'title', 'body']) },
45
+ repositories: { boolean: new Set(['json']), valued: new Set(['project', 'remote', 'provider', 'review']) },
46
+ integrations: { boolean: new Set(['json']), valued: new Set(['project', 'host-inventory-json']) },
47
+ protocols: {
48
+ boolean: new Set(['json', 'include-drafts', 'include-retired', 'publish']),
49
+ valued: new Set(['expected-revision', 'expected-digest', 'from', 'project']),
50
+ },
51
+ work: {
52
+ boolean: new Set(['json']),
53
+ valued: new Set([
54
+ 'action', 'action-json', 'decomposition', 'decomposition-json', 'result-json', 'expected-runtime-version', 'expected-version', 'project', 'result',
55
+ 'request', 'request-text', 'ticket', 'tracker', 'host-context-json', 'acceptance-criteria', 'run',
56
+ ]),
57
+ },
58
+ run: {
59
+ boolean: new Set(['details']),
60
+ valued: new Set(['project', 'harness']),
61
+ },
62
+ task: {
63
+ boolean: new Set(['details']),
64
+ valued: new Set(['project', 'run']),
65
+ },
66
+ setup: {
67
+ boolean: new Set(['global', 'write', 'json']),
68
+ valued: new Set(['project', 'target', 'remote', 'checks-json', 'dependencies-json']),
69
+ },
70
+ models: {
71
+ boolean: new Set(['json']),
72
+ valued: new Set(['profile', 'roles', 'role', 'project']),
73
+ },
74
+ support: {
75
+ boolean: new Set(['json', 'probe-harnesses', 'save']),
76
+ valued: new Set(['project']),
77
+ },
78
+ doctor: {
79
+ boolean: new Set(['json']),
80
+ valued: new Set(['project']),
81
+ },
82
+ feature: {
83
+ boolean: new Set(['json']),
84
+ valued: new Set([
85
+ 'client', 'expected-version', 'project', 'proposal-digest', 'request', 'request-text', 'ticket', 'tracker', 'acceptance-criteria', 'run',
86
+ ]),
87
+ },
88
+ preflight: {
89
+ boolean: new Set(['json']),
90
+ valued: new Set(['project', 'mode']),
91
+ },
92
+ status: {
93
+ boolean: new Set(['json']),
94
+ valued: new Set(['fixture', 'port']),
95
+ },
96
+ };
97
+ const NO_POSITIONAL_COMMANDS = new Set(['doctor', 'support', 'init', 'install', 'preflight', 'setup', 'uninstall']);
98
+
99
+ function targetSet(target) {
100
+ const normalized = typeof target === 'string' ? target.toLowerCase() : target;
101
+ if (normalized === 'both') return new Set(['claude', 'codex']);
102
+ if (normalized === 'claude' || normalized === 'codex') return new Set([normalized]);
103
+ return null;
104
+ }
105
+
106
+ function sameSet(left, right) {
107
+ return left.size === right.size && [...left].every(value => right.has(value));
108
+ }
109
+
110
+ function validateTargetSelectors(command, flags) {
111
+ if (!['install', 'uninstall', 'setup'].includes(command)) return;
112
+ const aliases = new Set();
113
+ if (flags.claude) aliases.add('claude');
114
+ if (flags.codex) aliases.add('codex');
115
+ const explicit = targetSet(flags.target);
116
+ if (flags.target !== undefined && !explicit) {
117
+ throw new ArgumentError("Invalid --target value. Use 'claude', 'codex', or 'both'.");
118
+ }
119
+ if (aliases.size > 0 && explicit && !sameSet(aliases, explicit)) {
120
+ throw new ArgumentError('Conflicting target selectors');
121
+ }
122
+ }
123
+
124
+ export class ArgumentError extends Error {
125
+ constructor(message) {
126
+ super(message);
127
+ this.name = 'ArgumentError';
128
+ }
129
+ }
130
+
131
+ export function parseArgs(argv) {
132
+ if (!Array.isArray(argv) || argv.some(argument => typeof argument !== 'string')) {
133
+ throw new TypeError('CLI arguments must be an array of strings');
134
+ }
135
+
136
+ const [command = null, ...tokens] = argv;
137
+ if (command === null) {
138
+ return { command: null, subcommand: null, operands: [], flags: {} };
139
+ }
140
+ if (!command || command.startsWith('-')) {
141
+ throw new ArgumentError('A command must be provided before flags');
142
+ }
143
+ if (!COMMANDS.has(command)) {
144
+ throw new ArgumentError(`Unknown command '${command}'`);
145
+ }
146
+
147
+ const flags = {};
148
+ const positionals = [];
149
+
150
+ for (let index = 0; index < tokens.length; index += 1) {
151
+ const token = tokens[index];
152
+ if (token.startsWith('--')) {
153
+ // Legacy parsing never supported `--`; reject it explicitly instead of
154
+ // ambiguously treating subsequent tokens as flags or operands.
155
+ if (token === '--') {
156
+ throw new ArgumentError("End-of-options separator '--' is not supported");
157
+ }
158
+ const body = token.slice(2);
159
+ const separator = body.indexOf('=');
160
+ const name = separator === -1 ? body : body.slice(0, separator);
161
+ let value = separator === -1 ? true : body.slice(separator + 1);
162
+ if (!FLAG_NAME.test(name) || (value === '' && !(command === 'delivery' && positionals[0] === 'review-update' && name === 'body' && separator !== -1))) {
163
+ throw new ArgumentError(`Malformed flag '${token}'`);
164
+ }
165
+ if (Object.hasOwn(flags, name)) {
166
+ throw new ArgumentError(`Duplicate flag '--${name}'`);
167
+ }
168
+ if (name === 'json' && value !== true) {
169
+ throw new ArgumentError("Flag '--json' does not take a value");
170
+ }
171
+ const commandOptions = LEGACY_OPTIONS[command] ?? STRICT_OPTIONS[command];
172
+ if (commandOptions) {
173
+ if (!commandOptions.boolean.has(name) && !commandOptions.valued.has(name)) {
174
+ throw new ArgumentError(`Unknown option '--${name}' for command '${command}'`);
175
+ }
176
+ if (commandOptions.boolean.has(name) && value !== true) {
177
+ throw new ArgumentError(`Flag '--${name}' does not take a value`);
178
+ }
179
+ if (commandOptions.valued.has(name) && value === true) {
180
+ if ((['task', 'work'].includes(command) && name === 'run') || command === 'delivery' || command === 'repositories' || command === 'integrations' || command === 'setup' || command === 'protocols' || ((command === 'install' || command === 'uninstall') && name === 'project') || command === 'init' || command === 'doctor' || command === 'support' || command === 'feature' || command === 'preflight' || command === 'status') {
181
+ const next = tokens[index + 1];
182
+ if (typeof next === 'string' && next.length > 0 && !next.startsWith('-')) {
183
+ value = next;
184
+ index += 1;
185
+ } else {
186
+ throw new ArgumentError(`Flag '--${name}' requires a value`);
187
+ }
188
+ } else {
189
+ throw new ArgumentError(`Flag '--${name}' requires a value`);
190
+ }
191
+ }
192
+ }
193
+ flags[name] = value;
194
+ } else if (!token || token.startsWith('-')) {
195
+ throw new ArgumentError(`Malformed argument '${token}'`);
196
+ } else {
197
+ positionals.push(token);
198
+ }
199
+ }
200
+
201
+ if (['feature', 'work'].includes(command) && flags.run !== undefined) {
202
+ const lifecycle = command === 'feature'
203
+ ? ['start', 'status', 'resume', 'cancel'] : ['prepare', 'next', 'status', 'submit', 'verify', 'recover'];
204
+ if (!lifecycle.includes(positionals[0])) throw new ArgumentError('--run is only supported for an existing run.');
205
+ if (positionals.length !== 1) throw new ArgumentError('Use one run selector: --run or a positional run ID.');
206
+ }
207
+
208
+ validateTargetSelectors(command, flags);
209
+ if (['install', 'uninstall', 'setup'].includes(command) && flags.global && flags.project !== undefined) {
210
+ throw new ArgumentError('--global and --project cannot be combined');
211
+ }
212
+ if (['install', 'uninstall'].includes(command)) {
213
+ if (flags.project !== undefined && !flags.minimal) throw new ArgumentError('--project requires --minimal');
214
+ if (flags.minimal && flags.all) throw new ArgumentError('--minimal and --all cannot be combined');
215
+ }
216
+
217
+ if (command === 'init' && flags.overwrite && !flags.write) {
218
+ throw new ArgumentError("Flag '--overwrite' requires '--write'");
219
+ }
220
+
221
+ if (NO_POSITIONAL_COMMANDS.has(command) && positionals.length > 0) {
222
+ throw new ArgumentError(`Command '${command}' does not accept positional arguments`);
223
+ }
224
+ if (command === 'status' && positionals.length !== 1) {
225
+ throw new ArgumentError("Command 'status' requires exactly one instance ID");
226
+ }
227
+ if (NESTED_COMMANDS.has(command) && positionals.length === 0) {
228
+ throw new ArgumentError(`Command '${command}' requires a subcommand`);
229
+ }
230
+ if (command === 'protocols' && !['add', 'import', 'validate', 'find', 'show', 'update', 'retire'].includes(positionals[0])) {
231
+ throw new ArgumentError('Unsupported protocol subcommand');
232
+ }
233
+ if (command === 'work' && !['propose', 'prepare', 'next', 'status', 'submit', 'verify', 'recover'].includes(positionals[0])) {
234
+ throw new ArgumentError('Unsupported work subcommand');
235
+ }
236
+ if (command === 'task' && !['status', 'start', 'approve', 'resume', 'deps', 'recover'].includes(positionals[0])) {
237
+ throw new ArgumentError('Unsupported task subcommand');
238
+ }
239
+
240
+ return {
241
+ command,
242
+ subcommand: NESTED_COMMANDS.has(command) ? positionals.shift() : null,
243
+ operands: positionals,
244
+ flags,
245
+ };
246
+ }
@@ -0,0 +1,70 @@
1
+ import { lstat, realpath } from 'node:fs/promises';
2
+ import { dirname, join, relative, resolve, sep } from 'node:path';
3
+
4
+ import { loadProjectConfig } from '../config/load.js';
5
+ import { runArgv } from '../discovery/tools.js';
6
+ import { CliError } from './output.js';
7
+
8
+ function fail(message) { throw new CliError(message, 'MISSING_CONFIGURATION'); }
9
+
10
+ async function gitRoot(directory, runner, env) {
11
+ const result = await runner(await gitExecutable(env), ['rev-parse', '--show-toplevel'], {
12
+ cwd: directory, shell: false, timeoutMs: 3_000, maxOutputBytes: 4 * 1024,
13
+ });
14
+ const output = typeof result?.stdout === 'string' ? result.stdout.trim() : '';
15
+ if (result?.code !== 0 || result?.timedOut || result?.truncated?.stdout
16
+ || !output.startsWith('/') || /[\u0000\r\n]/.test(output)) {
17
+ fail('Run this command from a Git project configured by rivet setup, or pass --project.');
18
+ }
19
+ return realpath(output);
20
+ }
21
+
22
+ export async function gitExecutable(env) {
23
+ const configured = env.RIVET_GIT_EXECUTABLE;
24
+ if (configured !== undefined) {
25
+ if (!configured.startsWith('/') || /[\u0000\r\n]/.test(configured)) fail('Configured Git executable is invalid.');
26
+ try { return await realpath(configured); }
27
+ catch { fail('Configured Git executable is unavailable.'); }
28
+ }
29
+ for (const candidate of ['/opt/homebrew/bin/git', '/usr/local/bin/git', '/usr/bin/git']) {
30
+ try { return await realpath(candidate); } catch {}
31
+ }
32
+ fail('Git is unavailable. Install Git or set RIVET_GIT_EXECUTABLE to its absolute path.');
33
+ }
34
+
35
+ export async function resolveConfiguredProject(cwd, explicit, options = {}) {
36
+ if (typeof cwd !== 'string' || !cwd.startsWith('/')
37
+ || (explicit !== undefined && (typeof explicit !== 'string' || !explicit || /[\u0000\r\n]/.test(explicit)))) {
38
+ throw new CliError('Project path is invalid.', 'INVALID_INPUT');
39
+ }
40
+ let selected;
41
+ try { selected = await realpath(resolve(cwd, explicit ?? '.')); }
42
+ catch { fail('Project directory was not found. Run from a configured project or pass --project.'); }
43
+ const root = await gitRoot(selected, options.runner ?? runArgv, options.env ?? process.env);
44
+ if (explicit !== undefined && selected !== root) {
45
+ throw new CliError('--project must name the configured Git project root.', 'INVALID_INPUT');
46
+ }
47
+ if (explicit === undefined) {
48
+ const inside = relative(root, selected);
49
+ if (inside === '..' || inside.startsWith(`..${sep}`)) fail('Current directory is outside the Git project.');
50
+ let ancestor = selected;
51
+ while (ancestor !== root) {
52
+ const nested = await lstat(join(ancestor, '.rivet')).catch(error => {
53
+ if (error?.code === 'ENOENT') return null;
54
+ throw error;
55
+ });
56
+ if (nested !== null) fail('A nested .rivet configuration is present. Pass the intended project root explicitly.');
57
+ ancestor = dirname(ancestor);
58
+ }
59
+ }
60
+ let configured;
61
+ try {
62
+ const marker = await lstat(join(root, '.rivet'));
63
+ if (!marker.isDirectory() || marker.isSymbolicLink()) fail('Project .rivet configuration is unsafe.');
64
+ configured = await loadProjectConfig(root);
65
+ } catch (error) {
66
+ if (error instanceof CliError) throw error;
67
+ fail('Rivet is not configured in this Git project. Run rivet setup --write first.');
68
+ }
69
+ return Object.freeze({ root, config: configured });
70
+ }
@@ -0,0 +1,38 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+
3
+ // Plan review has no reading deadline. EOF, stream errors and cancellation all
4
+ // decline; callers still revalidate the approved state before any mutation.
5
+ export async function readTaskAnswer(message, {input = process.stdin, output = process.stdout, signal} = {}) {
6
+ if (signal?.aborted) return '';
7
+ const readline = createInterface({input, output});
8
+ let decline;
9
+ const ended = new Promise(resolve => { decline = () => resolve(''); });
10
+ const abort = () => { decline(); readline.close(); };
11
+ readline.once('close', decline);
12
+ readline.on('error', decline);
13
+ input.once('error', decline);
14
+ output.once('error', decline);
15
+ signal?.addEventListener('abort', abort, {once:true});
16
+ try {
17
+ const answer = await Promise.race([readline.question(message), ended]);
18
+ return signal?.aborted ? '' : String(answer).trim();
19
+ } catch { return ''; }
20
+ finally {
21
+ signal?.removeEventListener('abort', abort);
22
+ input.removeListener('error', decline);
23
+ output.removeListener('error', decline);
24
+ readline.close();
25
+ }
26
+ }
27
+
28
+ export async function confirmTaskAction(message, options) {
29
+ return /^(?:y|yes)$/i.test(await readTaskAnswer(message, options));
30
+ }
31
+
32
+ export async function selectTaskOption(question, streams = {}) {
33
+ const prompt = `${question.message}\n${question.choices.map((choice,index)=>` ${index+1}. ${choice.label}`).join('\n')}\nNumber (blank to leave): `;
34
+ const answer = await readTaskAnswer(prompt, {...streams,signal:question.signal});
35
+ if (!/^[1-9][0-9]*$/.test(answer)) return null;
36
+ const index = Number(answer)-1;
37
+ return Number.isSafeInteger(index) && index < question.choices.length ? question.choices[index].value : null;
38
+ }
@@ -0,0 +1,22 @@
1
+ const STATES = Object.freeze({
2
+ proposed: 'Plan saved, waiting for your approval',
3
+ approved: 'Plan approved, ready to start',
4
+ running: 'Work is in progress',
5
+ blocked: 'Work paused and needs attention',
6
+ 'awaiting-final-approval': 'Changes passed checks and are ready for your review',
7
+ completed: 'Completed',
8
+ cancelled: 'Cancelled',
9
+ });
10
+ export function taskState(value) { return STATES[value] ?? 'Inspect task status for the next step'; }
11
+ export function failureGuidance(code, command) {
12
+ const task = ['run','task','feature','work'].includes(command);
13
+ switch(code) {
14
+ case 'INVALID_INPUT': return 'Check rivet --help for everyday commands, or rivet --help --advanced for harness commands.';
15
+ case 'MISSING_CONFIGURATION': return 'Run rivet doctor from your project. If it is not configured yet, preview rivet setup first.';
16
+ case 'BLOCKED_AUTHORITY': return 'Review the denied operation in your coding harness and request permission for that exact operation.';
17
+ case 'FAILED_GATE': return task ? 'Run rivet task status to inspect the failed checks. Fix the reported cause before continuing with rivet task resume.' : 'Run rivet doctor and resolve the reported failing check before retrying.';
18
+ case 'PROVIDER_UNAVAILABLE': return 'Check the reported harness or provider installation, authentication and permissions, then retry.';
19
+ case 'REPOSITORY_CONFLICT': return task ? 'Inspect rivet task status and your checkout. Preserve existing edits and follow the reported recovery step before retrying.' : 'Inspect the reported repository or saved-state conflict. Preserve existing edits before retrying.';
20
+ default: return 'Review the diagnostic report and attach it with reproduction steps at https://github.com/Agilno-Tech/rivet/issues/new.';
21
+ }
22
+ }