@markjaquith/agency 2.48.1 → 2.50.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 (44) hide show
  1. package/README.md +40 -9
  2. package/cli.ts +32 -0
  3. package/package.json +1 -1
  4. package/schemas/agency-graph-v1.schema.json +141 -38
  5. package/src/cli-parser.test.ts +120 -0
  6. package/src/cli-parser.ts +122 -7
  7. package/src/cli.test.ts +71 -0
  8. package/src/commands/claim.ts +26 -2
  9. package/src/commands/phase.ts +23 -2
  10. package/src/commands/review.ts +38 -0
  11. package/src/commands/task.ts +49 -7
  12. package/src/commands/work.test.ts +2 -0
  13. package/src/commands/work.ts +2 -2
  14. package/src/graph-schema.ts +35 -13
  15. package/src/protocol.ts +1 -0
  16. package/src/services/ArchiveService.test.ts +2 -2
  17. package/src/services/ArchiveService.ts +1 -0
  18. package/src/services/ClaimService.test.ts +59 -0
  19. package/src/services/ClaimService.ts +36 -2
  20. package/src/services/ContextService.ts +56 -4
  21. package/src/services/DoctorService.ts +45 -1
  22. package/src/services/GraphMutationService.ts +21 -0
  23. package/src/services/GraphService.ts +118 -54
  24. package/src/services/IntegrationService.test.ts +4 -1
  25. package/src/services/PhaseService.ts +55 -3
  26. package/src/services/PullRequestService.test.ts +22 -2
  27. package/src/services/PullRequestService.ts +44 -3
  28. package/src/services/ReadinessService.ts +7 -3
  29. package/src/services/ReviewService.test.ts +472 -0
  30. package/src/services/ReviewService.ts +404 -0
  31. package/src/services/SyncService.test.ts +68 -2
  32. package/src/services/SyncService.ts +123 -6
  33. package/src/services/TaskPhaseService.test.ts +182 -0
  34. package/src/services/TaskService.ts +128 -11
  35. package/src/services/WorkbaseService.test.ts +58 -0
  36. package/src/services/WorkbaseService.ts +22 -1
  37. package/src/services/WorktreeService.test.ts +16 -16
  38. package/src/services/WorktreeService.ts +76 -40
  39. package/src/test-utils.ts +2 -0
  40. package/src/work-view.ts +14 -6
  41. package/src/workbase/AGENTS.md +7 -2
  42. package/src/workbase/completion.ts +28 -0
  43. package/src/workbase/schemas.test.ts +57 -0
  44. package/src/workbase/schemas.ts +65 -0
package/README.md CHANGED
@@ -589,6 +589,26 @@ agency task create <id> --multi-phase
589
589
  [--ticket-url <url>] [--description <text>] [--epic <id>] [--json]
590
590
  ```
591
591
 
592
+ Create a pinned, read-only review task from the selected alias's origin:
593
+
594
+ ```text
595
+ agency task create <id> --review <alias> --pull-request <url-or-number>
596
+ agency task create <id> --review <alias> --ref <remote-ref>
597
+ agency review refresh <id> [--if-revision <hash>] [--json]
598
+ ```
599
+
600
+ Review creation fetches the source and records its exact 40-character commit.
601
+ GitHub pull requests use the base alias's `refs/pull/<number>/head`, including
602
+ fork pull requests exposed through that ref. Review workspaces contain one
603
+ detached checkout and no writable branch. Source movement is observed separately
604
+ from the pin and applied only by `review refresh`; sync, doctor, work, cleanup,
605
+ and archive never move the pin implicitly. Dirty or structurally unexpected
606
+ review checkouts block refresh and cleanup. Review tasks support normal status
607
+ and claim lifecycle, but reject phase conversion and delivery PR operations.
608
+ Each active or archived review task owns one internal task-scoped pin ref. A
609
+ refresh advances that ref transactionally; failed creation removes it. Archiving
610
+ retains the pin so a deleted source can still be restored and inspected.
611
+
592
612
  ### Noninteractive Use
593
613
 
594
614
  Agency never prompts when `--no-input` is set or stdin/stderr are not TTYs.
@@ -610,7 +630,8 @@ Inspect tasks:
610
630
  ```text
