@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,212 @@
1
+ import { parseAcceptanceCriteriaText } from '../work-request/contract.js';
2
+ import { isAbsolute } from 'node:path';
3
+
4
+ import { immutableJson } from '../clients/contract.js';
5
+ import { CliError, EXIT_CODES } from '../cli/output.js';
6
+
7
+ const RUN_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
8
+ const DIGEST = /^[a-f0-9]{64}$/;
9
+ const TICKET = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
10
+ const SUBCOMMANDS = new Set(['propose', 'start', 'run', 'status', 'resume', 'cancel']);
11
+
12
+ function fail(message, code = 'INVALID_INPUT') { throw new CliError(message, code); }
13
+
14
+ function supplementalCriteria(value) {
15
+ try { return parseAcceptanceCriteriaText(value); }
16
+ catch { fail('Acceptance criteria must be nonempty bounded text, one criterion per line, without duplicates, control characters or credentials.'); }
17
+ }
18
+
19
+ function service(dependencies, method) {
20
+ const feature = dependencies?.feature;
21
+ if (!feature || typeof feature !== 'object' || typeof feature[method] !== 'function') {
22
+ fail('Feature workflow is not configured.', 'MISSING_CONFIGURATION');
23
+ }
24
+ return feature;
25
+ }
26
+
27
+ async function invoke(feature, method, input, options) {
28
+ try {
29
+ return await feature[method](input, options);
30
+ } catch (error) {
31
+ if (error instanceof CliError) throw error;
32
+ const code = String(error?.code ?? '');
33
+ let publicCode = 'INTERNAL_ERROR';
34
+ if (code === 'ERR_FEATURE_WORKFLOW_CONFIGURATION' || code === 'ERR_APPLICATION_CONFIGURATION'
35
+ || code === 'ERR_TRACKER_PROVIDER_CONFIGURATION') {
36
+ publicCode = 'MISSING_CONFIGURATION';
37
+ } else if (code.startsWith('ERR_HOST_RUN_') || code === 'ERR_FEATURE_WORKFLOW_REPOSITORY' || code === 'ERR_FEATURE_WORKFLOW_STATE_CONFLICT'
38
+ || code === 'ERR_FEATURE_WORKFLOW_PROPOSAL_MISMATCH' || code === 'ERR_FEATURE_RUN_VERSION_CONFLICT'
39
+ || code.startsWith('ERR_GIT_')) {
40
+ publicCode = 'REPOSITORY_CONFLICT';
41
+ } else if (code === 'ERR_FEATURE_WORKFLOW_HOST_USE_WORK' || code === 'ERR_FEATURE_WORKFLOW_INVALID_INPUT' || code === 'ERR_INVALID_WORK_REQUEST'
42
+ || code === 'ERR_INVALID_FEATURE_PLAN' || code === 'ERR_INVALID_FEATURE_RUN') {
43
+ publicCode = 'INVALID_INPUT';
44
+ } else if (code.startsWith('ERR_PROVIDER_') || code.startsWith('ERR_AGENT_')) {
45
+ publicCode = 'PROVIDER_UNAVAILABLE';
46
+ }
47
+ const message = typeof error?.safeMessage === 'string' && error.safeMessage.length <= 1000
48
+ ? error.safeMessage : 'Unexpected rivet failure.';
49
+ throw new CliError(message, publicCode, { cause: error });
50
+ }
51
+ }
52
+
53
+ export { invoke as invokeFeature };
54
+
55
+ function project(value) {
56
+ if (typeof value !== 'string' || value.length < 2 || value.length > 4096 || !isAbsolute(value)
57
+ || /[\u0000\r\n]/.test(value)) fail("Feature option 'project' is invalid.");
58
+ return value;
59
+ }
60
+
61
+ function runId(value) {
62
+ if (typeof value !== 'string' || value.length > 64 || !RUN_ID.test(value)) fail('Feature run ID is invalid.');
63
+ return value;
64
+ }
65
+
66
+ function version(value) {
67
+ const parsed = typeof value === 'string' && /^[1-9][0-9]*$/.test(value) ? Number(value) : value;
68
+ if (!Number.isSafeInteger(parsed) || parsed < 1) fail("Feature option 'expected-version' is invalid.");
69
+ return parsed;
70
+ }
71
+
72
+ function flags(parsed, allowed) {
73
+ if (!parsed.flags || typeof parsed.flags !== 'object' || Array.isArray(parsed.flags)
74
+ || Object.keys(parsed.flags).some(key => !allowed.includes(key))) fail('Feature command options are invalid.');
75
+ return parsed.flags;
76
+ }
77
+
78
+ function lifecycleInput(parsed, allowed, { mutation = false, digest = false } = {}) {
79
+ const options = flags(parsed, [...allowed, 'run']);
80
+ const hasRunFlag = options.run !== undefined;
81
+ if (!Array.isArray(parsed.operands) || parsed.operands.length !== (hasRunFlag ? 0 : 1)) {
82
+ fail('Use one run selector: --run or a positional run ID.');
83
+ }
84
+ const input = { project: project(options.project), runId: runId(hasRunFlag ? options.run : parsed.operands[0]) };
85
+ if (mutation) input.expectedVersion = version(options['expected-version']);
86
+ if (digest) {
87
+ if (typeof options['proposal-digest'] !== 'string' || !DIGEST.test(options['proposal-digest'])) {
88
+ fail("Feature option 'proposal-digest' is invalid.");
89
+ }
90
+ input.proposalDigest = options['proposal-digest'];
91
+ }
92
+ return Object.freeze(input);
93
+ }
94
+
95
+ function sourceInput(parsed) {
96
+ const options = flags(parsed, [
97
+ 'project', 'request', 'request-text', 'ticket', 'tracker', 'client', 'json', 'acceptance-criteria',
98
+ ]);
99
+ if (!Array.isArray(parsed.operands) || parsed.operands.length !== 0) fail('Feature proposal commands do not accept a run ID.');
100
+ const selectors = ['request', 'request-text', 'ticket'].filter(key => options[key] !== undefined);
101
+ if (selectors.length !== 1) fail('Feature proposal requires exactly one request source.');
102
+ const selected = selectors[0];
103
+ const projectPath = project(options.project);
104
+ let source;
105
+ if (selected === 'request') {
106
+ if (typeof options.request !== 'string' || options.request.length < 2 || options.request.length > 4096
107
+ || !isAbsolute(options.request) || /[\u0000\r\n]/.test(options.request)) fail("Feature option 'request' is invalid.");
108
+ source = Object.freeze({ kind: 'file', value: options.request });
109
+ } else if (selected === 'request-text') {
110
+ if (typeof options['request-text'] !== 'string' || options['request-text'].length < 1
111
+ || Buffer.byteLength(options['request-text'], 'utf8') > 64 * 1024 || options['request-text'].includes('\0')) {
112
+ fail("Feature option 'request-text' is invalid.");
113
+ }
114
+ source = Object.freeze({ kind: 'inline', value: options['request-text'] });
115
+ } else {
116
+ if (typeof options.ticket !== 'string' || !TICKET.test(options.ticket)) fail("Feature option 'ticket' is invalid.");
117
+ source = Object.freeze({ kind: 'ticket', value: options.ticket });
118
+ }
119
+ if (selected !== 'ticket' && options['acceptance-criteria'] !== undefined) fail('--acceptance-criteria requires --ticket.');
120
+ if (selected !== 'ticket' && options.tracker !== undefined) fail("Feature option 'tracker' requires '--ticket'.");
121
+ if (options.tracker !== undefined && !['jira', 'linear'].includes(options.tracker)) fail("Feature option 'tracker' is invalid.");
122
+ if (options.client !== undefined && !['claude', 'codex'].includes(options.client)) fail("Feature option 'client' is invalid.");
123
+ if (selected !== 'ticket' && options.client === undefined) fail("Feature option 'client' is required for local requests.");
124
+ return Object.freeze({
125
+ project: projectPath,
126
+ source,
127
+ ...(options['acceptance-criteria'] === undefined ? {} : { userAcceptanceCriteria: supplementalCriteria(options['acceptance-criteria']) }),
128
+ ...(options.client === undefined ? {} : { client: options.client }),
129
+ ...(options.tracker === undefined ? {} : { tracker: options.tracker }),
130
+ });
131
+ }
132
+
133
+ function safeResult(value) {
134
+ try { return immutableJson(value); }
135
+ catch { fail('Feature workflow returned an invalid result.', 'INTERNAL_ERROR'); }
136
+ }
137
+
138
+ function proposalResult(value) {
139
+ const result = safeResult(value);
140
+ if (!result || typeof result !== 'object' || Array.isArray(result) || !RUN_ID.test(result.runId)
141
+ || result.runId.length > 64 || !Number.isSafeInteger(result.version) || result.version < 1
142
+ || typeof result.proposalDigest !== 'string' || !DIGEST.test(result.proposalDigest)
143
+ || (result.summary !== undefined && (typeof result.summary !== 'string' || result.summary.length > 4000))) {
144
+ fail('Feature workflow returned an invalid proposal.', 'INTERNAL_ERROR');
145
+ }
146
+ return result;
147
+ }
148
+
149
+ function emit(parsed, dependencies, result) {
150
+ const payload = { ok: true, command: 'feature', subcommand: parsed.subcommand, result: safeResult(result) };
151
+ if (parsed.flags.json) dependencies.output.json(payload);
152
+ else dependencies.output.log(`Feature ${parsed.subcommand}: ${payload.result.status ?? payload.result.runId ?? 'ok'}.`);
153
+ return EXIT_CODES.SUCCESS;
154
+ }
155
+
156
+ function displayCriteria(dependencies, request) {
157
+ if (!request?.criteriaProvenance) return;
158
+ dependencies.output.log('Tracker acceptance criteria:');
159
+ for (const item of request.criteriaProvenance.sourceAcceptanceCriteria) dependencies.output.log(` - ${JSON.stringify(item)}`);
160
+ if (!request.criteriaProvenance.sourceAcceptanceCriteria.length) dependencies.output.log(' (none supplied by tracker)');
161
+ dependencies.output.log('User-supplied acceptance criteria:');
162
+ for (const item of request.criteriaProvenance.userAcceptanceCriteria) dependencies.output.log(` - ${JSON.stringify(item)}`);
163
+ }
164
+
165
+ function displayProposal(dependencies, proposal) {
166
+ displayCriteria(dependencies, proposal.workRequest);
167
+ const summary = proposal.summary === undefined ? 'Ready for activation review.' : proposal.summary;
168
+ dependencies.output.log(`Feature proposal ${proposal.runId} (version ${proposal.version}, digest ${proposal.proposalDigest}): ${summary}`);
169
+ }
170
+
171
+ export async function featureCommand(parsed, dependencies) {
172
+ if (!parsed || parsed.command !== 'feature' || !SUBCOMMANDS.has(parsed.subcommand)) fail('Feature subcommand is invalid.');
173
+ if (parsed.subcommand === 'propose') {
174
+ const feature = service(dependencies, 'propose');
175
+ const result = proposalResult(await invoke(feature, 'propose', sourceInput(parsed)));
176
+ if (!parsed.flags.json) displayCriteria(dependencies, result.workRequest);
177
+ return emit(parsed, dependencies, result);
178
+ }
179
+ if (parsed.subcommand === 'run') {
180
+ if (parsed.flags?.json) fail("Feature command 'run' is interactive; use 'propose' and 'start' in JSON mode.");
181
+ const feature = service(dependencies, 'propose');
182
+ if (typeof feature.start !== 'function' || typeof feature.watch !== 'function'
183
+ || typeof dependencies.confirmFeatureActivation !== 'function') fail('Feature workflow is not configured.', 'MISSING_CONFIGURATION');
184
+ const input = sourceInput(parsed);
185
+ const proposal = proposalResult(await invoke(feature, 'propose', input));
186
+ displayProposal(dependencies, proposal);
187
+ if (await dependencies.confirmFeatureActivation(proposal) !== true) {
188
+ return emit(parsed, dependencies, { runId: proposal.runId, version: proposal.version, status: 'proposed', activation: 'declined' });
189
+ }
190
+ const started = safeResult(await invoke(feature, 'start', Object.freeze({
191
+ project: input.project, runId: proposal.runId, expectedVersion: proposal.version,
192
+ proposalDigest: proposal.proposalDigest,
193
+ })));
194
+ if (!Number.isSafeInteger(started.version) || started.version < 1) fail('Feature workflow returned an invalid result.', 'INTERNAL_ERROR');
195
+ const watched = await invoke(feature, 'watch', Object.freeze({ project: input.project, runId: proposal.runId, expectedVersion: started.version }));
196
+ return emit(parsed, dependencies, watched);
197
+ }
198
+ if (parsed.subcommand === 'status') {
199
+ const feature = service(dependencies, 'status');
200
+ return emit(parsed, dependencies, await invoke(feature, 'status', lifecycleInput(parsed, ['project', 'json'])));
201
+ }
202
+ if (parsed.subcommand === 'start') {
203
+ const feature = service(dependencies, 'start');
204
+ return emit(parsed, dependencies, await invoke(feature, 'start', lifecycleInput(
205
+ parsed, ['project', 'expected-version', 'proposal-digest', 'json'], { mutation: true, digest: true },
206
+ )));
207
+ }
208
+ const feature = service(dependencies, parsed.subcommand);
209
+ return emit(parsed, dependencies, await invoke(feature, parsed.subcommand, lifecycleInput(
210
+ parsed, ['project', 'expected-version', 'json'], { mutation: true },
211
+ )));
212
+ }
@@ -0,0 +1,231 @@
1
+ import {
2
+ cancelGoal, cancelNode, createCorrectiveNode, recoverRuntimeLock, recoverStalledNode, retryNode,
3
+ } from '../runtime/recovery.js';
4
+ import { containsSecretMaterial } from '../clients/contract.js';
5
+ import { validateEvent } from '../config/validate.js';
6
+ import { validatedGraphSnapshot } from '../graph/validate.js';
7
+ import { CliError, EXIT_CODES } from '../cli/output.js';
8
+
9
+ function immutable(value) {
10
+ if (Array.isArray(value)) return Object.freeze(value.map(immutable));
11
+ if (value && typeof value === 'object') return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, child]) => [key, immutable(child)])));
12
+ return value;
13
+ }
14
+
15
+ function boundedSnapshot(input) {
16
+ let seen = 0;
17
+ function visit(value, depth) {
18
+ if (depth > 32 || ++seen > 100_000) throw new TypeError('Invalid runtime state');
19
+ if (value === null || typeof value === 'boolean') return value;
20
+ if (typeof value === 'string') {
21
+ if (value.length > 16_384 || containsSecretMaterial(value)) throw new TypeError('Invalid runtime state');
22
+ return value;
23
+ }
24
+ if (typeof value === 'number') { if (!Number.isFinite(value)) throw new TypeError('Invalid runtime state'); return value; }
25
+ if (!value || typeof value !== 'object') throw new TypeError('Invalid runtime state');
26
+ if (Array.isArray(value)) {
27
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, 'length');
28
+ const length = lengthDescriptor?.value;
29
+ if (!Number.isSafeInteger(length) || length < 0 || length > 10_000 || Reflect.ownKeys(value).length !== length + 1) throw new TypeError('Invalid runtime state');
30
+ return Array.from({ length }, (_, index) => {
31
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
32
+ if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')) throw new TypeError('Invalid runtime state');
33
+ return visit(descriptor.value, depth + 1);
34
+ });
35
+ }
36
+ const keys = Reflect.ownKeys(value);
37
+ if (keys.length > 10_000 || keys.some(key => typeof key !== 'string')) throw new TypeError('Invalid runtime state');
38
+ const output = {};
39
+ for (const key of keys) {
40
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
41
+ if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')) throw new TypeError('Invalid runtime state');
42
+ output[key] = visit(descriptor.value, depth + 1);
43
+ }
44
+ return output;
45
+ }
46
+ return visit(input, 0);
47
+ }
48
+
49
+ async function read(instance) {
50
+ if (!instance || typeof instance.acquire !== 'function' || typeof instance.read !== 'function') throw new TypeError('Invalid runtime instance');
51
+ const lock = await instance.acquire();
52
+ try { return boundedSnapshot(await instance.read()); }
53
+ catch { throw new TypeError('Invalid runtime state'); }
54
+ finally { await lock.release(); }
55
+ }
56
+
57
+ export async function goalsStatus(instance) {
58
+ const state = await read(instance);
59
+ if (!Number.isSafeInteger(state.version) || state.version < 0 || (state.terminal !== null && state.terminal !== undefined && !['completed', 'blocked', 'cancelled', 'budget-exhausted', 'failed'].includes(state.terminal))) throw new TypeError('Invalid runtime state');
60
+ const graph = validatedGraphSnapshot(state.graph);
61
+ return immutable({ version: state.version, activated: state.activated === true, terminal: state.terminal ?? null, graphId: graph.id, graphStatus: graph.status, nodes: graph.nodes.slice(0, 1000).map(node => ({ id: node.id, parentId: node.parentId ?? null, role: node.owner.role, status: node.status })) });
62
+ }
63
+
64
+ export async function goalsEvents(instance, input = {}) {
65
+ let keys; let limit;
66
+ try { keys = Reflect.ownKeys(input); if (keys.some(key => key !== 'limit')) throw new TypeError(); limit = keys.includes('limit') ? input.limit : 100; }
67
+ catch { throw new TypeError('Invalid event limit'); }
68
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1000) throw new TypeError('Invalid event limit');
69
+ const state = await read(instance);
70
+ if (!Array.isArray(state.events) || state.events.length > 10_000) throw new TypeError('Invalid runtime state');
71
+ for (const event of state.events) { try { validateEvent(event); } catch { throw new TypeError('Invalid runtime state'); } }
72
+ return immutable(state.events.slice(-limit).map(event => ({
73
+ sequence: event.sequence, timestamp: event.timestamp, type: event.type,
74
+ ...(event.nodeId ? { nodeId: event.nodeId } : {}),
75
+ ...(event.priorState ? { priorState: event.priorState, newState: event.newState } : {}),
76
+ actorRole: event.actor?.role ?? 'system', evidenceCount: event.evidenceRefs?.length ?? 0,
77
+ })));
78
+ }
79
+
80
+ export async function goalsRecoverLock(target, input) { return recoverRuntimeLock(target, input); }
81
+ export async function goalsRetryNode(runtime, instance, input) {
82
+ if (!runtime || typeof runtime.retryNode !== 'function') throw new TypeError('Invalid runtime');
83
+ return runtime.retryNode(instance, input);
84
+ }
85
+ export async function goalsCancelNode(runtime, instance, input) {
86
+ if (!runtime || typeof runtime.cancelNode !== 'function') throw new TypeError('Invalid runtime');
87
+ return runtime.cancelNode(instance, input);
88
+ }
89
+ export async function goalsCancelGoal(runtime, instance, input) {
90
+ if (!runtime || typeof runtime.cancelGoal !== 'function') throw new TypeError('Invalid runtime');
91
+ return runtime.cancelGoal(instance, input);
92
+ }
93
+ export async function goalsCreateCorrectiveNode(instance, input) { return createCorrectiveNode(instance, input); }
94
+ export async function goalsRecoverStalledNode(instance, input) { return recoverStalledNode(instance, input); }
95
+
96
+ function cliFailure(message, code = 'INVALID_INPUT') { throw new CliError(message, code); }
97
+ function cliInstanceId(parsed) {
98
+ if (!parsed || parsed.command !== 'goals' || !Array.isArray(parsed.operands) || parsed.operands.length !== 1
99
+ || typeof parsed.operands[0] !== 'string' || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(parsed.operands[0])
100
+ || parsed.operands[0].length > 64) cliFailure('Goals commands require one explicit private instance ID.');
101
+ return parsed.operands[0];
102
+ }
103
+ function cliFlags(parsed, allowed) {
104
+ const flags = parsed?.flags;
105
+ if (!flags || typeof flags !== 'object' || Array.isArray(flags)) cliFailure('Goals command options are invalid.');
106
+ const keys = Object.keys(flags);
107
+ if (keys.some(key => !allowed.includes(key))) cliFailure('Goals command options are invalid.');
108
+ return flags;
109
+ }
110
+ function cliInteger(value, name, { minimum = 0, maximum = Number.MAX_SAFE_INTEGER } = {}) {
111
+ const parsed = typeof value === 'string' && /^(?:0|[1-9][0-9]*)$/.test(value) ? Number(value) : value;
112
+ if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) cliFailure(`Goals option '${name}' is invalid.`);
113
+ return parsed;
114
+ }
115
+ function cliId(value, name) {
116
+ if (typeof value !== 'string' || value.length > 64 || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value)) cliFailure(`Goals option '${name}' is invalid.`);
117
+ return value;
118
+ }
119
+ function cliReason(value) {
120
+ if (typeof value !== 'string' || value.length < 1 || value.length > 1000 || /[\u0000\r\n]/.test(value)) cliFailure("Goals option 'reason' is invalid.");
121
+ return value;
122
+ }
123
+ function cliDependencies(dependencies, { mutation = false, runtimeMethod } = {}) {
124
+ const context = dependencies?.orchestration;
125
+ if (!context || typeof context.resolveInstance !== 'function') cliFailure('Private orchestration is not configured.', 'MISSING_CONFIGURATION');
126
+ if (mutation && typeof context.requestFor !== 'function') cliFailure('Private orchestration mutation input is not configured.', 'MISSING_CONFIGURATION');
127
+ if (runtimeMethod && (!context.runtime || typeof context.runtime[runtimeMethod] !== 'function')) cliFailure('Private orchestration runtime is not configured.', 'MISSING_CONFIGURATION');
128
+ return context;
129
+ }
130
+ async function cliContext(instanceId, context) {
131
+ const instance = await context.resolveInstance(instanceId);
132
+ if (!instance || instance.id !== instanceId) cliFailure('Private orchestration instance is unavailable.', 'MISSING_CONFIGURATION');
133
+ return { context, instance, instanceId };
134
+ }
135
+ const MUTATION_INPUT_SHAPES = Object.freeze({
136
+ 'recover-lock': { allowed: ['expectedVersion', 'nowMs', 'authority', 'approval', 'approvalRegistry', 'expectedApproverId'], required: ['expectedVersion', 'nowMs', 'authority', 'approval', 'approvalRegistry', 'expectedApproverId'] },
137
+ 'retry-node': { allowed: ['expectedVersion', 'nodeId', 'authority', 'reason', 'nowMs'], required: ['expectedVersion', 'nodeId', 'authority', 'reason'] },
138
+ 'cancel-node': { allowed: ['expectedVersion', 'nodeId', 'authority', 'nowMs'], required: ['expectedVersion', 'nodeId', 'authority'] },
139
+ 'cancel-goal': { allowed: ['expectedVersion', 'authority', 'nowMs'], required: ['expectedVersion', 'authority'] },
140
+ 'create-corrective-node': { allowed: ['expectedVersion', 'sourceNodeId', 'nodeId', 'ownerId', 'authority', 'reason', 'evidenceRefs', 'evidenceTypes', 'nowMs'], required: ['expectedVersion', 'sourceNodeId', 'nodeId', 'ownerId', 'authority', 'reason', 'evidenceRefs'] },
141
+ 'recover-stalled-node': { allowed: ['expectedVersion', 'nodeId', 'authority', 'nowMs', 'reason'], required: ['expectedVersion', 'nodeId', 'authority', 'nowMs', 'reason'] },
142
+ });
143
+ const CLI_RUNTIME_BINDINGS = Object.freeze({ 'expected-version': 'expectedVersion', node: 'nodeId', source: 'sourceNodeId', owner: 'ownerId', reason: 'reason', 'now-ms': 'nowMs' });
144
+ function snapshotMutationArray(value) {
145
+ if (!Array.isArray(value)) throw new TypeError();
146
+ const length = Object.getOwnPropertyDescriptor(value, 'length')?.value;
147
+ if (!Number.isSafeInteger(length) || length < 0 || length > 8 || Reflect.ownKeys(value).length !== length + 1) throw new TypeError();
148
+ return Object.freeze(Array.from({ length }, (_unused, index) => {
149
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
150
+ if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')) throw new TypeError();
151
+ return descriptor.value;
152
+ }));
153
+ }
154
+ function snapshotMutationInput(input, subcommand, flags) {
155
+ const shape = MUTATION_INPUT_SHAPES[subcommand]; const output = Object.create(null);
156
+ try {
157
+ if (!shape || !input || typeof input !== 'object' || Array.isArray(input)) throw new TypeError();
158
+ const keys = Reflect.ownKeys(input);
159
+ if (keys.length > shape.allowed.length || keys.some(key => typeof key !== 'string' || !shape.allowed.includes(key))) throw new TypeError();
160
+ for (const key of keys) {
161
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
162
+ if (!descriptor?.enumerable) throw new TypeError();
163
+ const value = input[key];
164
+ output[key] = ['evidenceRefs', 'evidenceTypes'].includes(key) ? snapshotMutationArray(value) : value;
165
+ }
166
+ if (shape.required.some(key => !Object.hasOwn(output, key))) throw new TypeError();
167
+ } catch { cliFailure('Private orchestration mutation input is invalid.', 'MISSING_CONFIGURATION'); }
168
+ for (const [flag, field] of Object.entries(CLI_RUNTIME_BINDINGS)) {
169
+ if (Object.hasOwn(flags, flag) && output[field] !== flags[flag]) cliFailure('Private orchestration mutation input does not match the CLI request.', 'MISSING_CONFIGURATION');
170
+ }
171
+ return Object.freeze(output);
172
+ }
173
+ async function cliMutationInput(context, parsed, instanceId, flags) {
174
+ const input = await context.requestFor(Object.freeze({ command: 'goals', subcommand: parsed.subcommand, instanceId, flags }));
175
+ return snapshotMutationInput(input, parsed.subcommand, flags);
176
+ }
177
+ function emitCliResult(parsed, dependencies, result) {
178
+ const payload = { ok: true, command: 'goals', subcommand: parsed.subcommand, result };
179
+ if (parsed.flags.json) dependencies.output.json(payload);
180
+ else dependencies.output.log(`Goals ${parsed.subcommand}: ${result.terminal ?? result.graphStatus ?? 'ok'}.`);
181
+ return EXIT_CODES.SUCCESS;
182
+ }
183
+
184
+ export async function goalsCommand(parsed, dependencies) {
185
+ const instanceId = cliInstanceId(parsed);
186
+ let eventLimit; let mutationFlags; let runtimeMethod;
187
+ const mutations = new Set(['recover-lock', 'retry-node', 'cancel-node', 'cancel-goal', 'create-corrective-node', 'recover-stalled-node']);
188
+ if (parsed.subcommand === 'status') cliFlags(parsed, ['json']);
189
+ else if (parsed.subcommand === 'events') {
190
+ const flags = cliFlags(parsed, ['json', 'limit']);
191
+ eventLimit = flags.limit === undefined ? 100 : cliInteger(flags.limit, 'limit', { minimum: 1, maximum: 1000 });
192
+ } else if (mutations.has(parsed.subcommand)) {
193
+ const shape = {
194
+ 'recover-lock': { ids: [], reason: false },
195
+ 'retry-node': { ids: ['node'], reason: true },
196
+ 'cancel-node': { ids: ['node'], reason: false },
197
+ 'cancel-goal': { ids: [], reason: false },
198
+ 'create-corrective-node': { ids: ['source', 'node', 'owner'], reason: true },
199
+ 'recover-stalled-node': { ids: ['node'], reason: true },
200
+ }[parsed.subcommand];
201
+ const flags = cliFlags(parsed, ['json', 'expected-version', 'now-ms', ...shape.ids, ...(shape.reason ? ['reason'] : [])]);
202
+ mutationFlags = {
203
+ ...flags,
204
+ 'expected-version': cliInteger(flags['expected-version'], 'expected-version'),
205
+ ...(flags['now-ms'] === undefined ? {} : { 'now-ms': cliInteger(flags['now-ms'], 'now-ms') }),
206
+ ...Object.fromEntries(shape.ids.map(name => [name, cliId(flags[name], name)])),
207
+ ...(shape.reason ? { reason: cliReason(flags.reason) } : {}),
208
+ };
209
+ runtimeMethod = ({ 'retry-node': 'retryNode', 'cancel-node': 'cancelNode', 'cancel-goal': 'cancelGoal', 'recover-stalled-node': 'recoverStalledNode' })[parsed.subcommand];
210
+ }
211
+ else cliFailure('Unknown goals subcommand.');
212
+ const mutation = mutations.has(parsed.subcommand);
213
+ const context = cliDependencies(dependencies, { mutation, runtimeMethod });
214
+ const input = mutation ? await cliMutationInput(context, parsed, instanceId, Object.freeze(mutationFlags)) : undefined;
215
+ const { instance } = await cliContext(instanceId, context);
216
+ let result;
217
+ if (parsed.subcommand === 'status') {
218
+ result = await goalsStatus(instance);
219
+ } else if (parsed.subcommand === 'events') {
220
+ result = await goalsEvents(instance, { limit: eventLimit });
221
+ } else {
222
+ if (parsed.subcommand === 'recover-lock') await goalsRecoverLock(instance, input);
223
+ else if (parsed.subcommand === 'retry-node') await goalsRetryNode(context.runtime, instance, input);
224
+ else if (parsed.subcommand === 'cancel-node') await goalsCancelNode(context.runtime, instance, input);
225
+ else if (parsed.subcommand === 'cancel-goal') await goalsCancelGoal(context.runtime, instance, input);
226
+ else if (parsed.subcommand === 'create-corrective-node') await goalsCreateCorrectiveNode(instance, input);
227
+ else if (parsed.subcommand === 'recover-stalled-node') await context.runtime.recoverStalledNode(instance, input);
228
+ result = await goalsStatus(instance);
229
+ }
230
+ return emitCliResult(parsed, dependencies, result);
231
+ }