@joshski/dust 0.1.88 → 0.1.90

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,5 @@
1
+ language js
2
+
3
+ `$value as unknown as $target` where {
4
+ register_diagnostic(span=$target, message="Avoid double-casting with 'as unknown as'. Prefer typed helpers/adapters, or add a local suppression with rationale at unavoidable interop boundaries.")
5
+ }
@@ -4,6 +4,7 @@
4
4
  * These types define the transport-agnostic event format used by both
5
5
  * the HTTP (loop) and WebSocket (bucket) paths.
6
6
  */
7
+ import type { CommandEvent } from './command-events';
7
8
  export type AgentSessionEvent = {
8
9
  type: 'agent-session-started';
9
10
  title: string;
@@ -25,6 +26,9 @@ export type AgentSessionEvent = {
25
26
  type: 'agent-event';
26
27
  provider: string;
27
28
  rawEvent: Record<string, unknown>;
29
+ } | {
30
+ type: 'command-event';
31
+ commandEvent: CommandEvent;
28
32
  };
29
33
  export interface EventMessage {
30
34
  sequence: number;
package/dist/artifacts.js CHANGED
@@ -409,6 +409,16 @@ function renderIdeaSection(ideaSection) {
409
409
  - [${ideaSection.ideaTitle}](../ideas/${ideaSection.ideaSlug}.md)
410
410
  `;
411
411
  }
412
+ function renderRepositoryHintsSection(repositoryHint) {
413
+ if (!repositoryHint) {
414
+ return "";
415
+ }
416
+ return `
417
+ ## Repository Hints
418
+
419
+ ${repositoryHint}
420
+ `;
421
+ }
412
422
  function renderTask(title, openingSentence, definitionOfDone, ideaSection, options) {
413
423
  const descriptionParagraph = options?.description !== undefined ? `
414
424
  ${options.description}
@@ -419,12 +429,14 @@ ${renderResolvedQuestions(options.resolvedQuestions)}
419
429
  const ideaSectionContent = `
420
430
  ${renderIdeaSection(ideaSection)}
421
431
  `;
432
+ const repositoryHintsSection = renderRepositoryHintsSection(options?.repositoryHint);
422
433
  return `# ${title}
423
434
 
424
435
  ${openingSentence}
425
436
  ${descriptionParagraph}${resolvedSection}${ideaSectionContent}## Blocked By
426
437
 
427
438
  (none)
439
+ ${repositoryHintsSection}
428
440
 
429
441
  ## Definition of Done
430
442
 
@@ -439,13 +451,11 @@ async function createIdeaTransitionTask(fileSystem, dustPath, workflowType, pref
439
451
  const filePath = `${dustPath}/tasks/${filename}`;
440
452
  const baseOpeningSentence = openingSentenceTemplate(ideaTitle);
441
453
  const hint = await readWorkflowHint(fileSystem, dustPath, workflowType);
442
- const openingSentence = hint ? `${baseOpeningSentence}
443
-
444
- ${hint}` : baseOpeningSentence;
445
454
  const ideaSection = { heading: ideaSectionHeading, ideaTitle, ideaSlug };
446
- const content = renderTask(taskTitle, openingSentence, definitionOfDone, ideaSection, {
455
+ const content = renderTask(taskTitle, baseOpeningSentence, definitionOfDone, ideaSection, {
447
456
  description: taskOptions?.description,
448
- resolvedQuestions: taskOptions?.resolvedQuestions
457
+ resolvedQuestions: taskOptions?.resolvedQuestions,
458
+ repositoryHint: hint ?? undefined
449
459
  });
450
460
  await fileSystem.writeFile(filePath, content);
451
461
  return { filePath };
@@ -488,12 +498,10 @@ async function createIdeaTask(fileSystem, dustPath, options) {
488
498
  const filePath2 = `${dustPath}/tasks/${filename2}`;
489
499
  const baseOpeningSentence2 = `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/\`. Run \`${cmd} principles\` and \`${cmd} facts\` for relevant context.`;
490
500
  const hint2 = await readWorkflowHint(fileSystem, dustPath, "expedite-idea");
491
- const openingSentence2 = hint2 ? `${baseOpeningSentence2}
492
-
493
- ${hint2}` : baseOpeningSentence2;
501
+ const repositoryHintsSection2 = renderRepositoryHintsSection(hint2 ?? undefined);
494
502
  const content2 = `# ${taskTitle2}
495
503
 
496
- ${openingSentence2}
504
+ ${baseOpeningSentence2}
497
505
 
498
506
  ## Idea Description
499
507
 
@@ -502,6 +510,7 @@ ${description}
502
510
  ## Blocked By
503
511
 
504
512
  (none)
513
+ ${repositoryHintsSection2}
505
514
 
506
515
  ## Definition of Done
507
516
 
@@ -517,12 +526,10 @@ ${description}
517
526
  const filePath = `${dustPath}/tasks/${filename}`;
518
527
  const baseOpeningSentence = `Research this idea thoroughly, then create one or more idea files in \`.dust/ideas/\`. Read the codebase for relevant context, flesh out the description, and identify any ambiguity. Where aspects are unclear or could go multiple ways, add open questions to the idea file. If you add open questions, use \`## Open Questions\` with \`### Question?\` headings and one or more \`#### Option\` headings beneath each question, and only add questions that are meaningful decisions worth asking. Run \`${cmd} principles\` and \`${cmd} facts\` for relevant context.`;
519
528
  const hint = await readWorkflowHint(fileSystem, dustPath, "add-idea");
520
- const openingSentence = hint ? `${baseOpeningSentence}
521
-
522
- ${hint}` : baseOpeningSentence;
529
+ const repositoryHintsSection = renderRepositoryHintsSection(hint ?? undefined);
523
530
  const content = `# ${taskTitle}
524
531
 
525
- ${openingSentence}
532
+ ${baseOpeningSentence}
526
533
 
527
534
  ## Idea Description
528
535
 
@@ -531,6 +538,7 @@ ${description}
531
538
  ## Blocked By
532
539
 
533
540
  (none)
541
+ ${repositoryHintsSection}
534
542
 
535
543
  ## Definition of Done
536
544
 
package/dist/audits.js CHANGED
@@ -778,6 +778,204 @@ function slowTests() {
778
778
  - [ ] Proposed ideas for optimizing the slowest tests
779
779
  `;
780
780
  }
781
+ function namingConsistency() {
782
+ return dedent`
783
+ # Naming Consistency
784
+
785
+ Review high-confidence naming consistency issues for equivalent factory/constructor creation APIs.
786
+
787
+ ${ideasHint}
788
+
789
+ ## Scope
790
+
791
+ Focus only on high-confidence factory/constructor naming inconsistencies where intent is clearly the same:
792
+ - Equivalent creation abstractions using \`build*\`, \`create*\`, \`make*\`, or \`new*\`
793
+ - Cases where names differ but behavior and role clearly indicate the same creation concept
794
+
795
+ Out of scope:
796
+ - Canonical artifact-list ordering or shape checks
797
+ - Broad terminology drift and ambiguous language choices (covered by the \`ubiquitous-language\` audit)
798
+
799
+ ## Analysis Steps
800
+
801
+ 1. Identify factory/constructor APIs with equivalent behavior that use \`build*\`, \`create*\`, \`make*\`, or \`new*\` naming variants
802
+ 2. Group findings by shared creation concept to avoid duplicate reporting
803
+ 3. Keep only high-confidence inconsistencies where the compared usages clearly represent the same concept
804
+ 4. Recommend only high-confidence renames with clear semantic equivalence; do not propose speculative broad renames
805
+ 5. Preserve Functional Core, Imperative Shell boundaries in recommendations (pure matching/analysis logic separated from IO shell)
806
+
807
+ ## Output
808
+
809
+ For each inconsistency, provide:
810
+ - **Locations** - File paths and line numbers where inconsistent factory/constructor names appear
811
+ - **Inconsistent term set** - The observed naming variants (for example \`createWidget\`, \`buildWidget\`, \`makeWidget\`)
812
+ - **Canonical proposal** - The recommended canonical name and rationale
813
+ - **Migration strategy** - Choose either **incremental** (aliases/adapters then cleanup) or **one-shot** (single coordinated rename), with rationale
814
+
815
+ ## Principles
816
+
817
+ - [Consistent Naming](../principles/consistent-naming.md)
818
+ - [Naming Matters](../principles/naming-matters.md)
819
+ - [Clarity Over Brevity](../principles/clarity-over-brevity.md)
820
+ - [Functional Core, Imperative Shell](../principles/functional-core-imperative-shell.md)
821
+ - [Small Units](../principles/small-units.md)
822
+
823
+ ## Blocked By
824
+
825
+ (none)
826
+
827
+ ## Definition of Done
828
+
829
+ - [ ] Reviewed high-confidence factory/constructor naming consistency for equivalent creation APIs
830
+ - [ ] Constrained findings to \`build*\`, \`create*\`, \`make*\`, and \`new*\` naming variants with clearly equivalent intent
831
+ - [ ] Documented each finding with locations, inconsistent term set, canonical proposal, and migration strategy
832
+ - [ ] Chose incremental or one-shot migration strategy for each canonical proposal
833
+ - [ ] Avoided speculative broad renames
834
+ - [ ] Avoided artifact-list ordering/shape checks and broad terminology drift
835
+ - [ ] Proposed ideas for naming consistency improvements identified
836
+ `;
837
+ }
838
+ function primitiveObsession() {
839
+ return dedent`
840
+ # Primitive Obsession
841
+
842
+ Review high-confidence primitive obsession where call sites use free-form literals instead of canonical domain representations.
843
+
844
+ ${ideasHint}
845
+
846
+ ## Scope
847
+
848
+ Focus only on two high-confidence slices:
849
+ - Existing-type drift for domain string concepts
850
+ - Numeric magic values where naming/domain wrappers would improve clarity
851
+
852
+ Existing-type drift scope:
853
+ - Call sites using free-form string literals where a canonical domain type already exists
854
+ - Cases where the existing domain type is bypassed (for example artifact directory names that should use \`ArtifactType\`)
855
+ - High-confidence matches where intent is clear and the existing type is directly applicable
856
+
857
+ Numeric magic value scope:
858
+ - Thresholds, limits, retries, and timing values whose domain meaning is clear at call sites
859
+ - High-confidence literals that would be clearer as named constants or existing domain wrappers
860
+ - Examples: retry counts like \`3\`, timeout values like \`30_000\`, batch limits like \`100\`
861
+
862
+ Out of scope:
863
+ - Proposing entirely new domain types in this slice
864
+ - Ambiguous literals where no canonical existing type or constant naming opportunity can be identified with high confidence
865
+ - Obvious local loop indices/counters and trivial literals like \`0\` or \`1\` where no domain meaning exists
866
+
867
+ ## Analysis Steps
868
+
869
+ 1. Identify domain string concepts with existing canonical types (enums, unions, branded strings, or shared constants)
870
+ 2. Search for free-form string literals that represent those same concepts at call sites
871
+ 3. Identify numeric literals used as thresholds, limits, retries, or timing values where domain meaning is clear
872
+ 4. Keep only high-confidence findings (exclude ambiguous values and obvious local indices/counters)
873
+ 5. Group duplicate call-site drift by concept to avoid repetitive findings
874
+ 6. Preserve Functional Core, Imperative Shell boundaries in recommendations (pure matching/analysis logic separated from IO shell)
875
+ 7. Recommend incremental migrations only; avoid speculative introduction of brand-new types
876
+
877
+ ## Output
878
+
879
+ For each finding, provide:
880
+ - **Locations** - File paths and line numbers where primitive literals are used
881
+ - **Primitive pattern** - The free-form literal pattern currently used (string concept or numeric role)
882
+ - **Constant/type opportunity** - The canonical existing type or named constant/domain wrapper that should be used instead
883
+ - **Incremental migration path** - A safe sequence of steps to migrate call sites with minimal risk
884
+
885
+ For numeric findings specifically, include:
886
+ - **Locations** - File paths and line numbers for the numeric literals
887
+ - **Numeric pattern** - The repeated threshold/limit/retry/timing literal pattern
888
+ - **Constant/type opportunity** - A named constant or existing domain wrapper to encode intent
889
+ - **Incremental migration path** - Steps to introduce the constant/wrapper and migrate call sites safely
890
+
891
+ ## Principles
892
+
893
+ - [Functional Core, Imperative Shell](../principles/functional-core-imperative-shell.md)
894
+ - [Small Units](../principles/small-units.md)
895
+ - [Make the Change Easy](../principles/make-the-change-easy.md)
896
+ - [Naming Matters](../principles/naming-matters.md)
897
+
898
+ ## Blocked By
899
+
900
+ (none)
901
+
902
+ ## Definition of Done
903
+
904
+ - [ ] Reviewed high-confidence existing-type drift for domain string literals and numeric magic values
905
+ - [ ] Constrained findings to cases where canonical domain types or clear constant/wrapper opportunities already exist
906
+ - [ ] Documented each finding with locations, primitive pattern, constant/type opportunity, and incremental migration path
907
+ - [ ] Documented numeric findings with locations, numeric pattern, constant/type opportunity, and incremental migration path
908
+ - [ ] Preserved Functional Core, Imperative Shell boundaries in recommendations
909
+ - [ ] Avoided speculative introduction of entirely new types
910
+ - [ ] Proposed ideas for primitive obsession improvements identified
911
+ `;
912
+ }
913
+ function singleResponsibilityViolations() {
914
+ return dedent`
915
+ # Single Responsibility Violations
916
+
917
+ Review high-confidence single responsibility violations driven by responsibility count at function level.
918
+
919
+ ${ideasHint}
920
+
921
+ ## Scope
922
+
923
+ Focus only on high-confidence function-level findings where one function clearly combines 3+ distinct responsibilities.
924
+
925
+ Responsibility examples:
926
+ - Parsing/validation
927
+ - Domain decision/execution logic
928
+ - Side effects or IO coordination
929
+ - Presentation/formatting/reporting
930
+ - Cross-module orchestration
931
+
932
+ Include both runtime code and test helpers.
933
+
934
+ Out of scope:
935
+ - Module-level layer-mixing findings (future slices)
936
+ - Collector/orchestrator hotspot findings based on collaborator/parameter load (future slices)
937
+ - Functions with only one or two responsibilities
938
+ - Ambiguous style-only concerns without clear responsibility boundaries
939
+ - Broad rewrite recommendations without clear extraction seams
940
+
941
+ ## Analysis Steps
942
+
943
+ 1. Identify runtime functions and test helpers that appear to mix concerns
944
+ 2. Keep findings only when 3+ distinct responsibilities are clearly present in the same function
945
+ 3. Validate a concrete extraction seam for each responsibility split (no speculative or style-only recommendations)
946
+ 4. Keep recommendations incremental and high-confidence only
947
+ 5. Preserve Functional Core, Imperative Shell boundaries by extracting pure logic from imperative shells where possible
948
+
949
+ ## Output
950
+
951
+ For each finding, provide:
952
+ - **Location** - File path and function name where applicable
953
+ - **Responsibility split** - Distinct responsibilities currently mixed (for example parsing, execution, presentation)
954
+ - **Severity** - \`high\`, \`medium\`, or \`low\` based on extraction urgency and coupling risk
955
+ - **Suggested extraction plan** - A small-step plan describing what to extract first, with Functional Core, Imperative Shell boundaries preserved
956
+
957
+ ## Principles
958
+
959
+ - [Functional Core, Imperative Shell](../principles/functional-core-imperative-shell.md)
960
+ - [Decoupled Code](../principles/decoupled-code.md)
961
+ - [Small Units](../principles/small-units.md)
962
+ - [Make the Change Easy](../principles/make-the-change-easy.md)
963
+ - [Context-Optimised Code](../principles/context-optimised-code.md)
964
+
965
+ ## Blocked By
966
+
967
+ (none)
968
+
969
+ ## Definition of Done
970
+
971
+ - [ ] Reviewed high-confidence function-level findings where 3+ distinct responsibilities are combined
972
+ - [ ] Included runtime code and test helpers in scope
973
+ - [ ] Documented each finding with location, responsibility split, severity, and suggested extraction plan
974
+ - [ ] Preserved Functional Core, Imperative Shell boundaries in recommendations
975
+ - [ ] Kept recommendations high-confidence only with clear concern boundaries
976
+ - [ ] Proposed ideas for substantial responsibility-splitting work identified
977
+ `;
978
+ }
781
979
  function ubiquitousLanguage() {
782
980
  return dedent`
783
981
  # Ubiquitous Language
@@ -911,10 +1109,13 @@ var stockAuditFunctions = {
911
1109
  "global-state": globalState,
912
1110
  "ideas-from-commits": ideasFromCommits,
913
1111
  "ideas-from-principles": ideasFromPrinciples,
1112
+ "naming-consistency": namingConsistency,
914
1113
  "performance-review": performanceReview,
1114
+ "primitive-obsession": primitiveObsession,
915
1115
  "refactoring-opportunities": refactoringOpportunities,
916
1116
  "repository-context": repositoryContext,
917
1117
  "security-review": securityReview,
1118
+ "single-responsibility-violations": singleResponsibilityViolations,
918
1119
  "slow-tests": slowTests,
919
1120
  "stale-ideas": staleIdeas,
920
1121
  "test-coverage": testCoverage,
@@ -0,0 +1,29 @@
1
+ import type { CommandEventMessage } from '../command-events';
2
+ import type { ToolDefinition } from './server-messages';
3
+ export interface CommandEventsProxy {
4
+ port: number;
5
+ stop: () => Promise<void>;
6
+ }
7
+ export interface ToolExecutionRequest {
8
+ toolName: string;
9
+ arguments: string[];
10
+ repositoryId: string;
11
+ }
12
+ export type ToolExecutionStatus = 'success' | 'tool-not-found' | 'error';
13
+ export interface ToolExecutionResult {
14
+ status: ToolExecutionStatus;
15
+ output?: string;
16
+ error?: string;
17
+ }
18
+ interface CommandEventsProxyHandlers {
19
+ forwardEvent: (event: CommandEventMessage) => void;
20
+ getTools: () => ToolDefinition[];
21
+ forwardToolExecution: (request: ToolExecutionRequest) => Promise<ToolExecutionResult>;
22
+ }
23
+ export declare function isCommandEventMessage(payload: unknown): payload is CommandEventMessage;
24
+ /**
25
+ * Start a local HTTP proxy that accepts command events on POST /events.
26
+ * Accepted payloads are forwarded to the bucket WebSocket channel.
27
+ */
28
+ export declare function startCommandEventsProxy(handlers: CommandEventsProxyHandlers): Promise<CommandEventsProxy>;
29
+ export {};
@@ -4,7 +4,7 @@
4
4
  * Manages the async loop that picks tasks and runs Claude sessions
5
5
  * for a single repository.
6
6
  */
7
- import type { AgentSessionEvent, EventMessage } from '../agent-events';
7
+ import type { EventMessage } from '../agent-events';
8
8
  import { type run as claudeRun, type RunnerDependencies } from '../claude/run';
9
9
  import type { OutputSink } from '../claude/types';
10
10
  import { type LoopEmitFn, type SendAgentEventFn } from '../cli/commands/loop';
@@ -35,7 +35,7 @@ export declare function buildEventMessage(parameters: {
35
35
  sessionId: string;
36
36
  repository: string;
37
37
  repoId?: number;
38
- event: AgentSessionEvent;
38
+ event: EventMessage['event'];
39
39
  agentSessionId?: string;
40
40
  }): EventMessage;
41
41
  /**
@@ -8,6 +8,7 @@ import { spawn as nodeSpawn } from 'node:child_process';
8
8
  import { run as claudeRun } from '../claude/run';
9
9
  import type { CommandDependencies, FileSystem } from '../cli/types';
10
10
  import type { DockerDependencies } from '../docker/docker-agent';
11
+ import type { ToolExecutionRequest, ToolExecutionResult } from './command-events-proxy';
11
12
  import { type BucketEmitFn, type SendEventFn } from './events';
12
13
  import { type LogBuffer } from './log-buffer';
13
14
  import type { ToolDefinition } from './server-messages';
@@ -53,6 +54,8 @@ export interface RepositoryDependencies {
53
54
  dockerDeps?: Partial<DockerDependencies>;
54
55
  /** Function to get current tool definitions */
55
56
  getTools?: () => ToolDefinition[];
57
+ /** Forward tool execution requests to the bucket server */
58
+ forwardToolExecution?: (request: ToolExecutionRequest) => Promise<ToolExecutionResult>;
56
59
  }
57
60
  /**
58
61
  * Start (or restart) the per-repository loop and keep loopPromise state accurate.
@@ -1,9 +1,8 @@
1
- import { spawn as nodeSpawn } from 'node:child_process';
2
- import { createInterface as nodeCreateInterface } from 'node:readline';
1
+ import type { CreateReadlineForEvents, SpawnForEvents } from '../process/spawn-contract';
3
2
  import type { DockerSpawnConfig, RawEvent, SpawnOptions } from './types';
4
3
  export interface EventSourceDependencies {
5
- spawn: typeof nodeSpawn;
6
- createInterface: typeof nodeCreateInterface;
4
+ spawn: SpawnForEvents;
5
+ createInterface: CreateReadlineForEvents;
7
6
  }
8
7
  export declare const defaultDependencies: EventSourceDependencies;
9
8
  /**
@@ -109,6 +109,8 @@ interface IterationOptions {
109
109
  docker?: DockerSpawnConfig;
110
110
  /** Pre-formatted tools section to inject into the prompt */
111
111
  toolsSection?: string;
112
+ /** Port of the command events proxy for this iteration */
113
+ proxyPort?: number;
112
114
  }
113
115
  export declare function runOneIteration(dependencies: CommandDependencies, loopDependencies: LoopDependencies, onLoopEvent: LoopEmitFn, onAgentEvent?: SendAgentEventFn, options?: IterationOptions): Promise<IterationResult>;
114
116
  export declare function parseMaxIterations(commandArguments: string[]): number;
@@ -1,9 +1,8 @@
1
- import { spawn as nodeSpawn } from 'node:child_process';
2
- import { createInterface as nodeCreateInterface } from 'node:readline';
3
1
  import type { RawEvent, SpawnOptions } from '../claude/types';
2
+ import type { CreateReadlineForEvents, SpawnForEvents } from '../process/spawn-contract';
4
3
  export interface EventSourceDependencies {
5
- spawn: typeof nodeSpawn;
6
- createInterface: typeof nodeCreateInterface;
4
+ spawn: SpawnForEvents;
5
+ createInterface: CreateReadlineForEvents;
7
6
  }
8
7
  export declare const defaultDependencies: EventSourceDependencies;
9
8
  export declare function spawnCodex(prompt: string, options?: SpawnOptions, dependencies?: EventSourceDependencies): AsyncGenerator<RawEvent>;
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Command event types for the dust back channel protocol.
3
3
  *
4
- * These types define structured events emitted by dust commands
5
- * to a file descriptor specified by DUST_EVENTS_FD. Events are
6
- * written as newline-delimited JSON using the CommandEventMessage envelope.
4
+ * These types define structured events emitted by dust commands.
5
+ * Events are transported via DUST_PROXY_PORT (HTTP POST /events),
6
+ * using the same envelope.
7
7
  */
8
8
  /**
9
9
  * Events emitted by dust commands.