/** * tm_board_write — the write side of the blackboard (2026-09-23). * * Why it exists: the board is the one place an oversized deliverable is * supposed to go, but reaching it needs a write-capable file tool, and three of * the six roles carry none: `architect` and `researcher` get no write/edit/bash * from `whitelist()` and `reviewer` has only the read-only bash whose redirects * P3 refuses — all three keep the read-capable tm_* set, so they are not * tool-less. Every "write the design doc to the board" dispatch therefore ended * as `BLACKBOARD WRITE FAILED` with the document pasted inline anyway — the * reply shape this team exists to enforce (skeleton + path, never a wall of * text) was un-followable for exactly the two roles that produce the longest * artifacts. Even the documented session folder is out of reach: creating it * means running `Get-Date`, which those roles cannot run either. * * What this is NOT: a general file writer. It places a NEW markdown file * inside `///` and nothing else — no * overwrite (a revision is a new `-rN` round, per the documented layout), no * traversal (each segment is a sanitized slug, then realpath-verified against * the root), no workspace or user paths, no `.env` family. The reply returns * the path plus the byte count, never the content: bytes riding back through * the context window is the very thing the board exists to avoid. */ import type { ToolResult } from "../types.js"; import type { TmPipelines } from "./pipelines.js"; /** Content cap for one board file (characters). A deliverable past this is not * a board file — it is two, or it is the wrong tool. */ export declare const BOARD_MAX_CONTENT_CHARS = 200000; /** Files per session folder (across its task dirs): the TTL sweeper is the only * reclaim path by design, so an unbounded count is a disk leak. */ export declare const BOARD_MAX_FILES_PER_SESSION = 200; export declare const BOARD_MAX_TASKS_PER_SESSION = 40; /** `yyyyMMdd-HHmmss`, local — the SAME shape the blackboard note tells the lead * to build with `Get-Date -Format yyyyMMdd-HHmmss` / `date +%Y%m%d-%H%M%S`, so * a tool-made session folder is indistinguishable from a hand-made one. */ export declare function sessionKeyStamp(now?: Date): string; /** * One path segment out of untrusted model text. Separators and `..` are * REWRITTEN rather than rejected (a model that writes `auth/design` means the * task `auth-design`), anything that cannot survive a filename is dropped, and * CJK is preserved because this project's users write CJK. "" means "nothing * usable was given", which the caller turns into an args error. */ export declare function sanitizeSlug(raw: unknown, max?: number): string; /** `NN` for the next file in a task dir: max existing ordinal + 1, so a hand * -written `07-…md` the lead created is counted too. */ export declare function nextOrdinal(names: readonly string[]): number; /** The round suffix for a revision: "" when the base name is free, else * `-r2`, `-r3`… — never a rewrite, because the board's history IS the audit * trail the lead reads back. */ export declare function roundSuffix(names: readonly string[], base: string): string; /** The `NN` that anchors a revision: a family keeps the number its first file * got, so `01-architect-design.md` and `01-architect-design-r2.md` sit * together in a listing. Advancing the ordinal instead (the first version of * this code did) means a revision never gets the `-rN` suffix the documented * layout promises, and the round history becomes invisible. */ export declare function findFamily(names: readonly string[], role: string, topic: string): { base: string; round: string; } | null; export interface BoardTarget { dir: string; file: string; rel: string; round: string; } /** * Plan the write: `///--.md`. * Every segment is sanitized and the resolved session/task pair is checked * against the root by REALPATH, because a task dir somebody made earlier could * be a symlink pointing at `~/.ssh` — and a writer that only strings paths * together would follow it happily. */ export declare function planBoardWrite(opts: { root: string; session: string; task: string; role: string; topic: string; list: (dir: string) => string[]; }): BoardTarget | { error: string; }; /** Count what this session folder already holds, so the caps can be enforced * before another 400 files arrive. Never throws: an unreadable tree simply * counts as nothing. */ export declare function sessionUsage(root: string, session: string): { files: number; tasks: number; }; /** Atomic-ish: write a sibling temp file then rename, so a crash mid-write * cannot leave a half-written board file that a reader mistakes for the real * deliverable. (win32 rename over an existing target throws — which is fine * here, because the name was chosen as unused.) */ export declare function writeNewFile(file: string, text: string): void; export declare function buildBoardWriteTool(deps: { pipelines: TmPipelines; /** The board root the blackboard note publishes: `/.git/opencode-team` * (or the sharded temp fallback). Injected, not recomputed, so a write can * never land somewhere the lead was never told to read. */ boardRoot: string; cfg?: { boardMaxChars?: number; boardMaxFiles?: number; }; args?: Record; /** seam for the session-key stamp in tests */ stamp?: () => string; }): { description: string; args: Record; execute: (rawArgs: Record, ctx: unknown) => Promise; }; //# sourceMappingURL=board.d.ts.map