@markjaquith/agency 3.1.1 → 3.2.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.
package/README.md CHANGED
@@ -1028,7 +1028,7 @@ until restored.
1028
1028
 
1029
1029
  ```text
1030
1030
  agency work [<directory> | --epic <epic-id>] [--agent <name>] [--auto] [--print-command]
1031
- agency work prepare [target] [--evidence <json-or-path>] [--dry-run] [--json]
1031
+ agency work prepare [target] [--evidence <json-or-path>] [--force] [--dry-run] [--json]
1032
1032
  agency worktree <list|inspect|prepare|remove|rebuild|repair>
1033
1033
  agency push [--json]
1034
1034
  agency pr create <task-id> [phase-id] [--draft] [--title <title>] [--head <branch>] [--base <branch>] [--label <label>] [--force] [--json]
@@ -1062,9 +1062,11 @@ work and reading context. It does not prescribe how callers present or execute
1062
1062
  prepared work.
1063
1063
  The evidence argument may be an evidence object, task-creation JSON, or a path to
1064
1064
  either. Use `--dry-run` to report planned fetch, branch, and worktree changes
1065
- without applying them. Validation reuse never skips readiness, active-claim,
1066
- repository, ownership, reference-drift, dirty-workspace, or worktree safety
1067
- checks.
1065
+ without applying them. Use `--force` to prepare work blocked by readiness, such
1066
+ as a follow-on phase whose dependency is still active; preparation still does
1067
+ not launch an agent or change lifecycle status. Validation reuse never skips
1068
+ readiness, active-claim, repository, ownership, reference-drift, dirty-workspace,
1069
+ or worktree safety checks.
1068
1070
 
1069
1071
  The authoritative implementation locations for this contract are
1070
1072
  `src/commands/task.ts` (creation output),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "3.1.1",
3
+ "version": "3.2.0",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -397,9 +397,11 @@ describe("strict CLI parsing", () => {
397
397
  commandName: "work",
398
398
  values: { force: true },
399
399
  })
400
- expect(() => parseCli(["work", "prepare", "example", "--force"])).toThrow(
401
- "cannot be combined",
402
- )
400
+ expect(parseCli(["work", "prepare", "example", "--force"])).toMatchObject({
401
+ commandName: "work",
402
+ args: ["prepare", "example"],
403
+ values: { force: true },
404
+ })
403
405
  })
404
406
 
405
407
  test("preserves every argument after pr without parsing it", () => {
package/src/cli-parser.ts CHANGED
@@ -917,7 +917,7 @@ const commands = {
917
917
  },
918
918
  work: {
919
919
  usage:
920
- "agency work [<directory-or-task-id> | --epic <epic-id>] [--agent <name>] [--auto] | agency work prepare [target] [--dry-run] [--json]",
920
+ "agency work [<directory-or-task-id> | --epic <epic-id>] [--agent <name>] [--auto] | agency work prepare [target] [--force] [--dry-run] [--json]",
921
921
  options: {
922
922
  ...commonOptions,
923
923
  ...entitySelectorOptions,
@@ -933,7 +933,7 @@ const commands = {
933
933
  },
934
934
  command: {
935
935
  usage:
936
- "agency work [<directory-or-task-id> | --epic <epic-id>] [--agent <name>] [--auto] | agency work prepare [target] [--dry-run] [--json]",
936
+ "agency work [<directory-or-task-id> | --epic <epic-id>] [--agent <name>] [--auto] | agency work prepare [target] [--force] [--dry-run] [--json]",
937
937
  minArgs: 0,
938
938
  maxArgs: 2,
939
939
  options: [
@@ -1691,8 +1691,7 @@ export function parseCli(args: readonly string[]): ParsedCli {
1691
1691
  parsed.values.claude ||
1692
1692
  parsed.values.agent ||
1693
1693
  parsed.values.auto ||
1694
- parsed.values["print-command"] ||
1695
- parsed.values.force))
1694
+ parsed.values["print-command"]))
1696
1695
  ) {
1697
1696
  throw usageError(
1698
1697
  preparing
package/src/cli.test.ts CHANGED
@@ -1310,7 +1310,12 @@ status: open
1310
1310
  ...process.env,
1311
1311
  ...contract.environment,
1312
1312
  XDG_CONFIG_HOME: isolatedConfigHome,
1313
+ OPENCODE_DISABLE_AUTOUPDATE: "1",
1313
1314
  OPENCODE_DISABLE_EXTERNAL_SKILLS: "1",
1315
+ OPENCODE_DISABLE_MODELS_FETCH: "1",
1316
+ OPENCODE_CONFIG_CONTENT: JSON.stringify({
1317
+ model: "opencode/big-pickle",
1318
+ }),
1314
1319
  }
1315
1320
  const probe = Bun.spawnSync(["opencode", "debug", "agent", "build"], {
1316
1321
  cwd: contract.cwd,
@@ -1409,7 +1414,9 @@ status: open
1409
1414
  ...process.env,
1410
1415
  PATH: `${agencyBin}:${process.env.PATH ?? ""}`,
1411
1416
  XDG_CONFIG_HOME: isolatedConfigHome,
1417
+ OPENCODE_DISABLE_AUTOUPDATE: "1",
1412
1418
  OPENCODE_DISABLE_EXTERNAL_SKILLS: "1",
1419
+ OPENCODE_DISABLE_MODELS_FETCH: "1",
1413
1420
  }
1414
1421
  delete directEnvironment.OPENCODE_CONFIG
1415
1422
  delete directEnvironment.OPENCODE_CONFIG_CONTENT
@@ -1480,6 +1487,7 @@ status: open
1480
1487
  env: {
1481
1488
  ...directEnvironment,
1482
1489
  OPENCODE_CONFIG_CONTENT: JSON.stringify({
1490
+ model: "opencode/big-pickle",
1483
1491
  permission: { external_directory: { "*": "deny" } },
1484
1492
  }),
1485
1493
  },
@@ -1578,7 +1586,7 @@ status: open
1578
1586
  graph.nodes.find((node: any) => node.id === "task:pipeline").status,
1579
1587
  ).toBe("working")
1580
1588
  },
1581
- 180_000,
1589
+ 360_000,
1582
1590
  )
1583
1591
 
1584
1592
  test("envelopes help and version output in machine mode", async () => {
@@ -569,6 +569,49 @@ describe("work command", () => {
569
569
  ).rejects.toThrow("Recalled repository conflicts")
570
570
  })
571
571
 
572
+ test("force prepares a phase blocked by an active dependency without launching or changing status", async () => {
573
+ const blocked = createHarness({
574
+ workspace: multiPhaseWorkspace,
575
+ multiPhaseTasks: ["example"],
576
+ guardError: new Error("Phase dependency is working"),
577
+ })
578
+ await expect(
579
+ blocked.runPrepare({
580
+ cwd: "/workbase",
581
+ taskId: "example",
582
+ phaseId: "implementation",
583
+ }),
584
+ ).rejects.toThrow("Phase dependency is working")
585
+ expect(blocked.events).toEqual(["guard"])
586
+
587
+ const forced = createHarness({
588
+ workspace: multiPhaseWorkspace,
589
+ multiPhaseTasks: ["example"],
590
+ guardError: new Error("Phase dependency is working"),
591
+ })
592
+ await forced.runPrepare({
593
+ cwd: "/workbase",
594
+ taskId: "example",
595
+ phaseId: "implementation",
596
+ force: true,
597
+ silent: true,
598
+ })
599
+
600
+ expect(forced.guards).toEqual([
601
+ {
602
+ target: "execution-unit:phase/example/implementation",
603
+ override: true,
604
+ },
605
+ ])
606
+ expect(forced.events).toEqual(["guard", "materialize"])
607
+ expect(forced.materializeOptions[0]).toMatchObject({
608
+ force: true,
609
+ validationAlreadyPerformed: true,
610
+ })
611
+ expect(forced.launches).toEqual([])
612
+ expect(forced.statusUpdates).toEqual([])
613
+ })
614
+
572
615
  test("launches an epic agent from an epic directory", async () => {
573
616
  const harness = createHarness()
574
617
 
@@ -577,7 +577,7 @@ export const workPrepare = (options: WorkOptions = {}) =>
577
577
 
578
578
  export const help = `