611
631
  agency task list [filters] [--json]
612
632
  agency task show <id> [--json]
613
- agency task status <id> <open|working|dropped> [--json]
633
+ agency task status <id> <open|working|done|dropped>
634
+ [--no-pull-request --summary <text> [--evidence-url <url>]] [--json]
614
635
  agency task update <id> [metadata options] [--json]
615
636
  agency task rename <id> <new-id> [--json]
616
637
  agency task move <id> (--epic <epic-id> | --no-epic) [--json]
@@ -649,7 +670,8 @@ agency phase create <task-id> <phase-id>
649
670
 
650
671
  agency phase list <task-id> [filters] [--json]
651
672
  agency phase show <task-id> <phase-id> [--json]
652
- agency phase status <task-id> <phase-id> <open|working|dropped> [--json]
673
+ agency phase status <task-id> <phase-id> <open|working|done|dropped>
674
+ [--no-pull-request --summary <text> [--evidence-url <url>]] [--json]
653
675
  agency phase update <task-id> <phase-id> [metadata options] [--json]
654
676
  agency phase rename <task-id> <phase-id> <new-id> [--json]
655
677
  agency phase dependency <add|remove> <task-id> <phase-id> <dependency-id>
@@ -672,8 +694,12 @@ writing anything.
672
694
  Single-phase tasks and phases store status in YAML. New execution units start
673
695
  `open`, and `agency work` marks the selected execution unit `working` immediately
674
696
  before launch. Running `agency work` again can relaunch unclaimed `working` work.
675
- The `done` status is reserved for an authoritative merged pull request and is
676
- applied by `agency sync --apply`, not by task or phase status commands.
697
+ By default, `done` requires an authoritative merged pull request and is applied
698
+ by `agency sync --apply`. Work whose intended outcome genuinely requires no pull
699
+ request may instead use an explicit `--no-pull-request --summary <text>` status
700
+ transition. Agency records the summary, completion time, and optional evidence
701
+ URL durably; reopening removes that evidence. This exceptional path refuses work
702
+ that already has a recorded pull request.
677
703
  Use explicit claims only when an external orchestrator needs coordinated
678
704
  ownership. The interactive work selector displays status markers before
679
705
  execution units. Existing working and delegated work may be released to `open`
@@ -704,15 +730,19 @@ agency claim <task-id> [phase-id] --claimant <id> --runner <id>
704
730
  agency release <task-id> [phase-id] --session-id <id>
705
731
  --revision <sha256> [--json]
706
732
  agency finish <task-id> [phase-id] --session-id <id>
