@arbiterforge/ca-pi 0.14.10 → 0.14.12

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 (38) hide show
  1. package/README.md +7 -2
  2. package/package.json +1 -1
  3. package/plugins/ca-pi/CHANGELOG.md +28 -0
  4. package/plugins/ca-pi/SKILLS.md +2 -2
  5. package/plugins/ca-pi/SPRINT.md +47 -0
  6. package/plugins/ca-pi/generated/command-catalog.json +2 -2
  7. package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-amd64 +0 -0
  8. package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-arm64 +0 -0
  9. package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-amd64 +0 -0
  10. package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-arm64 +0 -0
  11. package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-amd64.exe +0 -0
  12. package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-arm64.exe +0 -0
  13. package/plugins/ca-pi/helpers/artifacts/release.json +6 -6
  14. package/plugins/ca-pi/hooks/_approvallib.py +58 -22
  15. package/plugins/ca-pi/hooks/_artifactauthoritylib.py +1686 -0
  16. package/plugins/ca-pi/hooks/_artifactlib.py +14 -4
  17. package/plugins/ca-pi/hooks/_artifactpromptlib.py +177 -0
  18. package/plugins/ca-pi/hooks/_host.py +1 -1
  19. package/plugins/ca-pi/hooks/_overrideappendlib.py +5 -1
  20. package/plugins/ca-pi/hooks/_prerequisitelib.py +44 -22
  21. package/plugins/ca-pi/hooks/_reconciliationlib.py +472 -0
  22. package/plugins/ca-pi/hooks/_sprintapprovallib.py +302 -0
  23. package/plugins/ca-pi/hooks/artifact-authority-hook.py +72 -0
  24. package/plugins/ca-pi/hooks/artifact-authority.py +80 -0
  25. package/plugins/ca-pi/hooks/artifact-reconcile.py +57 -0
  26. package/plugins/ca-pi/hooks/hostapi.py +1 -1
  27. package/plugins/ca-pi/hooks/prompt-submit.py +20 -3
  28. package/plugins/ca-pi/includes/artifacts.md +99 -12
  29. package/plugins/ca-pi/includes/sprint-authority.md +69 -0
  30. package/plugins/ca-pi/package.json +1 -1
  31. package/plugins/ca-pi/routines/INDEX.md +7 -4
  32. package/plugins/ca-pi/routines/brainstorming/SKILL.md +97 -29
  33. package/plugins/ca-pi/routines/commit-gate/SKILL.md +7 -1
  34. package/plugins/ca-pi/routines/executing-plans/SKILL.md +32 -15
  35. package/plugins/ca-pi/routines/skill-author/SKILL.md +26 -5
  36. package/plugins/ca-pi/routines/writing-plans/SKILL.md +22 -10
  37. package/plugins/ca-pi/skills/ca-commit/SKILL.md +170 -16
  38. package/plugins/ca-pi/skills/ca-new-skill/SKILL.md +116 -21
package/README.md CHANGED
@@ -10,7 +10,7 @@ project context. You decide. codeArbiter enforces.
10
10
  <img alt="Claude Code plugin" src="https://img.shields.io/badge/Claude_Code-plugin-d97757">
11
11
  <img alt="Codex plugin" src="https://img.shields.io/badge/OpenAI_Codex-plugin-10a37f">
12
12
  <img alt="Pi Feature Forge preview" src="https://img.shields.io/badge/ca--pi-Feature_Forge_preview-d97757">
13
- <img alt="version 2.21.10" src="https://img.shields.io/badge/version-2.21.10-2b7489">
13
+ <img alt="version 2.21.12" src="https://img.shields.io/badge/version-2.21.12-2b7489">
14
14
  <img alt="core lanes" src="https://img.shields.io/badge/core_lanes-18-555">
15
15
  <img alt="skills" src="https://img.shields.io/badge/skills-23-555">
16
16
  <img alt="agents" src="https://img.shields.io/badge/agents-19-555">
@@ -124,10 +124,15 @@ Current packaging and shared-core parity are continuously verified, while that h
124
124
  stays labeled rather than being promoted to evidence for a newer adapter:
125
125
 
126
126
  ```text
127
- codex plugin marketplace add arbiterForge/codeArbiter
127
+ codex plugin marketplace add arbiterForge/codeArbiter --ref ca-codex-marketplace
128
128
  codex plugin add ca-codex@codearbiter
129
129
  ```
