@joshski/dust 0.1.67 → 0.1.68

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.
package/README.md CHANGED
@@ -15,6 +15,16 @@ npx dust init
15
15
 
16
16
  This creates a [.dust](./.dust/facts/dust-directory-structure.md) directory and adds an [instruction](./.dust/facts/agents-md-instruction.md) to your `AGENTS.md` file.
17
17
 
18
+ ## Adding Tasks
19
+
20
+ Use your AI coding CLI (Claude Code, Codex, etc.) to add and refine tasks:
21
+
22
+ ```bash
23
+ claude "add a task to refactor the auth module"
24
+ ```
25
+
26
+ Ideas (`.dust/ideas/`) are backlog items you may or may not do later. Tasks (`.dust/tasks/`) are ready to work on now. Both are markdown files that agents and humans can read and edit.
27
+
18
28
  ## Running Agents
19
29
 
20
30
  Start an agent on a single task:
package/dist/artifacts.js CHANGED
@@ -454,7 +454,7 @@ async function createCaptureIdeaTask(fileSystem, dustPath, options) {
454
454
  const filePath2 = `${dustPath}/tasks/${filename2}`;
455
455
  const content2 = `# ${taskTitle2}
456
456
 
457
- Research this idea thoroughly, then create one or more narrowly-scoped task files in \`.dust/tasks/\`. Review \`.dust/principles/\` and \`.dust/facts/\` for relevant context. Each task should deliver a thin but complete vertical slice of working software.
457
+ Research this idea briefly. If confident the implementation is straightforward (clear scope, minimal risk, no open questions), implement directly and commit. Otherwise, create one or more narrowly-scoped task files in \`.dust/tasks/\`. Review \`.dust/principles/\` and \`.dust/facts/\` for relevant context.
458
458
 
459
459
  ## Idea Description
460
460
 
@@ -466,9 +466,9 @@ ${description}
466
466
 
467
467
  ## Definition of Done
468
468
 
469
- - [ ] One or more new tasks are created in \`.dust/tasks/\`
470
- - [ ] Tasks link to relevant principles from \`.dust/principles/\`
471
- - [ ] Tasks are narrowly scoped vertical slices
469
+ - [ ] Idea is implemented directly OR one or more new tasks are created in \`.dust/tasks/\`
470
+ - [ ] If tasks were created, they link to relevant principles from \`.dust/principles/\`
471
+ - [ ] Changes are committed with a clear commit message
472
472
  `;
473
473
  await fileSystem.writeFile(filePath2, content2);
474
474
  return { filePath: filePath2 };
@@ -33,4 +33,9 @@ export interface AuditsRepository {
33
33
  * Changes the title from "# Original Title" to "# Audit: Original Title"
34
34
  */
35
35
  export declare function transformAuditContent(content: string): string;
36
+ /**
37
+ * Injects an Ad-hoc Scope section after the opening description, before ## Scope.
38
+ * The ad-hoc details are passed through without validation.
39
+ */
40
+ export declare function injectAdHocScope(content: string, adHocDetails: string): string;
36
41
  export declare function buildAuditsRepository(fileSystem: FileSystem, dustPath: string): AuditsRepository;
package/dist/audits.js CHANGED
@@ -64,6 +64,102 @@ function dedent(strings, ...values) {
64
64
 
65
65
  // lib/audits/stock-audits.ts
66
66
  var ideasHint = "Review existing ideas in `./.dust/ideas/` to understand what has been proposed or considered historically, then create new idea files in `./.dust/ideas/` for any issues you identify, avoiding duplication.";
67
+ function dataAccessReview() {
68
+ return dedent`
69
+ # Data Access Review
70
+
71
+ Review data access patterns for performance issues and optimization opportunities.
72
+
73
+ ${ideasHint}
74
+
75
+ ## Scope
76
+
77
+ Focus on these areas:
78
+
79
+ 1. **N+1 query patterns** - Loops that make individual data requests instead of batching
80
+ 2. **Missing indexes** - Database schema files lacking indexes on frequently queried columns
81
+ 3. **Inefficient data loading** - Over-fetching (loading more data than needed) or under-fetching (requiring multiple round trips)
82
+ 4. **Caching opportunities** - Repeated lookups that could benefit from memoization or caching
83
+ 5. **Batch processing** - Sequential operations that could be parallelized or batched
84
+ 6. **Connection management** - Connection pooling configuration and resource cleanup
85
+
86
+ ## Analysis Steps
87
+
88
+ 1. Search for loops containing data access calls (API requests, database queries, file reads)
89
+ 2. Review database schema or migration files for index definitions
90
+ 3. Identify functions that make multiple related data requests
91
+ 4. Look for repeated identical lookups within the same request lifecycle
92
+ 5. Check for proper resource cleanup (connection closing, stream ending)
93
+
94
+ ## Applicability
95
+
96
+ This audit applies to codebases that interact with:
97
+ - Databases (SQL, NoSQL, ORM queries)
98
+ - External APIs (REST, GraphQL, gRPC)
99
+ - File systems (reading/writing files)
100
+ - Caches (Redis, Memcached, in-memory)
101
+
102
+ If none of these apply, document that finding and skip the detailed analysis.
103
+
104
+ ## Principles
105
+
106
+ - [Decoupled Code](../principles/decoupled-code.md) - Data access should be isolated for testability
107
+ - [Fast Feedback](../principles/fast-feedback.md) - Efficient data access enables faster feedback loops
108
+ - [Maintainable Codebase](../principles/maintainable-codebase.md) - Good data patterns improve maintainability
109
+
110
+ ## Blocked By
111
+
112
+ (none)
113
+
114
+ ## Definition of Done
115
+
116
+ - [ ] Searched for N+1 query patterns (loops with data access)
117
+ - [ ] Reviewed database schemas for missing indexes (if applicable)
118
+ - [ ] Identified over-fetching or under-fetching patterns
119
+ - [ ] Found repeated lookups that could be cached
120
+ - [ ] Checked for sequential operations that could be batched
121
+ - [ ] Verified connection/resource cleanup is handled properly
122
+ - [ ] Proposed ideas for any data access improvements identified
123
+ `;
124
+ }
125
+ function coverageExclusions() {
126
+ return dedent`
127
+ # Coverage Exclusions
128
+
129
+ Review coverage exclusion configuration to identify opportunities for removal through refactoring.
130
+
131
+ ${ideasHint}
132
+
133
+ ## Scope
134
+
135
+ Focus on these areas:
136
+
137
+ 1. **Current exclusions** - Review all exclusions in \`vitest.config.ts\` or equivalent
138
+ 2. **Justification** - Is each exclusion still necessary?
139
+ 3. **Tooling limitations** - Can workarounds be found for coverage tool issues?
140
+ 4. **Decoupling opportunities** - Can excluded code be restructured to enable testing?
141
+ 5. **Entry point patterns** - Can hard-to-test entry points be decoupled from logic?
142
+
143
+ ## Principles
144
+
145
+ - [Decoupled Code](../principles/decoupled-code.md)
146
+ - [Unit Test Coverage](../principles/unit-test-coverage.md)
147
+ - [Comprehensive Test Coverage](../principles/comprehensive-test-coverage.md)
148
+ - [Make Changes with Confidence](../principles/make-changes-with-confidence.md)
149
+
150
+ ## Blocked By
151
+
152
+ (none)
153
+
154
+ ## Definition of Done
155
+
156
+ - [ ] Identified all coverage exclusions in the project
157
+ - [ ] Documented the reason each exclusion exists
158
+ - [ ] Evaluated whether each exclusion is still necessary
159
+ - [ ] Identified exclusions that could be removed through decoupling
160
+ - [ ] Proposed ideas for refactoring where feasible
161
+ `;
162
+ }
67
163
  function componentReuse() {
68
164
  return dedent`
69
165
  # Component Reuse
@@ -274,6 +370,56 @@ function ideasFromPrinciples() {
274
370
  - [ ] Proposed new ideas for unmet or underserved principles
275
371
  `;
276
372
  }
373
+ function refactoringOpportunities() {
374
+ return dedent`
375
+ # Refactoring Opportunities
376
+
377
+ Analyze recent commits to identify code needing structural improvements.
378
+
379
+ ${ideasHint}
380
+
381
+ ## Scope
382
+
383
+ Analyze commits since the last refactoring-opportunities audit (check \`.dust/done/\` for previous runs). Focus on these signals:
384
+
385
+ 1. **File churn** - Files modified frequently across multiple commits may have unclear responsibilities or be accumulating technical debt
386
+ 2. **Size growth** - Files that have grown significantly may benefit from decomposition
387
+ 3. **Commit message patterns** - Look for messages containing "fix", "workaround", "temporary", "hack", or "TODO" that indicate shortcuts taken
388
+
389
+ ## Analysis Steps
390
+
391
+ 1. Run \`git log --since="<last-audit-date>" --name-only --pretty=format:"COMMIT:%s"\` to get commits with their messages and changed files
392
+ 2. Count file modification frequency to identify high-churn files
393
+ 3. Check current sizes of frequently-modified files with \`wc -l\`
394
+ 4. Review commit messages for patterns suggesting technical debt
395
+
396
+ ## Output
397
+
398
+ For each refactoring opportunity identified, provide:
399
+ - **File path** - The specific file needing attention
400
+ - **Signal** - What triggered this recommendation (churn, size, commit pattern)
401
+ - **Specific suggestion** - A concrete refactoring action (e.g., "Extract the validation logic into a separate module", not just "consider refactoring")
402
+
403
+ ## Principles
404
+
405
+ - [Boy Scout Rule](../principles/boy-scout-rule.md) - Leave code better than found, but capture large cleanups as separate tasks
406
+ - [Make the Change Easy](../principles/make-the-change-easy.md) - Refactor until the change becomes straightforward
407
+ - [Make Changes with Confidence](../principles/make-changes-with-confidence.md) - Tests and checks enable safe refactoring
408
+ - [Reasonably DRY](../principles/reasonably-dry.md) - Extract only when duplication represents the same concept
409
+
410
+ ## Blocked By
411
+
412
+ (none)
413
+
414
+ ## Definition of Done
415
+
416
+ - [ ] Identified high-churn files (modified in 3+ commits since last audit)
417
+ - [ ] Flagged files exceeding 300 lines that grew significantly
418
+ - [ ] Noted commits with concerning message patterns
419
+ - [ ] Provided specific refactoring suggestions for each opportunity
420
+ - [ ] Created ideas for any substantial refactoring work identified
421
+ `;
422
+ }
277
423
  function performanceReview() {
278
424
  return dedent`
279
425
  # Performance Review
@@ -414,17 +560,129 @@ function testCoverage() {
414
560
  - [ ] Proposed ideas for any test coverage gaps identified
415
561
  `;
416
562
  }
563
+ function errorHandling() {
564
+ return dedent`
565
+ # Error Handling
566
+
567
+ Review error handling patterns for consistency and agent-friendliness.
568
+
569
+ ${ideasHint}
570
+
571
+ ## Scope
572
+
573
+ Focus on these areas:
574
+
575
+ 1. **Silently swallowed errors** - Empty catch blocks, \`.catch(() => {})\`, errors caught but not logged or re-thrown
576
+ 2. **Missing error context** - Errors converted to booleans or generic messages that lose details
577
+ 3. **Fire-and-forget promises** - Promises without \`.catch()\` or \`await\` that may fail silently
578
+ 4. **Non-actionable error messages** - Error messages that say what went wrong but not how to fix it
579
+ 5. **Inconsistent error recovery** - Similar error scenarios handled differently across the codebase
580
+
581
+ ## Analysis Steps
582
+
583
+ 1. Search for empty catch blocks: \`catch {}\`, \`catch () {}\`, \`.catch(() => {})\`
584
+ 2. Look for patterns that discard error details: \`catch { return false }\`, \`catch { return null }\`
585
+ 3. Find promises without error handling: unassigned or not-awaited promises
586
+ 4. Review error messages in \`throw\` statements and \`context.stderr()\` calls for actionability
587
+ 5. Compare error handling patterns across similar operations for consistency
588
+
589
+ ## Output
590
+
591
+ For each error handling issue identified, provide:
592
+ - **Location** - File path and line number
593
+ - **Pattern** - Which category of issue (swallowed, missing context, fire-and-forget, etc.)
594
+ - **Impact** - What failures could go unnoticed or be hard to debug
595
+ - **Suggestion** - Specific fix (add logging, propagate error, add recovery guidance)
596
+
597
+ ## Principles
598
+
599
+ - [Actionable Errors](../principles/actionable-errors.md) - Error messages should tell you what to do next
600
+ - [Debugging Tooling](../principles/debugging-tooling.md) - Agents need readable, structured error output
601
+ - [Stop the Line](../principles/stop-the-line.md) - Problems should be fixed at source, not hidden
602
+
603
+ ## Blocked By
604
+
605
+ (none)
606
+
607
+ ## Definition of Done
608
+
609
+ - [ ] Searched for empty catch blocks and silent error swallowing
610
+ - [ ] Identified patterns that discard error details
611
+ - [ ] Found fire-and-forget promises without error handling
612
+ - [ ] Reviewed error messages for actionability
613
+ - [ ] Compared error handling consistency across similar operations
614
+ - [ ] Proposed ideas for any error handling improvements identified
615
+ `;
616
+ }
617
+ function ubiquitousLanguage() {
618
+ return dedent`
619
+ # Ubiquitous Language
620
+
621
+ Verify terminology consistency across code, documentation, and user interface.
622
+
623
+ ${ideasHint}
624
+
625
+ ## Scope
626
+
627
+ Focus on these areas:
628
+
629
+ 1. **Terminology drift** - Do recent changes introduce terms that deviate from established vocabulary?
630
+ 2. **Code-to-docs alignment** - Are variables, functions, and types named consistently with documentation?
631
+ 3. **User interface consistency** - Do UI labels and messages match the terms used in code and docs?
632
+ 4. **Glossary adherence** - If a glossary exists, is it being followed?
633
+ 5. **Acronym and abbreviation usage** - Are shortened forms used consistently?
634
+
635
+ ## Analysis Steps
636
+
637
+ 1. Identify key domain terms from documentation, README, or existing glossary
638
+ 2. Review recent commits for new terminology or naming choices
639
+ 3. Compare code identifiers against documented terminology
640
+ 4. Check user-facing strings for consistency with technical naming
641
+ 5. Flag deviations where the same concept uses different names
642
+
643
+ ## Output
644
+
645
+ For each terminology issue identified, provide:
646
+ - **Term in question** - The inconsistent or unclear term
647
+ - **Where found** - File paths and locations where the term appears
648
+ - **Recommended action** - Standardize on existing term, or propose a new canonical name
649
+
650
+ ## Principles
651
+
652
+ - [Naming Matters](../principles/naming-matters.md) - Good naming reduces waste by eliminating confusion
653
+ - [Consistent Naming](../principles/consistent-naming.md) - Names should follow established conventions
654
+ - [Clarity Over Brevity](../principles/clarity-over-brevity.md) - Names should be descriptive and self-documenting
655
+
656
+ ## Blocked By
657
+
658
+ (none)
659
+
660
+ ## Definition of Done
661
+
662
+ - [ ] Identified key domain terms from project documentation
663
+ - [ ] Reviewed recent commits for terminology consistency
664
+ - [ ] Compared code naming against documentation vocabulary
665
+ - [ ] Checked user-facing text for alignment with code terms
666
+ - [ ] Documented any terminology drift or inconsistencies found
667
+ - [ ] Proposed ideas for standardizing inconsistent terminology
668
+ `;
669
+ }
417
670
  var stockAuditFunctions = {
418
671
  "agent-developer-experience": agentDeveloperExperience,
419
672
  "component-reuse": componentReuse,
673
+ "coverage-exclusions": coverageExclusions,
674
+ "data-access-review": dataAccessReview,
420
675
  "dead-code": deadCode,
676
+ "error-handling": errorHandling,
421
677
  "facts-verification": factsVerification,
422
678
  "ideas-from-commits": ideasFromCommits,
423
679
  "ideas-from-principles": ideasFromPrinciples,
424
680
  "performance-review": performanceReview,
681
+ "refactoring-opportunities": refactoringOpportunities,
425
682
  "security-review": securityReview,
426
683
  "stale-ideas": staleIdeas,
427
- "test-coverage": testCoverage
684
+ "test-coverage": testCoverage,
685
+ "ubiquitous-language": ubiquitousLanguage
428
686
  };
429
687
  function loadStockAudits() {
430
688
  return Object.entries(stockAuditFunctions).sort(([a], [b]) => a.localeCompare(b)).map(([name, render]) => {
@@ -443,6 +701,24 @@ function transformAuditContent(content) {
443
701
  const originalTitle = titleMatch[1];
444
702
  return content.replace(/^#\s+.+$/m, `# Audit: ${originalTitle}`);
445
703
  }
704
+ function injectAdHocScope(content, adHocDetails) {
705
+ const scopeMatch = content.match(/\n## Scope\n/);
706
+ if (scopeMatch?.index !== undefined) {
707
+ const insertIndex = scopeMatch.index;
708
+ const adHocSection = `
709
+ ## Ad-hoc Scope
710
+
711
+ ${adHocDetails}
712
+ `;
713
+ return content.slice(0, insertIndex) + adHocSection + content.slice(insertIndex);
714
+ }
715
+ return `${content}
716
+
717
+ ## Ad-hoc Scope
718
+
719
+ ${adHocDetails}
720
+ `;
721
+ }
446
722
  function buildAuditsRepository(fileSystem, dustPath) {
447
723
  const userAuditsPath = `${dustPath}/config/audits`;
448
724
  const tasksPath = `${dustPath}/tasks`;
@@ -511,5 +787,6 @@ function buildAuditsRepository(fileSystem, dustPath) {
511
787
  export {
512
788
  transformAuditContent,
513
789
  loadStockAudits,
790
+ injectAdHocScope,
514
791
  buildAuditsRepository
515
792
  };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Bucket event types and WebSocket event sending.
3
+ *
4
+ * BucketEvent types are local-only (UI lifecycle). Wire events use
5
+ * EventMessage from agent-events.ts and are sent via createEventMessageSender.
6
+ */
7
+ import type { EventMessage } from '../agent-events';
8
+ export declare const WS_OPEN = 1;
9
+ export declare const WS_CLOSED = 3;
10
+ export interface WebSocketLike {
11
+ onopen: (() => void) | null;
12
+ onclose: ((event: {
13
+ code: number;
14
+ reason: string;
15
+ }) => void) | null;
16
+ onerror: ((error: Error) => void) | null;
17
+ onmessage: ((event: {
18
+ data: string;
19
+ }) => void) | null;
20
+ close: () => void;
21
+ send: (data: string) => void;
22
+ readyState: number;
23
+ }
24
+ interface BucketConnectedEvent {
25
+ type: 'bucket.connected';
26
+ }
27
+ interface BucketDisconnectedEvent {
28
+ type: 'bucket.disconnected';
29
+ code: number;
30
+ reason: string;
31
+ }
32
+ export interface BucketRepositoryAddedEvent {
33
+ type: 'bucket.repository_added';
34
+ repository: string;
35
+ }
36
+ export interface BucketRepositoryRemovedEvent {
37
+ type: 'bucket.repository_removed';
38
+ repository: string;
39
+ }
40
+ export interface BucketErrorEvent {
41
+ type: 'bucket.error';
42
+ repository?: string;
43
+ error: string;
44
+ }
45
+ export type BucketEvent = BucketConnectedEvent | BucketDisconnectedEvent | BucketRepositoryAddedEvent | BucketRepositoryRemovedEvent | BucketErrorEvent;
46
+ export type BucketEmitFn = (event: BucketEvent) => void;
47
+ export type SendEventFn = (msg: EventMessage) => void;
48
+ export declare function formatBucketEvent(event: BucketEvent): string;
49
+ export declare function createEventMessageSender(getWebSocket: () => WebSocketLike | null): SendEventFn;
50
+ export {};
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Ring buffer for capturing subprocess output in dust bucket.
3
+ *
4
+ * Each repository has its own log buffer with a fixed maximum size.
5
+ * When the buffer exceeds the limit, it trims to the target size.
6
+ */
7
+ export interface LogLine {
8
+ text: string;
9
+ stream: 'stdout' | 'stderr';
10
+ timestamp: number;
11
+ }
12
+ export interface LogBuffer {
13
+ lines: LogLine[];
14
+ maxLines: number;
15
+ trimToLines: number;
16
+ }
17
+ /**
18
+ * Create a new log buffer with default settings.
19
+ */
20
+ export declare function createLogBuffer(maxLines?: number, trimToLines?: number): LogBuffer;
21
+ /**
22
+ * Append a log line to the buffer, trimming if necessary.
23
+ */
24
+ export declare function appendLogLine(buffer: LogBuffer, line: LogLine): void;
25
+ /**
26
+ * Create a log line from raw output.
27
+ */
28
+ export declare function createLogLine(text: string, stream: 'stdout' | 'stderr', timestamp?: number): LogLine;
29
+ /**
30
+ * Get all lines from the buffer.
31
+ */
32
+ export declare function getLogLines(buffer: LogBuffer): readonly LogLine[];
33
+ /**
34
+ * Get the most recent N lines from the buffer.
35
+ */
36
+ export declare function getRecentLines(buffer: LogBuffer, count: number): LogLine[];
37
+ /**
38
+ * Clear all lines from the buffer.
39
+ */
40
+ export declare function clearLogBuffer(buffer: LogBuffer): void;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Path utilities for dust bucket operations.
3
+ *
4
+ * Pure functions that compute paths from explicit parameters,
5
+ * following the "functional core, imperative shell" pattern.
6
+ */
7
+ /**
8
+ * Environment variables used by getReposDir.
9
+ * Includes an index signature for compatibility with process.env.
10
+ */
11
+ interface ReposDirEnv {
12
+ DUST_REPOS_DIR?: string;
13
+ [key: string]: string | undefined;
14
+ }
15
+ /**
16
+ * Compute the repositories directory path.
17
+ *
18
+ * If DUST_REPOS_DIR is set in the environment, returns that value.
19
+ * Otherwise, returns the default path: ~/.dust/repos
20
+ *
21
+ * @param env - Environment variables object
22
+ * @param homeDir - User's home directory path
23
+ * @returns The resolved repositories directory path
24
+ */
25
+ export declare function getReposDir(env: ReposDirEnv, homeDir: string): string;
26
+ export {};
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Git operations for dust bucket repository management.
3
+ *
4
+ * Handles cloning, removing, and path resolution for repositories.
5
+ */
6
+ import type { spawn as nodeSpawn } from 'node:child_process';
7
+ import type { CommandDependencies } from '../cli/types';
8
+ /**
9
+ * Get the directory path for a repository.
10
+ */
11
+ export declare function getRepoPath(repoName: string, reposDir: string): string;
12
+ /**
13
+ * Clone a repository to a temporary directory.
14
+ */
15
+ export declare function cloneRepository(repository: {
16
+ name: string;
17
+ gitUrl: string;
18
+ }, targetPath: string, spawn: typeof nodeSpawn, context: CommandDependencies['context']): Promise<boolean>;
19
+ /**
20
+ * Remove a repository directory.
21
+ */
22
+ export declare function removeRepository(path: string, spawn: typeof nodeSpawn, context: CommandDependencies['context']): Promise<boolean>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Loop orchestration for dust bucket repositories.
3
+ *
4
+ * Manages the async loop that picks tasks and runs Claude sessions
5
+ * for a single repository.
6
+ */
7
+ import type { AgentSessionEvent, EventMessage } from '../agent-events';
8
+ import type { SendEventFn } from './events';
9
+ import { type LogBuffer } from './log-buffer';
10
+ import type { RepositoryDependencies, RepositoryState } from './repository';
11
+ /**
12
+ * Create stdout/stderr callbacks that append to a log buffer.
13
+ * Extracted for testability (v8 coverage limitation on inline callbacks).
14
+ */
15
+ export declare function createLogCallbacks(logBuffer: LogBuffer): {
16
+ stdout: (msg: string) => void;
17
+ stderr: (msg: string) => void;
18
+ };
19
+ /**
20
+ * Flush any pending partial line and log all segments of multi-line text.
21
+ * Returns the new partial line state (always empty string after flush).
22
+ * Extracted for testability (v8 coverage limitation on inline callbacks).
23
+ */
24
+ export declare function flushAndLogMultiLine(partialLine: string, text: string, logBuffer: LogBuffer): string;
25
+ /**
26
+ * Build an EventMessage from agent session event data.
27
+ * Extracted for testability (v8 coverage limitation on inline callbacks).
28
+ */
29
+ export declare function buildEventMessage(parameters: {
30
+ sequence: number;
31
+ sessionId: string;
32
+ repository: string;
33
+ event: AgentSessionEvent;
34
+ agentSessionId?: string;
35
+ }): EventMessage;
36
+ /**
37
+ * Create a wake-up handler that resolves the wait promise.
38
+ * The handler guards against being called after a newer wait has started.
39
+ * Extracted for testability (v8 coverage limitation on inline callbacks).
40
+ */
41
+ export declare function createWakeUpHandler(repoState: RepositoryState, resolve: () => void): () => void;
42
+ /**
43
+ * Run the async loop for a single repository.
44
+ */
45
+ export declare function runRepositoryLoop(repoState: RepositoryState, repoDeps: RepositoryDependencies, sendEvent?: SendEventFn, sessionId?: string): Promise<void>;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Repository types, parsing, and manager orchestration for dust bucket.
3
+ *
4
+ * Git operations live in repository-git.ts.
5
+ * Loop orchestration lives in repository-loop.ts.
6
+ */
7
+ import { spawn as nodeSpawn } from 'node:child_process';
8
+ import { run as claudeRun } from '../claude/run';
9
+ import type { CommandDependencies, FileSystem } from '../cli/types';
10
+ import { type BucketEmitFn, type SendEventFn } from './events';
11
+ import { type LogBuffer } from './log-buffer';
12
+ export { cloneRepository, getRepoPath, removeRepository, } from './repository-git';
13
+ export { runRepositoryLoop } from './repository-loop';
14
+ export interface Repository {
15
+ name: string;
16
+ gitUrl: string;
17
+ url?: string;
18
+ id?: string;
19
+ }
20
+ export interface RepositoryState {
21
+ repository: Repository;
22
+ path: string;
23
+ loopPromise: Promise<void> | null;
24
+ stopRequested: boolean;
25
+ logBuffer: LogBuffer;
26
+ agentStatus: 'idle' | 'busy';
27
+ wakeUp?: () => void;
28
+ taskAvailablePending?: boolean;
29
+ cancelCurrentIteration?: () => void;
30
+ }
31
+ /**
32
+ * Interface for the subset of bucket state needed by repository management.
33
+ * Avoids circular dependency between repository.ts and bucket.ts.
34
+ */
35
+ export interface RepositoryManager {
36
+ repositories: Map<string, RepositoryState>;
37
+ logBuffers: Map<string, LogBuffer>;
38
+ emit: BucketEmitFn;
39
+ sendEvent: SendEventFn;
40
+ sessionId: string;
41
+ }
42
+ export interface RepositoryDependencies {
43
+ spawn: typeof nodeSpawn;
44
+ run: typeof claudeRun;
45
+ fileSystem: FileSystem;
46
+ sleep: (ms: number) => Promise<void>;
47
+ getReposDir: () => string;
48
+ }
49
+ /**
50
+ * Start (or restart) the per-repository loop and keep loopPromise state accurate.
51
+ */
52
+ export declare function startRepositoryLoop(repoState: RepositoryState, repoDeps: RepositoryDependencies, sendEvent?: SendEventFn, sessionId?: string): void;
53
+ export declare function createDefaultRepositoryDependencies(fileSystem: FileSystem): RepositoryDependencies;
54
+ /**
55
+ * Parse repository from message data.
56
+ * Supports both simple names and git URLs.
57
+ */
58
+ export declare function parseRepository(data: unknown): Repository | null;
59
+ /**
60
+ * Add a repository to the manager.
61
+ */
62
+ export declare function addRepository(repository: Repository, manager: RepositoryManager, repoDeps: RepositoryDependencies, context: CommandDependencies['context']): Promise<void>;
63
+ /**
64
+ * Remove a repository from the manager.
65
+ */
66
+ export declare function removeRepositoryFromManager(repoName: string, manager: RepositoryManager, repoDeps: RepositoryDependencies, context: CommandDependencies['context']): Promise<void>;
67
+ /**
68
+ * Handle a repository-list message from the server.
69
+ */
70
+ export declare function handleRepositoryList(repositories: unknown[], manager: RepositoryManager, repoDeps: RepositoryDependencies, context: CommandDependencies['context']): Promise<void>;
@@ -0,0 +1,2 @@
1
+ import type { ClaudeEvent, RawEvent } from './types';
2
+ export declare function parseRawEvent(raw: RawEvent): Generator<ClaudeEvent>;
@@ -0,0 +1,15 @@
1
+ import { spawnClaudeCode as defaultSpawnClaudeCode } from './spawn-claude-code';
2
+ import { createStdoutSink as defaultCreateStdoutSink, streamEvents as defaultStreamEvents } from './streamer';
3
+ import type { RawEventCallback, SpawnOptions } from './types';
4
+ interface RunOptions {
5
+ spawnOptions?: SpawnOptions;
6
+ onRawEvent?: RawEventCallback;
7
+ }
8
+ export interface RunnerDependencies {
9
+ spawnClaudeCode: typeof defaultSpawnClaudeCode;
10
+ createStdoutSink: typeof defaultCreateStdoutSink;
11
+ streamEvents: typeof defaultStreamEvents;
12
+ }
13
+ export declare const defaultRunnerDependencies: RunnerDependencies;
14
+ export declare function run(prompt: string, options?: SpawnOptions | RunOptions, dependencies?: RunnerDependencies): Promise<void>;
15
+ export {};
@@ -0,0 +1,9 @@
1
+ import { spawn as nodeSpawn } from 'node:child_process';
2
+ import { createInterface as nodeCreateInterface } from 'node:readline';
3
+ import type { RawEvent, SpawnOptions } from './types';
4
+ export interface EventSourceDependencies {
5
+ spawn: typeof nodeSpawn;
6
+ createInterface: typeof nodeCreateInterface;
7
+ }
8
+ export declare const defaultDependencies: EventSourceDependencies;
9
+ export declare function spawnClaudeCode(prompt: string, options?: SpawnOptions, dependencies?: EventSourceDependencies): AsyncGenerator<RawEvent>;
@@ -0,0 +1,17 @@
1
+ import type { ClaudeEvent, OutputSink, RawEvent, RawEventCallback } from './types';
2
+ /**
3
+ * Process a stream of raw events and write output to the sink.
4
+ * This is the core streaming logic, separated from I/O concerns.
5
+ * @param events - Stream of raw events from Claude Code
6
+ * @param sink - Output sink for formatted display
7
+ * @param onRawEvent - Optional callback invoked for each raw event before processing
8
+ */
9
+ export declare function streamEvents(events: AsyncIterable<RawEvent>, sink: OutputSink, onRawEvent?: RawEventCallback): Promise<void>;
10
+ /**
11
+ * Process a single typed event and write to the sink.
12
+ * Exported for fine-grained testing.
13
+ */
14
+ export declare function processEvent(event: ClaudeEvent, sink: OutputSink, state: {
15
+ hadTextOutput: boolean;
16
+ }): void;
17
+ export declare function createStdoutSink(): OutputSink;