@joshski/dust 0.1.103 → 0.1.104

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 (36) hide show
  1. package/dist/agents/detection.d.ts +1 -1
  2. package/dist/agents.js +1 -1
  3. package/dist/artifacts/facts.d.ts +2 -2
  4. package/dist/artifacts/ideas.d.ts +2 -2
  5. package/dist/artifacts/index.d.ts +2 -2
  6. package/dist/artifacts/principles.d.ts +2 -2
  7. package/dist/artifacts/tasks.d.ts +2 -2
  8. package/dist/artifacts/workflow-tasks.d.ts +6 -0
  9. package/dist/artifacts.js +142 -25
  10. package/dist/audits/index.d.ts +1 -0
  11. package/dist/audits.js +24 -1
  12. package/dist/bucket/repository-loop.d.ts +4 -4
  13. package/dist/bucket/repository.d.ts +10 -0
  14. package/dist/claude/run.d.ts +3 -9
  15. package/dist/claude/spawn-claude-code.d.ts +1 -1
  16. package/dist/claude/types.d.ts +9 -0
  17. package/dist/cli/commands/focus.d.ts +2 -1
  18. package/dist/cli/shared/agent-shared.d.ts +4 -4
  19. package/dist/cli/types.d.ts +1 -1
  20. package/dist/codex/run.d.ts +3 -9
  21. package/dist/codex/spawn-codex.d.ts +1 -1
  22. package/dist/config/settings.d.ts +5 -5
  23. package/dist/core-principles.js +5 -16
  24. package/dist/docker/docker-agent.d.ts +0 -4
  25. package/dist/dust.js +375 -422
  26. package/dist/filesystem/types.d.ts +5 -1
  27. package/dist/lint/validators/content-validator.d.ts +1 -0
  28. package/dist/lint/validators/directory-validator.d.ts +3 -3
  29. package/dist/lint/validators/idea-validator.d.ts +3 -3
  30. package/dist/lint/validators/link-validator.d.ts +2 -2
  31. package/dist/loop/iteration.d.ts +2 -3
  32. package/dist/patch/index.d.ts +13 -0
  33. package/dist/patch.js +48 -115
  34. package/dist/proxy/helper-token.d.ts +2 -2
  35. package/dist/validation.js +38 -115
  36. package/package.json +2 -2
@@ -27,4 +27,4 @@ export type AgentType = Agent['type'];
27
27
  * 3. CODEX_HOME or CODEX_CI → Codex
28
28
  * 4. Fallback → unknown Agent
29
29
  */