130
130
 
131
+ The `ca-codex-marketplace` branch is the promoted catalog. Each entry pins the
132
+ exact immutable Git commit assembled from the same qualified member set as the
133
+ release archive; `main` remains the development source tree and is not a
134
+ substitute for the promoted binary-bearing distribution.
135
+
131
136
  Open `/hooks`, review and trust the `ca-codex` handlers, then start a fresh task. Run `$ca-init` and
132
137
  `$ca-doctor`.
133
138
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arbiterforge/ca-pi",
3
- "version": "0.14.10",
3
+ "version": "0.14.12",
4
4
  "license": "AGPL-3.0-only",
5
5
  "repository": {
6
6
  "type": "git",
@@ -4,6 +4,34 @@ All notable changes to `ca-pi` are documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.14.12] - 2026-09-23
8
+
9
+ ### Fixed
10
+
11
+
12
+ - Add one-reply initial HTML sprint approval and explicitly delegated SMARTS
13
+ task-method revisions, with native atomic recovery and unchanged acceptance gates.
14
+ - Generate commit and new-skill public entries from the complete owning
15
+ procedures, retaining their private routine paths and exact gates.
16
+ - Preserve the caller's coherent sprint scope and required spec-to-plan
17
+ continuation without manufacturing method-selection approval questions.
18
+ - Distinguish initial artifact approval from existing delegation and require
19
+ the current qualified host-owned authority path; no new host support is implied.
20
+ - Keep complete authored HTML checkpoint membership across resume and review,
21
+ without adding attended execution pauses to autonomous sprint work.
22
+
23
+ ## [0.14.11] - 2026-09-23
24
+
25
+ ### Fixed
26
+
27
+ - Package the shared closed artifact-authority and reconciliation kernel while
28
+ retaining fail-closed behavior for host observation seams that Pi does not
29
+ implement.
30
+ - Preserve typed workspace identity and durable reconciliation retry/cancel
31
+ behavior in the shared kernel without claiming unsupported Pi host authority.
32
+ - Reject unproven partial-commit and changed-parent recovery in the shared
33
+ kernel without claiming unsupported Pi host authority.
34
+
7
35
  ## [0.14.10] - 2026-09-22
8
36
 
9
37
  ### Fixed
@@ -58,7 +58,7 @@ skill is invoked — never bulk-read this directory.
58
58
 
59
59
  | Skill | Purpose |
60
60
  |---|---|
61
- | `/ca-commit` | Run the full commit gate — the only sanctioned path to a git commit. |
61
+ | `/ca-commit` | Create a verified local Git commit when committing changes is requested. Not for explaining commit history, drafting a message only, or postponing a commit. Applies every commit gate and never implies a push or PR. |
62
62
  | `/ca-pr` | Open a pull request the only sanctioned way — clear every BLOCK-level review finding, then stage the PR. Never a direct write to the default branch. |
63
63
  | `/ca-release` | Cut a release the only sanctioned way — derive the target's declared version policy from the commit log, roll its changelog, compose an annotated tag, and optionally publish its exact declared assets. Takes the declared target's name as its only argument, or --dry-run to preview one with no write. The only path to a version tag. |
64
64
 
@@ -107,7 +107,7 @@ skill is invoked — never bulk-read this directory.
107
107
 
108
108
  | Skill | Purpose |
109
109
  |---|---|
110
- | `/ca-new-skill` | Author a new codeArbiter skill: prove the gap is real, get the spec approved, then write it. |
110
+ | `/ca-new-skill` | Assess a request for a new reusable codeArbiter skill, prove the uncovered gap, agree scope, author and integrate it. Questions and one-off tasks do not authorize adding a skill. |
111
111
 
112
112
  ### Help
113
113
 
@@ -73,6 +73,33 @@ explicit "decide it this way" steers here. STOP for explicit user approval of th
73
73
  plan before autonomy begins. A blocking `[CONFIRM-NN]` is resolved here, with the user — never carried
74
74
  into autonomous execution unresolved.
75
75
 