579
579
  Usage: agency work [<directory-or-task-id> | --epic <epic-id>] [--agent <name>] [--auto]
580
- agency work prepare [target] [--evidence <json-or-path>] [--dry-run] [--json]
580
+ agency work prepare [target] [--evidence <json-or-path>] [--force] [--dry-run] [--json]
581
581
 
582
582
  Launch an agent for an epic, task, or phase. With no directory, select one
583
583
  interactively. A positional argument resolves as a directory first, then as a task
@@ -592,7 +592,8 @@ changes without fetching, creating branches, or creating worktrees.
592
592
  It emits revision-bound validation evidence and an idempotent external-orchestrator
593
593
  contract. Evidence is reused only while the target, workbase, configuration, and
594
594
  repository mapping remain unchanged. Dynamic readiness and workspace safety checks
595
- always run.
595
+ always run. With prepare, --force overrides readiness without launching or changing
596
+ lifecycle status.
596
597
 
597
598
  Options:
598
599
  --epic <id> Work on an epic
@@ -605,8 +606,8 @@ Options:
605
606
  --print-command Print cwd, argv, and non-secret environment without launch
606
607
  --opencode Require the OpenCode preset
607
608
  --claude Require the Claude Code preset
608
- --force Override readiness; reopen terminal execution units
609
- --evidence <value> Validation evidence JSON or a path to JSON (prepare only)
609
+ --force Override readiness; launched terminal work is reopened
610
+ --evidence <value> Validation evidence JSON or a path to JSON (prepare only)
610
611
  --no-input Never open an interactive selector
611
612
 
612
613
  Without interactive input, provide an explicit workbase or cwd and an entity