30
- export declare function detectAgent(env?: NodeJS.ProcessEnv): Agent;
30
+ export declare function detectAgent(env: NodeJS.ProcessEnv): Agent;
package/dist/agents.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // lib/agents/detection.ts
2
- function detectAgent(env = process.env) {
2
+ function detectAgent(env) {
3
3
  if (env.CLAUDECODE) {
4
4
  if (env.CLAUDE_CODE_REMOTE) {
5
5
  return { type: "claude-code-web", name: "Claude Code Web" };
@@ -1,4 +1,4 @@
1
- import type { ReadableFileSystem } from '../filesystem/types';
1
+ import type { FileReader } from '../filesystem/types';
2
2
  export interface Fact {
3
3
  slug: string;
4
4
  title: string;
@@ -7,4 +7,4 @@ export interface Fact {
7
7
  /**
8
8
  * Parses a fact markdown file into a structured Fact object.
9
9
  */
10
- export declare function parseFact(fileSystem: ReadableFileSystem, dustPath: string, slug: string): Promise<Fact>;
10
+ export declare function parseFact(fileSystem: FileReader, dustPath: string, slug: string): Promise<Fact>;
@@ -1,4 +1,4 @@
1
- import type { ReadableFileSystem } from '../filesystem/types';
1
+ import type { FileReader } from '../filesystem/types';
2
2
  export interface IdeaOption {
3
3
  name: string;
4
4
  description: string;
@@ -27,7 +27,7 @@ export declare function parseOpenQuestions(content: string): IdeaOpenQuestion[];
27
27
  /**
28
28
  * Parses an idea markdown file into a structured Idea object.
29
29
  */
30
- export declare function parseIdea(fileSystem: ReadableFileSystem, dustPath: string, slug: string): Promise<Idea>;
30
+ export declare function parseIdea(fileSystem: FileReader, dustPath: string, slug: string): Promise<Idea>;
31
31
  /**
32
32
  * Parses idea markdown into a structured object that can be bound to a UI
33
33
  * and serialized back to markdown.
@@ -5,8 +5,8 @@ import { extractTitle } from '../markdown/markdown-utilities';
5
5
  import { type Principle } from './principles';
6
6
  import { type Task } from './tasks';
7
7
  import { type ParsedArtifact, type ParsedMarkdownLink, type ParsedSection, parseArtifact } from './parsed-artifact';
8
- import { type AllWorkflowTasks, CAPTURE_IDEA_PREFIX, type CreateIdeaTransitionTaskResult, type DecomposeIdeaOptions, findAllWorkflowTasks, type IdeaInProgress, type OpenQuestionResponse, type ParsedCaptureIdeaTask, parseResolvedQuestions, type WorkflowTaskMatch, type WorkflowTaskType } from './workflow-tasks';
9
- export type { AllWorkflowTasks, CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedArtifact, ParsedCaptureIdeaTask, ParsedIdeaContent, ParsedMarkdownLink, ParsedSection, Principle, Task, WorkflowTaskMatch, WorkflowTaskType, };
8
+ import { type AllWorkflowTasks, CAPTURE_IDEA_PREFIX, type CreateIdeaTransitionTaskResult, type DecomposeIdeaOptions, findAllWorkflowTasks, type IdeaInProgress, type OpenQuestionResponse, type ParsedCaptureIdeaTask, parseResolvedQuestions, type TaskType, type WorkflowTaskMatch, type WorkflowTaskType } from './workflow-tasks';
9
+ export type { AllWorkflowTasks, CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedArtifact, ParsedCaptureIdeaTask, ParsedIdeaContent, ParsedMarkdownLink, ParsedSection, Principle, Task, TaskType, WorkflowTaskMatch, WorkflowTaskType, };
10
10
  export interface TaskGraphNode {
11
11
  task: Task;
12
12
  workflowType: WorkflowTaskType | null;
@@ -1,4 +1,4 @@
1
- import type { ReadableFileSystem } from '../filesystem/types';
1
+ import type { FileReader } from '../filesystem/types';
2
2
  export interface Principle {
3
3
  slug: string;
4
4
  title: string;
@@ -9,4 +9,4 @@ export interface Principle {
9
9
  /**
10
10
  * Parses a principle markdown file into a structured Principle object.
11
11
  */
12
- export declare function parsePrinciple(fileSystem: ReadableFileSystem, dustPath: string, slug: string): Promise<Principle>;
12
+ export declare function parsePrinciple(fileSystem: FileReader, dustPath: string, slug: string): Promise<Principle>;
@@ -1,4 +1,4 @@
1
- import type { ReadableFileSystem } from '../filesystem/types';
1
+ import type { FileReader } from '../filesystem/types';
2
2
  export interface Task {
3
3
  slug: string;
4
4
  title: string;
@@ -10,4 +10,4 @@ export interface Task {
10
10
  /**
11
11
  * Parses a task markdown file into a structured Task object.
12
12
  */
13
- export declare function parseTask(fileSystem: ReadableFileSystem, dustPath: string, slug: string): Promise<Task>;
13
+ export declare function parseTask(fileSystem: FileReader, dustPath: string, slug: string): Promise<Task>;
@@ -21,6 +21,12 @@ export interface ParsedCaptureIdeaTask {
21
21
  * 6. Add .md extension
22
22
  */
23
23
  export declare function titleToFilename(title: string): string;
24
+ export type TaskType = 'implement' | 'capture' | 'refine' | 'decompose' | 'shelve';
25
+ /**
26
+ * Extracts and validates the task type from the ## Task Type section.
27
+ * Returns the task type if found and valid, null otherwise.
28
+ */
29
+ export declare function parseTaskType(content: string): TaskType | null;
24
30
  export type WorkflowTaskType = 'refine-idea' | 'decompose-idea' | 'shelve-idea' | 'expedite-idea';
25
31
  export interface WorkflowTaskMatch {
26
32
  type: WorkflowTaskType;
package/dist/artifacts.js CHANGED
@@ -522,6 +522,54 @@ var EXPEDITE_IDEA_PREFIX = "Expedite Idea: ";
522
522
  function titleToFilename(title) {
523
523
  return `${title.toLowerCase().replace(/\./g, "-").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}.md`;
524
524
  }
525
+ var VALID_TASK_TYPES = [
526
+ "implement",
527
+ "capture",
528
+ "refine",
529
+ "decompose",
530
+ "shelve"
531
+ ];
532
+ function parseTaskType(content) {
533
+ const lines = content.split(`
534
+ `);
535
+ let inSection = false;
536
+ let inCodeFence = false;
537
+ for (const line of lines) {
538
+ if (line.startsWith("```")) {
539
+ inCodeFence = !inCodeFence;
540
+ continue;
541
+ }
542
+ if (inCodeFence)
543
+ continue;
544
+ if (line.startsWith("## ")) {
545
+ inSection = line.trimEnd() === "## Task Type";
546
+ continue;
547
+ }
548
+ if (!inSection)
549
+ continue;
550
+ if (line.startsWith("# "))
551
+ break;
552
+ const trimmed = line.trim();
553
+ if (trimmed && VALID_TASK_TYPES.includes(trimmed)) {
554
+ return trimmed;
555
+ }
556
+ }
557
+ return null;
558
+ }
559
+ function taskTypeToWorkflowType(taskType) {
560
+ switch (taskType) {
561
+ case "refine":
562
+ return "refine-idea";
563
+ case "decompose":
564
+ return "decompose-idea";
565
+ case "shelve":
566
+ return "shelve-idea";
567
+ case "implement":
568
+ return "expedite-idea";
569
+ default:
570
+ return null;
571
+ }
572
+ }
525
573
  var WORKFLOW_HINT_PATHS = {
526
574
  "refine-idea": "config/hints/refine-idea.md",
527
575
  "decompose-idea": "config/hints/decompose-idea.md",
@@ -588,16 +636,32 @@ async function findAllWorkflowTasks(fileSystem, dustPath) {
588
636
  continue;
589
637
  const title = titleMatch[1].trim();
590
638
  const taskSlug = file.replace(/\.md$/, "");
591
- if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
592
- captureIdeaTasks.push({
593
- taskSlug,
594
- ideaTitle: title.slice(CAPTURE_IDEA_PREFIX.length)
595
- });
596
- } else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
597
- captureIdeaTasks.push({
598
- taskSlug,
599
- ideaTitle: title.slice(EXPEDITE_IDEA_PREFIX.length)
600
- });
639
+ const taskType = parseTaskType(content);
640
+ if (taskType === "capture" || taskType === "implement") {
641
+ let ideaTitle = null;
642
+ if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
643
+ ideaTitle = title.slice(CAPTURE_IDEA_PREFIX.length);
644
+ } else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
645
+ ideaTitle = title.slice(EXPEDITE_IDEA_PREFIX.length);
646
+ }
647
+ if (ideaTitle) {
648
+ captureIdeaTasks.push({
649
+ taskSlug,
650
+ ideaTitle
651
+ });
652
+ }
653
+ } else if (!taskType) {
654
+ if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
655
+ captureIdeaTasks.push({
656
+ taskSlug,
657
+ ideaTitle: title.slice(CAPTURE_IDEA_PREFIX.length)
658
+ });
659
+ } else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
660
+ captureIdeaTasks.push({
661
+ taskSlug,
662
+ ideaTitle: title.slice(EXPEDITE_IDEA_PREFIX.length)
663
+ });
664
+ }
601
665
  }
602
666
  for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
603
667
  const linkedSlug = extractIdeaSlugFromSection(content, heading);
@@ -625,17 +689,49 @@ async function findWorkflowTaskForIdea(fileSystem, dustPath, ideaSlug) {
625
689
  const files = await fileSystem.readdir(tasksPath);
626
690
  for (const file of files.filter((f) => f.endsWith(".md")).toSorted()) {
627
691
  const content = await fileSystem.readFile(`${tasksPath}/${file}`);
628
- for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
629
- const linkedSlug = extractIdeaSlugFromSection(content, heading);
630
- if (linkedSlug === ideaSlug) {
631
- const taskSlug = file.replace(/\.md$/, "");
632
- return {
633
- type,
634
- ideaSlug,
635
- taskSlug,
636
- resolvedQuestions: parseResolvedQuestions(content)
637
- };
638
- }
692
+ const taskSlug = file.replace(/\.md$/, "");
693
+ const match = findWorkflowMatch(content, ideaSlug, taskSlug);
694
+ if (match) {
695
+ return match;
696
+ }
697
+ }
698
+ return null;
699
+ }
700
+ function findWorkflowMatch(content, ideaSlug, taskSlug) {
701
+ const taskType = parseTaskType(content);
702
+ if (taskType) {
703
+ const workflowType = taskTypeToWorkflowType(taskType);
704
+ if (workflowType) {
705
+ const match = findWorkflowMatchByType(content, ideaSlug, taskSlug, workflowType);
706
+ if (match)
707
+ return match;
708
+ }
709
+ }
710
+ for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
711
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
712
+ if (linkedSlug === ideaSlug) {
713
+ return {
714
+ type,
715
+ ideaSlug,
716
+ taskSlug,
717
+ resolvedQuestions: parseResolvedQuestions(content)
718
+ };
719
+ }
720
+ }
721
+ return null;
722
+ }
723
+ function findWorkflowMatchByType(content, ideaSlug, taskSlug, workflowType) {
724
+ for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
725
+ if (type !== workflowType)
726
+ continue;
727
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
728
+ if (linkedSlug === ideaSlug) {
729
+ return {
730
+ type: workflowType,
731
+ ideaSlug,
732
+ taskSlug,
733
+ resolvedQuestions: parseResolvedQuestions(content)
734
+ };
639
735
  }
640
736
  }
641
737
  return null;
@@ -719,7 +815,7 @@ function renderRepositoryHintsSection(repositoryHint) {
719
815
  ${repositoryHint}
720
816
  `;
721
817
  }
722
- function renderTask(title, openingSentence, definitionOfDone, ideaSection, options) {
818
+ function renderTask(title, openingSentence, definitionOfDone, ideaSection, taskType, options) {
723
819
  const descriptionParagraph = options?.description !== undefined ? `
724
820
  ${options.description}
725
821
  ` : "";
@@ -733,7 +829,12 @@ ${renderIdeaSection(ideaSection)}
733
829
  return `# ${title}
734
830
 
735
831
  ${openingSentence}
736
- ${descriptionParagraph}${resolvedSection}${ideaSectionContent}## Blocked By
832
+ ${descriptionParagraph}${resolvedSection}${ideaSectionContent}
833
+ ## Task Type
834
+
835
+ ${taskType}
836
+
837
+ ## Blocked By
737
838
 
738
839
  (none)
739
840
  ${repositoryHintsSection}
@@ -752,7 +853,8 @@ async function createIdeaTransitionTask(fileSystem, dustPath, workflowType, pref
752
853
  const baseOpeningSentence = openingSentenceTemplate(ideaTitle);
753
854
  const hint = await readWorkflowHint(fileSystem, dustPath, workflowType);
754
855
  const ideaSection = { heading: ideaSectionHeading, ideaTitle, ideaSlug };
755
- const content = renderTask(taskTitle, baseOpeningSentence, definitionOfDone, ideaSection, {
856
+ const taskType = workflowType === "refine-idea" ? "refine" : workflowType === "decompose-idea" ? "decompose" : workflowType === "shelve-idea" ? "shelve" : "implement";
857
+ const content = renderTask(taskTitle, baseOpeningSentence, definitionOfDone, ideaSection, taskType, {
756
858
  description: taskOptions?.description,
757
859
  resolvedQuestions: taskOptions?.resolvedQuestions,
758
860
  repositoryHint: hint ?? undefined
@@ -818,6 +920,10 @@ ${baseOpeningSentence2}
818
920
 
819
921
  ${description}
820
922
 
923
+ ## Task Type
924
+
925
+ implement
926
+
821
927
  ## Blocked By
822
928
 
823
929
  (none)
@@ -846,6 +952,10 @@ ${baseOpeningSentence}
846
952
 
847
953
  ${description}
848
954
 
955
+ ## Task Type
956
+
957
+ capture
958
+
849
959
  ## Blocked By
850
960
 
851
961
  (none)
@@ -873,9 +983,16 @@ async function parseCaptureIdeaTask(fileSystem, dustPath, taskSlug) {
873
983
  return null;
874
984
  }
875
985
  const title = titleMatch[1].trim();
986
+ const taskType = parseTaskType(content);
876
987
  let ideaTitle;
877
988
  let expedite;
878
- if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
989
+ if (taskType === "implement") {
990
+ expedite = true;
991
+ ideaTitle = title.startsWith(EXPEDITE_IDEA_PREFIX) ? title.slice(EXPEDITE_IDEA_PREFIX.length) : title;
992
+ } else if (taskType === "capture") {
993
+ expedite = false;
994
+ ideaTitle = title.startsWith(CAPTURE_IDEA_PREFIX) ? title.slice(CAPTURE_IDEA_PREFIX.length) : title;
995
+ } else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
879
996
  ideaTitle = title.slice(EXPEDITE_IDEA_PREFIX.length);
880
997
  expedite = true;
881
998
  } else if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
@@ -32,6 +32,7 @@ export interface AuditsRepository {
32
32
  /**
33
33
  * Transforms audit template content for the task file.
34
34
  * Changes the title from "# Original Title" to "# Audit: Original Title"
35
+ * and adds the Task Type section.
35
36
  */
36
37
  export declare function transformAuditContent(content: string): string;
37
38
  /**
package/dist/audits.js CHANGED
@@ -2497,7 +2497,30 @@ function transformAuditContent(content) {
2497
2497
  return content;
2498
2498
  }
2499
2499
  const originalTitle = titleMatch[1];
2500
- return content.replace(/^#\s+.+$/m, `# Audit: ${originalTitle}`);
2500
+ let transformed = content.replace(/^#\s+.+$/m, `# Audit: ${originalTitle}`);
2501
+ const lines = transformed.split(`
2502
+ `);
2503
+ let inCodeFence = false;
2504
+ let blockedByIndex = -1;
2505
+ for (let i = 0;i < lines.length; i++) {
2506
+ const line = lines[i];
2507
+ if (line.startsWith("```")) {
2508
+ inCodeFence = !inCodeFence;
2509
+ continue;
2510
+ }
2511
+ if (inCodeFence)
2512
+ continue;
2513
+ if (line === "## Blocked By") {
2514
+ blockedByIndex = i;
2515
+ break;
2516
+ }
2517
+ }
2518
+ if (blockedByIndex !== -1) {
2519
+ lines.splice(blockedByIndex, 0, "## Task Type", "", "implement", "");
2520
+ transformed = lines.join(`
2521
+ `);
2522
+ }
2523
+ return transformed;
2501
2524
  }
2502
2525
  function injectComment(content, comment) {
2503
2526
  const scopeMatch = content.match(/\n## Scope\n/);
@@ -5,8 +5,8 @@
5
5
  * for a single repository.
6
6
  */
7
7
  import type { EventMessage } from '../agent-events';
8
- import { type run as claudeRun, type RunnerDependencies } from '../claude/run';
9
- import type { OutputSink } from '../claude/types';
8
+ import { type RunnerDependencies } from '../claude/run';
9
+ import type { BoundRunFn, OutputSink } from '../claude/types';
10
10
  import { type LoopEmitFn } from '../loop/events';
11
11
  import type { SendAgentEventFn } from '../loop/wire-events';
12
12
  import { type RunnerDependencies as CodexRunnerDependencies, run as codexRun } from '../codex/run';
@@ -64,11 +64,11 @@ export declare function createStdoutSinkFactory(loopState: LoopState, logBuffer:
64
64
  /**
65
65
  * Create a run function that redirects Claude output to a log buffer.
66
66
  */
67
- export declare function createBufferRun(run: RepositoryDependencies['run'], bufferSinkDeps: RunnerDependencies): typeof claudeRun;
67
+ export declare function createBufferRun(run: RepositoryDependencies['run'], bufferSinkDeps: RunnerDependencies): BoundRunFn;
68
68
  /**
69
69
  * Create a run function that redirects Codex output to a log buffer.
70
70
  */
71
- export declare function createCodexBufferRun(run: typeof codexRun, codexBufferSinkDeps: CodexRunnerDependencies): typeof claudeRun;
71
+ export declare function createCodexBufferRun(run: typeof codexRun, codexBufferSinkDeps: CodexRunnerDependencies): BoundRunFn;
72
72
  /** No-op postEvent for LoopDependencies. */
73
73
  export declare function noOpPostEvent(): Promise<void>;
74
74
  /**
@@ -72,6 +72,16 @@ export interface RepositoryDependencies {
72
72
  /** Force Apple Container mode using bundled default Dockerfile */
73
73
  forceAppleContainer?: boolean;
74
74
  }
75
+ /**
76
+ * Handle loop completion: transition lifecycle and reset agent status.
77
+ * Extracted as a named function for testability.
78
+ */
79
+ export declare function handleLoopFinished(repoState: RepositoryState): void;
80
+ /**
81
+ * Create a cancel function for a running repository loop.
82
+ * Extracted as a named function for testability.
83
+ */
84
+ export declare function createLoopCancel(repoState: RepositoryState): () => void;
75
85
  /**
76
86
  * Start (or restart) the per-repository loop and keep lifecycle state accurate.
77
87
  */
@@ -1,15 +1,9 @@
1
- import { spawnClaudeCode as defaultSpawnClaudeCode } from './spawn-claude-code';
2
1
  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
- }
2
+ import type { RawEvent, RunOptions, SpawnOptions } from './types';
8
3
  export interface RunnerDependencies {
9
- spawnClaudeCode: typeof defaultSpawnClaudeCode;
4
+ spawnClaudeCode: (prompt: string, options: SpawnOptions) => AsyncGenerator<RawEvent>;
10
5
  createStdoutSink: typeof defaultCreateStdoutSink;
11
6
  streamEvents: typeof defaultStreamEvents;
12
7
  }
13
8
  export declare const defaultRunnerDependencies: RunnerDependencies;
14
- export declare function run(prompt: string, options?: SpawnOptions | RunOptions, dependencies?: RunnerDependencies): Promise<void>;
15
- export {};
9
+ export declare function run(prompt: string, options: SpawnOptions | RunOptions, dependencies: RunnerDependencies): Promise<void>;
@@ -21,4 +21,4 @@ export declare const defaultDependencies: EventSourceDependencies;
21
21
  * Build docker run arguments for spawning claude in a container.
22
22
  */
23
23
  export declare function buildDockerRunArguments(docker: DockerSpawnConfig, claudeArguments: string[], env: Record<string, string>): string[];
24
- export declare function spawnClaudeCode(prompt: string, options?: SpawnOptions, dependencies?: EventSourceDependencies): AsyncGenerator<RawEvent>;
24
+ export declare function spawnClaudeCode(prompt: string, options: SpawnOptions, dependencies: EventSourceDependencies): AsyncGenerator<RawEvent>;
@@ -79,3 +79,12 @@ export interface SpawnOptions {
79
79
  docker?: DockerSpawnConfig;
80
80
  }
81
81
  export type RawEventCallback = (event: RawEvent) => void;
82
+ export interface RunOptions {
83
+ spawnOptions?: SpawnOptions;
84
+ onRawEvent?: RawEventCallback;
85
+ }
86
+ /**
87
+ * A run function with its runner dependencies already bound.
88
+ * Used in LoopDependencies, AgentRunParams, etc.
89
+ */
90
+ export type BoundRunFn = (prompt: string, options: SpawnOptions | RunOptions) => Promise<void>;
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * Usage: dust focus "add login box"
8
8
  */
9
+ import type { TaskType } from '../../artifacts/workflow-tasks';
9
10
  import type { CommandDependencies, CommandResult } from '../types';
10
- export declare function buildImplementationInstructions(bin: string, hooksInstalled: boolean, taskTitle?: string, taskPath?: string, installCommand?: string, skipPreflightSteps?: boolean): string;
11
+ export declare function buildImplementationInstructions(bin: string, hooksInstalled: boolean, taskTitle?: string, taskPath?: string, installCommand?: string, skipPreflightSteps?: boolean, taskType?: TaskType): string;
11
12
  export declare function focus(dependencies: CommandDependencies): Promise<CommandResult>;
@@ -4,7 +4,7 @@
4
4
  * Provides common functionality used across all agent-* command files.
5
5
  */
6
6
  import { type AgentType } from '../../agents/detection';
7
- import type { CommandDependencies, DustSettings, ReadableFileSystem } from '../types';
7
+ import type { CommandDependencies, DustSettings, FileReader } from '../types';
8
8
  /**
9
9
  * Type-safe template variables for agent commands.
10
10
  * Uses real booleans instead of string-encoded booleans.
@@ -23,14 +23,14 @@ export interface TemplateVarsWithInstructions extends TemplateVars {
23
23
  * Loads agent-specific instructions from .dust/config/agents/{agent-type}.md
24
24
  * Returns empty string if file doesn't exist.
25
25
  */
26
- export declare function loadAgentInstructions(cwd: string, fileSystem: ReadableFileSystem, agentType: AgentType): Promise<string>;
27
- export declare function templateVariables(settings: DustSettings, hooksInstalled: boolean, env?: NodeJS.ProcessEnv, options?: {
26
+ export declare function loadAgentInstructions(cwd: string, fileSystem: FileReader, agentType: AgentType): Promise<string>;
27
+ export declare function templateVariables(settings: DustSettings, hooksInstalled: boolean, env: NodeJS.ProcessEnv, options?: {
28
28
  hasIdeaFile?: boolean;
29
29
  }): TemplateVars;
30
30
  /**
31
31
  * Creates template variables with agent-specific instructions loaded.
32
32
  */
33
- export declare function templateVariablesWithInstructions(cwd: string, fileSystem: ReadableFileSystem, settings: DustSettings, hooksInstalled: boolean, env?: NodeJS.ProcessEnv, options?: {
33
+ export declare function templateVariablesWithInstructions(cwd: string, fileSystem: FileReader, settings: DustSettings, hooksInstalled: boolean, env: NodeJS.ProcessEnv, options?: {
34
34
  hasIdeaFile?: boolean;
35
35
  }): Promise<TemplateVarsWithInstructions>;
36
36
  /**
@@ -4,7 +4,7 @@
4
4
  import type { CommandEvent } from '../command-events';
5
5
  import type { RuntimeConfig } from '../env-config';
6
6
  import type { FileSystem, GlobScanner } from '../filesystem/types';
7
- export type { FileSystem, GlobScanner, ReadableFileSystem, } from '../filesystem/types';
7
+ export type { FileReader, FileSystem, GlobScanner, ReadableFileSystem, } from '../filesystem/types';
8
8
  export interface CommandContext {
9
9
  cwd: string;
10
10
  stdout: (message: string) => void;
@@ -1,16 +1,10 @@
1
1
  import { createStdoutSink as defaultCreateStdoutSink } from '../claude/streamer';
2
- import type { RawEventCallback, SpawnOptions } from '../claude/types';
3
- import { spawnCodex as defaultSpawnCodex } from './spawn-codex';
2
+ import type { RawEvent, RunOptions, SpawnOptions } from '../claude/types';
4
3
  import { streamCodexEvents as defaultStreamCodexEvents } from './streamer';
5
- interface RunOptions {
6
- spawnOptions?: SpawnOptions;
7
- onRawEvent?: RawEventCallback;
8
- }
9
4
  export interface RunnerDependencies {
10
- spawnCodex: typeof defaultSpawnCodex;
5
+ spawnCodex: (prompt: string, options: SpawnOptions) => AsyncGenerator<RawEvent>;
11
6
  createStdoutSink: typeof defaultCreateStdoutSink;
12
7
  streamCodexEvents: typeof defaultStreamCodexEvents;
13
8
  }
14
9
  export declare const defaultRunnerDependencies: RunnerDependencies;
15
- export declare function run(prompt: string, options?: SpawnOptions | RunOptions, dependencies?: RunnerDependencies): Promise<void>;
16
- export {};
10
+ export declare function run(prompt: string, options: SpawnOptions | RunOptions, dependencies: RunnerDependencies): Promise<void>;
@@ -9,4 +9,4 @@ export declare const defaultDependencies: EventSourceDependencies;
9
9
  * Build docker run arguments for spawning codex in a container.
10
10
  */
11
11
  export declare function buildDockerRunArguments(docker: DockerSpawnConfig, codexArguments: string[], env: Record<string, string>): string[];
12
- export declare function spawnCodex(prompt: string, options?: SpawnOptions, dependencies?: EventSourceDependencies): AsyncGenerator<RawEvent>;
12
+ export declare function spawnCodex(prompt: string, options: SpawnOptions, dependencies: EventSourceDependencies): AsyncGenerator<RawEvent>;
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Reads optional configuration from .dust/config/settings.json
5
5
  */
6
- import type { CheckConfig, DustSettings, ReadableFileSystem } from '../cli/types';
6
+ import type { CheckConfig, DustSettings, FileReader } from '../cli/types';
7
7
  import type { RuntimeConfig } from '../env-config';
8
8
  export type { CheckConfig, DustSettings };
9
9
  interface SettingsViolation {
@@ -19,7 +19,7 @@ export declare function validateSettingsJson(content: string): SettingsViolation
19
19
  * 4. No lockfile + BUN_INSTALL env var set → bunx dust
20
20
  * 5. Default → npx dust
21
21
  */
22
- export declare function detectDustCommand(cwd: string, fileSystem: ReadableFileSystem, runtime: RuntimeConfig): string;
22
+ export declare function detectDustCommand(cwd: string, fileSystem: FileReader, runtime: RuntimeConfig): string;
23
23
  /**
24
24
  * Detects the appropriate install command based on lockfiles.
25
25
  * Returns null when:
@@ -28,7 +28,7 @@ export declare function detectDustCommand(cwd: string, fileSystem: ReadableFileS
28
28
  *
29
29
  * Priority within each ecosystem follows the order in LOCKFILE_COMMANDS.
30
30
  */
31
- export declare function detectInstallCommand(cwd: string, fileSystem: ReadableFileSystem): string | null;
31
+ export declare function detectInstallCommand(cwd: string, fileSystem: FileReader): string | null;
32
32
  /**
33
33
  * Detects the appropriate test command based on lockfiles and environment.
34
34
  * Priority:
@@ -40,5 +40,5 @@ export declare function detectInstallCommand(cwd: string, fileSystem: ReadableFi
40
40
  * 6. package.json exists → npm test
41
41
  * 7. Default → null (no test command)
42
42
  */
43
- export declare function detectTestCommand(cwd: string, fileSystem: ReadableFileSystem, runtime: RuntimeConfig): string | null;
44
- export declare function loadSettings(cwd: string, fileSystem: ReadableFileSystem, runtime: RuntimeConfig): Promise<DustSettings>;
43
+ export declare function detectTestCommand(cwd: string, fileSystem: FileReader, runtime: RuntimeConfig): string | null;
44
+ export declare function loadSettings(cwd: string, fileSystem: FileReader, runtime: RuntimeConfig): Promise<DustSettings>;
@@ -1,7 +1,7 @@
1
1
  // lib/core-principles.ts
2
2
  import { join, dirname } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { existsSync, readdirSync, statSync } from "node:fs";
4
+ import { existsSync, readdirSync } from "node:fs";
5
5
  import { readFile } from "node:fs/promises";
6
6
 
7
7
  // lib/markdown/markdown-utilities.ts
@@ -111,10 +111,7 @@ function getCorePrincipleTree(allPrinciples, config) {
111
111
  if (!parentSlug || !filteredSlugs.has(parentSlug)) {
112
112
  roots.push(node);
113
113
  } else {
114
- const parentNode = nodeBySlug.get(parentSlug);
115
- if (parentNode) {
116
- parentNode.children.push(node);
117
- }
114
+ nodeBySlug.get(parentSlug).children.push(node);
118
115
  }
119
116
  }
120
117
  sortNodes(roots);
@@ -122,18 +119,10 @@ function getCorePrincipleTree(allPrinciples, config) {
122
119
  }
123
120
 
124
121
  // lib/core-principles.ts
125
- function createReadableFileSystem() {
122
+ function createFileReader() {
126
123
  return {
127
124
  exists: existsSync,
128
- isDirectory: (path) => {
129
- try {
130
- return statSync(path).isDirectory();
131
- } catch {
132
- return false;
133
- }
134
- },
135
- readFile: (path) => readFile(path, "utf-8"),
136
- readdir: async (path) => readdirSync(path)
125
+ readFile: (path) => readFile(path, "utf-8")
137
126
  };
138
127
  }
139
128
  function locatePackagePrinciplesDir() {
@@ -150,7 +139,7 @@ async function readAllCorePrinciples() {
150
139
  const principlesDir = locatePackagePrinciplesDir();
151
140
  const packageRoot = dirname(dirname(principlesDir));
152
141
  const dustPath = join(packageRoot, ".dust");
153
- const fileSystem = createReadableFileSystem();
142
+ const fileSystem = createFileReader();
154
143
  const files = readdirSync(principlesDir);
155
144
  const mdFiles = files.filter((f) => f.endsWith(".md"));
156
145
  const principles = [];
@@ -23,10 +23,6 @@ export declare function buildDockerImage(config: {
23
23
  success: false;
24
24
  error: string;
25
25
  }>;
26
- /**
27
- * Check if a Dockerfile exists at the legacy .dust/Dockerfile location.
28
- */
29
- export declare function hasLegacyDockerfile(repoPath: string, dependencies: ContainerDependencies): boolean;
30
26
  type ContainerPrepareEvent = {
31
27
  type: 'loop.docker_detected';
32
28
  imageTag: string;