@joshski/dust 0.1.104 → 0.1.106

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.
@@ -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 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, };
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[];
@@ -21,15 +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 TaskType = 'implement' | 'capture' | 'refine' | 'decompose' | 'shelve';
24
+ export declare const VALID_TASK_TYPES: readonly ["implement", "capture", "refine", "decompose", "shelve"];
25
+ export type TaskType = (typeof VALID_TASK_TYPES)[number];
25
26
  /**
26
27
  * Extracts and validates the task type from the ## Task Type section.
27
28
  * Returns the task type if found and valid, null otherwise.
28
29
  */
29
30
  export declare function parseTaskType(content: string): TaskType | null;
30
- export type WorkflowTaskType = 'refine-idea' | 'decompose-idea' | 'shelve-idea' | 'expedite-idea';
31
31
  export interface WorkflowTaskMatch {
32
- type: WorkflowTaskType;
32
+ type: TaskType;
33
33
  ideaSlug: string;
34
34
  taskSlug: string;
35
35
  resolvedQuestions: OpenQuestionResponse[];
package/dist/artifacts.js CHANGED
@@ -556,39 +556,25 @@ function parseTaskType(content) {
556
556
  }
557
557
  return null;
558
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
- }
573
559
  var WORKFLOW_HINT_PATHS = {
574
- "refine-idea": "config/hints/refine-idea.md",
575
- "decompose-idea": "config/hints/decompose-idea.md",
576
- "shelve-idea": "config/hints/shelve-idea.md",
577
- "add-idea": "config/hints/add-idea.md",
578
- "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"
579
565
  };
