@pruddiman/hem 0.0.1-beta-5671db0

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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agents/arbiter-agent.d.ts +72 -0
  3. package/dist/agents/arbiter-agent.js +149 -0
  4. package/dist/agents/architecture-agent.d.ts +148 -0
  5. package/dist/agents/architecture-agent.js +459 -0
  6. package/dist/agents/base-agent.d.ts +44 -0
  7. package/dist/agents/base-agent.js +57 -0
  8. package/dist/agents/crossref-agent.d.ts +140 -0
  9. package/dist/agents/crossref-agent.js +560 -0
  10. package/dist/agents/crossref-arbiter-agent.d.ts +72 -0
  11. package/dist/agents/crossref-arbiter-agent.js +147 -0
  12. package/dist/agents/documentation-agent.d.ts +55 -0
  13. package/dist/agents/documentation-agent.js +159 -0
  14. package/dist/agents/exploration-agent.d.ts +58 -0
  15. package/dist/agents/exploration-agent.js +102 -0
  16. package/dist/agents/grouping-agent.d.ts +167 -0
  17. package/dist/agents/grouping-agent.js +557 -0
  18. package/dist/agents/index-agent.d.ts +86 -0
  19. package/dist/agents/index-agent.js +360 -0
  20. package/dist/agents/organization-agent.d.ts +144 -0
  21. package/dist/agents/organization-agent.js +607 -0
  22. package/dist/auth.d.ts +372 -0
  23. package/dist/auth.js +1072 -0
  24. package/dist/broadcast-mcp.d.ts +21 -0
  25. package/dist/broadcast-mcp.js +59 -0
  26. package/dist/changelog.d.ts +85 -0
  27. package/dist/changelog.js +223 -0
  28. package/dist/decision-queue.d.ts +173 -0
  29. package/dist/decision-queue.js +265 -0
  30. package/dist/diff-scope.d.ts +24 -0
  31. package/dist/diff-scope.js +28 -0
  32. package/dist/discovery.d.ts +54 -0
  33. package/dist/discovery.js +405 -0
  34. package/dist/grouping.d.ts +37 -0
  35. package/dist/grouping.js +343 -0
  36. package/dist/helpers/format.d.ts +5 -0
  37. package/dist/helpers/format.js +13 -0
  38. package/dist/helpers/index.d.ts +11 -0
  39. package/dist/helpers/index.js +11 -0
  40. package/dist/helpers/parsing.d.ts +52 -0
  41. package/dist/helpers/parsing.js +128 -0
  42. package/dist/helpers/paths.d.ts +41 -0
  43. package/dist/helpers/paths.js +67 -0
  44. package/dist/helpers/strings.d.ts +45 -0
  45. package/dist/helpers/strings.js +97 -0
  46. package/dist/index.d.ts +135 -0
  47. package/dist/index.js +1087 -0
  48. package/dist/merge-utils.d.ts +22 -0
  49. package/dist/merge-utils.js +34 -0
  50. package/dist/orchestrator.d.ts +194 -0
  51. package/dist/orchestrator.js +1169 -0
  52. package/dist/output.d.ts +106 -0
  53. package/dist/output.js +243 -0
  54. package/dist/progress.d.ts +228 -0
  55. package/dist/progress.js +644 -0
  56. package/dist/providers/copilot.d.ts +247 -0
  57. package/dist/providers/copilot.js +598 -0
  58. package/dist/providers/index.d.ts +15 -0
  59. package/dist/providers/index.js +12 -0
  60. package/dist/providers/opencode.d.ts +156 -0
  61. package/dist/providers/opencode.js +416 -0
  62. package/dist/providers/types.d.ts +156 -0
  63. package/dist/providers/types.js +16 -0
  64. package/dist/resources.d.ts +76 -0
  65. package/dist/resources.js +151 -0
  66. package/dist/search-index.d.ts +71 -0
  67. package/dist/search-index.js +187 -0
  68. package/dist/search-mcp.d.ts +25 -0
  69. package/dist/search-mcp.js +100 -0
  70. package/dist/server-utils.d.ts +56 -0
  71. package/dist/server-utils.js +135 -0
  72. package/dist/session.d.ts +227 -0
  73. package/dist/session.js +370 -0
  74. package/dist/types.d.ts +272 -0
  75. package/dist/types.js +5 -0
  76. package/dist/worktree.d.ts +82 -0
  77. package/dist/worktree.js +187 -0
  78. package/package.json +45 -0
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Standalone stdio-based MCP server for inter-agent broadcast communication.
4
+ *
5
+ * Exposes a single tool: `broadcast({ message: string })`
6
+ *
7
+ * This server is intentionally minimal. The real broadcast relay logic lives
8
+ * in the Hem orchestrator, which intercepts broadcast tool calls via SSE
9
+ * events and relays messages to peer agent sessions using `promptAsync()`.
10
+ *
11
+ * The server simply returns `{ status: "sent" }` to satisfy the agent's tool
12
+ * call — the orchestrator handles the actual message routing.
13
+ *
14
+ * Architecture:
15
+ * Agent calls broadcast() → MCP server returns "sent" →
16
+ * Orchestrator sees tool call in SSE → promptAsync() to all peers
17
+ *
18
+ * Registered in OpenCode via config.mcp as:
19
+ * { type: "local", command: ["node", "dist/broadcast-mcp.js"], enabled: true }
20
+ */
21
+ export {};
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Standalone stdio-based MCP server for inter-agent broadcast communication.
4
+ *
5
+ * Exposes a single tool: `broadcast({ message: string })`
6
+ *
7
+ * This server is intentionally minimal. The real broadcast relay logic lives
8
+ * in the Hem orchestrator, which intercepts broadcast tool calls via SSE
9
+ * events and relays messages to peer agent sessions using `promptAsync()`.
10
+ *
11
+ * The server simply returns `{ status: "sent" }` to satisfy the agent's tool
12
+ * call — the orchestrator handles the actual message routing.
13
+ *
14
+ * Architecture:
15
+ * Agent calls broadcast() → MCP server returns "sent" →
16
+ * Orchestrator sees tool call in SSE → promptAsync() to all peers
17
+ *
18
+ * Registered in OpenCode via config.mcp as:
19
+ * { type: "local", command: ["node", "dist/broadcast-mcp.js"], enabled: true }
20
+ */
21
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
23
+ import { z } from "zod/v4";
24
+ const server = new McpServer({
25
+ name: "hem-broadcast",
26
+ version: "1.0.0",
27
+ });
28
+ server.registerTool("broadcast", {
29
+ description: "Broadcast a message to all other parallel organization workers. " +
30
+ "Use this to communicate file renames, deletions, structural conventions, " +
31
+ "and brief acknowledgements. Messages should be short and actionable.",
32
+ inputSchema: {
33
+ message: z
34
+ .string()
35
+ .describe("The message to broadcast. Use structured prefixes: " +
36
+ "RENAMED:, DELETED:, CONVENTION:, ACK:, SUGGESTION:"),
37
+ },
38
+ }, async ({ message }) => {
39
+ // The actual relay is handled by the orchestrator via SSE interception.
40
+ // This tool just needs to return success so the agent can continue.
41
+ return {
42
+ content: [
43
+ {
44
+ type: "text",
45
+ text: JSON.stringify({ status: "sent", message }),
46
+ },
47
+ ],
48
+ };
49
+ });
50
+ async function main() {
51
+ const transport = new StdioServerTransport();
52
+ await server.connect(transport);
53
+ // stdout is the MCP transport — use stderr for logging
54
+ console.error("[hem-broadcast] MCP server running on stdio");
55
+ }
56
+ main().catch((err) => {
57
+ console.error("[hem-broadcast] Fatal:", err);
58
+ process.exit(1);
59
+ });
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Changelog generation and git-based diff scoping for Hem.
3
+ *
4
+ * Manages `changelog.md` in the documentation destination directory.
5
+ * Each Hem run appends a single entry recording the commit SHA, timestamp,
6
+ * and which doc files were created or updated.
7
+ *
8
+ * On subsequent runs, the previous SHA is extracted from `changelog.md`
9
+ * to compute which source files changed, enabling incremental generation.
10
+ *
11
+ * Reference: specs/004-changelog-diff-scope.
12
+ */
13
+ /**
14
+ * Reads the most recent commit SHA from the changelog file.
15
+ *
16
+ * Parses `{destinationPath}/changelog.md` for the first occurrence of
17
+ * `<!-- hem-sha: <SHA> -->`. Since entries are prepended (newest first),
18
+ * the first match is the most recent.
19
+ *
20
+ * @param destinationPath - Absolute or relative path to the docs directory.
21
+ * @returns The SHA string, or `null` if the file doesn't exist or has no marker.
22
+ */
23
+ export declare function readLastSHA(destinationPath: string): Promise<string | null>;
24
+ /**
25
+ * Computes which source files changed between a previous commit and HEAD.
26
+ *
27
+ * Runs `git diff --name-only <prevSHA>..HEAD` scoped to the source
28
+ * directory. Returns paths relative to `sourceRoot`. Downstream callers
29
+ * (e.g., `scopeToChangedFiles`) apply glob-level filtering, so this
30
+ * function returns all changed paths under `sourceRoot`.
31
+ *
32
+ * @param sourceRoot - Absolute path to the source directory.
33
+ * @param prevSHA - The commit SHA from the previous Hem run.
34
+ * @returns Array of relative paths of changed source files.
35
+ * @throws If git is not available, the repo is not a git repo, or the SHA is invalid.
36
+ */
37
+ export declare function computeChangedFiles(sourceRoot: string, prevSHA: string): string[];
38
+ /**
39
+ * Returns the current HEAD commit SHA.
40
+ *
41
+ * @param sourceRoot - Absolute path to a directory within the git repo.
42
+ * @returns The full commit SHA string.
43
+ * @throws If git is not available or the directory is not in a git repo.
44
+ */
45
+ export declare function getCurrentSHA(sourceRoot: string): string;
46
+ /**
47
+ * Determines which doc files were created or modified during this Hem run.
48
+ *
49
+ * Uses `git status --porcelain` on the destination directory to find
50
+ * untracked (new) and modified files. This captures changes made by
51
+ * doc agents, post-processing agents, and structural file writes.
52
+ *
53
+ * @param destinationPath - Absolute path to the docs directory.
54
+ * @returns Array of relative paths of new/modified `.md` files.
55
+ */
56
+ export declare function detectChangedDocs(destinationPath: string): string[];
57
+ /**
58
+ * Writes (or appends to) the changelog file with a new entry.
59
+ *
60
+ * Entries are prepended after the `# Changelog` heading so the most
61
+ * recent entry is always first. If the file doesn't exist, it is created
62
+ * with the heading.
63
+ *
64
+ * ### Entry format
65
+ *
66
+ * ```markdown
67
+ * ## 2026-02-21T15:30:00.000Z
68
+ * <!-- hem-sha: abc1234 -->
69
+ * - Updated: features/auth.md
70
+ * - Created: layers/database.md
71
+ * ```
72
+ *
73
+ * For initial runs:
74
+ * ```markdown
75
+ * ## 2026-02-21T15:30:00.000Z
76
+ * <!-- hem-sha: abc1234 -->
77
+ * - Initial documentation generation
78
+ * ```
79
+ *
80
+ * @param destinationPath - Absolute path to the docs directory.
81
+ * @param commitSHA - The HEAD commit SHA at the time of this run.
82
+ * @param docPaths - Relative paths of docs that were created/updated.
83
+ * @param isInitial - Whether this is the first Hem run (no prior changelog).
84
+ */
85
+ export declare function writeChangelogEntry(destinationPath: string, commitSHA: string, docPaths: string[], isInitial: boolean): Promise<void>;
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Changelog generation and git-based diff scoping for Hem.
3
+ *
4
+ * Manages `changelog.md` in the documentation destination directory.
5
+ * Each Hem run appends a single entry recording the commit SHA, timestamp,
6
+ * and which doc files were created or updated.
7
+ *
8
+ * On subsequent runs, the previous SHA is extracted from `changelog.md`
9
+ * to compute which source files changed, enabling incremental generation.
10
+ *
11
+ * Reference: specs/004-changelog-diff-scope.
12
+ */
13
+ import { resolve, join, relative } from "node:path";
14
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
15
+ import { execSync } from "node:child_process";
16
+ // ── SHA Marker ─────────────────────────────────────────────────────────
17
+ /**
18
+ * HTML comment pattern used to embed commit SHAs in changelog entries.
19
+ *
20
+ * Example: `<!-- hem-sha: abc1234def5678 -->`
21
+ */
22
+ const SHA_MARKER_RE = /<!-- hem-sha: ([0-9a-f]+) -->/;
23
+ // ── Read Previous SHA ─────────────────────────────────────────────────
24
+ /**
25
+ * Reads the most recent commit SHA from the changelog file.
26
+ *
27
+ * Parses `{destinationPath}/changelog.md` for the first occurrence of
28
+ * `<!-- hem-sha: <SHA> -->`. Since entries are prepended (newest first),
29
+ * the first match is the most recent.
30
+ *
31
+ * @param destinationPath - Absolute or relative path to the docs directory.
32
+ * @returns The SHA string, or `null` if the file doesn't exist or has no marker.
33
+ */
34
+ export async function readLastSHA(destinationPath) {
35
+ const changelogPath = join(resolve(destinationPath), "changelog.md");
36
+ let content;
37
+ try {
38
+ content = await readFile(changelogPath, "utf-8");
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ const match = SHA_MARKER_RE.exec(content);
44
+ return match?.[1] ?? null;
45
+ }
46
+ // ── Compute Changed Files ─────────────────────────────────────────────
47
+ /**
48
+ * Computes which source files changed between a previous commit and HEAD.
49
+ *
50
+ * Runs `git diff --name-only <prevSHA>..HEAD` scoped to the source
51
+ * directory. Returns paths relative to `sourceRoot`. Downstream callers
52
+ * (e.g., `scopeToChangedFiles`) apply glob-level filtering, so this
53
+ * function returns all changed paths under `sourceRoot`.
54
+ *
55
+ * @param sourceRoot - Absolute path to the source directory.
56
+ * @param prevSHA - The commit SHA from the previous Hem run.
57
+ * @returns Array of relative paths of changed source files.
58
+ * @throws If git is not available, the repo is not a git repo, or the SHA is invalid.
59
+ */
60
+ export function computeChangedFiles(sourceRoot, prevSHA) {
61
+ const absoluteSource = resolve(sourceRoot);
62
+ // Scope git diff to the source directory via `-- .`.
63
+ const cmd = `git diff --name-only ${prevSHA}..HEAD -- .`;
64
+ const output = execSync(cmd, {
65
+ cwd: absoluteSource,
66
+ encoding: "utf-8",
67
+ timeout: 15_000,
68
+ });
69
+ // `git diff --name-only` always returns paths relative to the repo root,
70
+ // regardless of cwd. We need to strip the source directory prefix so the
71
+ // returned paths are relative to sourceRoot (matching FileInfo.path).
72
+ const repoRoot = execSync("git rev-parse --show-toplevel", {
73
+ cwd: absoluteSource,
74
+ encoding: "utf-8",
75
+ timeout: 5_000,
76
+ }).trim();
77
+ const sourceRel = relative(repoRoot, absoluteSource).replace(/\\/g, "/");
78
+ return output
79
+ .split("\n")
80
+ .map((line) => line.trim())
81
+ .filter((line) => line.length > 0)
82
+ .map((line) => {
83
+ const normalized = line.replace(/\\/g, "/");
84
+ if (sourceRel && normalized.startsWith(sourceRel + "/")) {
85
+ return normalized.slice(sourceRel.length + 1);
86
+ }
87
+ return normalized;
88
+ });
89
+ }
90
+ // ── Get Current SHA ───────────────────────────────────────────────────
91
+ /**
92
+ * Returns the current HEAD commit SHA.
93
+ *
94
+ * @param sourceRoot - Absolute path to a directory within the git repo.
95
+ * @returns The full commit SHA string.
96
+ * @throws If git is not available or the directory is not in a git repo.
97
+ */
98
+ export function getCurrentSHA(sourceRoot) {
99
+ return execSync("git rev-parse HEAD", {
100
+ cwd: resolve(sourceRoot),
101
+ encoding: "utf-8",
102
+ timeout: 5_000,
103
+ }).trim();
104
+ }
105
+ // ── Detect Changed Doc Files ──────────────────────────────────────────
106
+ /**
107
+ * Determines which doc files were created or modified during this Hem run.
108
+ *
109
+ * Uses `git status --porcelain` on the destination directory to find
110
+ * untracked (new) and modified files. This captures changes made by
111
+ * doc agents, post-processing agents, and structural file writes.
112
+ *
113
+ * @param destinationPath - Absolute path to the docs directory.
114
+ * @returns Array of relative paths of new/modified `.md` files.
115
+ */
116
+ export function detectChangedDocs(destinationPath) {
117
+ const absoluteDest = resolve(destinationPath);
118
+ let output;
119
+ try {
120
+ output = execSync("git status --porcelain .", {
121
+ cwd: absoluteDest,
122
+ encoding: "utf-8",
123
+ timeout: 10_000,
124
+ });
125
+ }
126
+ catch {
127
+ // Not a git repo or git not available — return empty
128
+ return [];
129
+ }
130
+ const structural = new Set(["index.md", "architecture.md", "changelog.md"]);
131
+ return output
132
+ .split("\n")
133
+ .map((line) => line.trim())
134
+ .filter((line) => line.length > 0)
135
+ .map((line) => {
136
+ // git status --porcelain format: "XY <path>" or "XY <path> -> <path>"
137
+ // We want the path portion, which starts at index 3.
138
+ const pathPart = line.slice(3).trim();
139
+ // Handle renames: "old -> new"
140
+ const arrowIdx = pathPart.indexOf(" -> ");
141
+ return arrowIdx >= 0 ? pathPart.slice(arrowIdx + 4) : pathPart;
142
+ })
143
+ .filter((p) => p.endsWith(".md") && !structural.has(p));
144
+ }
145
+ // ── Write Changelog Entry ─────────────────────────────────────────────
146
+ /**
147
+ * Writes (or appends to) the changelog file with a new entry.
148
+ *
149
+ * Entries are prepended after the `# Changelog` heading so the most
150
+ * recent entry is always first. If the file doesn't exist, it is created
151
+ * with the heading.
152
+ *
153
+ * ### Entry format
154
+ *
155
+ * ```markdown
156
+ * ## 2026-02-21T15:30:00.000Z
157
+ * <!-- hem-sha: abc1234 -->
158
+ * - Updated: features/auth.md
159
+ * - Created: layers/database.md
160
+ * ```
161
+ *
162
+ * For initial runs:
163
+ * ```markdown
164
+ * ## 2026-02-21T15:30:00.000Z
165
+ * <!-- hem-sha: abc1234 -->
166
+ * - Initial documentation generation
167
+ * ```
168
+ *
169
+ * @param destinationPath - Absolute path to the docs directory.
170
+ * @param commitSHA - The HEAD commit SHA at the time of this run.
171
+ * @param docPaths - Relative paths of docs that were created/updated.
172
+ * @param isInitial - Whether this is the first Hem run (no prior changelog).
173
+ */
174
+ export async function writeChangelogEntry(destinationPath, commitSHA, docPaths, isInitial) {
175
+ const absoluteDest = resolve(destinationPath);
176
+ const changelogPath = join(absoluteDest, "changelog.md");
177
+ const timestamp = new Date().toISOString();
178
+ // Build the new entry
179
+ const entryLines = [
180
+ `## ${timestamp}`,
181
+ `<!-- hem-sha: ${commitSHA} -->`,
182
+ ];
183
+ if (isInitial) {
184
+ entryLines.push("- Initial documentation generation");
185
+ }
186
+ else {
187
+ for (const docPath of docPaths.sort()) {
188
+ entryLines.push(`- ${docPath}`);
189
+ }
190
+ if (docPaths.length === 0) {
191
+ entryLines.push("- No documentation changes");
192
+ }
193
+ }
194
+ const entry = entryLines.join("\n");
195
+ // Read existing content (or start fresh)
196
+ let existing = "";
197
+ try {
198
+ existing = await readFile(changelogPath, "utf-8");
199
+ }
200
+ catch {
201
+ // File doesn't exist — will be created
202
+ }
203
+ let newContent;
204
+ if (existing.length === 0) {
205
+ // Brand new file
206
+ newContent = `# Changelog\n\n${entry}\n`;
207
+ }
208
+ else {
209
+ // Prepend after the "# Changelog" heading line
210
+ const headingEnd = existing.indexOf("\n");
211
+ if (headingEnd >= 0) {
212
+ const heading = existing.slice(0, headingEnd);
213
+ const rest = existing.slice(headingEnd + 1);
214
+ newContent = `${heading}\n\n${entry}\n${rest}`;
215
+ }
216
+ else {
217
+ // File has content but no newline (unusual) — just prepend
218
+ newContent = `# Changelog\n\n${entry}\n`;
219
+ }
220
+ }
221
+ await mkdir(absoluteDest, { recursive: true });
222
+ await writeFile(changelogPath, newContent, "utf-8");
223
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Decision queue for Hem's parallel organization pass.
3
+ *
4
+ * Solves the race condition where the arbiter issues a MERGE and a DELETE
5
+ * for the same source file simultaneously. The MERGE must complete (worker
6
+ * reads source → writes merged target) before the DELETE removes the source.
7
+ *
8
+ * ## How it works
9
+ *
10
+ * 1. **MERGE src INTO dst** — relayed immediately. The queue records that
11
+ * `src` has a pending read (the merge worker needs to read it).
12
+ * A filesystem watcher on `dst` waits for the merged file to appear.
13
+ *
14
+ * 2. **DELETE filepath** — if `filepath` has a pending MERGE read, the
15
+ * DELETE is queued. Otherwise it's relayed immediately.
16
+ *
17
+ * 3. **UPDATE-LINKS / free-form** — always relayed immediately (graceful
18
+ * degradation for unparsed decisions).
19
+ *
20
+ * ## Release triggers
21
+ *
22
+ * - **Filesystem watcher** (`fs.watch`) detects when the merge target file
23
+ * is written, releasing queued DELETEs for the corresponding source.
24
+ * - **Worker completion** — when a worker's `promptAndWait` resolves, any
25
+ * decisions still blocked on that worker's actions are released.
26
+ * - **Timeout** — queued decisions are released after a configurable
27
+ * deadline (default 60 s) to prevent permanent blocking.
28
+ *
29
+ * The watcher uses `node:fs` callback-based `watch()` (not the async
30
+ * iterator from `node:fs/promises`) so we can `.close()` it cleanly.
31
+ */
32
+ /** Default timeout (ms) after which a queued decision is released regardless. */
33
+ export declare const QUEUE_TIMEOUT_MS = 60000;
34
+ /**
35
+ * Parsed DECISION variants.
36
+ *
37
+ * - `merge` — MERGE <src> INTO <dst>
38
+ * - `delete` — DELETE <filepath>
39
+ * - `update-links` — UPDATE-LINKS <filepath>
40
+ * - `freeform` — anything else addressed to @org-worker-N
41
+ */
42
+ export type DecisionAction = {
43
+ kind: "merge";
44
+ worker: string;
45
+ src: string;
46
+ dst: string;
47
+ } | {
48
+ kind: "delete";
49
+ worker: string;
50
+ filePath: string;
51
+ } | {
52
+ kind: "update-links";
53
+ worker: string;
54
+ filePath: string;
55
+ } | {
56
+ kind: "freeform";
57
+ worker: string;
58
+ };
59
+ /**
60
+ * Parse a DECISION message into a structured action.
61
+ * Returns `undefined` if the message is not a DECISION.
62
+ *
63
+ * NOTE: `@all-workers` decisions intentionally return `undefined` here
64
+ * because the regex only matches `@org-worker-\d+`. This is correct —
65
+ * `@all-workers` decisions are broadcast to all workers by the SSE relay
66
+ * routing logic and do not need file-based sequencing via DecisionQueue.
67
+ */
68
+ export declare function parseDecision(message: string): DecisionAction | undefined;
69
+ /** A DELETE decision waiting for a MERGE to complete. */
70
+ export interface QueuedDecision {
71
+ /** Relay target (worker session). */
72
+ target: {
73
+ id: string;
74
+ label: string;
75
+ };
76
+ /** Full relay text to send when released. */
77
+ relayText: string;
78
+ /** Always "DELETE" — only DELETEs are queued. */
79
+ action: "DELETE";
80
+ /** The file this DELETE operates on (the merge source). */
81
+ filePath: string;
82
+ /** The merge target file we're waiting to see written. */
83
+ blockedBy: string;
84
+ /** Timestamp (ms) when the decision was queued. */
85
+ queuedAt: number;
86
+ /** Timeout handle — cleared when the decision is released. */
87
+ timer: ReturnType<typeof setTimeout>;
88
+ }
89
+ /**
90
+ * Callback that the queue invokes to relay a decision to a worker.
91
+ * The caller provides the actual `promptAsync` call.
92
+ */
93
+ export type RelayFn = (target: {
94
+ id: string;
95
+ label: string;
96
+ }, relayText: string) => Promise<void>;
97
+ /**
98
+ * Filesystem-watched decision queue.
99
+ *
100
+ * Instantiated once per `runParallel()` invocation. Call `start()` before
101
+ * the SSE relay loop and `stop()` in the `finally` block.
102
+ */
103
+ export declare class DecisionQueue {
104
+ /** Files that have a pending MERGE read (source → merge-target). */
105
+ private pendingReads;
106
+ /** Queued DELETE decisions blocked on a pending MERGE. */
107
+ private queue;
108
+ /** The filesystem watcher (recursive), or null if not started. */
109
+ private watcher;
110
+ /** Absolute path to the documentation destination directory. */
111
+ private readonly destPath;
112
+ /** Timeout in ms for queued decisions. */
113
+ private readonly timeoutMs;
114
+ /** Relay callback provided by the caller. */
115
+ private readonly relay;
116
+ /** Optional verbose logger. */
117
+ private readonly verbose?;
118
+ constructor(opts: {
119
+ destPath: string;
120
+ relay: RelayFn;
121
+ timeoutMs?: number;
122
+ verbose?: (msg: string) => void;
123
+ });
124
+ /**
125
+ * Start the filesystem watcher on the destination directory.
126
+ * Must be called before processing any decisions.
127
+ */
128
+ start(): void;
129
+ /**
130
+ * Stop the filesystem watcher and release all queued decisions.
131
+ * Call in the `finally` block of `runParallel()`.
132
+ */
133
+ stop(): void;
134
+ /**
135
+ * Process a parsed DECISION and either relay it immediately or queue it.
136
+ *
137
+ * @param action - Parsed decision action.
138
+ * @param target - The worker session to relay to.
139
+ * @param relayText - Full relay text string.
140
+ */
141
+ handleDecision(action: DecisionAction, target: {
142
+ id: string;
143
+ label: string;
144
+ }, relayText: string): Promise<void>;
145
+ /**
146
+ * Called when a worker completes. Releases any queued decisions that
147
+ * were blocked on actions by that worker (identified by label match
148
+ * in the target).
149
+ */
150
+ releaseForWorker(workerLabel: string): void;
151
+ /**
152
+ * Returns the number of currently queued (blocked) decisions.
153
+ */
154
+ get pendingCount(): number;
155
+ /**
156
+ * Returns a snapshot of pending reads (source → merge-target).
157
+ * Useful for testing.
158
+ */
159
+ get pendingReadSnapshot(): ReadonlyMap<string, string>;
160
+ /** Enqueue a blocked DELETE decision. */
161
+ private enqueue;
162
+ /**
163
+ * Release a single queued decision — relay it and remove from queue.
164
+ */
165
+ private release;
166
+ /** Release all queued decisions (used during stop/cleanup). */
167
+ private releaseAll;
168
+ /**
169
+ * Filesystem watcher callback. When a file in the destination directory
170
+ * changes, check if it matches a merge target and release blocked DELETEs.
171
+ */
172
+ private onFileChange;
173
+ }