@joshski/dust 0.1.103 → 0.1.105

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 (38) 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 +3 -3
  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 +8 -2
  9. package/dist/artifacts.js +128 -36
  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 +374 -426
  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/task.d.ts +2 -1
  34. package/dist/patch.js +64 -133
  35. package/dist/proxy/helper-token.d.ts +2 -2
  36. package/dist/types.d.ts +1 -1
  37. package/dist/validation.js +39 -115
  38. 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,11 +5,11 @@ 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 } from './workflow-tasks';
9
+ export type { AllWorkflowTasks, CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedArtifact, ParsedCaptureIdeaTask, ParsedIdeaContent, ParsedMarkdownLink, ParsedSection, Principle, Task, TaskType, WorkflowTaskMatch, };
10
10
  export interface TaskGraphNode {
11
11
  task: Task;
12
- workflowType: WorkflowTaskType | null;
12
+ workflowType: TaskType | null;
13
13
  }
14
14
  export interface TaskGraph {
15
15
  nodes: TaskGraphNode[];
@@ -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,9 +21,15 @@ export interface ParsedCaptureIdeaTask {
21
21
  * 6. Add .md extension
22
22
  */
23
23
  export declare function titleToFilename(title: string): string;
24
- export type WorkflowTaskType = 'refine-idea' | 'decompose-idea' | 'shelve-idea' | 'expedite-idea';
24
+ export declare const VALID_TASK_TYPES: readonly ["implement", "capture", "refine", "decompose", "shelve"];
25
+ export type TaskType = (typeof VALID_TASK_TYPES)[number];
26
+ /**
27
+ * Extracts and validates the task type from the ## Task Type section.
28
+ * Returns the task type if found and valid, null otherwise.
29
+ */
30
+ export declare function parseTaskType(content: string): TaskType | null;
25
31
  export interface WorkflowTaskMatch {
26
- type: WorkflowTaskType;
32
+ type: TaskType;
27
33
  ideaSlug: string;
28
34
  taskSlug: string;
29
35
  resolvedQuestions: OpenQuestionResponse[];
package/dist/artifacts.js CHANGED
@@ -522,25 +522,59 @@ 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
+ }
525
559
  var WORKFLOW_HINT_PATHS = {
526
- "refine-idea": "config/hints/refine-idea.md",
527
- "decompose-idea": "config/hints/decompose-idea.md",
528
- "shelve-idea": "config/hints/shelve-idea.md",
529
- "add-idea": "config/hints/add-idea.md",
530
- "expedite-idea": "config/hints/expedite-idea.md"
560
+ refine: "config/hints/refine-idea.md",
561
+ decompose: "config/hints/decompose-idea.md",
562
+ shelve: "config/hints/shelve-idea.md",
563
+ capture: "config/hints/add-idea.md",
564
+ implement: "config/hints/expedite-idea.md"
531
565
  };
532
- async function readWorkflowHint(fileSystem, dustPath, workflowType) {
533
- const hintPath = `${dustPath}/${WORKFLOW_HINT_PATHS[workflowType]}`;
566
+ async function readWorkflowHint(fileSystem, dustPath, taskType) {
567
+ const hintPath = `${dustPath}/${WORKFLOW_HINT_PATHS[taskType]}`;
534
568
  if (!fileSystem.exists(hintPath)) {
535
569
  return null;
536
570
  }
537
571
  return fileSystem.readFile(hintPath);
538
572
  }
539
573
  var WORKFLOW_SECTION_HEADINGS = [
540
- { type: "refine-idea", heading: "Refines Idea" },
541
- { type: "decompose-idea", heading: "Decomposes Idea" },
542
- { type: "shelve-idea", heading: "Shelves Idea" },
543
- { type: "expedite-idea", heading: "Expedites Idea" }
574
+ { type: "refine", heading: "Refines Idea" },
575
+ { type: "decompose", heading: "Decomposes Idea" },
576
+ { type: "shelve", heading: "Shelves Idea" },
577
+ { type: "implement", heading: "Expedites Idea" }
544
578
  ];
545
579
  function extractIdeaSlugFromSection(content, sectionHeading) {
546
580
  const lines = content.split(`
@@ -588,16 +622,32 @@ async function findAllWorkflowTasks(fileSystem, dustPath) {
588
622
  continue;
589
623
  const title = titleMatch[1].trim();
590
624
  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
- });
625
+ const taskType = parseTaskType(content);
626
+ if (taskType === "capture" || taskType === "implement") {
627
+ let ideaTitle = null;
628
+ if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
629
+ ideaTitle = title.slice(CAPTURE_IDEA_PREFIX.length);
630
+ } else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
631
+ ideaTitle = title.slice(EXPEDITE_IDEA_PREFIX.length);
632
+ }
633
+ if (ideaTitle) {
634
+ captureIdeaTasks.push({
635
+ taskSlug,
636
+ ideaTitle
637
+ });
638
+ }
639
+ } else if (!taskType) {
640
+ if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
641
+ captureIdeaTasks.push({
642
+ taskSlug,
643
+ ideaTitle: title.slice(CAPTURE_IDEA_PREFIX.length)
644
+ });
645
+ } else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
646
+ captureIdeaTasks.push({
647
+ taskSlug,
648
+ ideaTitle: title.slice(EXPEDITE_IDEA_PREFIX.length)
649
+ });
650
+ }
601
651
  }
602
652
  for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
603
653
  const linkedSlug = extractIdeaSlugFromSection(content, heading);
@@ -625,12 +675,23 @@ async function findWorkflowTaskForIdea(fileSystem, dustPath, ideaSlug) {
625
675
  const files = await fileSystem.readdir(tasksPath);
626
676
  for (const file of files.filter((f) => f.endsWith(".md")).toSorted()) {
627
677
  const content = await fileSystem.readFile(`${tasksPath}/${file}`);
628
- for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
678
+ const taskSlug = file.replace(/\.md$/, "");
679
+ const match = findWorkflowMatch(content, ideaSlug, taskSlug);
680
+ if (match) {
681
+ return match;
682
+ }
683
+ }
684
+ return null;
685
+ }
686
+ function findWorkflowMatch(content, ideaSlug, taskSlug) {
687
+ const taskType = parseTaskType(content);
688
+ if (taskType) {
689
+ const heading = WORKFLOW_SECTION_HEADINGS.find((h) => h.type === taskType)?.heading;
690
+ if (heading) {
629
691
  const linkedSlug = extractIdeaSlugFromSection(content, heading);
630
692
  if (linkedSlug === ideaSlug) {
631
- const taskSlug = file.replace(/\.md$/, "");
632
693
  return {
633
- type,
694
+ type: taskType,
634
695
  ideaSlug,
635
696
  taskSlug,
636
697
  resolvedQuestions: parseResolvedQuestions(content)
@@ -638,6 +699,17 @@ async function findWorkflowTaskForIdea(fileSystem, dustPath, ideaSlug) {
638
699
  }
639
700
  }
640
701
  }
702
+ for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
703
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
704
+ if (linkedSlug === ideaSlug) {
705
+ return {
706
+ type,
707
+ ideaSlug,
708
+ taskSlug,
709
+ resolvedQuestions: parseResolvedQuestions(content)
710
+ };
711
+ }
712
+ }
641
713
  return null;
642
714
  }
643
715
  async function readIdeaTitle(fileSystem, dustPath, ideaSlug) {
@@ -719,7 +791,7 @@ function renderRepositoryHintsSection(repositoryHint) {
719
791
  ${repositoryHint}
720
792
  `;
721
793
  }
722
- function renderTask(title, openingSentence, definitionOfDone, ideaSection, options) {
794
+ function renderTask(title, openingSentence, definitionOfDone, ideaSection, taskType, options) {
723
795
  const descriptionParagraph = options?.description !== undefined ? `
724
796
  ${options.description}
725
797
  ` : "";
@@ -733,7 +805,12 @@ ${renderIdeaSection(ideaSection)}
733
805
  return `# ${title}
734
806
 
735
807
  ${openingSentence}
736
- ${descriptionParagraph}${resolvedSection}${ideaSectionContent}## Blocked By
808
+ ${descriptionParagraph}${resolvedSection}${ideaSectionContent}
809
+ ## Task Type
810
+
811
+ ${taskType}
812
+
813
+ ## Blocked By
737
814
 
738
815
  (none)
739
816
  ${repositoryHintsSection}
@@ -744,15 +821,15 @@ ${definitionOfDone.map((item) => `- ${item}`).join(`
744
821
  `)}
745
822
  `;
746
823
  }
747
- async function createIdeaTransitionTask(fileSystem, dustPath, workflowType, prefix, ideaSlug, openingSentenceTemplate, definitionOfDone, ideaSectionHeading, taskOptions) {
824
+ async function createIdeaTransitionTask(fileSystem, dustPath, taskType, prefix, ideaSlug, openingSentenceTemplate, definitionOfDone, ideaSectionHeading, taskOptions) {
748
825
  const ideaTitle = await readIdeaTitle(fileSystem, dustPath, ideaSlug);
749
826
  const taskTitle = `${prefix}${ideaTitle}`;
750
827
  const filename = titleToFilename(taskTitle);
751
828
  const filePath = `${dustPath}/tasks/${filename}`;
752
829
  const baseOpeningSentence = openingSentenceTemplate(ideaTitle);
753
- const hint = await readWorkflowHint(fileSystem, dustPath, workflowType);
830
+ const hint = await readWorkflowHint(fileSystem, dustPath, taskType);
754
831
  const ideaSection = { heading: ideaSectionHeading, ideaTitle, ideaSlug };
755
- const content = renderTask(taskTitle, baseOpeningSentence, definitionOfDone, ideaSection, {
832
+ const content = renderTask(taskTitle, baseOpeningSentence, definitionOfDone, ideaSection, taskType, {
756
833
  description: taskOptions?.description,
757
834
  resolvedQuestions: taskOptions?.resolvedQuestions,
758
835
  repositoryHint: hint ?? undefined
@@ -762,7 +839,7 @@ async function createIdeaTransitionTask(fileSystem, dustPath, workflowType, pref
762
839
  }
763
840
  async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description, openQuestionResponses, dustCommand) {
764
841
  const cmd = dustCommand ?? "dust";
765
- return createIdeaTransitionTask(fileSystem, dustPath, "refine-idea", "Refine Idea: ", ideaSlug, (ideaTitle) => `Thoroughly research this idea and refine it into a well-defined proposal. Read the idea file, explore the codebase for relevant context, and identify any ambiguity. Where aspects are unclear or could go multiple ways, add open questions to the idea file. Run \`${cmd} principles\` for alignment and \`${cmd} facts\` for relevant design decisions. See [${ideaTitle}](../ideas/${ideaSlug}.md). 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.`, [
842
+ return createIdeaTransitionTask(fileSystem, dustPath, "refine", "Refine Idea: ", ideaSlug, (ideaTitle) => `Thoroughly research this idea and refine it into a well-defined proposal. Read the idea file, explore the codebase for relevant context, and identify any ambiguity. Where aspects are unclear or could go multiple ways, add open questions to the idea file. Run \`${cmd} principles\` for alignment and \`${cmd} facts\` for relevant design decisions. See [${ideaTitle}](../ideas/${ideaSlug}.md). 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.`, [
766
843
  "Idea is thoroughly researched with relevant codebase context",
767
844
  "Open questions are added for any ambiguous or underspecified aspects",
768
845
  "Open questions follow the required heading format and focus on high-value decisions",
@@ -774,7 +851,7 @@ async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description,
774
851
  }
775
852
  async function decomposeIdea(fileSystem, dustPath, options, dustCommand) {
776
853
  const cmd = dustCommand ?? "dust";
777
- return createIdeaTransitionTask(fileSystem, dustPath, "decompose-idea", "Decompose Idea: ", options.ideaSlug, (ideaTitle) => `Create one or more well-defined tasks from this idea. Prefer smaller, narrowly scoped tasks that each deliver a thin but complete vertical slice of working software -- a path through the system that can be tested end-to-end -- rather than component-oriented tasks (like "add schema" or "build endpoint") that only work once all tasks are done. Split the idea into multiple tasks if it covers more than one logical change. Run \`${cmd} principles\` to link relevant principles and \`${cmd} facts\` for design decisions that should inform the task. See [${ideaTitle}](../ideas/${options.ideaSlug}.md).`, [
854
+ return createIdeaTransitionTask(fileSystem, dustPath, "decompose", "Decompose Idea: ", options.ideaSlug, (ideaTitle) => `Create one or more well-defined tasks from this idea. Prefer smaller, narrowly scoped tasks that each deliver a thin but complete vertical slice of working software -- a path through the system that can be tested end-to-end -- rather than component-oriented tasks (like "add schema" or "build endpoint") that only work once all tasks are done. Split the idea into multiple tasks if it covers more than one logical change. Run \`${cmd} principles\` to link relevant principles and \`${cmd} facts\` for design decisions that should inform the task. See [${ideaTitle}](../ideas/${options.ideaSlug}.md).`, [
778
855
  "One or more new tasks are created in .dust/tasks/",
779
856
  "Task's Principles section links to relevant principles from .dust/principles/",
780
857
  "The original idea is deleted or updated to reflect remaining scope"
@@ -784,11 +861,11 @@ async function decomposeIdea(fileSystem, dustPath, options, dustCommand) {
784
861
  });
785
862
  }
786
863
  async function createShelveIdeaTask(fileSystem, dustPath, ideaSlug, description, _dustCommand) {
787
- return createIdeaTransitionTask(fileSystem, dustPath, "shelve-idea", "Shelve Idea: ", ideaSlug, (ideaTitle) => `Archive this idea and remove it from the active backlog. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, ["Idea file is deleted", "Rationale is recorded in the commit message"], "Shelves Idea", { description });
864
+ return createIdeaTransitionTask(fileSystem, dustPath, "shelve", "Shelve Idea: ", ideaSlug, (ideaTitle) => `Archive this idea and remove it from the active backlog. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, ["Idea file is deleted", "Rationale is recorded in the commit message"], "Shelves Idea", { description });
788
865
  }
789
866
  async function createExpediteIdeaTask(fileSystem, dustPath, ideaSlug, description, dustCommand) {
790
867
  const cmd = dustCommand ?? "dust";
791
- return createIdeaTransitionTask(fileSystem, dustPath, "expedite-idea", "Expedite Idea: ", ideaSlug, (ideaTitle) => `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. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, [
868
+ return createIdeaTransitionTask(fileSystem, dustPath, "implement", "Expedite Idea: ", ideaSlug, (ideaTitle) => `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. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, [
792
869
  "Idea is implemented directly OR one or more new tasks are created in `.dust/tasks/`",
793
870
  "If tasks were created, they link to relevant principles from `.dust/principles/`",
794
871
  "Changes are committed with a clear commit message"
@@ -808,7 +885,7 @@ async function createIdeaTask(fileSystem, dustPath, options) {
808
885
  const filename2 = titleToFilename(taskTitle2);
809
886
  const filePath2 = `${dustPath}/tasks/${filename2}`;
810
887
  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.`;
811
- const hint2 = await readWorkflowHint(fileSystem, dustPath, "expedite-idea");
888
+ const hint2 = await readWorkflowHint(fileSystem, dustPath, "implement");
812
889
  const repositoryHintsSection2 = renderRepositoryHintsSection(hint2 ?? undefined);
813
890
  const content2 = `# ${taskTitle2}
814
891
 
@@ -818,6 +895,10 @@ ${baseOpeningSentence2}
818
895
 
819
896
  ${description}
820
897
 
898
+ ## Task Type
899
+
900
+ implement
901
+
821
902
  ## Blocked By
822
903
 
823
904
  (none)
@@ -836,7 +917,7 @@ ${repositoryHintsSection2}
836
917
  const filename = titleToFilename(taskTitle);
837
918
  const filePath = `${dustPath}/tasks/${filename}`;
838
919
  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.`;
839
- const hint = await readWorkflowHint(fileSystem, dustPath, "add-idea");
920
+ const hint = await readWorkflowHint(fileSystem, dustPath, "capture");
840
921
  const repositoryHintsSection = renderRepositoryHintsSection(hint ?? undefined);
841
922
  const content = `# ${taskTitle}
842
923
 
@@ -846,6 +927,10 @@ ${baseOpeningSentence}
846
927
 
847
928
  ${description}
848
929
 
930
+ ## Task Type
931
+
932
+ capture
933
+
849
934
  ## Blocked By
850
935
 
851
936
  (none)
@@ -873,9 +958,16 @@ async function parseCaptureIdeaTask(fileSystem, dustPath, taskSlug) {
873
958
  return null;
874
959
  }
875
960
  const title = titleMatch[1].trim();
961
+ const taskType = parseTaskType(content);
876
962
  let ideaTitle;
877
963
  let expedite;
878
- if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
964
+ if (taskType === "implement") {
965
+ expedite = true;
966
+ ideaTitle = title.startsWith(EXPEDITE_IDEA_PREFIX) ? title.slice(EXPEDITE_IDEA_PREFIX.length) : title;
967
+ } else if (taskType === "capture") {
968
+ expedite = false;
969
+ ideaTitle = title.startsWith(CAPTURE_IDEA_PREFIX) ? title.slice(CAPTURE_IDEA_PREFIX.length) : title;
970
+ } else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
879
971
  ideaTitle = title.slice(EXPEDITE_IDEA_PREFIX.length);
880
972
  expedite = true;
881
973
  } 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>;