@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,370 @@
1
+ /**
2
+ * OpenCode session utilities for Hem.
3
+ *
4
+ * Provides the `SessionClient` interface, the centralized `SessionTracker`
5
+ * for monitoring session completion, the shared `promptAndWait()` helper
6
+ * for sending prompts, and JSON extraction helpers used by agent classes.
7
+ *
8
+ * `promptAndWait()` uses the async prompt flow:
9
+ * 1. `session.promptAsync()` — fire the prompt without blocking
10
+ * 2. Register the session with the centralized `SessionTracker`
11
+ * 3. Wait for the tracker to resolve (session disappears from the
12
+ * server's status map, meaning it has completed)
13
+ * 4. Read `session.messages()` to extract the response
14
+ *
15
+ * LLM-specific prompt building and session orchestration have been
16
+ * extracted into dedicated agent classes under `src/agents/`.
17
+ */
18
+ import { isAuthExpired, AuthExpiredError } from "./auth.js";
19
+ // ── Constants ───────────────────────────────────────────────────────────
20
+ /** How often the tracker polls session status (ms). */
21
+ export const POLL_INTERVAL_MS = 250;
22
+ /** Hard ceiling timeout — if a session hasn't completed after this long,
23
+ * give up and recover from tool history (ms). */
24
+ export const MAX_PROMPT_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
25
+ /** Tighter timeout for exploration sessions — they typically finish in 2-3 min.
26
+ * Surfaces hangs 25 minutes earlier than the default ceiling. */
27
+ export const EXPLORATION_TIMEOUT_MS = 5 * 60 * 1_000; // 5 minutes
28
+ /** Timeout for documentation generation sessions — more complex than
29
+ * exploration but still bounded; surfaces hangs 20 minutes earlier. */
30
+ export const DOCUMENTATION_TIMEOUT_MS = 10 * 60 * 1_000; // 10 minutes
31
+ /**
32
+ * Format elapsed time as a compact human-readable string.
33
+ * @internal
34
+ */
35
+ function formatElapsedCompact(ms) {
36
+ const totalSeconds = Math.round(ms / 1000);
37
+ if (totalSeconds < 60)
38
+ return `${totalSeconds}s`;
39
+ const minutes = Math.floor(totalSeconds / 60);
40
+ const seconds = totalSeconds % 60;
41
+ return `${minutes}m ${seconds}s`;
42
+ }
43
+ /**
44
+ * Creates a centralized session tracker with subagent awareness.
45
+ *
46
+ * The tracker subscribes to SSE `session.created` events to detect child
47
+ * sessions spawned by tracked parents. A parent is only resolved as "done"
48
+ * when both the parent and all its descendants have completed.
49
+ *
50
+ * If the SSE subscription fails, the tracker gracefully degrades to the
51
+ * original poll-only behavior (no child tracking).
52
+ *
53
+ * @param client - The OpenCode SDK client (for `session.status()` and `event.subscribe()`).
54
+ * @param options - Configuration: verbose logger, poll interval, per-session timeout.
55
+ * @returns A `SessionTracker` instance.
56
+ */
57
+ export function createSessionTracker(client, options) {
58
+ const verbose = options?.verbose;
59
+ const interval = options?.pollIntervalMs ?? POLL_INTERVAL_MS;
60
+ const ceiling = options?.maxTimeoutMs ?? MAX_PROMPT_TIMEOUT_MS;
61
+ const tracked = new Map();
62
+ /**
63
+ * Maps a child session ID to the root tracked parent session ID.
64
+ * This covers both direct children and nested descendants.
65
+ */
66
+ const childToRoot = new Map();
67
+ let intervalHandle = null;
68
+ let sseStopped = false;
69
+ /** Resolves when the SSE loop exits (for clean shutdown). */
70
+ let sseLoopDone = null;
71
+ // ── SSE subscription for session.created events ──────────────────
72
+ function startSseSubscription() {
73
+ // Fire-and-forget — errors are non-fatal
74
+ void (async () => {
75
+ try {
76
+ const { stream } = await client.event.subscribe();
77
+ for await (const event of stream) {
78
+ if (sseStopped)
79
+ break;
80
+ if (event.type === "session.created") {
81
+ const props = event.properties;
82
+ const childId = props.session?.id;
83
+ const parentId = props.session?.parentID;
84
+ if (!childId || !parentId)
85
+ continue;
86
+ // Case 1: Parent is a directly tracked session
87
+ if (tracked.has(parentId)) {
88
+ childToRoot.set(childId, parentId);
89
+ tracked.get(parentId).children.add(childId);
90
+ if (verbose) {
91
+ const parentLabel = tracked.get(parentId).label;
92
+ verbose(`[tracker] · Subagent ${childId.slice(0, 8)}… spawned by "${parentLabel}"`);
93
+ }
94
+ continue;
95
+ }
96
+ // Case 2: Parent is a known child — trace to root
97
+ if (childToRoot.has(parentId)) {
98
+ const rootId = childToRoot.get(parentId);
99
+ const rootEntry = tracked.get(rootId);
100
+ if (rootEntry) {
101
+ childToRoot.set(childId, rootId);
102
+ rootEntry.children.add(childId);
103
+ if (verbose) {
104
+ verbose(`[tracker] · Nested subagent ${childId.slice(0, 8)}… (root: "${rootEntry.label}")`);
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ catch (err) {
112
+ // SSE subscription failure is non-fatal — degrade to poll-only
113
+ if (!sseStopped && verbose) {
114
+ verbose(`[tracker] ⚠ SSE subscription error (degrading to poll-only): ${err instanceof Error ? err.message : String(err)}`);
115
+ }
116
+ }
117
+ finally {
118
+ sseLoopDone?.();
119
+ }
120
+ })();
121
+ }
122
+ // Start SSE immediately so we catch child sessions from the very first prompt
123
+ startSseSubscription();
124
+ function startLoop() {
125
+ if (intervalHandle !== null)
126
+ return;
127
+ intervalHandle = setInterval(pollOnce, interval);
128
+ }
129
+ function stopLoop() {
130
+ if (intervalHandle !== null) {
131
+ clearInterval(intervalHandle);
132
+ intervalHandle = null;
133
+ }
134
+ }
135
+ /**
136
+ * Check whether all descendants of a tracked parent have completed
137
+ * (i.e., are absent from the status map).
138
+ */
139
+ function allChildrenDone(entry, statusMap) {
140
+ for (const childId of entry.children) {
141
+ if (statusMap[childId] !== undefined) {
142
+ return false;
143
+ }
144
+ }
145
+ return true;
146
+ }
147
+ async function pollOnce() {
148
+ if (tracked.size === 0) {
149
+ stopLoop();
150
+ return;
151
+ }
152
+ let statusMap = {};
153
+ try {
154
+ const result = await client.session.status();
155
+ if (result.error) {
156
+ if (isAuthExpired(result.error)) {
157
+ // Auth expired — reject ALL tracked sessions
158
+ const err = new AuthExpiredError("the configured provider", result.error);
159
+ for (const [id, entry] of tracked) {
160
+ entry.reject(err);
161
+ tracked.delete(id);
162
+ }
163
+ stopLoop();
164
+ return;
165
+ }
166
+ // Transient error — skip this poll cycle
167
+ if (verbose)
168
+ verbose(`[tracker] Status poll error: ${String(result.error)}`);
169
+ return;
170
+ }
171
+ statusMap = result.data ?? {};
172
+ }
173
+ catch (err) {
174
+ if (isAuthExpired(err)) {
175
+ const authErr = new AuthExpiredError("the configured provider", err);
176
+ for (const [id, entry] of tracked) {
177
+ entry.reject(authErr);
178
+ tracked.delete(id);
179
+ }
180
+ stopLoop();
181
+ return;
182
+ }
183
+ // Transient error — skip this poll cycle
184
+ if (verbose)
185
+ verbose(`[tracker] Status poll error: ${String(err)}`);
186
+ return;
187
+ }
188
+ const now = Date.now();
189
+ for (const [sessionId, entry] of tracked) {
190
+ // Check timeout first
191
+ if (now >= entry.deadline) {
192
+ const elapsed = formatElapsedCompact(now - entry.startedAt);
193
+ if (verbose) {
194
+ const childCount = entry.children.size;
195
+ const childSuffix = childCount > 0 ? `, ${childCount} subagent(s)` : "";
196
+ verbose(`[tracker] \u2717 "${entry.label}" timed out (${tracked.size - 1} active, ${elapsed}${childSuffix})`);
197
+ }
198
+ // Clean up child mappings
199
+ for (const childId of entry.children) {
200
+ childToRoot.delete(childId);
201
+ }
202
+ entry.resolve("timeout");
203
+ tracked.delete(sessionId);
204
+ continue;
205
+ }
206
+ // Parent is done when it's absent from status map AND all children are too
207
+ const parentDone = statusMap[sessionId] === undefined;
208
+ if (parentDone && allChildrenDone(entry, statusMap)) {
209
+ const elapsed = formatElapsedCompact(now - entry.startedAt);
210
+ if (verbose) {
211
+ const childCount = entry.children.size;
212
+ const childSuffix = childCount > 0 ? ` (${childCount} subagent(s) completed)` : "";
213
+ verbose(`[tracker] \u2713 "${entry.label}" done (${tracked.size - 1} active, ${elapsed})${childSuffix}`);
214
+ }
215
+ // Clean up child mappings
216
+ for (const childId of entry.children) {
217
+ childToRoot.delete(childId);
218
+ }
219
+ entry.resolve("done");
220
+ tracked.delete(sessionId);
221
+ }
222
+ // If parent or any child is still busy — do nothing
223
+ }
224
+ // Pause poll loop if no sessions remain
225
+ if (tracked.size === 0) {
226
+ stopLoop();
227
+ }
228
+ }
229
+ return {
230
+ track(sessionId, label) {
231
+ const displayLabel = label ?? sessionId.slice(0, 20) + "...";
232
+ return new Promise((resolve, reject) => {
233
+ tracked.set(sessionId, {
234
+ label: displayLabel,
235
+ startedAt: Date.now(),
236
+ deadline: Date.now() + ceiling,
237
+ resolve,
238
+ reject,
239
+ children: new Set(),
240
+ });
241
+ if (verbose) {
242
+ verbose(`[tracker] \u00B7 Tracking "${displayLabel}" (${tracked.size} active)`);
243
+ }
244
+ startLoop();
245
+ });
246
+ },
247
+ stop() {
248
+ sseStopped = true;
249
+ stopLoop();
250
+ // Resolve any remaining tracked sessions as timeout
251
+ for (const [id, entry] of tracked) {
252
+ // Clean up child mappings
253
+ for (const childId of entry.children) {
254
+ childToRoot.delete(childId);
255
+ }
256
+ entry.resolve("timeout");
257
+ tracked.delete(id);
258
+ }
259
+ },
260
+ };
261
+ }
262
+ // ── Prompt helper ────────────────────────────────────────────────────────
263
+ /**
264
+ * Sends a prompt to an OpenCode session and returns the complete response.
265
+ *
266
+ * Uses the async prompt flow:
267
+ * 1. `session.promptAsync()` fires the prompt without blocking.
268
+ * 2. Registers the session with the centralized `SessionTracker` and
269
+ * waits for it to complete (disappear from the status map).
270
+ * 3. Reads `session.messages()` to extract the assistant's response.
271
+ * 4. If the hard ceiling is hit, recovers file paths from tool history.
272
+ *
273
+ * @param client - The OpenCode SDK client.
274
+ * @param sessionId - The session to prompt.
275
+ * @param parts - The message parts to send.
276
+ * @param options - Settings: tracker (required), verbose callback, agent selection.
277
+ * @returns The response parts from the assistant message.
278
+ * @throws {AuthExpiredError} If auth expires during the API call.
279
+ * @throws {Error} If the prompt fails to be accepted.
280
+ */
281
+ export async function promptAndWait(client, sessionId, parts, options) {
282
+ const verbose = options.verbose;
283
+ // ── Step 1: Fire the prompt asynchronously ─────────────────────────
284
+ if (verbose)
285
+ verbose(`[opencode] Sending prompt to session ${sessionId}...`);
286
+ try {
287
+ const asyncResult = await client.session.promptAsync({
288
+ path: { id: sessionId },
289
+ body: { parts, agent: options.agent },
290
+ });
291
+ if (asyncResult.error) {
292
+ if (isAuthExpired(asyncResult.error)) {
293
+ throw new AuthExpiredError("the configured provider", asyncResult.error);
294
+ }
295
+ throw new Error(`Prompt rejected: ${String(asyncResult.error)}`);
296
+ }
297
+ }
298
+ catch (err) {
299
+ if (err instanceof AuthExpiredError)
300
+ throw err;
301
+ if (isAuthExpired(err)) {
302
+ throw new AuthExpiredError("the configured provider", err);
303
+ }
304
+ throw err;
305
+ }
306
+ // ── Step 2: Wait for session to complete via centralized tracker ────
307
+ const trackResult = await options.tracker.track(sessionId, options.label);
308
+ // ── Step 3: Read messages ──────────────────────────────────────────
309
+ const messages = await readSessionMessages(client, sessionId);
310
+ const lastAssistant = findLastAssistantMessage(messages);
311
+ if (!lastAssistant) {
312
+ if (verbose)
313
+ verbose(`[opencode] No assistant message found in session ${sessionId}`);
314
+ return [];
315
+ }
316
+ const responseParts = lastAssistant.parts;
317
+ if (trackResult === "timeout") {
318
+ if (verbose) {
319
+ verbose(`[opencode] Session ${sessionId} timed out`);
320
+ }
321
+ // No files recovered — return whatever response parts exist (may be empty)
322
+ }
323
+ if (verbose) {
324
+ const textLen = responseParts
325
+ .filter((p) => p.type === "text")
326
+ .reduce((sum, p) => sum + (p.text?.length ?? 0), 0);
327
+ verbose(`[opencode] Response received (${textLen} chars)`);
328
+ }
329
+ return responseParts;
330
+ }
331
+ // ── Message reading helpers ─────────────────────────────────────────────
332
+ /**
333
+ * Reads all messages from a session.
334
+ *
335
+ * @throws {AuthExpiredError} If auth expires during the API call.
336
+ */
337
+ async function readSessionMessages(client, sessionId) {
338
+ let result;
339
+ try {
340
+ result = await client.session.messages({ path: { id: sessionId } });
341
+ }
342
+ catch (err) {
343
+ if (isAuthExpired(err)) {
344
+ throw new AuthExpiredError("the configured provider", err);
345
+ }
346
+ throw err;
347
+ }
348
+ if (result.error) {
349
+ if (isAuthExpired(result.error)) {
350
+ throw new AuthExpiredError("the configured provider", result.error);
351
+ }
352
+ throw new Error(`Failed to read messages: ${String(result.error)}`);
353
+ }
354
+ return result.data ?? [];
355
+ }
356
+ /**
357
+ * Finds the last assistant message in a message list.
358
+ */
359
+ function findLastAssistantMessage(messages) {
360
+ for (let i = messages.length - 1; i >= 0; i--) {
361
+ if (messages[i].info.role === "assistant") {
362
+ return messages[i];
363
+ }
364
+ }
365
+ return undefined;
366
+ }
367
+ // ── Sleep helper ────────────────────────────────────────────────────────
368
+ function sleep(ms) {
369
+ return new Promise((resolve) => setTimeout(resolve, ms));
370
+ }
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Shared type definitions for Hem.
3
+ * All interfaces mirror the data model in specs/001-hem-cli/data-model.md.
4
+ */
5
+ /** Represents the current authentication state detected at startup. */
6
+ export interface AuthState {
7
+ /** Whether any LLM provider credentials exist. */
8
+ hasCredentials: boolean;
9
+ /** List of providers with active credentials. */
10
+ connectedProviders: ConnectedProvider[];
11
+ /** Currently resolved model (from --model flag, preferences, or default). */
12
+ activeModel: ModelSelection | undefined;
13
+ /** True if no preferences file exists and no credentials detected. */
14
+ isFirstRun: boolean;
15
+ }
16
+ /** A provider with active credentials. */
17
+ export interface ConnectedProvider {
18
+ /** Provider identifier (e.g., `anthropic`, `opencode`, `github-copilot`). */
19
+ id: string;
20
+ /** Display name (e.g., "Anthropic", "Opencode"). */
21
+ name: string;
22
+ /** How the provider was authenticated. */
23
+ authMethod: "oauth" | "api-key" | "env-var";
24
+ }
25
+ /** The resolved model to use for documentation generation. */
26
+ export interface ModelSelection {
27
+ /** Provider identifier (e.g., `opencode`, `anthropic`). */
28
+ providerID: string;
29
+ /** Model identifier (e.g., `gpt-5-nano`, `claude-sonnet-4`). */
30
+ modelID: string;
31
+ }
32
+ /** Persisted user preferences stored in `~/.config/hem/preferences.json`. */
33
+ export interface UserPreferences {
34
+ /** Preferred model selection. */
35
+ model: ModelSelection | undefined;
36
+ /** Whether user acknowledged data security warnings for free models. */
37
+ agreedToFreeModelTerms: boolean;
38
+ /** ISO 8601 timestamp of last successful run. */
39
+ lastUsedAt: string | undefined;
40
+ }
41
+ /** Project-local configuration stored in `{cwd}/.hem/config.json`. */
42
+ export interface ProjectConfig {
43
+ /** Selected provider and model for this project. */
44
+ model: ModelSelection;
45
+ }
46
+ /** A free model fetched from the Opencode catalog. */
47
+ export interface FreeModelInfo {
48
+ /** Model identifier in Zen (e.g., `gpt-5-nano`). */
49
+ id: string;
50
+ /** Display name (e.g., "GPT-5 Nano"). */
51
+ name: string;
52
+ /** Always `"opencode"` for Zen models. */
53
+ providerID: string;
54
+ /** Data security classification. */
55
+ dataRetention: "zero-retention" | "may-train" | "unknown";
56
+ /** Human-readable data security note. */
57
+ retentionNote: string;
58
+ }
59
+ /** Represents a single discovered source file. */
60
+ export interface FileInfo {
61
+ /** Relative path from source root (e.g., `user/controller.ts`). */
62
+ path: string;
63
+ /** Absolute filesystem path. */
64
+ absolutePath: string;
65
+ /** File extension including dot (e.g., `.ts`). */
66
+ extension: string;
67
+ /** File size in bytes. */
68
+ size: number;
69
+ /** Whether the file was detected as binary. */
70
+ isBinary: boolean;
71
+ }
72
+ /** A collection of related source files to be documented together. */
73
+ export interface FileGroup {
74
+ /** Unique group identifier (e.g., `user-feature`, `controllers-layer`). */
75
+ id: string;
76
+ /** Human-readable group name (e.g., "User Feature", "Controllers"). */
77
+ label: string;
78
+ /** Whether grouped by feature slice or architectural layer. */
79
+ type: "vertical" | "horizontal";
80
+ /** The files in this group (2-5 files typically). */
81
+ files: FileInfo[];
82
+ /** Common parent directory of the grouped files. */
83
+ directory: string;
84
+ }
85
+ /**
86
+ * Result of a single doc-agent run for one file group.
87
+ * The agent writes files directly to disk; the pipeline discovers
88
+ * what was produced by scanning the destination directory.
89
+ */
90
+ export interface GenerationResult {
91
+ /** The `FileGroup.id` this result corresponds to. */
92
+ groupId: string;
93
+ /** Current status. */
94
+ status: "generated" | "failed";
95
+ /** Error message if generation failed. */
96
+ error: string | undefined;
97
+ }
98
+ /** Tracks the state of a single OpenCode documentation session. */
99
+ export interface GenerationSession {
100
+ /** OpenCode session ID. */
101
+ sessionId: string;
102
+ /** The `FileGroup.id` being documented. */
103
+ groupId: string;
104
+ /** Session status. */
105
+ status: "pending" | "active" | "completed" | "failed" | "retrying";
106
+ /** Unix timestamp when session started. */
107
+ startedAt: number | undefined;
108
+ /** Unix timestamp when session completed. */
109
+ completedAt: number | undefined;
110
+ /** Number of retry attempts (max 1). */
111
+ retryCount: number;
112
+ /** Error message if failed. */
113
+ error: string | undefined;
114
+ }
115
+ /** Shared context passed to each doc-agent session. */
116
+ export interface GenerationContext {
117
+ /** Name of the project being documented. */
118
+ projectName: string;
119
+ /** Absolute path to the source root directory. */
120
+ sourceRoot: string;
121
+ /** Absolute path to the documentation destination directory. */
122
+ destinationPath: string;
123
+ /** Summary of all groups (for cross-reference awareness). */
124
+ allGroups: {
125
+ id: string;
126
+ label: string;
127
+ files: string[];
128
+ }[];
129
+ /** All exploration findings from the exploration phase. */
130
+ explorationFindings: ExplorationFindings[];
131
+ /** This specific group's exploration findings. */
132
+ groupFindings?: ExplorationFindings;
133
+ /** Existing documentation files found in destination (for merge-in-place). */
134
+ existingDocs: Array<{
135
+ path: string;
136
+ content: string;
137
+ }>;
138
+ /**
139
+ * Paths of existing docs that were not selected as highly relevant but
140
+ * still exist in the destination directory. Provided for awareness only
141
+ * (agents know these paths exist but do NOT receive their content).
142
+ */
143
+ mentionedDocPaths?: string[];
144
+ }
145
+ /** Terminal progress display state, used as React state in the Ink dashboard component. */
146
+ export interface ProgressState {
147
+ /** Current pipeline phase. */
148
+ phase: "discovery" | "grouping" | "exploration" | "generation" | "organization" | "crossref" | "architecture" | "indexing" | "complete" | "error";
149
+ /** Display label for the active model (e.g., `anthropic/claude-sonnet-4`). */
150
+ modelLabel: string;
151
+ /** Human-readable provider display name (e.g., "Anthropic", "Opencode"). */
152
+ providerLabel: string;
153
+ /** Total files discovered. */
154
+ totalFiles: number;
155
+ /** Number of binary files skipped. */
156
+ binaryFilesSkipped: number;
157
+ /** Total file groups. */
158
+ totalGroups: number;
159
+ /** Number of feature (vertical) groups. */
160
+ featureGroups: number;
161
+ /** Number of layer (horizontal) groups. */
162
+ layerGroups: number;
163
+ /** Total file groups to generate docs for. */
164
+ totalPages: number;
165
+ /** Per-group status for the generation dashboard. */
166
+ groupStatuses: GroupStatus[];
167
+ /** Files generated in the indexing phase. */
168
+ indexFiles: string[];
169
+ /** Finished sessions. */
170
+ completedSessions: number;
171
+ /** Failed sessions. */
172
+ failedSessions: number;
173
+ /** Pipeline start timestamp. */
174
+ startedAt: number;
175
+ /** Pipeline completion timestamp. */
176
+ completedAt: number | undefined;
177
+ /** Warnings to display in the summary. */
178
+ warnings: string[];
179
+ /** Per-group status for the exploration dashboard. */
180
+ explorationStatuses: GroupStatus[];
181
+ /** Whether all exploration work has finished (all groups settled). */
182
+ explorationComplete: boolean;
183
+ }
184
+ /** Per-group status for the Ink generation dashboard. */
185
+ export interface GroupStatus {
186
+ /** References `FileGroup.id`. */
187
+ groupId: string;
188
+ /** Human-readable group label (e.g., "User Feature"). */
189
+ label: string;
190
+ /** Current status. */
191
+ status: "queued" | "generating" | "completed" | "failed";
192
+ /** Failure reason (populated when `status` is `"failed"`). */
193
+ error?: string;
194
+ /**
195
+ * Sub-agent progress for multi-agent exploration/generation.
196
+ * Present when the project exceeds the large-project threshold and
197
+ * multiple agents are used per group. `total` is the number of
198
+ * sub-agents assigned to this group; `completed` tracks how many
199
+ * have finished successfully.
200
+ */
201
+ subAgentProgress?: {
202
+ completed: number;
203
+ total: number;
204
+ };
205
+ }
206
+ /** Parsed command-line arguments. */
207
+ export interface CLIOptions {
208
+ /** Source directory path. Default: `"./src"`. */
209
+ source: string;
210
+ /** Destination directory path. Default: `"./docs"`. */
211
+ destination: string;
212
+ /** Glob pattern for file matching. Default: `"**\/*"`. */
213
+ files: string;
214
+ /** Max parallel sessions. Default: auto-detected from system resources via `computeMaxConcurrency()`. */
215
+ concurrency: number;
216
+ /** Model override (e.g., `"opencode/gpt-5-nano"`). */
217
+ model: string | undefined;
218
+ /** API key for the provider in `model`. Requires `model` to be set. */
219
+ apiKey: string | undefined;
220
+ /**
221
+ * Explicit project name override (`--name` flag).
222
+ * When set, skips auto-detection from manifest files.
223
+ */
224
+ name: string | undefined;
225
+ /** Which subcommand was invoked. Default: `"generate"`. */
226
+ command: "generate" | "auth" | "config";
227
+ /** Auth subcommand action. */
228
+ authAction: "login" | "list" | "logout" | undefined;
229
+ /** Provider ID for `auth logout <id>`. */
230
+ authTarget: string | undefined;
231
+ /** Enable verbose logging to stderr. */
232
+ verbose: boolean;
233
+ /**
234
+ * Force full re-generation even when a changelog exists.
235
+ * Bypasses diff-scoping and re-documents all source files.
236
+ * Behavior may be unpredictable when docs already exist.
237
+ */
238
+ full: boolean;
239
+ /**
240
+ * Run the pipeline in an isolated git worktree on a new branch, then commit
241
+ * and push the branch for review. The worktree is created at `.hem/worktree/`
242
+ * and removed after the run.
243
+ */
244
+ worktree: boolean;
245
+ }
246
+ /**
247
+ * Output of the ExplorationAgent for a single file group.
248
+ *
249
+ * The exploration agent writes a natural-language report covering:
250
+ * summary, questions to answer, integrations discovered, complexity
251
+ * signals, style guides found, and cross-group dependencies.
252
+ * Downstream agents (doc, arch) consume `text` directly in their prompts.
253
+ */
254
+ export interface ExplorationFindings {
255
+ /** The `FileGroup.id` this exploration covers. */
256
+ groupId: string;
257
+ /** Natural-language exploration report produced by the explore agent. */
258
+ text: string;
259
+ }
260
+ /**
261
+ * Permission configuration for a single agent class.
262
+ */
263
+ export interface AgentPermissionConfig {
264
+ /** OpenCode agent name (e.g., `"hem-explore"`). */
265
+ agentName: string;
266
+ /** Edit permission. */
267
+ edit: "allow" | "deny";
268
+ /** Bash permission — glob-pattern allowlist or simple allow/deny. */
269
+ bash: Record<string, "allow" | "deny"> | "allow" | "deny";
270
+ /** Webfetch permission. */
271
+ webfetch: "allow" | "deny";
272
+ }
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared type definitions for Hem.
3
+ * All interfaces mirror the data model in specs/001-hem-cli/data-model.md.
4
+ */
5
+ export {};