580
- async function readWorkflowHint(fileSystem, dustPath, workflowType) {
581
- const hintPath = `${dustPath}/${WORKFLOW_HINT_PATHS[workflowType]}`;
566
+ async function readWorkflowHint(fileSystem, dustPath, taskType) {
567
+ const hintPath = `${dustPath}/${WORKFLOW_HINT_PATHS[taskType]}`;
582
568
  if (!fileSystem.exists(hintPath)) {
583
569
  return null;
584
570
  }
585
571
  return fileSystem.readFile(hintPath);
586
572
  }
587
573
  var WORKFLOW_SECTION_HEADINGS = [
588
- { type: "refine-idea", heading: "Refines Idea" },
589
- { type: "decompose-idea", heading: "Decomposes Idea" },
590
- { type: "shelve-idea", heading: "Shelves Idea" },
591
- { 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" }
592
578
  ];
593
579
  function extractIdeaSlugFromSection(content, sectionHeading) {
594
580
  const lines = content.split(`
@@ -663,15 +649,30 @@ async function findAllWorkflowTasks(fileSystem, dustPath) {
663
649
  });
664
650
  }
665
651
  }
666
- for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
667
- const linkedSlug = extractIdeaSlugFromSection(content, heading);
668
- if (linkedSlug) {
669
- workflowTasksByIdeaSlug.set(linkedSlug, {
670
- type,
671
- ideaSlug: linkedSlug,
672
- taskSlug,
673
- resolvedQuestions: parseResolvedQuestions(content)
674
- });
652
+ if (taskType) {
653
+ const heading = WORKFLOW_SECTION_HEADINGS.find((h) => h.type === taskType)?.heading;
654
+ if (heading) {
655
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
656
+ if (linkedSlug) {
657
+ workflowTasksByIdeaSlug.set(linkedSlug, {
658
+ type: taskType,
659
+ ideaSlug: linkedSlug,
660
+ taskSlug,
661
+ resolvedQuestions: parseResolvedQuestions(content)
662
+ });
663
+ }
664
+ }
665
+ } else {
666
+ for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
667
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
668
+ if (linkedSlug) {
669
+ workflowTasksByIdeaSlug.set(linkedSlug, {
670
+ type,
671
+ ideaSlug: linkedSlug,
672
+ taskSlug,
673
+ resolvedQuestions: parseResolvedQuestions(content)
674
+ });
675
+ }
675
676
  }
676
677
  }
677
678
  }
@@ -700,11 +701,17 @@ async function findWorkflowTaskForIdea(fileSystem, dustPath, ideaSlug) {
700
701
  function findWorkflowMatch(content, ideaSlug, taskSlug) {
701
702
  const taskType = parseTaskType(content);
702
703
  if (taskType) {
703
- const workflowType = taskTypeToWorkflowType(taskType);
704
- if (workflowType) {
705
- const match = findWorkflowMatchByType(content, ideaSlug, taskSlug, workflowType);
706
- if (match)
707
- return match;
704
+ const heading = WORKFLOW_SECTION_HEADINGS.find((h) => h.type === taskType)?.heading;
705
+ if (heading) {
706
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
707
+ if (linkedSlug === ideaSlug) {
708
+ return {
709
+ type: taskType,
710
+ ideaSlug,
711
+ taskSlug,
712
+ resolvedQuestions: parseResolvedQuestions(content)
713
+ };
714
+ }
708
715
  }
709
716
  }
710
717
  for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
@@ -720,22 +727,6 @@ function findWorkflowMatch(content, ideaSlug, taskSlug) {
720
727
  }
721
728
  return null;
722
729
  }
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
- };
735
- }
736
- }
737
- return null;
738
- }
739
730
  async function readIdeaTitle(fileSystem, dustPath, ideaSlug) {
740
731
  const ideaPath = `${dustPath}/ideas/${ideaSlug}.md`;
741
732
  if (!fileSystem.exists(ideaPath)) {
@@ -845,15 +836,14 @@ ${definitionOfDone.map((item) => `- ${item}`).join(`
845
836
  `)}
846
837
  `;
847
838
  }
848
- async function createIdeaTransitionTask(fileSystem, dustPath, workflowType, prefix, ideaSlug, openingSentenceTemplate, definitionOfDone, ideaSectionHeading, taskOptions) {
839
+ async function createIdeaTransitionTask(fileSystem, dustPath, taskType, prefix, ideaSlug, openingSentenceTemplate, definitionOfDone, ideaSectionHeading, taskOptions) {
849
840
  const ideaTitle = await readIdeaTitle(fileSystem, dustPath, ideaSlug);
850
841
  const taskTitle = `${prefix}${ideaTitle}`;
851
842
  const filename = titleToFilename(taskTitle);
852
843
  const filePath = `${dustPath}/tasks/${filename}`;
853
844
  const baseOpeningSentence = openingSentenceTemplate(ideaTitle);
854
- const hint = await readWorkflowHint(fileSystem, dustPath, workflowType);
845
+ const hint = await readWorkflowHint(fileSystem, dustPath, taskType);
855
846
  const ideaSection = { heading: ideaSectionHeading, ideaTitle, ideaSlug };
856
- const taskType = workflowType === "refine-idea" ? "refine" : workflowType === "decompose-idea" ? "decompose" : workflowType === "shelve-idea" ? "shelve" : "implement";
857
847
  const content = renderTask(taskTitle, baseOpeningSentence, definitionOfDone, ideaSection, taskType, {
858
848
  description: taskOptions?.description,
859
849
  resolvedQuestions: taskOptions?.resolvedQuestions,
@@ -864,7 +854,7 @@ async function createIdeaTransitionTask(fileSystem, dustPath, workflowType, pref
864
854
  }
865
855
  async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description, openQuestionResponses, dustCommand) {
866
856
  const cmd = dustCommand ?? "dust";
867
- 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.`, [
857
+ 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.`, [
868
858
  "Idea is thoroughly researched with relevant codebase context",
869
859
  "Open questions are added for any ambiguous or underspecified aspects",
870
860
  "Open questions follow the required heading format and focus on high-value decisions",
@@ -876,7 +866,7 @@ async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description,
876
866
  }
877
867
  async function decomposeIdea(fileSystem, dustPath, options, dustCommand) {
878
868
  const cmd = dustCommand ?? "dust";
879
- 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).`, [
869
+ 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).`, [
880
870
  "One or more new tasks are created in .dust/tasks/",
881
871
  "Task's Principles section links to relevant principles from .dust/principles/",
882
872
  "The original idea is deleted or updated to reflect remaining scope"
@@ -886,11 +876,11 @@ async function decomposeIdea(fileSystem, dustPath, options, dustCommand) {
886
876
  });
887
877
  }
888
878
  async function createShelveIdeaTask(fileSystem, dustPath, ideaSlug, description, _dustCommand) {
889
- 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 });
879
+ 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 });
890
880
  }
891
881
  async function createExpediteIdeaTask(fileSystem, dustPath, ideaSlug, description, dustCommand) {
892
882
  const cmd = dustCommand ?? "dust";
893
- 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).`, [
883
+ 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).`, [
894
884
  "Idea is implemented directly OR one or more new tasks are created in `.dust/tasks/`",
895
885
  "If tasks were created, they link to relevant principles from `.dust/principles/`",
896
886
  "Changes are committed with a clear commit message"
@@ -910,7 +900,7 @@ async function createIdeaTask(fileSystem, dustPath, options) {
910
900
  const filename2 = titleToFilename(taskTitle2);
911
901
  const filePath2 = `${dustPath}/tasks/${filename2}`;
912
902
  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.`;
913
- const hint2 = await readWorkflowHint(fileSystem, dustPath, "expedite-idea");
903
+ const hint2 = await readWorkflowHint(fileSystem, dustPath, "implement");
914
904
  const repositoryHintsSection2 = renderRepositoryHintsSection(hint2 ?? undefined);
915
905
  const content2 = `# ${taskTitle2}
916
906
 
@@ -942,7 +932,7 @@ ${repositoryHintsSection2}
942
932
  const filename = titleToFilename(taskTitle);
943
933
  const filePath = `${dustPath}/tasks/${filename}`;
944
934
  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.`;
945
- const hint = await readWorkflowHint(fileSystem, dustPath, "add-idea");
935
+ const hint = await readWorkflowHint(fileSystem, dustPath, "capture");
946
936
  const repositoryHintsSection = renderRepositoryHintsSection(hint ?? undefined);
947
937
  const content = `# ${taskTitle}
948
938
 
package/dist/dust.js CHANGED
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
7
7
  var require_package = __commonJS((exports, module) => {
8
8
  module.exports = {
9
9
  name: "@joshski/dust",
10
- version: "0.1.104",
10
+ version: "0.1.106",
11
11
  description: "Flow state for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
@@ -712,7 +712,7 @@ async function loadSettings(cwd, fileSystem, runtime) {
712
712
  }
713
713
 
714
714
  // lib/version.ts
715
- var DUST_VERSION = "0.1.104";
715
+ var DUST_VERSION = "0.1.106";
716
716
 
717
717
  // lib/cli/middleware.ts
718
718
  function applyMiddleware(middlewares, execute) {
@@ -5153,15 +5153,198 @@ function extractFirstSentence2(paragraph) {
5153
5153
  return match ? match[1] : null;
5154
5154
  }
5155
5155
 
5156
- // lib/lint/validators/content-validator.ts
5157
- var REQUIRED_TASK_HEADINGS = ["Task Type", "Blocked By", "Definition of Done"];
5158
- var ALLOWED_TASK_TYPES = new Set([
5156
+ // lib/artifacts/workflow-tasks.ts
5157
+ var CAPTURE_IDEA_PREFIX = "Add Idea: ";
5158
+ var EXPEDITE_IDEA_PREFIX = "Expedite Idea: ";
5159
+ function titleToFilename(title) {
5160
+ return `${title.toLowerCase().replace(/\./g, "-").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}.md`;
5161
+ }
5162
+ var VALID_TASK_TYPES = [
5159
5163
  "implement",
5160
5164
  "capture",
5161
5165
  "refine",
5162
5166
  "decompose",
5163
5167
  "shelve"
5164
- ]);
5168
+ ];
5169
+ function parseTaskType(content) {
5170
+ const lines = content.split(`
5171
+ `);
5172
+ let inSection = false;
5173
+ let inCodeFence = false;
5174
+ for (const line of lines) {
5175
+ if (line.startsWith("```")) {
5176
+ inCodeFence = !inCodeFence;
5177
+ continue;
5178
+ }
5179
+ if (inCodeFence)
5180
+ continue;
5181
+ if (line.startsWith("## ")) {
5182
+ inSection = line.trimEnd() === "## Task Type";
5183
+ continue;
5184
+ }
5185
+ if (!inSection)
5186
+ continue;
5187
+ if (line.startsWith("# "))
5188
+ break;
5189
+ const trimmed = line.trim();
5190
+ if (trimmed && VALID_TASK_TYPES.includes(trimmed)) {
5191
+ return trimmed;
5192
+ }
5193
+ }
5194
+ return null;
5195
+ }
5196
+ var WORKFLOW_SECTION_HEADINGS = [
5197
+ { type: "refine", heading: "Refines Idea" },
5198
+ { type: "decompose", heading: "Decomposes Idea" },
5199
+ { type: "shelve", heading: "Shelves Idea" },
5200
+ { type: "implement", heading: "Expedites Idea" }
5201
+ ];
5202
+ function extractIdeaSlugFromSection(content, sectionHeading) {
5203
+ const lines = content.split(`
5204
+ `);
5205
+ let inSection = false;
5206
+ let inCodeFence = false;
5207
+ for (const line of lines) {
5208
+ if (line.startsWith("```")) {
5209
+ inCodeFence = !inCodeFence;
5210
+ continue;
5211
+ }
5212
+ if (inCodeFence)
5213
+ continue;
5214
+ if (line.startsWith("## ")) {
5215
+ inSection = line.trimEnd() === `## ${sectionHeading}`;
5216
+ continue;
5217
+ }
5218
+ if (!inSection)
5219
+ continue;
5220
+ if (line.startsWith("# "))
5221
+ break;
5222
+ const linkMatch = line.match(MARKDOWN_LINK_PATTERN);
5223
+ if (linkMatch) {
5224
+ const target = linkMatch[2];
5225
+ const slugMatch = target.match(/([^/]+)\.md$/);
5226
+ if (slugMatch) {
5227
+ return slugMatch[1];
5228
+ }
5229
+ }
5230
+ }
5231
+ return null;
5232
+ }
5233
+ async function findAllWorkflowTasks(fileSystem, dustPath) {
5234
+ const tasksPath = `${dustPath}/tasks`;
5235
+ const captureIdeaTasks = [];
5236
+ const workflowTasksByIdeaSlug = new Map;
5237
+ if (!fileSystem.exists(tasksPath)) {
5238
+ return { captureIdeaTasks, workflowTasksByIdeaSlug };
5239
+ }
5240
+ const files = await fileSystem.readdir(tasksPath);
5241
+ for (const file of files.filter((f) => f.endsWith(".md")).toSorted()) {
5242
+ const content = await fileSystem.readFile(`${tasksPath}/${file}`);
5243
+ const titleMatch = content.match(/^#\s+(.+)$/m);
5244
+ if (!titleMatch)
5245
+ continue;
5246
+ const title = titleMatch[1].trim();
5247
+ const taskSlug = file.replace(/\.md$/, "");
5248
+ const taskType = parseTaskType(content);
5249
+ if (taskType === "capture" || taskType === "implement") {
5250
+ let ideaTitle = null;
5251
+ if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
5252
+ ideaTitle = title.slice(CAPTURE_IDEA_PREFIX.length);
5253
+ } else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
5254
+ ideaTitle = title.slice(EXPEDITE_IDEA_PREFIX.length);
5255
+ }
5256
+ if (ideaTitle) {
5257
+ captureIdeaTasks.push({
5258
+ taskSlug,
5259
+ ideaTitle
5260
+ });
5261
+ }
5262
+ } else if (!taskType) {
5263
+ if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
5264
+ captureIdeaTasks.push({
5265
+ taskSlug,
5266
+ ideaTitle: title.slice(CAPTURE_IDEA_PREFIX.length)
5267
+ });
5268
+ } else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
5269
+ captureIdeaTasks.push({
5270
+ taskSlug,
5271
+ ideaTitle: title.slice(EXPEDITE_IDEA_PREFIX.length)
5272
+ });
5273
+ }
5274
+ }
5275
+ if (taskType) {
5276
+ const heading = WORKFLOW_SECTION_HEADINGS.find((h) => h.type === taskType)?.heading;
5277
+ if (heading) {
5278
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
5279
+ if (linkedSlug) {
5280
+ workflowTasksByIdeaSlug.set(linkedSlug, {
5281
+ type: taskType,
5282
+ ideaSlug: linkedSlug,
5283
+ taskSlug,
5284
+ resolvedQuestions: parseResolvedQuestions(content)
5285
+ });
5286
+ }
5287
+ }
5288
+ } else {
5289
+ for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
5290
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
5291
+ if (linkedSlug) {
5292
+ workflowTasksByIdeaSlug.set(linkedSlug, {
5293
+ type,
5294
+ ideaSlug: linkedSlug,
5295
+ taskSlug,
5296
+ resolvedQuestions: parseResolvedQuestions(content)
5297
+ });
5298
+ }
5299
+ }
5300
+ }
5301
+ }
5302
+ return { captureIdeaTasks, workflowTasksByIdeaSlug };
5303
+ }
5304
+ function parseResolvedQuestions(content) {
5305
+ const lines = content.split(`
5306
+ `);
5307
+ const results = [];
5308
+ let inSection = false;
5309
+ let currentQuestion = null;
5310
+ let inCodeFence = false;
5311
+ for (const line of lines) {
5312
+ if (line.startsWith("```")) {
5313
+ inCodeFence = !inCodeFence;
5314
+ continue;
5315
+ }
5316
+ if (inCodeFence)
5317
+ continue;
5318
+ if (line.startsWith("## ")) {
5319
+ inSection = line.trimEnd() === "## Resolved Questions";
5320
+ currentQuestion = null;
5321
+ continue;
5322
+ }
5323
+ if (!inSection)
5324
+ continue;
5325
+ if (line.startsWith("# "))
5326
+ break;
5327
+ if (line.startsWith("### ")) {
5328
+ currentQuestion = line.slice(4).trimEnd();
5329
+ continue;
5330
+ }
5331
+ if (currentQuestion !== null) {
5332
+ const decisionMatch = line.match(/^\*\*Decision:\*\*\s*(.+)$/);
5333
+ if (decisionMatch) {
5334
+ results.push({
5335
+ question: currentQuestion,
5336
+ chosenOption: decisionMatch[1].trimEnd()
5337
+ });
5338
+ currentQuestion = null;
5339
+ }
5340
+ }
5341
+ }
5342
+ return results;
5343
+ }
5344
+
5345
+ // lib/lint/validators/content-validator.ts
5346
+ var REQUIRED_TASK_HEADINGS = ["Task Type", "Blocked By", "Definition of Done"];
5347
+ var ALLOWED_TASK_TYPES = new Set(VALID_TASK_TYPES);
5165
5348
  var MAX_OPENING_SENTENCE_LENGTH = 150;
5166
5349
  var NON_IMPERATIVE_STARTERS = new Set([
5167
5350
  "the",
@@ -9586,180 +9769,6 @@ async function parsePrinciple(fileSystem, dustPath, slug) {
9586
9769
  };
9587
9770
  }
9588
9771
 
9589
- // lib/artifacts/workflow-tasks.ts
9590
- var CAPTURE_IDEA_PREFIX = "Add Idea: ";
9591
- var EXPEDITE_IDEA_PREFIX = "Expedite Idea: ";
9592
- function titleToFilename(title) {
9593
- return `${title.toLowerCase().replace(/\./g, "-").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}.md`;
9594
- }
9595
- var VALID_TASK_TYPES = [
9596
- "implement",
9597
- "capture",
9598
- "refine",
9599
- "decompose",
9600
- "shelve"
9601
- ];
9602
- function parseTaskType(content) {
9603
- const lines = content.split(`
9604
- `);
9605
- let inSection = false;
9606
- let inCodeFence = false;
9607
- for (const line of lines) {
9608
- if (line.startsWith("```")) {
9609
- inCodeFence = !inCodeFence;
9610
- continue;
9611
- }
9612
- if (inCodeFence)
9613
- continue;
9614
- if (line.startsWith("## ")) {
9615
- inSection = line.trimEnd() === "## Task Type";
9616
- continue;
9617
- }
9618
- if (!inSection)
9619
- continue;
9620
- if (line.startsWith("# "))
9621
- break;
9622
- const trimmed = line.trim();
9623
- if (trimmed && VALID_TASK_TYPES.includes(trimmed)) {
9624
- return trimmed;
9625
- }
9626
- }
9627
- return null;
9628
- }
9629
- var WORKFLOW_SECTION_HEADINGS = [
9630
- { type: "refine-idea", heading: "Refines Idea" },
9631
- { type: "decompose-idea", heading: "Decomposes Idea" },
9632
- { type: "shelve-idea", heading: "Shelves Idea" },
9633
- { type: "expedite-idea", heading: "Expedites Idea" }
9634
- ];
9635
- function extractIdeaSlugFromSection(content, sectionHeading) {
9636
- const lines = content.split(`
9637
- `);
9638
- let inSection = false;
9639
- let inCodeFence = false;
9640
- for (const line of lines) {
9641
- if (line.startsWith("```")) {
9642
- inCodeFence = !inCodeFence;
9643
- continue;
9644
- }
9645
- if (inCodeFence)
9646
- continue;
9647
- if (line.startsWith("## ")) {
9648
- inSection = line.trimEnd() === `## ${sectionHeading}`;
9649
- continue;
9650
- }
9651
- if (!inSection)
9652
- continue;
9653
- if (line.startsWith("# "))
9654
- break;
9655
- const linkMatch = line.match(MARKDOWN_LINK_PATTERN);
9656
- if (linkMatch) {
9657
- const target = linkMatch[2];
9658
- const slugMatch = target.match(/([^/]+)\.md$/);
9659
- if (slugMatch) {
9660
- return slugMatch[1];
9661
- }
9662
- }
9663
- }
9664
- return null;
9665
- }
9666
- async function findAllWorkflowTasks(fileSystem, dustPath) {
9667
- const tasksPath = `${dustPath}/tasks`;
9668
- const captureIdeaTasks = [];
9669
- const workflowTasksByIdeaSlug = new Map;
9670
- if (!fileSystem.exists(tasksPath)) {
9671
- return { captureIdeaTasks, workflowTasksByIdeaSlug };
9672
- }
9673
- const files = await fileSystem.readdir(tasksPath);
9674
- for (const file of files.filter((f) => f.endsWith(".md")).toSorted()) {
9675
- const content = await fileSystem.readFile(`${tasksPath}/${file}`);
9676
- const titleMatch = content.match(/^#\s+(.+)$/m);
9677
- if (!titleMatch)
9678
- continue;
9679
- const title = titleMatch[1].trim();
9680
- const taskSlug = file.replace(/\.md$/, "");
9681
- const taskType = parseTaskType(content);
9682
- if (taskType === "capture" || taskType === "implement") {
9683
- let ideaTitle = null;
9684
- if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
9685
- ideaTitle = title.slice(CAPTURE_IDEA_PREFIX.length);
9686
- } else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
9687
- ideaTitle = title.slice(EXPEDITE_IDEA_PREFIX.length);
9688
- }
9689
- if (ideaTitle) {
9690
- captureIdeaTasks.push({
9691
- taskSlug,
9692
- ideaTitle
9693
- });
9694
- }
9695
- } else if (!taskType) {
9696
- if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
9697
- captureIdeaTasks.push({
9698
- taskSlug,
9699
- ideaTitle: title.slice(CAPTURE_IDEA_PREFIX.length)
9700
- });
9701
- } else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
9702
- captureIdeaTasks.push({
9703
- taskSlug,
9704
- ideaTitle: title.slice(EXPEDITE_IDEA_PREFIX.length)
9705
- });
9706
- }
9707
- }
9708
- for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
9709
- const linkedSlug = extractIdeaSlugFromSection(content, heading);
9710
- if (linkedSlug) {
9711
- workflowTasksByIdeaSlug.set(linkedSlug, {
9712
- type,
9713
- ideaSlug: linkedSlug,
9714
- taskSlug,
9715
- resolvedQuestions: parseResolvedQuestions(content)
9716
- });
9717
- }
9718
- }
9719
- }
9720
- return { captureIdeaTasks, workflowTasksByIdeaSlug };
9721
- }
9722
- function parseResolvedQuestions(content) {
9723
- const lines = content.split(`
9724
- `);
9725
- const results = [];
9726
- let inSection = false;
9727
- let currentQuestion = null;
9728
- let inCodeFence = false;
9729
- for (const line of lines) {
9730
- if (line.startsWith("```")) {
9731
- inCodeFence = !inCodeFence;
9732
- continue;
9733
- }
9734
- if (inCodeFence)
9735
- continue;
9736
- if (line.startsWith("## ")) {
9737
- inSection = line.trimEnd() === "## Resolved Questions";
9738
- currentQuestion = null;
9739
- continue;
9740
- }
9741
- if (!inSection)
9742
- continue;
9743
- if (line.startsWith("# "))
9744
- break;
9745
- if (line.startsWith("### ")) {
9746
- currentQuestion = line.slice(4).trimEnd();
9747
- continue;
9748
- }
9749
- if (currentQuestion !== null) {
9750
- const decisionMatch = line.match(/^\*\*Decision:\*\*\s*(.+)$/);
9751
- if (decisionMatch) {
9752
- results.push({
9753
- question: currentQuestion,
9754
- chosenOption: decisionMatch[1].trimEnd()
9755
- });
9756
- currentQuestion = null;
9757
- }
9758
- }
9759
- }
9760
- return results;
9761
- }
9762
-
9763
9772
  // lib/artifacts/index.ts
9764
9773
  var ARTIFACT_TYPES = [
9765
9774
  "facts",
@@ -11018,13 +11027,14 @@ function getCorePrinciplesPath() {
11018
11027
  // lib/cli/commands/list.ts
11019
11028
  function workflowTypeToStatus(type) {
11020
11029
  switch (type) {
11021
- case "refine-idea":
11030
+ case "refine":
11022
11031
  return "refining";
11023
- case "decompose-idea":
11032
+ case "decompose":
11024
11033
  return "decomposing";
11025
- case "shelve-idea":
11034
+ case "shelve":
11026
11035
  return "shelving";
11027
- case "expedite-idea":
11036
+ case "implement":
11037
+ case "capture":
11028
11038
  return "expediting";
11029
11039
  }
11030
11040
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Task serialization and patch building functions.
3
3
  */
4
+ import type { TaskType } from '../artifacts/workflow-tasks';
4
5
  export interface StandardTaskInput {
5
6
  type?: undefined;
6
7
  title: string;
@@ -10,7 +11,7 @@ export interface StandardTaskInput {
10
11
  definitionOfDone: string[];
11
12
  }
12
13
  export interface WorkflowTaskInput {
13
- type: 'capture-idea' | 'refine-idea' | 'decompose-idea' | 'shelve-idea';
14
+ type: Extract<TaskType, 'capture' | 'refine' | 'decompose' | 'shelve'>;
14
15
  ideaSlug: string;
15
16
  definitionOfDone?: string[];
16
17
  }
package/dist/patch.js CHANGED
@@ -142,6 +142,13 @@ function extractFirstSentence(paragraph) {
142
142
  function titleToFilename(title) {
143
143
  return `${title.toLowerCase().replace(/\./g, "-").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}.md`;
144
144
  }
145
+ var VALID_TASK_TYPES = [
146
+ "implement",
147
+ "capture",
148
+ "refine",
149
+ "decompose",
150
+ "shelve"
151
+ ];
145
152
 
146
153
  // lib/artifacts/index.ts
147
154
  var ARTIFACT_TYPES = [
@@ -237,13 +244,7 @@ function validateAuditHeadings(artifact) {
237
244
 
238
245
  // lib/lint/validators/content-validator.ts
239
246
  var REQUIRED_TASK_HEADINGS = ["Task Type", "Blocked By", "Definition of Done"];
240
- var ALLOWED_TASK_TYPES = new Set([
241
- "implement",
242
- "capture",
243
- "refine",
244
- "decompose",
245
- "shelve"
246
- ]);
247
+ var ALLOWED_TASK_TYPES = new Set(VALID_TASK_TYPES);
247
248
  var MAX_OPENING_SENTENCE_LENGTH = 150;
248
249
  var NON_IMPERATIVE_STARTERS = new Set([
249
250
  "the",
@@ -1115,30 +1116,28 @@ function buildPrincipleFiles(input, slug) {
1115
1116
 
1116
1117
  // lib/patch/task.ts
1117
1118
  var WORKFLOW_SECTION_HEADINGS = {
1118
- "capture-idea": "Captures Idea",
1119
- "refine-idea": "Refines Idea",
1120
- "decompose-idea": "Decomposes Idea",
1121
- "shelve-idea": "Shelves Idea"
1119
+ capture: "Captures Idea",
1120
+ refine: "Refines Idea",
1121
+ decompose: "Decomposes Idea",
1122
+ shelve: "Shelves Idea"
1122
1123
  };
1123
1124
  var WORKFLOW_TITLE_PREFIXES = {
1124
- "capture-idea": "Add Idea: ",
1125
- "refine-idea": "Refine Idea: ",
1126
- "decompose-idea": "Decompose Idea: ",
1127
- "shelve-idea": "Shelve Idea: "
1125
+ capture: "Add Idea: ",
1126
+ refine: "Refine Idea: ",
1127
+ decompose: "Decompose Idea: ",
1128
+ shelve: "Shelve Idea: "
1128
1129
  };
1129
1130
  var WORKFLOW_OPENING_SENTENCES = {
1130
- "capture-idea": "Research this idea thoroughly and create an idea file.",
1131
- "refine-idea": "Thoroughly research this idea and refine it into a well-defined proposal.",
1132
- "decompose-idea": "Create one or more well-defined tasks from this idea.",
1133
- "shelve-idea": "Archive this idea and remove it from the active backlog."
1131
+ capture: "Research this idea thoroughly and create an idea file.",
1132
+ refine: "Thoroughly research this idea and refine it into a well-defined proposal.",
1133
+ decompose: "Create one or more well-defined tasks from this idea.",
1134
+ shelve: "Archive this idea and remove it from the active backlog."
1134
1135
  };
1135
1136
  var WORKFLOW_DEFAULT_DEFINITION_OF_DONE = {
1136
- "capture-idea": ["Idea file is created in .dust/ideas/"],
1137
- "refine-idea": [
1138
- "Idea is thoroughly researched with relevant codebase context"
1139
- ],
1140
- "decompose-idea": ["One or more new tasks are created in .dust/tasks/"],
1141
- "shelve-idea": ["Idea file is deleted"]
1137
+ capture: ["Idea file is created in .dust/ideas/"],
1138
+ refine: ["Idea is thoroughly researched with relevant codebase context"],
1139
+ decompose: ["One or more new tasks are created in .dust/tasks/"],
1140
+ shelve: ["Idea file is deleted"]
1142
1141
  };
1143
1142
  function ideaSlugToTitle(slug) {
1144
1143
  return slug.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
@@ -1200,7 +1199,6 @@ function serializeWorkflowTask(input) {
1200
1199
  const ideaSection = `## ${sectionHeading}
1201
1200
 
1202
1201
  - [${ideaTitle}](../ideas/${input.ideaSlug}.md)`;
1203
- const taskType = input.type === "capture-idea" ? "capture" : input.type === "refine-idea" ? "refine" : input.type === "decompose-idea" ? "decompose" : "shelve";
1204
1202
  return `# ${title}
1205
1203
 
1206
1204
  ${openingSentence}
@@ -1209,7 +1207,7 @@ ${ideaSection}
1209
1207
 
1210
1208
  ## Task Type
1211
1209
 
1212
- ${taskType}
1210
+ ${input.type}
1213
1211
 
1214
1212
  ## Blocked By
1215
1213
 
package/dist/types.d.ts CHANGED
@@ -8,7 +8,7 @@ export type { AgentSessionEvent, EventMessage } from './agent-events';
8
8
  export type { Idea, IdeaOpenQuestion, IdeaOption, ParsedIdeaContent, } from './artifacts/ideas';
9
9
  export type { ArtifactType, TaskGraph, TaskGraphNode } from './artifacts/index';
10
10
  export type { Task } from './artifacts/tasks';
11
- export type { CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, IdeaInProgress, OpenQuestionResponse, ParsedCaptureIdeaTask, WorkflowTaskMatch, WorkflowTaskType, } from './artifacts/workflow-tasks';
11
+ export type { CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, IdeaInProgress, OpenQuestionResponse, ParsedCaptureIdeaTask, WorkflowTaskMatch, } from './artifacts/workflow-tasks';
12
12
  export type { Repository } from './bucket/repository';
13
13
  export type { ToolExecutionErrorResult, ToolExecutionRequestMessage, ToolExecutionResult, ToolExecutionResultMessage, ToolExecutionSuccessResult, ToolExecutionToolNotFoundResult, } from './bucket/tool-execution-protocol';
14
14
  export { isToolExecutionRequestMessage, isToolExecutionResultMessage, } from './bucket/tool-execution-protocol';
@@ -207,6 +207,13 @@ function extractFirstSentence(paragraph) {
207
207
  function titleToFilename(title) {
208
208
  return `${title.toLowerCase().replace(/\./g, "-").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}.md`;
209
209
  }
210
+ var VALID_TASK_TYPES = [
211
+ "implement",
212
+ "capture",
213
+ "refine",
214
+ "decompose",
215
+ "shelve"
216
+ ];
210
217
 
211
218
  // lib/artifacts/index.ts
212
219
  var ARTIFACT_TYPES = [
@@ -234,13 +241,7 @@ function validateAuditHeadings(artifact) {
234
241
 
235
242
  // lib/lint/validators/content-validator.ts
236
243
  var REQUIRED_TASK_HEADINGS = ["Task Type", "Blocked By", "Definition of Done"];
237
- var ALLOWED_TASK_TYPES = new Set([
238
- "implement",
239
- "capture",
240
- "refine",
241
- "decompose",
242
- "shelve"
243
- ]);
244
+ var ALLOWED_TASK_TYPES = new Set(VALID_TASK_TYPES);
244
245
  var MAX_OPENING_SENTENCE_LENGTH = 150;
245
246
  var NON_IMPERATIVE_STARTERS = new Set([
246
247
  "the",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshski/dust",
3
- "version": "0.1.104",
3
+ "version": "0.1.106",
4
4
  "description": "Flow state for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {