@afokapu/atdd-flow 0.3.33 → 0.3.34

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.
@@ -0,0 +1,44 @@
1
+ schema_version: 1.0.0
2
+ rule_id: atdd-flow.collaboration
3
+ kind: policy
4
+ status: active
5
+ name: Collaborate through durable accountable delegation
6
+ statement: >-
7
+ Agents use accountable delegation, evidence-based escalation, economical delivery,
8
+ and bounded two-way resolution while keeping work within its assigned project and task.
9
+ content:
10
+ canonical_policy: >-
11
+ Delegation flows operator → main → named coordinator → driver. Each level proactively
12
+ dispatches safe independent work, verifies durable delivery and visible execution, and
13
+ escalates material blockers upward with exact evidence, an economical recommendation,
14
+ viable alternatives and tradeoffs, and safe work that can continue. Choose the smallest
15
+ non-duplicative compliant implementation; do not skip lifecycle or gates. Use bounded
16
+ two-way evidence dialogue with the responsible actor to resolve assumptions or
17
+ disagreement; time-box it and escalate only unresolved decisions. Distinguish persisted,
18
+ delivered, acknowledged, awaiting actor, and executing. Never infer authorization or
19
+ restart productive sessions merely to relay policy. Proactive next-task discovery stays
20
+ within the same project. Keep one bounded active task and worktree per driver; assign
21
+ adjacent work separately. Cross-project dialogue is only for a real dependency or
22
+ interface through counterpart authority. Prefer atomic assignment; unassigned work needs
23
+ a project-main decision proposal with purpose, priority, recommended assignee,
24
+ alternatives, consequences, dependencies, and safe work meanwhile. Promptly complete,
25
+ supersede, reject, or time-bound defer work. Coordinating actors reconcile their durable
26
+ inbox as the first action at every session, turn, and task resumption, and after every
27
+ bounded action, phase completion, or wait before the next action. An actionable
28
+ expects_result remains visible until answered, executed, delegated with verified delivery
29
+ and follow-up, or explicitly deferred, superseded, or closed. Messages only communicate,
30
+ evidence, consensus, authorization, or status; they never request executable work unless it
31
+ references an existing assigned task. Tasks define, assign, execute, evidence, and complete
32
+ work; every code or repository mutation requires that task first. The operator accepts and
33
+ receipts inbound communication from any seat, but sends substantive direction, results, and
34
+ wakes only to main@project. Only main@project executes that project repository merge; the
35
+ operator may authorize, relay, or inspect but never merges.
36
+ projection: >-
37
+ Keep this full policy only in this convention. Managed AGENTS.md and CLAUDE.md blocks
38
+ contain only its canonical pointer and preserve all user-authored content outside the
39
+ marked block.
40
+ metadata:
41
+ aliases:
42
+ - ATDD-FLOW-COLLABORATION
43
+ implementation:
44
+ type: source-resolved
@@ -6,6 +6,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
6
  import { clearStaleRuntimeState, clearRuntimeState, heartbeatRuntimeState, ownsRuntimeState, registerRuntimeState, runtimeOwnerToken, withOwnedRuntimeState } from "../../src/runtime-state";
7
7
  import { paths, readYaml, type Seat } from "../../src/core";
8
8
  import { directiveNotice, nextDirective } from "../../src/tasks";
9
+ import { runtimeStartupInstruction } from "../../src/collaboration-policy";
9
10
 
10
11
  type Thread = { id?: unknown; participants?: unknown; subject?: unknown };
11
12
  export type Mail = { id?: unknown; from?: unknown; to?: unknown; subject?: unknown; created_at?: unknown };
@@ -63,7 +64,6 @@ const defaultHeartbeatMs = 15_000;
63
64
  const defaultStaleAfterMs = 60_000;
64
65
  const defaultActivationIntervalMs = 25;
65
66
  const defaultActivationTimeoutMs = 60_000;
66
- const lifecycleConventionPath = "conventions/atdd-workflow.workflow/atdd-workflow.workflow.lifecycle.convention.yaml";
67
67
 
68
68
  function participants(thread: Thread) {
69
69
  return Array.isArray(thread.participants) && thread.participants.every((entry) => typeof entry === "string") ? thread.participants as string[] : [];
@@ -358,7 +358,7 @@ export default function (pi: ExtensionAPI) {
358
358
  root, seat, piSession, herdrSession, pane,
359
359
  activate: async () => {
360
360
  if (!await current.activate()) return;
361
- pi.sendMessage({ customType: "atdd-flow-start", content: `SYSTEM: you are ${seat}. Read your durable seat and assigned work with: atdd-flow open ${seat}. Convention: ${lifecycleConventionPath}. Continue assigned in_progress work until it is review-ready or explicitly blocked.`, display: true, details: { seat, root } }, { triggerTurn: true, deliverAs: "followUp" });
361
+ pi.sendMessage({ customType: "atdd-flow-start", content: runtimeStartupInstruction(seat), display: true, details: { seat, root } }, { triggerTurn: true, deliverAs: "followUp" });
362
362
  await sendTaskDirective(pi, root, seat);
363
363
  if (ctx.hasUI) ctx.ui.notify(`ATDD Flow native mail active for ${seat}`, "info");
364
364
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-flow",
3
- "version": "0.3.33",
3
+ "version": "0.3.34",
4
4
  "description": "Filesystem-first Desk registry for projects, seats, tasks, threads, and handoffs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,76 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { parse } from "yaml";
5
+
6
+ export const collaborationConventionPath = "conventions/atdd-flow.collaboration/atdd-flow.collaboration.convention.yaml";
7
+ const startMarker = "<!-- atdd-flow:collaboration-policy:start -->";
8
+ const endMarker = "<!-- atdd-flow:collaboration-policy:end -->";
9
+ export const collaborationPolicyPointer = `ATDD Flow collaboration policy: node_modules/@afokapu/atdd-flow/${collaborationConventionPath} (source checkout: ${collaborationConventionPath})`;
10
+
11
+ type Convention = { content?: { canonical_policy?: unknown } };
12
+
13
+ function conventionFile() {
14
+ return join(import.meta.dir, "..", collaborationConventionPath);
15
+ }
16
+
17
+ /** The convention is the sole policy text; projections and runtimes resolve it from here. */
18
+ export function canonicalCollaborationPolicy() {
19
+ const convention = parse(readFileSync(conventionFile(), "utf8")) as Convention;
20
+ const policy = convention.content?.canonical_policy;
21
+ if (typeof policy !== "string" || !policy.trim()) throw new Error("Collaboration convention requires content.canonical_policy.");
22
+ return policy.trim();
23
+ }
24
+
25
+ function block() {
26
+ return `${startMarker}\n${collaborationPolicyPointer}\n${endMarker}\n`;
27
+ }
28
+
29
+ function escaped(value: string) {
30
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31
+ }
32
+
33
+ const existingBlock = new RegExp(`${escaped(startMarker)}\\r?\\n[\\s\\S]*?${escaped(endMarker)}(?:\\r?\\n)?`, "g");
34
+
35
+ /** Replaces every prior managed block with one canonical pointer while retaining all other bytes. */
36
+ export function projectCollaborationPolicyText(source: string) {
37
+ const matches = [...source.matchAll(existingBlock)];
38
+ if (matches.length) {
39
+ const first = matches[0]!;
40
+ const before = source.slice(0, first.index);
41
+ const after = source.slice((first.index ?? 0) + first[0].length).replace(existingBlock, "");
42
+ return `${before}${block()}${after}`;
43
+ }
44
+ if (!source) return block();
45
+ return `${source}${source.endsWith("\n") ? "\n" : "\n\n"}${block()}`;
46
+ }
47
+
48
+ function lineCount(text: string) {
49
+ return text ? text.replace(/\r?\n$/, "").split(/\r?\n/).length : 0;
50
+ }
51
+
52
+ /** Idempotently projects the canonical pointer while preserving user content and refusing oversized instruction files. */
53
+ export async function projectCollaborationPolicy(root: string) {
54
+ const projections = await Promise.all(["AGENTS.md", "CLAUDE.md"].map(async (name) => {
55
+ const file = join(root, name);
56
+ let source = "";
57
+ try { source = await readFile(file, "utf8"); }
58
+ catch (error) {
59
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
60
+ }
61
+ return { name, file, source, next: projectCollaborationPolicyText(source) };
62
+ }));
63
+ for (const { name, source, next } of projections) {
64
+ if (lineCount(source) > 20 || lineCount(next) > 20) {
65
+ throw new Error(`${name} exceeds the 20-line cap; refusing projection without changing user content.`);
66
+ }
67
+ }
68
+ await Promise.all(projections.map(async ({ file, source, next }) => {
69
+ if (next !== source) await writeFile(file, next, "utf8");
70
+ }));
71
+ }
72
+
73
+ /** The one startup handoff for supported Flow-managed runtimes, resolved from the canonical convention. */
74
+ export function runtimeStartupInstruction(seat: string) {
75
+ return `SYSTEM: you are ${seat}. Collaboration policy: ${canonicalCollaborationPolicy()} Canonical convention: ${collaborationConventionPath}. Read your durable seat and assigned work with: atdd-flow open ${seat}. Continue assigned in_progress work until it is review-ready or explicitly blocked.`;
76
+ }
package/src/seats.ts CHANGED
@@ -13,6 +13,7 @@ import { seatTasks } from "./tasks";
13
13
  import { misplacedSeats, projectHerdrSeat } from "./multiplexer";
14
14
  import { isRuntimeStateStale, readRuntimeState } from "./runtime-state";
15
15
  import { resolveExactSessionAdoptionAuthorization } from "./adoption-authorizations";
16
+ import { projectCollaborationPolicy } from "./collaboration-policy";
16
17
 
17
18
  const lifecycleConventionPath = "conventions/atdd-workflow.workflow/atdd-workflow.workflow.lifecycle.convention.yaml";
18
19
 
@@ -51,6 +52,7 @@ export async function init(root: string, name: string, args: string[]) {
51
52
  const config = { schema: "atdd-workflow/desk/v1" as const, desk: name, application: "herdr", executables: defaultExecutables() };
52
53
  await Promise.all([mkdir(paths(root).work, { recursive: true }), mkdir(paths(root).threads, { recursive: true })]);
53
54
  await Promise.all([atomicYaml(paths(root).desk, config), atomicYaml(paths(root).models, defaultModels())]);
55
+ await projectCollaborationPolicy(root);
54
56
  if (args.includes("--git") && !await exists(join(root, ".git"))) await run(["git", "init", "--initial-branch=main", root]);
55
57
  console.log(`Initialized Desk ${root}`);
56
58
  }
@@ -58,6 +60,7 @@ export async function init(root: string, name: string, args: string[]) {
58
60
  export async function migrate(root: string) {
59
61
  if (await migrateDesk(root)) console.log("Migrated legacy coordination registry to desk.yaml");
60
62
  else console.log("Desk registry already exists");
63
+ await projectCollaborationPolicy(root);
61
64
  }
62
65
 
63
66
  export async function initProject(root: string, name: string) {