@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,579 @@
1
+ import { reference, runtimeSkill } from './project-reference.js';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { join, resolve } from 'node:path';
4
+
5
+ import { CliError, EXIT_CODES } from '../cli/output.js';
6
+ import {
7
+ createMutationGuard,
8
+ ensureContainedDirectory,
9
+ findProjectRoot,
10
+ withPinnedTargetDirectory,
11
+ } from '../commands/install.js';
12
+
13
+ const MANIFEST_NAME = '.rivet-install.json';
14
+ const SKILL_NAME = 'SKILL.md';
15
+ const MANIFEST_SCHEMA_VERSION = 1;
16
+ const MAX_MANIFEST_BYTES = 16 * 1024;
17
+ const MAX_PACKAGE_BYTES = 64 * 1024;
18
+ const MAX_SKILL_BYTES = 512 * 1024;
19
+ const SHA256 = /^[a-f0-9]{64}$/;
20
+ const PACKAGE_NAME = '@agilno-tech/rivet';
21
+
22
+ function conflict(message, cause) {
23
+ return new CliError(message, 'REPOSITORY_CONFLICT', cause ? { cause } : {});
24
+ }
25
+
26
+ function installationError(error) {
27
+ const seen = new Set();
28
+ let current = error;
29
+ for (let depth = 0; depth < 8 && current && typeof current === 'object' && !seen.has(current); depth += 1) {
30
+ seen.add(current);
31
+ const code = Object.getOwnPropertyDescriptor(current, 'code')?.value;
32
+ if (code === 'EACCES' || code === 'EPERM') {
33
+ return new CliError('Access to a protected Rivet skill directory was denied. Approve this operation through the harness, or run the reviewed Rivet setup, install or uninstall command from your terminal. The operation may be partial; inspect its state before retrying.', 'BLOCKED_AUTHORITY');
34
+ }
35
+ current = Object.getOwnPropertyDescriptor(current, 'cause')?.value;
36
+ }
37
+ return error;
38
+ }
39
+
40
+ function lstatIfExists(path, fs) {
41
+ try {
42
+ return fs.lstatSync(path);
43
+ } catch (error) {
44
+ if (error?.code === 'ENOENT') return null;
45
+ throw error;
46
+ }
47
+ }
48
+
49
+ function identity(status) {
50
+ return Object.freeze({ dev: status.dev, ino: status.ino });
51
+ }
52
+
53
+ function sameIdentity(left, right) {
54
+ return Boolean(left && right && left.dev === right.dev && left.ino === right.ino);
55
+ }
56
+
57
+ function sha256(bytes) {
58
+ return createHash('sha256').update(bytes).digest('hex');
59
+ }
60
+
61
+ function readRegularFile(path, fs, { maxBytes, label, missing = false }) {
62
+ const before = lstatIfExists(path, fs);
63
+ if (!before) {
64
+ if (missing) return null;
65
+ throw new CliError(`${label} is missing.`, 'MISSING_CONFIGURATION');
66
+ }
67
+ if (before.isSymbolicLink() || !before.isFile() || before.size > maxBytes) {
68
+ throw conflict(`${label} is not a safe regular file.`);
69
+ }
70
+
71
+ let descriptor;
72
+ try {
73
+ descriptor = fs.openSync(path, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
74
+ const opened = fs.fstatSync(descriptor);
75
+ if (!opened.isFile() || opened.size > maxBytes || !sameIdentity(identity(before), identity(opened))) {
76
+ throw conflict(`${label} changed during validation.`);
77
+ }
78
+ const bytes = fs.readFileSync(descriptor);
79
+ const after = lstatIfExists(path, fs);
80
+ if (!after || !sameIdentity(identity(opened), identity(after))) {
81
+ throw conflict(`${label} changed during validation.`);
82
+ }
83
+ return { bytes, identity: identity(opened), hash: sha256(bytes) };
84
+ } catch (error) {
85
+ if (error instanceof CliError) throw error;
86
+ throw conflict(`Could not safely read ${label}.`, error);
87
+ } finally {
88
+ if (descriptor !== undefined) fs.closeSync(descriptor);
89
+ }
90
+ }
91
+
92
+ function readSource(dependencies, projectRoot) {
93
+ const { fs, packageRoot } = dependencies;
94
+ let metadata;
95
+ try {
96
+ const packageFile = readRegularFile(join(packageRoot, 'package.json'), fs, {
97
+ maxBytes: MAX_PACKAGE_BYTES,
98
+ label: 'Rivet package metadata',
99
+ });
100
+ metadata = JSON.parse(packageFile.bytes.toString('utf8'));
101
+ } catch (error) {
102
+ if (error instanceof CliError) throw error;
103
+ throw new CliError('Rivet package metadata is invalid.', 'MISSING_CONFIGURATION', { cause: error });
104
+ }
105
+ if (
106
+ metadata?.name !== PACKAGE_NAME
107
+ || typeof metadata.version !== 'string'
108
+ || metadata.version.length < 1
109
+ || metadata.version.length > 128
110
+ ) {
111
+ throw new CliError('Rivet package metadata is invalid.', 'MISSING_CONFIGURATION');
112
+ }
113
+ const skill = readRegularFile(join(packageRoot, 'templates', 'harness', SKILL_NAME), fs, {
114
+ maxBytes: MAX_SKILL_BYTES,
115
+ label: 'Rivet harness skill template',
116
+ });
117
+ const pinned = projectRoot ? reference(projectRoot, fs) : null;
118
+ const skillBytes = dependencies.managedSkillBytes ?? (pinned ? runtimeSkill(skill.bytes, pinned.id) : skill.bytes);
119
+ if (!Buffer.isBuffer(skillBytes) || skillBytes.length > MAX_SKILL_BYTES) throw conflict('Managed skill source is invalid.');
120
+ return Object.freeze({
121
+ package: Object.freeze({ name: PACKAGE_NAME, version: metadata.version }),
122
+ skillBytes,
123
+ skillHash: sha256(skillBytes),
124
+ });
125
+ }
126
+
127
+ function selectedTargets(flags) {
128
+ if (flags.claude && flags.codex) return ['claude', 'codex'];
129
+ if (flags.claude) return ['claude'];
130
+ if (flags.codex) return ['codex'];
131
+ const requested = typeof flags.target === 'string' ? flags.target.toLowerCase() : flags.target;
132
+ if (requested === undefined || requested === 'both') return ['claude', 'codex'];
133
+ if (requested === 'claude' || requested === 'codex') return [requested];
134
+ throw new CliError("Invalid --target value. Use 'claude', 'codex', or 'both'.", 'INVALID_INPUT');
135
+ }
136
+
137
+ function projectScope(parsed, dependencies) {
138
+ const { fs } = dependencies;
139
+ if (parsed.flags.global) return null;
140
+
141
+ if (parsed.flags.project !== undefined) {
142
+ if (typeof parsed.flags.project !== 'string' || parsed.flags.project.length === 0) {
143
+ throw new CliError('The --project value must be a directory path.', 'INVALID_INPUT');
144
+ }
145
+ const root = resolve(dependencies.cwd(), parsed.flags.project);
146
+ const status = lstatIfExists(root, fs);
147
+ if (!status || status.isSymbolicLink() || !status.isDirectory()) {
148
+ throw new CliError('The selected project must be an existing regular directory.', 'INVALID_INPUT');
149
+ }
150
+ return root;
151
+ }
152
+
153
+ const discovered = findProjectRoot(dependencies.cwd(), fs);
154
+ if (!discovered) {
155
+ throw new CliError(
156
+ 'Could not find project root (no .git or package.json found in any parent directory).\nRun from inside a project, pass --project, or use --global.',
157
+ 'INVALID_INPUT',
158
+ );
159
+ }
160
+ return discovered;
161
+ }
162
+
163
+ function targetLocation(target, parsed, dependencies, projectRoot) {
164
+ const global = parsed.flags.global === true;
165
+ const scopeRoot = global ? resolve(dependencies.home()) : projectRoot;
166
+ const skillsDir = global
167
+ ? join(scopeRoot, target === 'claude' ? '.claude' : '.agents', 'skills')
168
+ : join(scopeRoot, target === 'claude' ? '.claude' : '.agents', 'skills');
169
+ const guard = createMutationGuard(scopeRoot, skillsDir, dependencies.fs);
170
+ const skillDir = join(guard.targetDir, 'rivet');
171
+ return { target, skillsDir: guard.targetDir, skillDir, guard };
172
+ }
173
+
174
+ function validateManifest(value, expectedTarget) {
175
+ if (
176
+ !value
177
+ || typeof value !== 'object'
178
+ || Array.isArray(value)
179
+ || value.schemaVersion !== MANIFEST_SCHEMA_VERSION
180
+ || !value.package
181
+ || typeof value.package !== 'object'
182
+ || ![PACKAGE_NAME, '@agilno/rivet'].includes(value.package.name)
183
+ || typeof value.package.version !== 'string'
184
+ || value.package.version.length < 1
185
+ || value.package.version.length > 128
186
+ || value.target !== expectedTarget
187
+ || !Array.isArray(value.files)
188
+ || value.files.length !== 1
189
+ || value.files[0]?.path !== SKILL_NAME
190
+ || !SHA256.test(value.files[0]?.sha256)
191
+ ) {
192
+ throw conflict('The Rivet install manifest is invalid or unsupported.');
193
+ }
194
+ return value;
195
+ }
196
+
197
+ function manifestBytes(source, target) {
198
+ return Buffer.from(`${JSON.stringify({
199
+ schemaVersion: MANIFEST_SCHEMA_VERSION,
200
+ package: source.package,
201
+ target,
202
+ files: [{ path: SKILL_NAME, sha256: source.skillHash }],
203
+ }, null, 2)}\n`);
204
+ }
205
+
206
+ function inspectTarget(location, operation, source, fs) {
207
+ const { guard, skillDir, target } = location;
208
+ guard.assertPath(location.skillsDir);
209
+ guard.assertPath(skillDir);
210
+ guard.assertPath(join(skillDir, SKILL_NAME));
211
+ guard.assertPath(join(skillDir, MANIFEST_NAME));
212
+
213
+ const directoryStatus = lstatIfExists(skillDir, fs);
214
+ if (!directoryStatus) {
215
+ return {
216
+ ...location,
217
+ action: operation === 'install' ? 'install' : 'unchanged',
218
+ directoryIdentity: null,
219
+ manifest: null,
220
+ manifestIdentity: null,
221
+ skillIdentity: null,
222
+ skillHash: null,
223
+ };
224
+ }
225
+ if (directoryStatus.isSymbolicLink() || !directoryStatus.isDirectory()) {
226
+ throw conflict(`The managed ${target} skill destination is not a safe directory.`);
227
+ }
228
+
229
+ const manifestFile = readRegularFile(join(skillDir, MANIFEST_NAME), fs, {
230
+ maxBytes: MAX_MANIFEST_BYTES,
231
+ label: `Rivet ${target} install manifest`,
232
+ missing: true,
233
+ });
234
+ if (!manifestFile) {
235
+ if (operation === 'uninstall') {
236
+ return {
237
+ ...location,
238
+ action: 'unchanged',
239
+ directoryIdentity: identity(directoryStatus),
240
+ manifest: null,
241
+ manifestIdentity: null,
242
+ skillIdentity: null,
243
+ skillHash: null,
244
+ };
245
+ }
246
+ throw conflict(`The ${target} rivet skill directory already exists and is not managed by this package.`);
247
+ }
248
+
249
+ let manifest;
250
+ try {
251
+ manifest = validateManifest(JSON.parse(manifestFile.bytes.toString('utf8')), target);
252
+ } catch (error) {
253
+ if (error instanceof CliError) throw error;
254
+ throw conflict(`The Rivet ${target} install manifest is invalid.`, error);
255
+ }
256
+ const skillFile = readRegularFile(join(skillDir, SKILL_NAME), fs, {
257
+ maxBytes: MAX_SKILL_BYTES,
258
+ label: `Managed Rivet ${target} skill`,
259
+ missing: true,
260
+ });
261
+ const recordedHash = manifest.files[0].sha256;
262
+ let action;
263
+ if (operation === 'uninstall') {
264
+ if (skillFile && skillFile.hash !== recordedHash) {
265
+ throw conflict(`The managed ${target} Rivet skill was edited; it was preserved.`);
266
+ }
267
+ action = 'uninstall';
268
+ } else if (!skillFile) {
269
+ action = 'repair';
270
+ } else if (skillFile.hash === recordedHash) {
271
+ action = skillFile.hash === source.skillHash
272
+ && manifest.package.name === source.package.name
273
+ && manifest.package.version === source.package.version
274
+ ? 'unchanged'
275
+ : 'update';
276
+ } else if (skillFile.hash === source.skillHash) {
277
+ // The skill rename completed but the manifest rename did not. Only the
278
+ // exact bytes currently shipped by this package are safe to recover.
279
+ action = 'repair-manifest';
280
+ } else {
281
+ throw conflict(`The managed ${target} Rivet skill was edited; it was preserved.`);
282
+ }
283
+
284
+ return {
285
+ ...location,
286
+ action,
287
+ directoryIdentity: identity(directoryStatus),
288
+ manifest,
289
+ manifestIdentity: manifestFile.identity,
290
+ manifestHash: manifestFile.hash,
291
+ skillIdentity: skillFile?.identity ?? null,
292
+ skillHash: skillFile?.hash ?? null,
293
+ };
294
+ }
295
+
296
+ function publicPlan(internal) {
297
+ return {
298
+ schemaVersion: 1,
299
+ operation: internal.operation,
300
+ scope: internal.scope,
301
+ ...(internal.projectRoot ? { projectRoot: internal.projectRoot } : {}),
302
+ targets: internal.targets.map(target => ({
303
+ target: target.target,
304
+ skillsDir: target.skillsDir,
305
+ skillDir: target.skillDir,
306
+ manifestPath: join(target.skillDir, MANIFEST_NAME),
307
+ action: target.action,
308
+ ownedFiles: [SKILL_NAME],
309
+ })),
310
+ };
311
+ }
312
+
313
+ function buildPlan(parsed, dependencies) {
314
+ if (!parsed?.flags || parsed.flags.minimal !== true) {
315
+ throw new CliError('Managed installation requires --minimal.', 'INVALID_INPUT');
316
+ }
317
+ const operation = parsed.command === 'uninstall' ? 'uninstall' : 'install';
318
+ const projectRoot = projectScope(parsed, dependencies);
319
+ const source = readSource(dependencies, projectRoot);
320
+ const locations = selectedTargets(parsed.flags)
321
+ .map(target => targetLocation(target, parsed, dependencies, projectRoot));
322
+ const targets = locations.map(location => inspectTarget(
323
+ location,
324
+ operation,
325
+ source,
326
+ dependencies.fs,
327
+ ));
328
+ return {
329
+ operation,
330
+ scope: parsed.flags.global ? 'global' : 'project',
331
+ projectRoot,
332
+ source,
333
+ targets,
334
+ };
335
+ }
336
+
337
+ export function inspectManagedInstall(parsed, dependencies) {
338
+ try { return publicPlan(buildPlan(parsed, dependencies)); }
339
+ catch (error) { throw installationError(error); }
340
+ }
341
+
342
+ function writeAll(descriptor, bytes, fs) {
343
+ let offset = 0;
344
+ while (offset < bytes.byteLength) {
345
+ const count = fs.writeSync(descriptor, bytes, offset, bytes.byteLength - offset, offset);
346
+ if (!Number.isInteger(count) || count < 1) throw new Error('Managed file write made no progress.');
347
+ offset += count;
348
+ }
349
+ }
350
+
351
+ function writeNewFile(name, bytes, fs, mode = 0o644) {
352
+ let descriptor;
353
+ try {
354
+ descriptor = fs.openSync(
355
+ name,
356
+ fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | (fs.constants.O_NOFOLLOW ?? 0),
357
+ mode,
358
+ );
359
+ writeAll(descriptor, bytes, fs);
360
+ fs.fsyncSync(descriptor);
361
+ } finally {
362
+ if (descriptor !== undefined) fs.closeSync(descriptor);
363
+ }
364
+ }
365
+
366
+ function allocateName(prefix, fs) {
367
+ for (let attempt = 0; attempt < 8; attempt += 1) {
368
+ const name = `${prefix}-${randomUUID()}`;
369
+ if (!lstatIfExists(name, fs)) return name;
370
+ }
371
+ throw conflict('Could not allocate a private managed-install entry.');
372
+ }
373
+
374
+ function cleanupTempFile(name, fs) {
375
+ try {
376
+ const status = lstatIfExists(name, fs);
377
+ if (status && !status.isSymbolicLink() && status.isFile()) fs.unlinkSync(name);
378
+ } catch {
379
+ // Private temporary residue is safer than deleting an entry we did not verify.
380
+ }
381
+ }
382
+
383
+ function atomicReplace(name, bytes, fs, expectedIdentity, expectedHash) {
384
+ const temporary = allocateName('.rivet-write', fs);
385
+ try {
386
+ writeNewFile(temporary, bytes, fs);
387
+ if (expectedIdentity) {
388
+ const current = readRegularFile(name, fs, {
389
+ maxBytes: MAX_SKILL_BYTES,
390
+ label: 'Managed destination',
391
+ });
392
+ if (!sameIdentity(expectedIdentity, current.identity) || current.hash !== expectedHash) {
393
+ throw conflict('Managed destination changed during update.');
394
+ }
395
+ } else if (lstatIfExists(name, fs)) {
396
+ throw conflict('Managed destination appeared during update.');
397
+ }
398
+ fs.renameSync(temporary, name);
399
+ } finally {
400
+ cleanupTempFile(temporary, fs);
401
+ }
402
+ }
403
+
404
+ function cleanupStage(name, fs) {
405
+ try {
406
+ const status = lstatIfExists(name, fs);
407
+ if (!status || status.isSymbolicLink() || !status.isDirectory()) return;
408
+ for (const child of fs.readdirSync(name)) {
409
+ if (child !== SKILL_NAME && child !== MANIFEST_NAME) return;
410
+ const childPath = join(name, child);
411
+ const childStatus = lstatIfExists(childPath, fs);
412
+ if (!childStatus || childStatus.isSymbolicLink() || !childStatus.isFile()) return;
413
+ }
414
+ for (const child of fs.readdirSync(name)) fs.unlinkSync(join(name, child));
415
+ fs.rmdirSync(name);
416
+ } catch {
417
+ // Best effort only; never recurse through an entry that could have changed.
418
+ }
419
+ }
420
+
421
+ function installNewTarget(target, source, fs) {
422
+ const stage = allocateName('.rivet-install', fs);
423
+ try {
424
+ fs.mkdirSync(stage, { mode: 0o755 });
425
+ const stageStatus = fs.lstatSync(stage);
426
+ withPinnedTargetDirectory(stage, identity(stageStatus), fs, () => {
427
+ writeNewFile(SKILL_NAME, source.skillBytes, fs);
428
+ writeNewFile(MANIFEST_NAME, manifestBytes(source, target.target), fs);
429
+ });
430
+ if (lstatIfExists('rivet', fs)) {
431
+ throw conflict(`The ${target.target} rivet skill destination appeared during installation.`);
432
+ }
433
+ fs.renameSync(stage, 'rivet');
434
+ } finally {
435
+ cleanupStage(stage, fs);
436
+ }
437
+ }
438
+
439
+ function readCurrentHash(name, expectedIdentity, fs, label, missing = false) {
440
+ const file = readRegularFile(name, fs, { maxBytes: MAX_SKILL_BYTES, label, missing });
441
+ if (expectedIdentity && (!file || !sameIdentity(expectedIdentity, file.identity))) {
442
+ throw conflict(`${label} changed after prevalidation.`);
443
+ }
444
+ if (!expectedIdentity && file) throw conflict(`${label} appeared after prevalidation.`);
445
+ return file;
446
+ }
447
+
448
+ function installExistingTarget(target, source, fs) {
449
+ const skill = readCurrentHash(
450
+ SKILL_NAME,
451
+ target.skillIdentity,
452
+ fs,
453
+ `Managed Rivet ${target.target} skill`,
454
+ true,
455
+ );
456
+ const manifest = readRegularFile(MANIFEST_NAME, fs, {
457
+ maxBytes: MAX_MANIFEST_BYTES,
458
+ label: `Rivet ${target.target} install manifest`,
459
+ });
460
+ if (!sameIdentity(target.manifestIdentity, manifest.identity) || manifest.hash !== target.manifestHash) {
461
+ throw conflict(`Rivet ${target.target} install manifest changed after prevalidation.`);
462
+ }
463
+ if (skill && skill.hash !== target.skillHash) {
464
+ throw conflict(`Managed Rivet ${target.target} skill changed after prevalidation.`);
465
+ }
466
+
467
+ if (target.action === 'update' || target.action === 'repair') {
468
+ atomicReplace(SKILL_NAME, source.skillBytes, fs, skill?.identity ?? null, skill?.hash ?? null);
469
+ }
470
+ atomicReplace(
471
+ MANIFEST_NAME,
472
+ manifestBytes(source, target.target),
473
+ fs,
474
+ manifest.identity,
475
+ manifest.hash,
476
+ );
477
+ }
478
+
479
+ function installTarget(target, source, fs) {
480
+ target.guard.assertPath(target.skillsDir);
481
+ const skillsIdentity = ensureContainedDirectory(target.guard, fs);
482
+ target.guard.assertPath(target.skillDir);
483
+ withPinnedTargetDirectory(target.skillsDir, skillsIdentity, fs, () => {
484
+ if (target.action === 'install') {
485
+ installNewTarget(target, source, fs);
486
+ return;
487
+ }
488
+ if (target.action === 'unchanged') return;
489
+ const currentDirectory = lstatIfExists('rivet', fs);
490
+ if (!currentDirectory || currentDirectory.isSymbolicLink() || !currentDirectory.isDirectory()
491
+ || !sameIdentity(target.directoryIdentity, identity(currentDirectory))) {
492
+ throw conflict(`The ${target.target} rivet skill directory changed after prevalidation.`);
493
+ }
494
+ withPinnedTargetDirectory('rivet', target.directoryIdentity, fs, () => {
495
+ installExistingTarget(target, source, fs);
496
+ });
497
+ });
498
+ target.guard.assertPath(target.skillDir);
499
+ }
500
+
501
+ function unlinkVerified(name, expectedIdentity, fs, label) {
502
+ const status = lstatIfExists(name, fs);
503
+ if (!status) {
504
+ if (expectedIdentity) throw conflict(`${label} disappeared after prevalidation.`);
505
+ return;
506
+ }
507
+ if (status.isSymbolicLink() || !status.isFile() || !sameIdentity(expectedIdentity, identity(status))) {
508
+ throw conflict(`${label} changed after prevalidation.`);
509
+ }
510
+ fs.unlinkSync(name);
511
+ }
512
+
513
+ function uninstallTarget(target, fs) {
514
+ if (target.action === 'unchanged') return;
515
+ const skillsStatus = lstatIfExists(target.skillsDir, fs);
516
+ if (!skillsStatus || skillsStatus.isSymbolicLink() || !skillsStatus.isDirectory()) {
517
+ throw conflict(`The ${target.target} skills directory changed after prevalidation.`);
518
+ }
519
+ target.guard.assertPath(target.skillDir);
520
+ withPinnedTargetDirectory(target.skillsDir, identity(skillsStatus), fs, () => {
521
+ const directoryStatus = lstatIfExists('rivet', fs);
522
+ if (!directoryStatus || directoryStatus.isSymbolicLink() || !directoryStatus.isDirectory()
523
+ || !sameIdentity(target.directoryIdentity, identity(directoryStatus))) {
524
+ throw conflict(`The ${target.target} rivet skill directory changed after prevalidation.`);
525
+ }
526
+ withPinnedTargetDirectory('rivet', target.directoryIdentity, fs, () => {
527
+ if (target.skillIdentity) {
528
+ const skill = readRegularFile(SKILL_NAME, fs, {
529
+ maxBytes: MAX_SKILL_BYTES,
530
+ label: `Managed Rivet ${target.target} skill`,
531
+ });
532
+ if (!sameIdentity(target.skillIdentity, skill.identity) || skill.hash !== target.skillHash) {
533
+ throw conflict(`Managed Rivet ${target.target} skill changed after prevalidation.`);
534
+ }
535
+ unlinkVerified(SKILL_NAME, target.skillIdentity, fs, `Managed Rivet ${target.target} skill`);
536
+ }
537
+ const manifest = readRegularFile(MANIFEST_NAME, fs, {
538
+ maxBytes: MAX_MANIFEST_BYTES,
539
+ label: `Rivet ${target.target} install manifest`,
540
+ });
541
+ if (!sameIdentity(target.manifestIdentity, manifest.identity) || manifest.hash !== target.manifestHash) {
542
+ throw conflict(`Rivet ${target.target} install manifest changed after prevalidation.`);
543
+ }
544
+ unlinkVerified(MANIFEST_NAME, target.manifestIdentity, fs, `Rivet ${target.target} install manifest`);
545
+ });
546
+ const remaining = fs.readdirSync('rivet');
547
+ if (remaining.length === 0) fs.rmdirSync('rivet');
548
+ });
549
+ }
550
+
551
+ function emitSuccess(parsed, dependencies, plan) {
552
+ const changed = plan.targets.filter(target => target.action !== 'unchanged').length;
553
+ const result = publicPlan(plan);
554
+ if (parsed.flags.json) {
555
+ dependencies.output.json({ ok: true, command: plan.operation, result });
556
+ } else if (plan.operation === 'install') {
557
+ dependencies.output.log(`Installed the Rivet entry skill ${plan.scope === 'global' ? 'globally' : 'in the project'} for: ${plan.targets.map(target => target.target).join(', ')}`);
558
+ plan.targets.forEach(target => dependencies.output.log(` -> ${target.target}: ${target.skillDir}`));
559
+ } else if (changed === 0) {
560
+ dependencies.output.log(`No managed Rivet entry skill found for: ${plan.targets.map(target => target.target).join(', ')}`);
561
+ } else {
562
+ dependencies.output.log(`Uninstalled the Rivet entry skill for: ${plan.targets.map(target => target.target).join(', ')}`);
563
+ plan.targets.forEach(target => dependencies.output.log(` -> ${target.target}: ${target.skillDir}`));
564
+ }
565
+ }
566
+
567
+ export async function managedInstall(parsed, dependencies) {
568
+ try {
569
+ const plan = buildPlan(parsed, dependencies);
570
+ for (const target of plan.targets) {
571
+ if (plan.operation === 'install') installTarget(target, plan.source, dependencies.fs);
572
+ else uninstallTarget(target, dependencies.fs);
573
+ }
574
+ emitSuccess(parsed, dependencies, plan);
575
+ return EXIT_CODES.SUCCESS;
576
+ } catch (error) {
577
+ throw installationError(error);
578
+ }
579
+ }
@@ -0,0 +1,33 @@
1
+ import { join } from 'node:path';
2
+ import { CliError } from '../cli/output.js';
3
+ import integrity from './runtime-integrity.cjs';
4
+ const { runtimeIntegrity, legacyRuntimeIntegrity } = integrity;
5
+ const NAME = '.rivet.cjs';
6
+ const fail = message => { throw new CliError(message, 'REPOSITORY_CONFLICT'); };
7
+ function regular(file, fs, maximum) {
8
+ const before=fs.lstatSync(file);
9
+ if(!before.isFile()||before.isSymbolicLink()||before.size>maximum)fail('Project runtime reference is unsafe.');
10
+ const fd=fs.openSync(file,fs.constants.O_RDONLY|(fs.constants.O_NOFOLLOW??0));
11
+ try{
12
+ const opened=fs.fstatSync(fd),bytes=fs.readFileSync(fd),after=fs.fstatSync(fd);
13
+ if(opened.dev!==before.dev||opened.ino!==before.ino||bytes.length!==before.size||after.size!==before.size||after.mtimeMs!==before.mtimeMs||after.ctimeMs!==before.ctimeMs)fail('Project runtime reference changed.');
14
+ return bytes;
15
+ }finally{fs.closeSync(fd);}
16
+ }
17
+ // Version 1 is an ownership contract: future formats must retain this renderer
18
+ // and verifier for recognizing unchanged references written by older releases.
19
+ export function launcher(id) { return renderLauncher(id, runtimeIntegrity); }
20
+ function renderLauncher(id, verifier) {
21
+ return `// Rivet project source v1: ${id}\n'use strict';\nconst path = require('node:path');\nconst cache = path.join(require('node:fs').realpathSync(require('node:os').homedir()), '.cache', 'rivet', 'project-runtimes');\ntry {\n if (Number(process.versions.node.split('.')[0]) < 22) throw new Error('Node.js 22 or newer is required.');\n const fs = require('node:fs');\n if (fs.realpathSync(cache) !== cache) throw new Error('Private runtime cache path is unsafe.');\n const index = path.join(cache, 'source-${id}-' + process.platform + '-' + process.arch + '.json');\n const stat = fs.lstatSync(index);\n if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1024) throw new Error('Private runtime index is unsafe.');\n const fd = fs.openSync(index, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));\n let record;\n try {\n const opened = fs.fstatSync(fd), buffer = Buffer.alloc(1025), size = fs.readSync(fd, buffer, 0, buffer.length, 0), after = fs.fstatSync(fd);\n if (opened.dev !== stat.dev || opened.ino !== stat.ino || size !== stat.size || after.size !== stat.size || after.mtimeMs !== stat.mtimeMs || after.ctimeMs !== stat.ctimeMs) throw new Error('Private runtime index changed.');\n record = JSON.parse(buffer.subarray(0, size).toString('utf8'));\n } finally { fs.closeSync(fd); }\n if (Object.keys(record).length !== 1 || !/^[a-f0-9]{64}$/.test(record.runtimeId)) throw new Error('Private runtime index is invalid.');\n const runtime = (${verifier.toString()})(path.join(cache, record.runtimeId), record.runtimeId, '${id}');\n import(require('node:url').pathToFileURL(runtime.entry).href).catch(error => { console.error(error.safeMessage || 'Pinned Rivet failed.'); process.exitCode = 1; });\n} catch (error) { console.error(error.message); process.exitCode = 1; }\n`;
22
+ }
23
+ export function reference(root, fs) {
24
+ const file = join(root, NAME), stat = fs.lstatSync(file, { throwIfNoEntry: false });
25
+ if (!stat) return null;
26
+ const bytes = regular(file, fs, 32768), match = /^\/\/ Rivet project source v1: ([a-f0-9]{64})\n/.exec(bytes.toString());
27
+ if (!match || ![launcher(match[1]), renderLauncher(match[1], legacyRuntimeIntegrity)].includes(bytes.toString())) fail('The project runtime reference is unowned or edited; it was preserved.');
28
+ return { id: match[1], bytes, dev: stat.dev, ino: stat.ino };
29
+ }
30
+ export function runtimeSkill(source, id) {
31
+ const text = source.toString('utf8').replace(/^If `rivet` is not on `PATH`.*$/m, '');
32
+ return Buffer.from(text + `\n## Pinned project runtime\n\nFor EVERY command shown as \`rivet\` above, use \`node <absolute-project-root>/.rivet.cjs\` followed by the same arguments. Discover the project root yourself; do not ask the user for it. This project pins Rivet source ${id}; the runtime and resolved dependency inventory are private to this machine. Use the project reference even when a global Rivet is available. Do not use a floating npx fallback.\n`);
33
+ }