76
+ ### Initial HTML approval sequence
77
+
78
+ Prepare one review packet containing the ready spec, ready draft plan, adversarial findings and
79
+ explicit delegation terms. The installed private pair adapter in
80
+ `<plugin-root>/includes/artifacts.md` owns the combined one-reply transaction:
81
+
82
+ 1. Finish the spec as a ready draft. Let `writing-plans` use `_preflight_plan_authoring` with
83
+ `draft_for_pair: true` only for this full-lane initial sprint. Populate the same-slug plan as
84
+ `draft_preview`, validate both, and review their complete criteria, paths, tests and checkpoints.
85
+ Neither draft has execution authority. Do not separately approve or `plan-bind` this pair.
86
+ 2. Arm both exact identities with `arm-sprint`. Include `--delegate-methods` only when the presented
87
+ terms explicitly ask the user to delegate existing task-method revisions. Present the exact returned reply;
88
+ it names both artifacts and `delegate-methods` or `approve-only`, not an ambiguous generic yes.
89
+ 3. The existing qualified host observes that single reply. `sprint-approve` validates both frozen
90
+ definitions and commits approval plus plan binding in one recoverable native transaction.
91
+ Verify both current approved identities before execution. No execution before both approvals.
92
+
93
+ One reply approves the reviewed pair, not future unbounded work. `delegate-methods` does not grant
94
+ initial self-approval, product decisions, prerequisite satisfaction, new files, verification changes,
95
+ commit/push, provider/spending/disclosure/publication authority, or any security bypass. Keep
96
+ no per-feature interviews and no attended execution pauses. A declined, changed or unsupported pair
97
+ retains drafts; capability absence is not permission to invent a legacy shadow or receipt.
98
+ If an observed reply was interrupted, `resume-sprint` replays its stored transaction without asking
99
+ for the same reply again. An armed request with no observed reply cannot be resumed as approval.
100
+ The existing sequential single-artifact path remains available for `/feature` and older workflows;
101
+ Markdown retains its original approval contract.
102
+
76
103
  **Recorded-intent read — BEFORE spec approval, fail-soft (ADR-0025):** consult
77
104
  `decisions/decision-log.md`, the accepted-ADR index, the `plans/` artifacts' section headings, and
78
105
  `open-questions.md`'s deferred sections — index-first, bodies only when the index names them
@@ -128,6 +155,26 @@ ambiguity, a trade-off, a non-obvious option — DECIDE rather than stop:
128
155
  before `· confidence:`. The `low`-confidence
129
156
  entries are exactly what the user reviews in the morning; nothing is hidden behind autonomy.
130
157
 
158
+ ### Typed SMARTS method authority
159
+
160
+ After the pair's explicit `delegate-methods` approval, use the private `smarts-apply` producer for a
161
+ material change to an existing task's method steps. First perform SMARTS Step 0 and the existing
162
+ project/ADR/scope checks. Submit the actual options, six-lens comparison, selected option, strength
163
+ and rationale with the exact plan revision/model hash and the retained initial `grant_receipt`.
164
+ Use the private request shape and adapter in `<plugin-root>/includes/artifacts.md`; a pasted matrix
165
+ or an append-only log is not itself authority. A single evidenced option is allowed; an in-scope tie
166
+ uses the existing priorities rather than another user interview. Do not manufacture alternatives.
167
+
168
+ The producer retains all protected plan fields and requires the original user-approved scope. It can
169
+ change only existing task `steps`; paths, tests, criteria, dependencies, prerequisites, rollback and
170
+ checkpoint membership cannot expand. It quiesces affected writers by refusing `IN_PROGRESS` and
171
+ invalidates affected checkpoint/dependency proof before another attempt, without clearing `BLOCKED`
172
+ members or their recorded reasons. A blocked method revision
173
+ therefore resumes through fresh `eligible`, context tickets, verification and review, never auto-pass.
174
+ The original log still records material reasoning and source citations. No result implies accepted
175
+ work or independently proves the truth of model judgment. Changes outside the granted boundary use
176
+ their actual authority owner; this producer does not satisfy prerequisites or waive security stops.
177
+
131
178
  ### Hard gates — true stops, even mid-sprint
132
179
 
133
180
  NEVER auto-decided. Halt and surface to the user:
@@ -125,7 +125,7 @@
125
125
  },
