@afokapu/atdd-flow 0.3.84 → 0.3.86

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.
@@ -33,6 +33,10 @@ content:
33
33
  receipts inbound communication from any seat, but sends substantive direction, results, and
34
34
  wakes only to main@project. Only main@project executes that project repository merge; the
35
35
  operator may authorize, relay, or inspect but never merges.
36
+ Manage every project main, coordinator, and driver seat only through atdd-flow
37
+ canonical commands (spawn, bind, pi runtime launch, task lifecycle, handover); start,
38
+ stop, rebind, or succeed a seat by hand only under explicit owner direction and always
39
+ complete the supported bind/reconciliation immediately after.
36
40
  Post decisions and open questions as messages when they happen.
37
41
  projection: >-
38
42
  Keep this full policy only in this convention. Managed AGENTS.md and CLAUDE.md blocks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-flow",
3
- "version": "0.3.84",
3
+ "version": "0.3.86",
4
4
  "description": "Filesystem-first Desk registry for projects, seats, tasks, threads, and handoffs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -55,7 +55,17 @@ function lineCount(text: string) {
55
55
  return text ? text.replace(/\r?\n$/, "").split(/\r?\n/).length : 0;
56
56
  }
57
57
 
58
- /** Idempotently projects the canonical pointer while preserving user content and refusing oversized instruction files. */
58
+ /** The managed block plus one separator line is the most a projection may add to any file. */
59
+ const maxManagedDeltaLines = lineCount(block()) + 1;
60
+
61
+ /** Fails closed when a projection would grow a file by more than the managed block; owner content is never counted. */
62
+ function assertBoundedDelta(name: string, source: string, next: string) {
63
+ if (lineCount(next) - lineCount(source) > maxManagedDeltaLines) {
64
+ throw new Error(`${name} projection would add more than ${maxManagedDeltaLines} managed lines; refusing projection without changing user content.`);
65
+ }
66
+ }
67
+
68
+ /** Idempotently projects the canonical pointer while preserving every owner byte outside the managed markers. */
59
69
  export async function projectCollaborationPolicy(root: string) {
60
70
  const projections = await Promise.all(["AGENTS.md", "CLAUDE.md"].map(async (name) => {
61
71
  const file = join(root, name);
@@ -66,11 +76,7 @@ export async function projectCollaborationPolicy(root: string) {
66
76
  }
67
77
  return { name, file, source, next: projectCollaborationPolicyText(source) };
68
78
  }));
69
- for (const { name, source, next } of projections) {
70
- if (lineCount(source) > 20 || lineCount(next) > 20) {
71
- throw new Error(`${name} exceeds the 20-line cap; refusing projection without changing user content.`);
72
- }
73
- }
79
+ for (const { name, source, next } of projections) assertBoundedDelta(name, source, next);
74
80
  await Promise.all(projections.map(async ({ file, source, next }) => {
75
81
  if (next !== source) await writeFile(file, next, "utf8");
76
82
  }));
package/src/seats.ts CHANGED
@@ -255,7 +255,7 @@ async function taskCoordinatorBase(root: string, config: Project, task: Task, re
255
255
  const head = await runOutput(["git", "-C", coordinator.worktree, "rev-parse", "HEAD"]);
256
256
  if (head !== branchHead) throw new Error(`Coordinator ${coordinator.address} head is stale relative to ${branch}.`);
257
257
  } else {
258
- try { await assertBranchReady(mode, coordinator.worktree, branch); }
258
+ try { await assertBranchReady(mode, coordinator.worktree, branch, coordinator.role === "main"); }
259
259
  catch (error) { throw new Error(`Coordinator ${coordinator.address} is not ready: ${(error as Error).message}`); }
260
260
  }
261
261
  return { coordinator, branch, commit: branchHead };
@@ -331,7 +331,7 @@ async function spawnTaskDriver(root: string, config: Project, role: Role, name:
331
331
  const currentBranch = await branchCommit(mode, base.coordinator.worktree, coordinatorBase.branch);
332
332
  if (currentBranch !== coordinatorBase.commit) throw new Error(`Coordinator ${base.coordinator.address} advanced during driver provisioning.`);
333
333
  if (!baseArgument) {
334
- const current = await assertBranchReady(mode, base.coordinator.worktree, coordinatorBase.branch);
334
+ const current = await assertBranchReady(mode, base.coordinator.worktree, coordinatorBase.branch, base.coordinator.role === "main");
335
335
  if (current !== coordinatorBase.commit) throw new Error(`Coordinator ${base.coordinator.address} advanced during driver provisioning.`);
336
336
  }
337
337
  await atomicYaml(paths(root).seatFile(address), record);
package/src/vcs.ts CHANGED
@@ -103,7 +103,11 @@ export async function isAncestor(cwd: string, ancestor: string, descendant: stri
103
103
  catch { return false; }
104
104
  }
105
105
 
106
- export async function assertBranchReady(mode: VcsMode, cwd: string, branch: string) {
106
+ /**
107
+ * A branch is ready when its checkout is clean and at the branch tip. `allowDirtyParent` additionally accepts a Jujutsu working copy
108
+ * whose parent is exactly the branch tip with uncommitted changes (a migrated dirty primary); conflicts and any other lineage stay refused.
109
+ */
110
+ export async function assertBranchReady(mode: VcsMode, cwd: string, branch: string, allowDirtyParent = false) {
107
111
  if (mode === "git") {
108
112
  if (await runOutput(["git", "-C", cwd, "status", "--porcelain"])) throw new Error(`Worktree ${cwd} is dirty.`);
109
113
  const checkedOut = await runOutput(["git", "-C", cwd, "branch", "--show-current"]);
@@ -120,7 +124,7 @@ export async function assertBranchReady(mode: VcsMode, cwd: string, branch: stri
120
124
  runOutput(["jj", "log", "-r", "@-", "--no-graph", "-T", "commit_id"], cwd),
121
125
  runOutput(["jj", "diff", "-r", "@", "--name-only"], cwd),
122
126
  ]);
123
- if (parent === expected && !changed) return expected;
127
+ if (parent === expected && (!changed || allowDirtyParent)) return expected;
124
128
  }
125
129
  throw new Error(`Worktree ${cwd} is stale relative to ${branch}.`);
126
130
  }