707
- --revision <sha256> --outcome <done|dropped> [--json]
733
+ --revision <sha256> --outcome <done|dropped>
734
+ [--no-pull-request --summary <text> [--evidence-url <url>]] [--json]
708
735
  ```
709
736
 
710
737
  An active claim sets status to `working`. Release returns it to `open`. Finish
711
738
  records the claim outcome and ownership history; a `done` claim outcome leaves
712
739
  the execution unit `working` until its pull request is merged, while `dropped`
713
- remains terminal. Conflicts return the current revision and complete ownership
714
- record in the machine error envelope rather than overwriting it. Expired claims
715
- may be replaced with a revision-guarded claim.
740
+ remains terminal. For a genuine non-PR outcome, `--no-pull-request` atomically
741
+ records completion evidence, finishes the claim, and sets the execution unit to
742
+ `done`; `--summary` is required and `--evidence-url` is optional. Conflicts return
743
+ the current revision and complete ownership record in the machine error envelope
744
+ rather than overwriting it. Expired claims may be replaced with a
745
+ revision-guarded claim.
716
746
 
717
747
  `agency work` does not claim execution units. It refuses active explicit claims,
718
748
  marks open execution work `working`, and launches the runner. External
@@ -761,7 +791,8 @@ their built-in presets. Launches are interactive and promptless by default; use
761
791
  `--auto` to send Agency's generated context prompt.
762
792
 
763
793
  `agency work prepare` resolves an execution unit and creates or reuses its
764
- writable and reference worktrees without launching an agent or changing status.
794
+ writable and reference worktrees, or its single pinned review checkout, without
795
+ launching an agent or changing status.
765
796
  Its JSON result includes document and checkout paths, resolved commits, actions,
766
797
  and Git operations. Use `--dry-run` to report planned fetch, branch, and worktree
767
798
  changes without applying them.
package/cli.ts CHANGED
@@ -43,6 +43,8 @@ import { SyncService } from "./src/services/SyncService"
43
43
  import { ReadinessService } from "./src/services/ReadinessService"
44
44
  import { GraphMutationService } from "./src/services/GraphMutationService"
45
45
  import { DoctorService } from "./src/services/DoctorService"
46
+ import { ReviewService } from "./src/services/ReviewService"
47
+ import { review, help as reviewHelp } from "./src/commands/review"
46
48
  import {
47
49
  claimCommand,
48
50
  claimHelp,
@@ -75,6 +77,7 @@ const CliLayer = Layer.mergeAll(
75
77
  ReadinessService.Default,
76
78
  GraphMutationService.Default,
77
79
  DoctorService.Default,
80
+ ReviewService.Default,
78
81
  )
79
82
 
80
83
  /**
@@ -205,6 +208,9 @@ const commands: Record<string, Command> = {
205
208
  sessionId: options["session-id"],
206
209
  revision: options.revision,
207
210
  outcome: options.outcome,
211
+ noPullRequest: options["no-pull-request"],
212
+ summary: options.summary,
213
+ evidenceUrl: options["evidence-url"],
208
214
  json: options.json,
209
215
  silent: options.silent,
210
216
  verbose: options.verbose,
@@ -298,6 +304,9 @@ const commands: Record<string, Command> = {
298
304
  clearReferences: options["clear-references"],
299
305
  prUrl: options["pr-url"],
300
306
  clearPr: options["clear-pr"],
307
+ noPullRequest: options["no-pull-request"],
308
+ summary: options.summary,
309
+ evidenceUrl: options["evidence-url"],
301
310
  ifRevision: options["if-revision"],
302
311
  dependsOn: options["depends-on"],
303
312
  firstPhase: options["first-phase"],
@@ -443,12 +452,18 @@ const commands: Record<string, Command> = {
443
452
  clearDescription: options["clear-description"],
444
453
  epic: options.epic,
445
454
  repo: options.repo?.[0],
455
+ review: options.review,
456
+ pullRequest: options["pull-request"],
457
+ ref: options.ref,
446
458
  references: options.reference,
447
459
  branch: options.branch,
448
460
  base: options.base,
449
461
  clearReferences: options["clear-references"],
450
462
  prUrl: options["pr-url"],
451
463
  clearPr: options["clear-pr"],
464
+ noPullRequest: options["no-pull-request"],
465
+ summary: options.summary,
466
+ evidenceUrl: options["evidence-url"],
452
467
  ifRevision: options["if-revision"],
453
468
  noEpic: options["no-epic"],
454
469
  multiPhase: options["multi-phase"],
@@ -468,6 +483,22 @@ const commands: Record<string, Command> = {
468
483
  )
469
484
  },
470
485
  },
486
+ review: {
487
+ run: async (args: string[], options: Record<string, any>) => {
488
+ if (options.help) return console.log(reviewHelp)
489
+ await runCommand(
490
+ review({
491
+ subcommand: args[0],
492
+ taskId: args[1],
493
+ ifRevision: options["if-revision"],
494
+ json: options.json,
495
+ silent: options.silent,
496
+ verbose: options.verbose,
497
+ cwd: options.cwd,
498
+ }),
499
+ )
500
+ },
501
+ },
471
502
  work: {
472
503
  run: async (args: string[], options: Record<string, any>) => {
473
504
  if (options.help) {
@@ -674,6 +705,7 @@ Commands:
674
705
  worktree <subcommand> Inspect and maintain managed worktrees
675
706
  next List or select ready execution units
676
707
  pr create Create a pull request for an execution unit
708
+ review refresh Explicitly refresh a pinned review task
677
709
  repo <subcommand> Manage workbase repositories
678
710
  status Show status for the current workbase
679
711
  doctor Diagnose workbase health and integrations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.48.1",
3
+ "version": "2.50.0",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -176,6 +176,7 @@
176
176
  { "$ref": "#/$defs/epicData" },
177
177
  { "$ref": "#/$defs/singleTaskData" },
178
178
  { "$ref": "#/$defs/multiTaskData" },
179
+ { "$ref": "#/$defs/reviewTaskData" },
179
180
  { "$ref": "#/$defs/phaseData" },
180
181
  { "$ref": "#/$defs/executionData" },
181
182
  { "$ref": "#/$defs/repositoryData" }
@@ -220,7 +221,8 @@
220
221
  "data": {
221
222
  "oneOf": [
222
223
  { "$ref": "#/$defs/singleTaskData" },
223
- { "$ref": "#/$defs/multiTaskData" }
224
+ { "$ref": "#/$defs/multiTaskData" },
225
+ { "$ref": "#/$defs/reviewTaskData" }
224
226
  ]
225
227
  },
226
228
  "workspace": { "$ref": "#/$defs/documentWorkspace" },
@@ -358,34 +360,72 @@
358
360
  "sha256": { "type": "string" }
359
361
  }
360
362
  },
361
- "phaseData": {
363
+ "reviewSource": {
364
+ "oneOf": [
365
+ {
366
+ "type": "object",
367
+ "additionalProperties": false,
368
+ "required": [
369
+ "kind",
370
+ "provider",
371
+ "repository",
372
+ "identifier",
373
+ "url",
374
+ "fetchRef"
375
+ ],
376
+ "properties": {
377
+ "kind": { "const": "pull-request" },
378
+ "provider": { "const": "github" },
379
+ "repository": { "type": "string" },
380
+ "identifier": { "type": "string", "pattern": "^\\d+$" },
381
+ "url": {
382
+ "type": "string",
383
+ "pattern": "^https://github\\.com/[^/]+/[^/]+/pull/\\d+$"
384
+ },
385
+ "fetchRef": { "type": "string", "pattern": "^refs/pull/\\d+/head$" }
386
+ }
387
+ },
388
+ {
389
+ "type": "object",
390
+ "additionalProperties": false,
391
+ "required": ["kind", "ref"],
392
+ "properties": {
393
+ "kind": { "const": "branch" },
394
+ "ref": { "type": "string", "pattern": "^refs/heads/" }
395
+ }
396
+ }
397
+ ]
398
+ },
399
+ "reviewRecord": {
362
400
  "type": "object",
363
401
  "additionalProperties": false,
364
- "required": ["repo", "branch", "base", "pr", "status", "sha256"],
402
+ "required": ["repo", "source", "commit", "refreshedAt"],
365
403
  "properties": {
366
- "description": { "type": "string" },
367
404
  "repo": { "type": "string" },
368
- "repos": {
369
- "type": "array",
370
- "items": { "$ref": "#/$defs/repositoryReference" }
371
- },
372
- "branch": { "type": "string" },
373
- "base": { "type": "string" },
374
- "pr": { "type": ["string", "null"] },
405
+ "source": { "$ref": "#/$defs/reviewSource" },
406
+ "commit": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
407
+ "refreshedAt": { "type": "string", "format": "date-time" }
408
+ }
409
+ },
410
+ "reviewTaskData": {
411
+ "type": "object",
412
+ "additionalProperties": false,
413
+ "required": ["ticketUrl", "review", "status", "sha256"],
414
+ "properties": {
415
+ "ticketUrl": { "type": ["string", "null"] },
416
+ "description": { "type": "string" },
417
+ "epic": { "type": "string" },
418
+ "review": { "$ref": "#/$defs/reviewRecord" },
375
419
  "status": { "$ref": "#/$defs/status" },
376
420
  "claim": { "$ref": "#/$defs/claim" },
377
421
  "sha256": { "type": "string" }
378
422
  }
379
423
  },
380
- "executionData": {
424
+ "phaseData": {
381
425
  "type": "object",
382
426
  "additionalProperties": false,
383
- "required": ["taskId", "repo", "branch", "base", "pr", "status"],
427
+ "required": ["repo", "branch", "base", "pr", "status", "sha256"],
384
428
  "properties": {
385
- "taskId": { "type": "string" },
386
- "phaseId": { "type": "string" },
387
- "ticketUrl": { "type": ["string", "null"] },
388
- "epic": { "type": "string" },
389
429
  "description": { "type": "string" },
390
430
  "repo": { "type": "string" },
391
431
  "repos": {
@@ -396,9 +436,50 @@
396
436
  "base": { "type": "string" },
397
437
  "pr": { "type": ["string", "null"] },
398
438
  "status": { "$ref": "#/$defs/status" },
399
- "claim": { "$ref": "#/$defs/claim" }
439
+ "claim": { "$ref": "#/$defs/claim" },
440
+ "sha256": { "type": "string" }
400
441
  }
401
442
  },
443
+ "executionData": {
444
+ "oneOf": [
445
+ {
446
+ "type": "object",
447
+ "additionalProperties": false,
448
+ "required": ["taskId", "repo", "branch", "base", "pr", "status"],
449
+ "properties": {
450
+ "taskId": { "type": "string" },
451
+ "phaseId": { "type": "string" },
452
+ "ticketUrl": { "type": ["string", "null"] },
453
+ "epic": { "type": "string" },
454
+ "description": { "type": "string" },
455
+ "repo": { "type": "string" },
456
+ "repos": {
457
+ "type": "array",
458
+ "items": { "$ref": "#/$defs/repositoryReference" }
459
+ },
460
+ "branch": { "type": "string" },
461
+ "base": { "type": "string" },
462
+ "pr": { "type": ["string", "null"] },
463
+ "status": { "$ref": "#/$defs/status" },
464
+ "claim": { "$ref": "#/$defs/claim" }
465
+ }
466
+ },
467
+ {
468
+ "type": "object",
469
+ "additionalProperties": false,
470
+ "required": ["taskId", "ticketUrl", "review", "status"],
471
+ "properties": {
472
+ "taskId": { "type": "string" },
473
+ "ticketUrl": { "type": ["string", "null"] },
474
+ "description": { "type": "string" },
475
+ "epic": { "type": "string" },
476
+ "review": { "$ref": "#/$defs/reviewRecord" },
477
+ "status": { "$ref": "#/$defs/status" },
478
+ "claim": { "$ref": "#/$defs/claim" }
479
+ }
480
+ }
481
+ ]
482
+ },
402
483
  "claim": {
403
484
  "type": "object",
404
485
  "additionalProperties": false,
@@ -469,26 +550,48 @@
469
550
  }
470
551
  },
471
552
  "executionGit": {
472
- "type": "object",
473
- "additionalProperties": false,
474
- "required": [
475
- "branch",
476
- "base",
477
- "branchCommit",
478
- "baseCommit",
479
- "checkoutCommit",
480
- "checkoutBranch",
481
- "dirty"
482
- ],
483
- "properties": {
484
- "branch": { "type": "string" },
485
- "base": { "type": "string" },
486
- "branchCommit": { "type": ["string", "null"] },
487
- "baseCommit": { "type": ["string", "null"] },
488
- "checkoutCommit": { "type": ["string", "null"] },
489
- "checkoutBranch": { "type": ["string", "null"] },
490
- "dirty": { "type": ["boolean", "null"] }
491
- }
553
+ "oneOf": [
554
+ {
555
+ "type": "object",
556
+ "additionalProperties": false,
557
+ "required": [
558
+ "branch",
559
+ "base",
560
+ "branchCommit",
561
+ "baseCommit",
562
+ "checkoutCommit",
563
+ "checkoutBranch",
564
+ "dirty"
565
+ ],
566
+ "properties": {
567
+ "branch": { "type": "string" },
568
+ "base": { "type": "string" },
569
+ "branchCommit": { "type": ["string", "null"] },
570
+ "baseCommit": { "type": ["string", "null"] },
571
+ "checkoutCommit": { "type": ["string", "null"] },
572
+ "checkoutBranch": { "type": ["string", "null"] },
573
+ "dirty": { "type": ["boolean", "null"] }
574
+ }
575
+ },
576
+ {
577
+ "type": "object",
578
+ "additionalProperties": false,
579
+ "required": [
580
+ "pinnedCommit",
581
+ "sourceCommit",
582
+ "checkoutCommit",
583
+ "checkoutBranch",
584
+ "dirty"
585
+ ],
586
+ "properties": {
587
+ "pinnedCommit": { "type": "string" },
588
+ "sourceCommit": { "type": ["string", "null"] },
589
+ "checkoutCommit": { "type": ["string", "null"] },
590
+ "checkoutBranch": { "type": ["string", "null"] },
591
+ "dirty": { "type": ["boolean", "null"] }
592
+ }
593
+ }
594
+ ]
492
595
  },
493
596
  "pr": {
494
597
  "oneOf": [
@@ -7,6 +7,68 @@ const expectUsageError = (args: string[], usage: string) => {
7
7
  }
8
8
 
9
9
  describe("strict CLI parsing", () => {
10
+ test("accepts review creation and refresh while enforcing one source", () => {
11
+ expect(
12
+ parseCli([
13
+ "task",
14
+ "create",
15
+ "review-it",
16
+ "--review",
17
+ "agency",
18
+ "--pull-request",
19
+ "42",
20
+ ]).values,
21
+ ).toMatchObject({ review: "agency", "pull-request": "42" })
22
+ expect(() =>
23
+ parseCli([
24
+ "task",
25
+ "create",
26
+ "review-it",
27
+ "--review",
28
+ "agency",
29
+ "--ref",
30
+ "feature/review",
31
+ ]),
32
+ ).not.toThrow()
33
+ expect(() =>
34
+ parseCli([
35
+ "review",
36
+ "refresh",
37
+ "review-it",
38
+ "--if-revision",
39
+ "a".repeat(64),
40
+ ]),
41
+ ).not.toThrow()
42
+ expect(() =>
43
+ parseCli([
44
+ "task",
45
+ "create",
46
+ "review-it",
47
+ "--review",
48
+ "agency",
49
+ "--pull-request",
50
+ "42",
51
+ "--ref",
52
+ "feature/review",
53
+ ]),
54
+ ).toThrow("exactly one")
55
+ for (const extra of [
56
+ ["--ref", "feature/review", "--repo", "agency"],
57
+ ["--ref", "feature/review", "--multi-phase"],
58
+ ]) {
59
+ expect(() =>
60
+ parseCli([
61
+ "task",
62
+ "create",
63
+ "review-it",
64
+ "--review",
65
+ "agency",
66
+ ...extra,
67
+ ]),
68
+ ).toThrow("cannot be combined")
69
+ }
70
+ })
71
+
10
72
  test("accepts every documented orchestration recipe command", () => {
11
73
  for (const args of orchestrationRecipes) {
12
74
  expect(() => parseCli(args)).not.toThrow()
@@ -362,6 +424,64 @@ describe("strict CLI parsing", () => {
362
424
  "working",
363
425
  ]),
364
426
  ).toThrow("must be 'done' or 'dropped'")
427
+ expect(
428
+ parseCli([
429
+ "finish",
430
+ "task",
431
+ "--session-id",
432
+ "job-1",
433
+ "--revision",
434
+ revision,
435
+ "--outcome",
436
+ "done",
437
+ "--no-pull-request",
438
+ "--summary",
439
+ "Investigation completed",
440
+ "--evidence-url",
441
+ "https://example.com/result",
442
+ ]),
443
+ ).toMatchObject({
444
+ values: {
445
+ "no-pull-request": true,
446
+ summary: "Investigation completed",
447
+ "evidence-url": "https://example.com/result",
448
+ },
449
+ })
450
+ expect(() =>
451
+ parseCli([
452
+ "finish",
453
+ "task",
454
+ "--session-id",
455
+ "job-1",
456
+ "--revision",
457
+ revision,
458
+ "--outcome",
459
+ "done",
460
+ "--no-pull-request",
461
+ ]),
462
+ ).toThrow("--summary' is required")
463
+ expect(() =>
464
+ parseCli([
465
+ "task",
466
+ "status",
467
+ "example",
468
+ "working",
469
+ "--no-pull-request",
470
+ "--summary",
471
+ "Not done",
472
+ ]),
473
+ ).toThrow("valid only with a done status")
474
+ expect(() =>
475
+ parseCli([
476
+ "phase",
477
+ "status",
478
+ "task",
479
+ "phase",
480
+ "done",
481
+ "--summary",
482
+ "Missing explicit flag",
483
+ ]),
484
+ ).toThrow("require '--no-pull-request'")
365
485
  })
366
486
 
367
487
  test("accepts valid mutation revisions and rejects malformed hashes", () => {