126
126
  "commit": {
127
127
  "name": "commit",
128
- "description": "Run the full commit gate — the only sanctioned path to a git commit.",
128
+ "description": "Create a verified local Git commit when committing changes is requested. Not for explaining commit history, drafting a message only, or postponing a commit. Applies every commit gate and never implies a push or PR.",
129
129
  "skillPath": "skills/ca-commit/SKILL.md",
130
130
  "visibility": "core",
131
131
  "workflow": "ship",
@@ -227,7 +227,7 @@
227
227
  },
228
228
  "new-skill": {
229
229
  "name": "new-skill",
230
- "description": "Author a new codeArbiter skill: prove the gap is real, get the spec approved, then write it.",
230
+ "description": "Assess a request for a new reusable codeArbiter skill, prove the uncovered gap, agree scope, author and integrate it. Questions and one-off tasks do not authorize adding a skill.",
231
231
  "skillPath": "skills/ca-new-skill/SKILL.md",
232
232
  "visibility": "advanced",
233
233
  "workflow": "extend",
@@ -6,32 +6,32 @@
6
6
  "binaries": {
7
7
  "darwin/amd64": {
8
8
  "file": "ca-artifact-darwin-amd64",
9
- "sha256": "fab98b0fd68bafe5f2f52941f704dc32bf623eaa68be18e9e68cc9bc07f05cac",
9
+ "sha256": "3fb697a6656fa88b6d6e3e7eadf4784be6d5f107582162879d22b6a59ea7221a",
10
10
  "native_tested": true
11
11
  },
12
12
  "darwin/arm64": {
13
13
  "file": "ca-artifact-darwin-arm64",
14
- "sha256": "44211f23050e42ac155432e6d3e3edc58fd7180171a982c6c4a39378ec15218d",
14
+ "sha256": "19eee315a7ea89c9433679fe2aaa4329e43863f9aadba6806bdf8bc604f56af8",
15
15
  "native_tested": true
16
16
  },
17
17
  "linux/amd64": {
18
18
  "file": "ca-artifact-linux-amd64",
19
- "sha256": "100e36cf452a79fb678ee2f99956953ae97da5b49bc673e77cef094a87bd1cab",
19
+ "sha256": "104063987dddfacb23164a3e2989a6207127527065ed629d60620ab54918c8e0",
20
20
  "native_tested": true
21
21
  },
22
22
  "linux/arm64": {
23
23
  "file": "ca-artifact-linux-arm64",
24
- "sha256": "6c1aa70e582e5054c8125cb89585bfd0f0eda6a2b32f3c67c9cdbff76ca78e0f",
24
+ "sha256": "ff31ec48cc24317675084bf4f62865ca59ea35a64632fbf7bdd983e4d13a7f80",
25
25
  "native_tested": true
26
26
  },
27
27
  "windows/amd64": {
28
28
  "file": "ca-artifact-windows-amd64.exe",
29
- "sha256": "6a17e6188fede9f8b8271d0f9eeab41512b58118e5b12781e62b4ce8dc0c5519",
29
+ "sha256": "5cfb75794adccb8d48841184fa4696617dc7b70b4dbf75eb565c21f261512de1",
30
30
  "native_tested": true
31
31
  },
32
32
  "windows/arm64": {
33
33
  "file": "ca-artifact-windows-arm64.exe",
34
- "sha256": "746bfd19ed124b14376326b05a435aad381d7a72093f0e8002fa66e186e64194",
34
+ "sha256": "b9a966ea1c1991e8d44bb8fde327dcd27ab75dcb2f370998139e60a0707f4baf",
35
35
  "native_tested": true
36
36
  }
37
37
  }
@@ -15,6 +15,8 @@ import sys
15
15
  from typing import Any
16
16
 
17
17
  import _artifactlib
18
+ import _artifactauthoritylib
19
+ import _artifactpromptlib
18
20
 
19
21
 
20
22
  PENDING = Path(".codearbiter/.markers/pending-user-approval.json")
@@ -150,6 +152,13 @@ def arm_user_approval(
150
152
  raise ApprovalError(
151
153
  "PENDING_APPROVAL", "resolve or clear the existing approval request first"
152
154
  ) from exc
155
+ try:
156
+ _artifactpromptlib.register(
157
+ root, "approval", artifact_id, f"approve {artifact_id} {token}"
158
+ )
159
+ except _artifactpromptlib.PromptRouteError:
160
+ (root / PENDING).unlink()
161
+ raise
153
162
  return {
154
163
  "artifact_id": artifact_id,
155
164
  "reply": f"approve {artifact_id} {token}",
@@ -182,6 +191,9 @@ def _load_pending(root: Path) -> dict[str, Any] | None:
182
191
  return None
183
192
  except (OSError, UnicodeError, ValueError) as exc:
184
193
  raise ApprovalError("INVALID_PENDING_APPROVAL", "pending approval is unreadable") from exc
194
+ if isinstance(value, dict) and value.get("format") == "codearbiter.pending-user-approval/0.2.0":
195
+ import _sprintapprovallib
196
+ return _sprintapprovallib.validate_pending(value)
185
197
  expected = {
186
198
  "format", "artifact_id", "kind", "revision", "model_sha256",
187
199
  "normative_sha256", "token_sha256",
@@ -216,7 +228,11 @@ def cancel_user_approval(root: str | Path, artifact_id: str) -> dict[str, Any]:
216
228
  "PENDING_APPROVAL_MISMATCH",
217
229
  "the pending approval request belongs to another artifact",
218
230
  )
231
+ if pending["format"] == "codearbiter.pending-user-approval/0.2.0":
232
+ import _sprintapprovallib
233
+ return _sprintapprovallib.cancel(root, pending)
219
234
  (root / PENDING).unlink()
235
+ _artifactpromptlib.unregister(root, "approval", artifact_id)
220
236
  return {"artifact_id": artifact_id, "cancelled": True}
221
237
 
222
238
 
@@ -232,6 +248,9 @@ def consume_user_approval(
232
248
  pending = _load_pending(root)
233
249
  if pending is None or not isinstance(prompt, str):
234
250
  return {"matched": False, "approved": False}
251
+ if pending["format"] == "codearbiter.pending-user-approval/0.2.0":
252
+ import _sprintapprovallib
253
+ return _sprintapprovallib.consume(root, client, pending, prompt, host=host, session_id=session_id)
235
254
  parts = prompt.split(" ")
236
255
  if len(parts) != 3 or parts[0] != "approve" or parts[1] != pending["artifact_id"]:
237
256
  return {"matched": False, "approved": False}
@@ -250,7 +269,6 @@ def consume_user_approval(
250
269
  _ready(client, current)
251
270
 
252
271
  event = {
253
- "format": "codearbiter.workflow-event/0.1.0",
254
272
  "kind": "approval",
255
273
  "authority_kind": "user_workflow",
256
274
  "subject": {
@@ -264,20 +282,12 @@ def consume_user_approval(
264
282
  "payload": {},
265
283
  "source_text": prompt,
266
284
  }
267
- event_bytes = _canonical(event)
268
- digest = hashlib.sha256(event_bytes).hexdigest()
269
- source_rel = SOURCE_DIR / f"{digest}.json"
270
- source = root / source_rel
271
285
  try:
272
- _write_new(root, source_rel, event_bytes)
273
- except FileExistsError:
274
- if source.read_bytes() != event_bytes:
275
- raise ApprovalError("INVALID_AUTHORITY_SOURCE", "content-addressed source differs")
276
-
277
- captured = client.call(
278
- "capture",
279
- {"source_ref": source_rel.as_posix(), "source_sha256": digest},
280
- )
286
+ captured = _artifactauthoritylib.capture_user_prompt(
287
+ root, client, event, host=host, session_id=session_id,
288
+ )
289
+ except _artifactauthoritylib.AuthorityError as exc:
290
+ raise ApprovalError("INVALID_AUTHORITY_SOURCE", str(exc)) from exc
281
291
  receipt = captured.get("receipt")
282
292
  if not isinstance(receipt, str) or not receipt:
283
293
  raise ApprovalError("INVALID_RECEIPT", "capture returned no receipt")
@@ -285,7 +295,7 @@ def consume_user_approval(
285
295
  "approve",
286
296
  {
287
297
  "artifact_id": current["artifact_id"],
288
- "operation_id": f"host-user-approval-{digest[:24]}",
298
+ "operation_id": f"host-user-approval-{hashlib.sha256(_canonical(event)).hexdigest()[:24]}",
289
299
  "expected": {
290
300
  "revision": current["revision"],
291
301
  "model_sha256": current["model_sha256"],
@@ -294,11 +304,12 @@ def consume_user_approval(
294
304
  },
295
305
  )
296
306
  (root / PENDING).unlink()
307
+ _artifactpromptlib.unregister(root, "approval", current["artifact_id"])
297
308
  return {
298
309
  "matched": True,
299
310
  "approved": True,
300
311
  "artifact_id": current["artifact_id"],
301
- "authority_source": source_rel.as_posix(),
312
+ "authority_source": captured["authority_source"],
302
313
  "receipt": receipt,
303
314
  "revision": approved.get("revision"),
304
315
  }
@@ -306,17 +317,20 @@ def consume_user_approval(
306
317
 
307
318
  def consume_from_hook(*, root: str | Path, plugin_root: str | Path, prompt: str,
308
319
  host: str, session_id: str) -> str:
309
- if not (Path(root) / PENDING).exists():
310
- return ""
311
320
  try:
321
+ routed = _artifactpromptlib.resolve("approval", prompt)
322
+ root = routed or Path(root)
323
+ if not (Path(root) / PENDING).exists():
324
+ return ""
312
325
  client = _artifactlib.ArtifactClient(
313
326
  root, Path(plugin_root) / "helpers" / "artifacts"
314
327
  )
315
328
  result = consume_user_approval(
316
329
  root, client, prompt, host=host, session_id=session_id
317
330
  )
318
- except (ApprovalError, _artifactlib.ArtifactError, OSError) as exc:
319
- if isinstance(prompt, str) and prompt.startswith("approve "):
331
+ except (ApprovalError, _artifactlib.ArtifactError,
332
+ _artifactpromptlib.PromptRouteError, OSError) as exc:
333
+ if isinstance(prompt, str) and prompt.startswith(("approve ", "approve-sprint ")):
320
334
  return f"codeArbiter: approval capture failed: {exc}"
321
335
  return ""
322
336
  if not result.get("approved"):
@@ -336,6 +350,17 @@ def main(argv: list[str] | None = None) -> int:
336
350
  cancel = sub.add_parser("cancel")
337
351
  cancel.add_argument("--root", required=True)
338
352
  cancel.add_argument("--artifact-id", required=True)
353
+ pair = sub.add_parser("arm-sprint")
354
+ pair.add_argument("--root", required=True)
355
+ pair.add_argument("--spec-id", required=True)
356
+ pair.add_argument("--plan-id", required=True)
357
+ pair.add_argument("--delegate-methods", action="store_true")
358
+ resume = sub.add_parser("resume-sprint")
359
+ resume.add_argument("--root", required=True)
360
+ resume.add_argument("--plan-id", required=True)
361
+ smarts = sub.add_parser("smarts")
362
+ smarts.add_argument("--root", required=True)
363
+ smarts.add_argument("--request-file", required=True)
339
364
  args = parser.parse_args(argv)
340
365
  if args.command == "cancel":
341
366
  print(json.dumps(cancel_user_approval(args.root, args.artifact_id)))
@@ -343,13 +368,24 @@ def main(argv: list[str] | None = None) -> int:
343
368
  client = _artifactlib.ArtifactClient(
344
369
  args.root, _artifactlib.helper_installation(__file__)
345
370
  )
346
- print(json.dumps(arm_user_approval(args.root, client, args.artifact_id)))
371
+ if args.command == "smarts":
372
+ import _sprintapprovallib
373
+ print(json.dumps(_sprintapprovallib.apply_decision(client, args.request_file)))
374
+ return 0
375
+ if args.command in {"arm-sprint", "resume-sprint"}:
376
+ import _sprintapprovallib
377
+ result = (_sprintapprovallib.arm(args.root, client, args.spec_id, args.plan_id, delegate_methods=args.delegate_methods)
378
+ if args.command == "arm-sprint" else _sprintapprovallib.resume(args.root, client, args.plan_id))
379
+ else:
380
+ result = arm_user_approval(args.root, client, args.artifact_id)
381
+ print(json.dumps(result))
347
382
  return 0
348
383
 
349
384
 
350
385
  if __name__ == "__main__":
351
386
  try:
352
387
  raise SystemExit(main())
353
- except (ApprovalError, _artifactlib.ArtifactError) as exc:
388
+ except (ApprovalError, _artifactlib.ArtifactError,
389
+ _artifactpromptlib.PromptRouteError) as exc:
354
390
  sys.stderr.write(str(exc) + "\n")
355
391
  raise SystemExit(1)