@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,224 @@
1
+ import { execFileSync } from 'node:child_process';
2
+
3
+ import { CliError, EXIT_CODES } from '../cli/output.js';
4
+ import { createStatusServer } from '../status/server.js';
5
+
6
+ const INSTANCE_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
7
+ const MAX_FIXTURE_BYTES = 512 * 1024;
8
+ const UTF8 = new TextDecoder('utf-8', { fatal: true });
9
+ const reflectApply = Reflect.apply;
10
+
11
+ function fail(message, code = 'INVALID_INPUT') { throw new CliError(message, code); }
12
+
13
+ function fixturePath(value) {
14
+ if (typeof value !== 'string' || value.length === 0 || value.length > 1_024 || value.includes('\0')
15
+ || value.startsWith('/') || value.includes('\\') || !/^[a-zA-Z0-9._/-]+$/.test(value)) fail('Status fixture path is invalid.');
16
+ const segments = value.split('/');
17
+ if (segments.some(segment => !segment || segment === '.' || segment === '..' || segment === '.git')) fail('Status fixture must be a repository-relative tracked file.');
18
+ return value;
19
+ }
20
+
21
+ function runGit(cwd, args, maximum = MAX_FIXTURE_BYTES + 1_024) {
22
+ return execFileSync('git', ['--literal-pathspecs', '-c', 'core.hooksPath=/dev/null', '-c', 'core.fsmonitor=false', '-C', cwd, ...args], {
23
+ encoding: null,
24
+ maxBuffer: maximum,
25
+ stdio: ['ignore', 'pipe', 'ignore'],
26
+ timeout: 5_000,
27
+ windowsHide: true,
28
+ });
29
+ }
30
+
31
+ function readFixture(cwd, path) {
32
+ try {
33
+ const commit = runGit(cwd, ['rev-parse', '--verify', 'HEAD^{commit}'], 256).toString('ascii').trim();
34
+ if (!/^[0-9a-f]{40,64}$/.test(commit)) fail('Status fixture could not be read.', 'MISSING_CONFIGURATION');
35
+ const entry = runGit(cwd, ['ls-tree', '-z', '--full-tree', commit, '--', path], 4_096);
36
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([^\0]+)\0$/.exec(entry.toString('utf8'));
37
+ if (!match || match[3] !== path) fail('Status fixture could not be read.', 'MISSING_CONFIGURATION');
38
+ const sizeText = runGit(cwd, ['cat-file', '-s', match[2]], 128).toString('ascii').trim();
39
+ if (!/^[1-9][0-9]{0,6}$/.test(sizeText)) fail('Status fixture is invalid.');
40
+ const size = Number(sizeText);
41
+ if (!Number.isSafeInteger(size) || size > MAX_FIXTURE_BYTES) fail('Status fixture is invalid.');
42
+ const bytes = runGit(cwd, ['cat-file', 'blob', match[2]], MAX_FIXTURE_BYTES + 1);
43
+ if (bytes.length !== size) fail('Status fixture is invalid.');
44
+ return JSON.parse(UTF8.decode(bytes));
45
+ } catch (error) {
46
+ if (error instanceof CliError) throw error;
47
+ fail('Status fixture could not be read.', 'MISSING_CONFIGURATION');
48
+ }
49
+ }
50
+
51
+ function validScript(input) {
52
+ if (!input || typeof input !== 'object' || Array.isArray(input)) return false;
53
+ return input.version === 1 && ['success', 'retry', 'failed', 'blocked', 'budget-exhausted'].includes(input.kind)
54
+ && input.output && typeof input.output === 'object' && !Array.isArray(input.output)
55
+ && Array.isArray(input.output.evidence) && input.output.evidence.length <= 64
56
+ && input.output.evidence.every(value => typeof value === 'string' && INSTANCE_ID.test(value) && value.length <= 96)
57
+ && input.usage && typeof input.usage === 'object' && !Array.isArray(input.usage)
58
+ && Number.isSafeInteger(input.usage.tokens) && input.usage.tokens >= 0
59
+ && typeof input.usage.costUsd === 'number' && Number.isFinite(input.usage.costUsd) && input.usage.costUsd >= 0;
60
+ }
61
+
62
+ function fixtureState(instanceId, input, nowMs) {
63
+ if (!input || typeof input !== 'object' || Array.isArray(input) || input.schemaVersion !== 1
64
+ || !Array.isArray(input.scripts) || input.scripts.length === 0 || input.scripts.length > 64
65
+ || !input.scripts.every(validScript)) fail('Status fixture has an unsupported shape.');
66
+ const workerNodes = input.scripts.map((script, index) => {
67
+ const id = `fixture-worker-${index + 1}`;
68
+ const status = script.kind === 'success' ? 'completed' : script.kind === 'retry' ? 'corrective' : script.kind;
69
+ return {
70
+ id, parentId: 'manager-plan', owner: { role: 'worker', id: `${id}-owner` }, dependencies: ['manager-plan'],
71
+ requiredEvidenceTypes: script.output.evidence.map(() => 'test'), evidenceRefs: [...script.output.evidence], status,
72
+ };
73
+ });
74
+ const events = input.scripts.map((script, index) => ({
75
+ schemaVersion: 1,
76
+ eventId: `fixture-event-${index + 1}`,
77
+ graphId: instanceId,
78
+ nodeId: `fixture-worker-${index + 1}`,
79
+ sequence: index + 1,
80
+ timestamp: new Date(nowMs - ((input.scripts.length - index) * 1_000)).toISOString(),
81
+ actor: { role: 'worker', id: `fixture-worker-${index + 1}-owner` },
82
+ ...(script.kind === 'retry'
83
+ ? { type: 'retry', retryReason: 'fixture-retry' }
84
+ : { type: 'state-transition', priorState: 'running', newState: workerNodes[index].status }),
85
+ }));
86
+ const evidence = input.scripts.flatMap((script, index) => script.output.evidence.map(id => ({
87
+ id, nodeId: `fixture-worker-${index + 1}`, type: 'test', approvalState: script.kind === 'success' ? 'approved' : 'pending',
88
+ })));
89
+ const tokens = input.scripts.reduce((sum, script) => sum + script.usage.tokens, 0);
90
+ const costUsd = input.scripts.reduce((sum, script) => sum + script.usage.costUsd, 0).toFixed(2);
91
+ return Object.freeze({
92
+ schemaVersion: 1, version: 1, activated: true, terminal: null, startedAtMs: nowMs - (input.scripts.length * 1_000),
93
+ graph: {
94
+ schemaVersion: 1, id: instanceId, status: workerNodes.every(node => node.status === 'completed') ? 'completed' : 'running',
95
+ nodes: [
96
+ { id: 'boss-plan', owner: { role: 'boss', id: 'portfolio-boss' }, dependencies: [], requiredEvidenceTypes: [], evidenceRefs: [], status: 'completed' },
97
+ { id: 'manager-plan', parentId: 'boss-plan', owner: { role: 'manager', id: 'engineering-manager' }, dependencies: ['boss-plan'], requiredEvidenceTypes: [], evidenceRefs: [], status: 'completed' },
98
+ ...workerNodes,
99
+ { id: 'human-final', parentId: 'boss-plan', owner: { role: 'boss', id: 'portfolio-boss' }, dependencies: workerNodes.map(node => node.id), requiredEvidenceTypes: ['human-approval'], evidenceRefs: ['final-approval'], approvalGate: 'final-delivery', status: 'ready' },
100
+ ],
101
+ },
102
+ events, attempts: Object.fromEntries(workerNodes.map(node => [node.id, node.status === 'corrective' ? 2 : 1])),
103
+ launchIntents: {}, heartbeats: {}, evidence,
104
+ usage: { tokens, costUsd, retries: workerNodes.filter(node => node.status === 'corrective').length, timeMinutes: 0, taskLimit: input.scripts.length },
105
+ limits: { tokens: Math.max(tokens, 1), costUsd: String(Math.max(Number(costUsd), 1)), retries: 3, timeMinutes: 60, taskLimit: Math.max(input.scripts.length, 1) },
106
+ });
107
+ }
108
+
109
+ async function readPrivateInstance(instance, options = {}) {
110
+ if (!instance || typeof instance !== 'object' || typeof instance.acquire !== 'function' || typeof instance.read !== 'function') fail('Private orchestration instance is unavailable.', 'MISSING_CONFIGURATION');
111
+ if (options.signal?.aborted) fail('Private orchestration instance is unavailable.', 'MISSING_CONFIGURATION');
112
+ const lock = await instance.acquire({ signal: options.signal });
113
+ if (!lock || typeof lock.release !== 'function') fail('Private orchestration instance is unavailable.', 'MISSING_CONFIGURATION');
114
+ try {
115
+ if (options.signal?.aborted) fail('Private orchestration instance is unavailable.', 'MISSING_CONFIGURATION');
116
+ return await instance.read({ signal: options.signal });
117
+ } finally { await lock.release(); }
118
+ }
119
+
120
+ function serverSurface(value) {
121
+ if (!value || typeof value !== 'object' || Array.isArray(value)
122
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return null;
123
+ const keys = Reflect.ownKeys(value);
124
+ if (keys.length !== 2 || keys.some(key => typeof key !== 'string' || !['start', 'close'].includes(key))) return null;
125
+ const copy = {};
126
+ for (const key of keys) {
127
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
128
+ if (!descriptor || !descriptor.enumerable || !Object.hasOwn(descriptor, 'value') || typeof descriptor.value !== 'function') return null;
129
+ copy[key] = descriptor.value;
130
+ }
131
+ return Object.freeze(copy);
132
+ }
133
+
134
+ function validAddress(value, requestedPort) {
135
+ if (!value || typeof value !== 'object' || Array.isArray(value)
136
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return null;
137
+ const keys = Reflect.ownKeys(value);
138
+ if (keys.length !== 3 || keys.some(key => typeof key !== 'string' || !['host', 'port', 'url'].includes(key))) return null;
139
+ const copy = {};
140
+ for (const key of keys) {
141
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
142
+ if (!descriptor || !descriptor.enumerable || !Object.hasOwn(descriptor, 'value')) return null;
143
+ copy[key] = descriptor.value;
144
+ }
145
+ return copy.host === '127.0.0.1' && Number.isSafeInteger(copy.port) && copy.port >= 1 && copy.port <= 65_535
146
+ && (requestedPort === 0 || copy.port === requestedPort)
147
+ && copy.url === `http://127.0.0.1:${copy.port}` ? Object.freeze(copy) : null;
148
+ }
149
+
150
+ async function closeServer(close) {
151
+ try { await Reflect.apply(close, undefined, []); } catch { /* public CLI reports one fixed failure */ }
152
+ }
153
+
154
+ function waitForProcessSignal(signal) {
155
+ if (signal?.aborted) return Promise.resolve();
156
+ return new Promise(resolvePromise => {
157
+ const finish = () => {
158
+ process.off('SIGINT', finish);
159
+ process.off('SIGTERM', finish);
160
+ signal?.removeEventListener?.('abort', finish);
161
+ resolvePromise();
162
+ };
163
+ process.once('SIGINT', finish);
164
+ process.once('SIGTERM', finish);
165
+ signal?.addEventListener?.('abort', finish, { once: true });
166
+ });
167
+ }
168
+
169
+ export async function statusCommand(parsed, dependencies) {
170
+ if (!parsed || parsed.command !== 'status' || parsed.subcommand !== null || !Array.isArray(parsed.operands)
171
+ || parsed.operands.length !== 1 || !INSTANCE_ID.test(parsed.operands[0]) || parsed.operands[0].length > 64
172
+ || !parsed.flags || Object.keys(parsed.flags).some(key => !['fixture', 'port', 'json'].includes(key))) fail('Status requires one explicit orchestration instance ID.');
173
+ const instanceId = parsed.operands[0];
174
+ const portValue = parsed.flags.port ?? '0';
175
+ if (typeof portValue !== 'string' || !/^(?:0|[1-9][0-9]{0,4})$/.test(portValue) || Number(portValue) > 65_535) fail('Status port is invalid.');
176
+ const context = dependencies?.status ?? {};
177
+ const now = typeof context.now === 'function' ? context.now : Date.now;
178
+ let readState;
179
+ if (parsed.flags.fixture !== undefined) {
180
+ const path = fixturePath(parsed.flags.fixture);
181
+ const fixture = readFixture(dependencies.cwd(), path);
182
+ const state = fixtureState(instanceId, fixture, now());
183
+ readState = async () => state;
184
+ } else {
185
+ if (typeof context.resolveInstance !== 'function') fail('Private orchestration is not configured.', 'MISSING_CONFIGURATION');
186
+ const instance = await context.resolveInstance(instanceId);
187
+ if (!instance || instance.id !== instanceId) fail('Private orchestration instance is unavailable.', 'MISSING_CONFIGURATION');
188
+ readState = options => readPrivateInstance(instance, options);
189
+ }
190
+ const factory = typeof context.createServer === 'function' ? context.createServer : createStatusServer;
191
+ let server;
192
+ try {
193
+ server = serverSurface(factory({ readState, now, limits: context.limits, pollIntervalMs: context.pollIntervalMs, readTimeoutMs: context.readTimeoutMs, maxSseClients: context.maxSseClients }));
194
+ } catch { fail('Status server is unavailable.', 'MISSING_CONFIGURATION'); }
195
+ if (!server) fail('Status server is unavailable.', 'MISSING_CONFIGURATION');
196
+ const requestedPort = Number(portValue);
197
+ let address;
198
+ try {
199
+ address = validAddress(await Reflect.apply(server.start, undefined, [{ port: requestedPort }]), requestedPort);
200
+ if (!address) throw new Error('invalid status address');
201
+ } catch {
202
+ await closeServer(server.close);
203
+ fail('Status server is unavailable.', 'MISSING_CONFIGURATION');
204
+ }
205
+ const waitFunction = typeof context.wait === 'function' ? context.wait : waitForProcessSignal;
206
+ const waitReceiver = typeof context.wait === 'function' ? context : undefined;
207
+ const wait = signal => reflectApply(waitFunction, waitReceiver, [signal]);
208
+ if (parsed.flags.json) {
209
+ try {
210
+ dependencies.output.json({ ok: true, command: 'status', instanceId, url: address.url });
211
+ if (typeof dependencies.registerStatusJsonLifecycle !== 'function') throw new Error('missing status lifecycle owner');
212
+ dependencies.registerStatusJsonLifecycle(wait, context.signal, server.close);
213
+ } catch {
214
+ await closeServer(server.close);
215
+ throw new CliError('Status server is unavailable.', 'MISSING_CONFIGURATION');
216
+ }
217
+ return EXIT_CODES.SUCCESS;
218
+ }
219
+ try {
220
+ dependencies.output.log(`Status: ${address.url}`);
221
+ await wait(context.signal);
222
+ } finally { await closeServer(server.close); }
223
+ return EXIT_CODES.SUCCESS;
224
+ }
@@ -0,0 +1,44 @@
1
+ import { collectSupportBundle, resolveSupportProject } from '../support/bundle.js';
2
+ import { saveSupportReport } from '../support/failure-report.js';
3
+ import { CliError, EXIT_CODES } from '../cli/output.js';
4
+
5
+ export async function supportCommand(parsed, dependencies) {
6
+ if (parsed.operands.length || Object.keys(parsed.flags).some(key => !['project','probe-harnesses','save','json'].includes(key))) {
7
+ throw new CliError('Use rivet support [--project=<path>] [--probe-harnesses] [--save] [--json].', 'INVALID_INPUT');
8
+ }
9
+ const root = await resolveSupportProject(dependencies.cwd(), parsed.flags.project);
10
+ const controller = new AbortController(), abort = () => controller.abort();
11
+ process.on('SIGINT', abort);
12
+ process.on('SIGTERM', abort);
13
+ let result, savedReport;
14
+ try {
15
+ const options = {
16
+ environment: dependencies.env,
17
+ signal: controller.signal,
18
+ probeHarnesses: parsed.flags['probe-harnesses'] === true,
19
+ ...(dependencies.support?.diagnose ? {diagnose: dependencies.support.diagnose} : {}),
20
+ ...(dependencies.support?.discoverHarnesses ? {discoverHarnesses: dependencies.support.discoverHarnesses} : {}),
21
+ };
22
+ if (parsed.flags.save === true) {
23
+ try {
24
+ const saved = await saveSupportReport(root, options);
25
+ result = saved.report;
26
+ savedReport = {path: saved.path};
27
+ } catch {
28
+ throw new CliError('Support report could not be saved. Try rivet support --json to display the report.', 'INTERNAL_ERROR');
29
+ }
30
+ } else result = await collectSupportBundle(root, options);
31
+ } finally {
32
+ process.off('SIGINT', abort);
33
+ process.off('SIGTERM', abort);
34
+ }
35
+ if (parsed.flags.json) dependencies.output.json({ok: true, result, ...(savedReport ? {savedReport} : {})});
36
+ else {
37
+ dependencies.output.log(`Support collection: ${result.collection.status}. Configuration: ${result.configuration.status}. Readiness: ${result.readiness.status}.`);
38
+ if (savedReport) {
39
+ dependencies.output.log(`Support report saved: ${savedReport.path}`);
40
+ dependencies.output.log('Review the file before attaching it to an issue at https://github.com/Agilno-Tech/rivet/issues/new. Nothing was uploaded.');
41
+ } else dependencies.output.log('Save a shareable report with rivet support --save. Add --probe-harnesses to include local CLI capability checks.');
42
+ }
43
+ return EXIT_CODES.SUCCESS;
44
+ }
@@ -0,0 +1,104 @@
1
+ import { applyVerifiedTask } from '../feature/local-approval.js';
2
+ import { createGitClient } from '../git/client.js';
3
+ import { withTerminalInterruption } from '../cli/interrupt.js';
4
+ import { CliError, EXIT_CODES, observeOutputErrors } from '../cli/output.js';
5
+ import { deliveryCommand } from './delivery.js';
6
+ import { invokeFeature } from './feature.js';
7
+
8
+ function fail(message, code = 'INVALID_INPUT') { throw new CliError(message, code); }
9
+ function visible(value) { return String(value).replace(/[\u0000-\u001f\u007f\u009b]/g, character => `\\u${character.codePointAt(0).toString(16).padStart(4,'0')}`); }
10
+ function displayPatch(value) { return String(value).split('\n').map(visible).join('\n'); }
11
+ const completionChoices = [
12
+ {value:'review',label:'Review changes'},
13
+ {value:'pull-request',label:'Pull-request delivery (recommended for team review)'},
14
+ {value:'local',label:'Apply locally (update the current default branch; no push)'},
15
+ {value:'leave',label:'Leave for later'},
16
+ ];
17
+
18
+ export async function finishTask(project, record, dependencies, options = {}) {
19
+ let failed = false;
20
+ const unobserve = observeOutputErrors(dependencies.output,()=>{failed=true;});
21
+ const assertDisplay = () => { if(failed) fail('Final review output failed. Nothing further was approved. Retry in a working terminal.', 'REPOSITORY_CONFLICT'); };
22
+ try { return await finishTaskAction(project,record,dependencies,{...options,assertDisplay}); }
23
+ finally { unobserve?.(); }
24
+ }
25
+
26
+ async function finishTaskAction(project, record, dependencies, options) {
27
+ if (!['awaiting-final-approval', 'completed'].includes(record.status)) fail('This task has no verified result ready for final review. Use rivet task status.', 'REPOSITORY_CONFLICT');
28
+ if (dependencies.terminalIsInteractive?.() !== true) fail('Run rivet task approve in an interactive terminal for final review.');
29
+ let reviewedStatus;
30
+ if (record.status !== 'completed') {
31
+ const status = await invokeFeature(dependencies.work, 'status', {project: project.root, runId: record.runId});
32
+ reviewedStatus = status;
33
+ if (status.deliveryReady !== true) fail('Final review requires current passing checks and a clean verified checkout. Use rivet task status.', 'REPOSITORY_CONFLICT');
34
+ dependencies.output.log('Changes passed checks and are ready for your review.');
35
+ dependencies.output.log(`Verified commit: ${visible(status.checkout.acceptedCommit)}`);
36
+ dependencies.output.log(`Integration checkout: ${visible(status.checkout.path)}`);
37
+ for (const path of status.verification.changedPaths) dependencies.output.log(`Changed: ${visible(path)}`);
38
+ for (const check of status.verification.checks) dependencies.output.log(`Check: ${visible(check.id)} ${visible(check.status)}`);
39
+ }
40
+ options.assertDisplay();
41
+ const finish = async signal => {
42
+ let choice = record.status === 'completed' ? 'local' : await dependencies.taskApprovalPrompt({
43
+ type: 'select', signal, message: 'How would you like to finish this task?',
44
+ choices: completionChoices,
45
+ });
46
+ options.assertDisplay();
47
+ while (choice === 'review') {
48
+ signal.throwIfAborted();
49
+ const baseline = reviewedStatus?.run?.featurePlan?.baselineCommit ?? record.featurePlan?.baselineCommit;
50
+ const tip = reviewedStatus?.checkout?.acceptedCommit;
51
+ const gitClient = await createGitClient({gitExecutable: await dependencies.resolveCommandExecutable('git')});
52
+ try {
53
+ const diff = await gitClient.reviewDiff(project.root,{fromSha:baseline,toSha:tip});
54
+ dependencies.output.log(diff.patch ? displayPatch(diff.patch) : 'No file changes.');
55
+ if(diff.truncated) dependencies.output.log('Diff preview was truncated. Inspect the integration checkout before approval.');
56
+ } catch {
57
+ fail('Could not display the complete diff. Inspect the integration checkout shown above, then retry rivet task approve. No changes were applied.', 'REPOSITORY_CONFLICT');
58
+ }
59
+ options.assertDisplay();
60
+ choice = await dependencies.taskApprovalPrompt({type:'select',signal,message:'How would you like to finish this task?',choices:completionChoices});
61
+ options.assertDisplay();
62
+ }
63
+ if (choice === 'leave' || choice === null || choice === undefined) { dependencies.output.log('Final review cancelled. Work is saved for later. Continue with rivet task resume.'); return EXIT_CODES.SUCCESS; }
64
+ if (choice === 'pull-request') {
65
+ signal.throwIfAborted();
66
+ const result = await deliveryCommand({command:'delivery',subcommand:'prepare',operands:[],flags:{project:project.root,run:record.runId}}, dependencies);
67
+ if (result === EXIT_CODES.SUCCESS) {
68
+ dependencies.output.log(`Delivery prepared locally. From project ${visible(project.root)}, publish and create the review with separate approvals:`);
69
+ dependencies.output.log(`rivet delivery publish --run=${record.runId}`);
70
+ dependencies.output.log(`rivet delivery review --run=${record.runId}`);
71
+ dependencies.output.log('No remote write or merge has been approved by this selection.');
72
+ }
73
+ return result;
74
+ }
75
+ if (choice !== 'local') fail('Select local application or pull-request delivery.');
76
+ const gitClient = await createGitClient({gitExecutable: await dependencies.resolveCommandExecutable('git')});
77
+ let outputFailed = false;
78
+ const unobserve = observeOutputErrors(dependencies.output, () => { outputFailed = true; });
79
+ try {
80
+ const result = await applyVerifiedTask({project:project.root,runId:record.runId,gitClient,signal,confirm:async preview => {
81
+ try {
82
+ dependencies.output.log(`Apply to local branch: ${visible(preview.targetBranch)}`);
83
+ dependencies.output.log(`From: ${visible(preview.baselineCommit)}`);
84
+ dependencies.output.log(`To: ${visible(preview.commitSha)}`);
85
+ for (const path of preview.changedPaths) dependencies.output.log(`Changed: ${visible(path)}`);
86
+ for (const check of preview.checks) dependencies.output.log(`Check: ${visible(check.id)} ${visible(check.status)}`);
87
+ dependencies.output.log('This updates the original checkout using a fast-forward only. It does not push or deploy.');
88
+ } catch { fail('Could not display the local application preview. Nothing was approved.', 'REPOSITORY_CONFLICT'); }
89
+ if (outputFailed || signal.aborted) return false;
90
+ const approved = await dependencies.confirmTaskApplication(preview,{signal});
91
+ options.assertDisplay();
92
+ return approved === true && !outputFailed && !signal.aborted;
93
+ }});
94
+ dependencies.output.log(`Local application: ${visible(result.status)}.`);
95
+ if (result.nextAction) dependencies.output.log(visible(result.nextAction));
96
+ return EXIT_CODES.SUCCESS;
97
+ } catch (error) {
98
+ if (error instanceof CliError) throw error;
99
+ fail(error?.safeMessage ?? 'Local application could not complete safely. Inspect task status and preserve the checkouts.', 'REPOSITORY_CONFLICT');
100
+ } finally {unobserve?.();}
101
+ };
102
+ return options.signal ? finish(options.signal) : withTerminalInterruption(finish);
103
+ }
104
+
@@ -0,0 +1,234 @@
1
+ import { join } from 'node:path';
2
+ import { randomUUID } from 'node:crypto';
3
+
4
+ import { CliError, EXIT_CODES } from '../cli/output.js';
5
+ import {
6
+ checkForUpdate,
7
+ installedSkillName,
8
+ getAgentNames,
9
+ getMandatoryNames,
10
+ getSkillNames,
11
+ resolveTargetLocation,
12
+ withPinnedTargetDirectory,
13
+ } from './install.js';
14
+
15
+ function lstatIfExists(path, fs) {
16
+ try {
17
+ return fs.lstatSync(path);
18
+ } catch (error) {
19
+ if (error?.code === 'ENOENT') return null;
20
+ throw error;
21
+ }
22
+ }
23
+
24
+ function fileIdentity(status) {
25
+ return Object.freeze({ dev: status.dev, ino: status.ino });
26
+ }
27
+
28
+ function sameFileIdentity(left, right) {
29
+ return left.dev === right.dev && left.ino === right.ino;
30
+ }
31
+
32
+ function allocateQuarantineName(fs) {
33
+ for (let attempt = 0; attempt < 8; attempt += 1) {
34
+ const name = `.rivet-uninstall-${randomUUID()}`;
35
+ if (!lstatIfExists(name, fs)) return name;
36
+ }
37
+ throw new CliError('Could not allocate a private uninstall entry.', 'REPOSITORY_CONFLICT');
38
+ }
39
+
40
+ function restoreQuarantine(fs, quarantineName, destinationName, expectedIdentity, requireIdentity = true) {
41
+ const quarantineStatus = lstatIfExists(quarantineName, fs);
42
+ if (!quarantineStatus) return false;
43
+ if (requireIdentity && !sameFileIdentity(expectedIdentity, quarantineStatus)) return false;
44
+ if (lstatIfExists(destinationName, fs)) return false;
45
+ fs.renameSync(quarantineName, destinationName);
46
+ return true;
47
+ }
48
+
49
+ function removeSelectedEntry(name, fs) {
50
+ const selectedStatus = lstatIfExists(name, fs);
51
+ if (!selectedStatus) return false;
52
+ if (selectedStatus.isSymbolicLink()) {
53
+ throw new CliError('Managed uninstall entry is a symbolic link.', 'REPOSITORY_CONFLICT');
54
+ }
55
+
56
+ const expectedIdentity = fileIdentity(selectedStatus);
57
+ const quarantineName = allocateQuarantineName(fs);
58
+ fs.renameSync(name, quarantineName);
59
+
60
+ const quarantinedStatus = lstatIfExists(quarantineName, fs);
61
+ if (!quarantinedStatus || !sameFileIdentity(expectedIdentity, quarantinedStatus)) {
62
+ try {
63
+ restoreQuarantine(fs, quarantineName, name, expectedIdentity, false);
64
+ } catch {
65
+ // A mismatched entry is never recursively removed.
66
+ }
67
+ throw new CliError('Managed uninstall entry changed during validation.', 'REPOSITORY_CONFLICT');
68
+ }
69
+
70
+ try {
71
+ fs.rmSync(quarantineName, { recursive: true, force: false });
72
+ } catch (error) {
73
+ try {
74
+ restoreQuarantine(fs, quarantineName, name, expectedIdentity, true);
75
+ } catch {
76
+ // Best-effort rollback preserves a matching quarantine for manual recovery.
77
+ }
78
+ throw error;
79
+ }
80
+ return true;
81
+ }
82
+
83
+ async function resolveTargets(parsed, dependencies) {
84
+ if (parsed.flags.claude && parsed.flags.codex) return ['claude', 'codex'];
85
+ if (parsed.flags.claude) return ['claude'];
86
+ if (parsed.flags.codex) return ['codex'];
87
+
88
+ const target = parsed.flags.target;
89
+ if (typeof target === 'string') {
90
+ const normalized = target.toLowerCase();
91
+ if (normalized === 'both') return ['claude', 'codex'];
92
+ if (normalized === 'claude' || normalized === 'codex') return [normalized];
93
+ }
94
+ if (target) {
95
+ throw new CliError("Invalid --target value. Use 'claude', 'codex', or 'both'.", 'INVALID_INPUT');
96
+ }
97
+
98
+ try {
99
+ const selected = await dependencies.prompt({
100
+ message: 'Select destinations to uninstall:',
101
+ choices: [
102
+ { name: 'Claude Code', value: 'claude', checked: true },
103
+ { name: 'Codex', value: 'codex', checked: true },
104
+ ],
105
+ });
106
+ if (selected.length === 0) {
107
+ dependencies.output.log('No destinations selected. Nothing to do.');
108
+ return null;
109
+ }
110
+ return selected;
111
+ } catch {
112
+ dependencies.output.log('\nCancelled.');
113
+ return null;
114
+ }
115
+ }
116
+
117
+ export async function uninstall(parsed, dependencies) {
118
+ if (parsed.flags['project-runtime']) {
119
+ const { projectRuntimeInstall } = await import('../install/project-runtime.js');
120
+ return projectRuntimeInstall(parsed, dependencies);
121
+ }
122
+ if (parsed.flags.minimal) {
123
+ const { managedInstall } = await import('../install/managed.js');
124
+ return managedInstall(parsed, dependencies);
125
+ }
126
+ const { fs, output, prompt } = dependencies;
127
+ const targets = await resolveTargets(parsed, dependencies);
128
+ if (!targets) return EXIT_CODES.SUCCESS;
129
+ const targetDirs = targets.map(target => resolveTargetLocation(target, parsed, dependencies));
130
+
131
+ for (const targetLocation of targetDirs) {
132
+ targetLocation.identity = targetLocation.guard.assertPath(targetLocation.dir);
133
+ }
134
+
135
+ const existingTargetDirs = targetDirs.filter(({ dir }) => fs.existsSync(dir));
136
+ if (existingTargetDirs.length === 0) {
137
+ output.warn('Warning: skills directories not found for selected targets. Nothing to uninstall.');
138
+ return EXIT_CODES.SUCCESS;
139
+ }
140
+
141
+ const mandatorySet = new Set(getMandatoryNames(dependencies).map(installedSkillName));
142
+ const existingOptional = new Set();
143
+ const existingMandatory = new Set();
144
+ const packageNames = [
145
+ ...getSkillNames(dependencies),
146
+ ...getAgentNames(dependencies),
147
+ ...getMandatoryNames(dependencies),
148
+ ].map(installedSkillName);
149
+ for (const { dir } of existingTargetDirs) {
150
+ for (const name of packageNames) {
151
+ if (fs.existsSync(join(dir, name))) {
152
+ if (mandatorySet.has(name)) existingMandatory.add(name);
153
+ else existingOptional.add(name);
154
+ }
155
+ }
156
+ }
157
+
158
+ const allOptional = [...existingOptional];
159
+ const allMandatory = [...existingMandatory];
160
+ if (allOptional.length === 0 && allMandatory.length === 0) {
161
+ output.warn("Warning: None of this package's skills were found at selected targets. Nothing removed.");
162
+ return EXIT_CODES.SUCCESS;
163
+ }
164
+
165
+ let selectedOptional;
166
+ let removeMandatory = false;
167
+ if (parsed.flags.all) {
168
+ selectedOptional = allOptional;
169
+ removeMandatory = true;
170
+ } else {
171
+ if (allMandatory.length > 0) {
172
+ output.log('\nMandatory skills (included in uninstall by default):');
173
+ allMandatory.forEach(name => output.log(` [required] ${name}`));
174
+ }
175
+ try {
176
+ const choices = [
177
+ ...allMandatory.map(name => ({ name: `${name} [mandatory]`, value: `mandatory:${name}`, checked: true })),
178
+ ...allOptional.map(name => ({ name, value: name, checked: true })),
179
+ ];
180
+ const raw = await prompt({
181
+ message: 'Select skills to uninstall (mandatory included by default):',
182
+ choices,
183
+ });
184
+ selectedOptional = raw.filter(value => !value.startsWith('mandatory:')).map(value => value);
185
+ removeMandatory = raw.some(value => value.startsWith('mandatory:'));
186
+ } catch {
187
+ output.log('\nCancelled.');
188
+ return EXIT_CODES.SUCCESS;
189
+ }
190
+ if (!removeMandatory && selectedOptional.length === 0) {
191
+ output.log('No skills selected. Nothing uninstalled.');
192
+ return EXIT_CODES.SUCCESS;
193
+ }
194
+ }
195
+
196
+ const selected = [
197
+ ...(removeMandatory ? allMandatory : []),
198
+ ...selectedOptional,
199
+ ];
200
+ const removed = [];
201
+ for (const name of selected) {
202
+ let removedSomewhere = false;
203
+ for (const { dir, guard, identity } of existingTargetDirs) {
204
+ const destination = join(dir, name);
205
+ guard.assertPath(destination);
206
+ if (fs.existsSync(destination)) {
207
+ try {
208
+ guard.assertPath(destination);
209
+ const didRemove = withPinnedTargetDirectory(
210
+ dir,
211
+ identity,
212
+ fs,
213
+ () => removeSelectedEntry(name, fs),
214
+ );
215
+ guard.assertPath(dir);
216
+ removedSomewhere ||= didRemove;
217
+ } catch (error) {
218
+ throw new CliError(`Failed to uninstall '${name}'.`, 'REPOSITORY_CONFLICT', {
219
+ cause: error,
220
+ });
221
+ }
222
+ }
223
+ }
224
+ if (removedSomewhere) removed.push(name);
225
+ }
226
+
227
+ output.log(`Uninstalled ${removed.length} skills for: ${targets.join(', ')}`);
228
+ removed.forEach(name => output.log(` - ${name}`));
229
+ existingTargetDirs.forEach(({ target, displayDir }) => {
230
+ output.log(` -> ${target}: ${displayDir}`);
231
+ });
232
+ await checkForUpdate(dependencies);
233
+ return EXIT_CODES.SUCCESS;
234
+ }