@afokapu/atdd-flow 0.3.65 → 0.3.66

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
@@ -130,7 +130,7 @@ atdd-flow task review example-app runtime-rollout --by driver.runtime@example-ap
130
130
  atdd-flow task done example-app runtime-rollout --by coordinator@example-app
131
131
  ```
132
132
 
133
- Proof is a compact PR, CI run, report, commit range, deployment, or thread reference. Dependencies gate prerequisites; independent tasks are parallel-ready. A coordinator can staff an unassigned ready task with `atdd-flow task assign <project> <task-id> --assignee <address> --by <coordinator-address>`; assignment is allowed only once while the task is `todo`. Use `task block` only for a real external blocker, then checkpoint exact state and next action. Once the blocker is resolved, only that coordinator can clear it with `atdd-flow task unblock <project> <task-id> --by <coordinator-address>` while the task remains `in_progress`. For deliveries explicitly governed by the `workflow` ATDD Bun profile, `review → done` additionally requires a durable final behavioral-review result with decision `APPROVE` for the current clean delivery commit. For an idle driver’s final task, `task done ... --retire-assignee` delegates clean-and-merged worktree retirement to ATDD Bun. A driver whose assigned tasks are all already `done` is retired with `atdd-flow seat retire <address> --by <coordinator-or-main>`, using the same checks. When every assigned task is `done` and the recorded worktree is absent from disk and from `git worktree list`, `seat retire` records the retirement without running worktree finish, deleting the branch, or recreating the worktree; a missing worktree that Git still registers fails closed. Both paths fail closed on unfinished tasks or a dirty or unmerged worktree; on success they record `retired` and a checkpoint, then close the driver's Herdr workspace when `close_inactive` is enabled. `main@<project>` may likewise retire a coordinator seat (never main, the operator, or a seat on the primary checkout) once every task it coordinates or is assigned is `done`, through the same worktree-finish, absent-and-unregistered, and fail-closed paths. Retirement runs in the seat worktree with ATDD Bun from the primary checkout's `node_modules/.bin`, else the primary checkout's own source when it is the ATDD Bun package, else `PATH`; never the seat worktree's copy, which may pin an older toolkit.
133
+ Proof is a compact PR, CI run, report, commit range, deployment, or thread reference. Dependencies gate prerequisites; independent tasks are parallel-ready. A coordinator can staff an unassigned ready task with `atdd-flow task assign <project> <task-id> --assignee <address> --by <coordinator-address>`; assignment is allowed only once while the task is `todo`. Use `task block` only for a real external blocker, then checkpoint exact state and next action. Once the blocker is resolved, only that coordinator can clear it with `atdd-flow task unblock <project> <task-id> --by <coordinator-address>` while the task remains `in_progress`. For deliveries explicitly governed by the `workflow` ATDD Bun profile, `review → done` additionally requires a durable final behavioral-review result with decision `APPROVE` for the current clean delivery commit. For an idle driver’s final task, `task done ... --retire-assignee` delegates clean-and-merged worktree retirement to ATDD Bun. A driver whose assigned tasks are all already `done` is retired with `atdd-flow seat retire <address> --by <coordinator-or-main>`, using the same checks. When every assigned task is `done` and the recorded worktree is absent from disk and from `git worktree list`, `seat retire` records the retirement without running worktree finish, deleting the branch, or recreating the worktree; a missing worktree that Git still registers fails closed. Both paths fail closed on unfinished tasks or a dirty or unmerged worktree; on success they record `retired` and a checkpoint, then close the driver's Herdr workspace when `close_inactive` is enabled. `main@<project>` may likewise retire a coordinator seat (never main or the operator) once every task it coordinates or is assigned is `done`. A non-main coordinator sharing the primary checkout is retired as a Desk record without worktree finish; other coordinators use the same worktree-finish, absent-and-unregistered, and fail-closed paths. Retirement runs in the seat worktree with ATDD Bun from the primary checkout's `node_modules/.bin`, else the primary checkout's own source when it is the ATDD Bun package, else `PATH`; never the seat worktree's copy, which may pin an older toolkit.
134
134
 
135
135
  At 80% context usage a seat writes a structured handover (template and process rules in the lifecycle convention), checks it with `atdd-flow handover <address> --check --file <path>`, and records it with `atdd-flow handover <address> --file <path>`. The seat keeps one durable `handover.yaml` beside its unchanged checkpoint. Recording rejects a missing header, Runtime line, or section, sections out of order, more than 150 lines, vague references such as `latest` or `that PR` without an exact ID, and a `supersedes` value that does not match the recorded handover id. A fresh session bound to the same seat reads it with `atdd-flow handover show <address>`.
136
136
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-flow",
3
- "version": "0.3.65",
3
+ "version": "0.3.66",
4
4
  "description": "Filesystem-first Desk registry for projects, seats, tasks, threads, and handoffs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,7 +41,7 @@
41
41
  "bun": ">=1.3.0"
42
42
  },
43
43
  "devDependencies": {
44
- "@afokapu/atdd-bun": "0.10.89"
44
+ "@afokapu/atdd-bun": "0.10.101"
45
45
  },
46
46
  "dependencies": {
47
47
  "@typesafe-ai/sdk": "^0.6.0",
package/src/tasks.ts CHANGED
@@ -242,7 +242,7 @@ async function retirementBun(root: string, projectName: string) {
242
242
  return [required(Bun.which("atdd-bun") ?? undefined, "atdd-bun in the primary checkout node_modules/.bin or on PATH")];
243
243
  }
244
244
 
245
- async function retireDriver(root: string, projectName: string, assignee: string, id: string, allowAbsentWorktree = false) {
245
+ async function retireDriver(root: string, projectName: string, assignee: string, id: string, allowAbsentWorktree = false, allowPrimaryCheckout = false) {
246
246
  const unfinished = (await allTasks(root, projectName))
247
247
  .filter((entry) => entry.id !== id && (entry.task.assignee === assignee || entry.task.coordinator === assignee) && !terminal(entry.task))
248
248
  .map((entry) => entry.id);
@@ -251,7 +251,10 @@ async function retireDriver(root: string, projectName: string, assignee: string,
251
251
  const owner = await seat(root, assignee);
252
252
  if (owner.retired) throw new Error(`Seat ${assignee} is already retired.`);
253
253
  try {
254
- const summary = (allowAbsentWorktree ? await absentUnregisteredWorktree(root, projectName, owner) : undefined)
254
+ const primary = (await project(root, projectName)).repository;
255
+ const primaryCheckout = allowPrimaryCheckout && primary && await canonicalPath(owner.worktree) === await canonicalPath(primary);
256
+ const summary = (primaryCheckout ? `Primary checkout ${owner.worktree} retained; retired without worktree finish.` : undefined)
257
+ ?? (allowAbsentWorktree ? await absentUnregisteredWorktree(root, projectName, owner) : undefined)
255
258
  ?? await runOutput([...await retirementBun(root, projectName), "worktree", "finish", "--delete-branch", "--vcs", vcsMode(await project(root, projectName))], owner.worktree);
256
259
  owner.retired = { task: `${projectName}/${id}`, completed_at: now(), summary };
257
260
  await atomicYaml(paths(root).seatFile(assignee), owner);
@@ -276,18 +279,11 @@ async function retireDriver(root: string, projectName: string, assignee: string,
276
279
  await closeRetiredDriverWorkspace(root, owner);
277
280
  }
278
281
 
279
- /**
280
- * Only main retires a coordinator, and never one seated on the primary checkout; a driver may also be
281
- * retired by the coordinator of every task assigned to it.
282
- */
282
+ /** Only main retires a coordinator; a driver may also be retired by the coordinator of every task assigned to it. */
283
283
  async function assertRetireAuthority(root: string, owner: Seat, actor: string, owned: ListedTask[]) {
284
284
  const main = `main@${owner.project}`;
285
285
  if (owner.role === "coordinator") {
286
286
  if (actor !== main) throw new Error(`Only ${main} may retire coordinator ${owner.address}.`);
287
- const primary = (await project(root, owner.project)).repository;
288
- if (primary && await canonicalPath(owner.worktree) === await canonicalPath(primary)) {
289
- throw new Error(`Cannot retire ${owner.address}; its worktree is the primary checkout.`);
290
- }
291
287
  } else if (actor !== main && owned.some((entry) => entry.task.coordinator !== actor)) {
292
288
  throw new Error(`Only ${main} or the coordinator of every task assigned to ${owner.address} may retire it.`);
293
289
  }
@@ -295,7 +291,8 @@ async function assertRetireAuthority(root: string, owner: Seat, actor: string, o
295
291
 
296
292
  /**
297
293
  * Retires a driver whose assigned tasks are all terminal (done, superseded, or rejected), or, for main only, a coordinator
298
- * (never main or the primary checkout) whose coordinated and assigned tasks are all terminal.
294
+ * (never main) whose coordinated and assigned tasks are all terminal. A non-main coordinator on the
295
+ * primary checkout retires its record without invoking worktree finish.
299
296
  */
300
297
  export async function retireSeat(root: string, address: string, args: string[]) {
301
298
  const resolved = await canonicalAddress(root, address);
@@ -310,7 +307,7 @@ export async function retireSeat(root: string, address: string, args: string[])
310
307
  const unfinished = owned.filter((entry) => !terminal(entry.task)).map((entry) => entry.id);
311
308
  if (unfinished.length) throw new Error(`Cannot retire ${resolved}; it still owns unfinished tasks: ${unfinished.join(", ")}.`);
312
309
  await assertRetireAuthority(root, owner, actor, owned);
313
- await retireDriver(root, owner.project, resolved, owned.at(-1)!.id, true);
310
+ await retireDriver(root, owner.project, resolved, owned.at(-1)!.id, true, coordinator && resolved !== `main@${owner.project}`);
314
311
  console.log(`${resolved} retired`);
315
312
  }
316
313