@joshski/dust 0.1.104 → 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.
@@ -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(`
@@ -700,11 +686,17 @@ async function findWorkflowTaskForIdea(fileSystem, dustPath, ideaSlug) {
700
686
  function findWorkflowMatch(content, ideaSlug, taskSlug) {
701
687
  const taskType = parseTaskType(content);
702
688
  if (taskType) {
703
- const workflowType = taskTypeToWorkflowType(taskType);
704
- if (workflowType) {
705
- const match = findWorkflowMatchByType(content, ideaSlug, taskSlug, workflowType);
706
- if (match)
707
- return match;
689
+ const heading = WORKFLOW_SECTION_HEADINGS.find((h) => h.type === taskType)?.heading;
690
+ if (heading) {
691
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
692
+ if (linkedSlug === ideaSlug) {
693
+ return {
694
+ type: taskType,
695
+ ideaSlug,
696
+ taskSlug,
697
+ resolvedQuestions: parseResolvedQuestions(content)
698
+ };
699
+ }
708
700
  }
709
701
  }
710
702
  for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
@@ -720,22 +712,6 @@ function findWorkflowMatch(content, ideaSlug, taskSlug) {
720
712
  }
721
713
  return null;
722
714
  }
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
715
  async function readIdeaTitle(fileSystem, dustPath, ideaSlug) {
740
716
  const ideaPath = `${dustPath}/ideas/${ideaSlug}.md`;
741
717
  if (!fileSystem.exists(ideaPath)) {
@@ -845,15 +821,14 @@ ${definitionOfDone.map((item) => `- ${item}`).join(`
845
821
  `)}
846
822
  `;
847
823
  }
848
- 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) {
849
825
  const ideaTitle = await readIdeaTitle(fileSystem, dustPath, ideaSlug);
850
826
  const taskTitle = `${prefix}${ideaTitle}`;
851
827
  const filename = titleToFilename(taskTitle);
852
828
  const filePath = `${dustPath}/tasks/${filename}`;
853
829
  const baseOpeningSentence = openingSentenceTemplate(ideaTitle);
854
- const hint = await readWorkflowHint(fileSystem, dustPath, workflowType);
830
+ const hint = await readWorkflowHint(fileSystem, dustPath, taskType);
855
831
  const ideaSection = { heading: ideaSectionHeading, ideaTitle, ideaSlug };
856
- const taskType = workflowType === "refine-idea" ? "refine" : workflowType === "decompose-idea" ? "decompose" : workflowType === "shelve-idea" ? "shelve" : "implement";
857
832
  const content = renderTask(taskTitle, baseOpeningSentence, definitionOfDone, ideaSection, taskType, {
858
833
  description: taskOptions?.description,
859
834
  resolvedQuestions: taskOptions?.resolvedQuestions,
@@ -864,7 +839,7 @@ async function createIdeaTransitionTask(fileSystem, dustPath, workflowType, pref
864
839
  }
865
840
  async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description, openQuestionResponses, dustCommand) {
866
841
  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.`, [
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.`, [
868
843
  "Idea is thoroughly researched with relevant codebase context",
869
844
  "Open questions are added for any ambiguous or underspecified aspects",
870
845
  "Open questions follow the required heading format and focus on high-value decisions",
@@ -876,7 +851,7 @@ async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description,
876
851
  }
877
852
  async function decomposeIdea(fileSystem, dustPath, options, dustCommand) {
878
853
  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).`, [
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).`, [
880
855
  "One or more new tasks are created in .dust/tasks/",
881
856
  "Task's Principles section links to relevant principles from .dust/principles/",
882
857
  "The original idea is deleted or updated to reflect remaining scope"
@@ -886,11 +861,11 @@ async function decomposeIdea(fileSystem, dustPath, options, dustCommand) {
886
861
  });
887
862
  }
888
863
  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 });
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 });
890
865
  }
891
866
  async function createExpediteIdeaTask(fileSystem, dustPath, ideaSlug, description, dustCommand) {
892
867
  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).`, [
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).`, [
894
869
  "Idea is implemented directly OR one or more new tasks are created in `.dust/tasks/`",
895
870
  "If tasks were created, they link to relevant principles from `.dust/principles/`",
896
871
  "Changes are committed with a clear commit message"
@@ -910,7 +885,7 @@ async function createIdeaTask(fileSystem, dustPath, options) {
910
885
  const filename2 = titleToFilename(taskTitle2);
911
886
  const filePath2 = `${dustPath}/tasks/${filename2}`;
912
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.`;
913
- const hint2 = await readWorkflowHint(fileSystem, dustPath, "expedite-idea");
888
+ const hint2 = await readWorkflowHint(fileSystem, dustPath, "implement");
914
889
  const repositoryHintsSection2 = renderRepositoryHintsSection(hint2 ?? undefined);
915
890
  const content2 = `# ${taskTitle2}
916
891
 
@@ -942,7 +917,7 @@ ${repositoryHintsSection2}
942
917
  const filename = titleToFilename(taskTitle);
943
918
  const filePath = `${dustPath}/tasks/${filename}`;
944
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.`;
945
- const hint = await readWorkflowHint(fileSystem, dustPath, "add-idea");
920
+ const hint = await readWorkflowHint(fileSystem, dustPath, "capture");
946
921
  const repositoryHintsSection = renderRepositoryHintsSection(hint ?? undefined);
947
922
  const content = `# ${taskTitle}
948
923
 
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.105",
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.105";
716
716
 
717
717
  // lib/cli/middleware.ts
718
718
  function applyMiddleware(middlewares, execute) {
@@ -5153,15 +5153,183 @@ 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
+ for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
5276
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
5277
+ if (linkedSlug) {
5278
+ workflowTasksByIdeaSlug.set(linkedSlug, {
5279
+ type,
5280
+ ideaSlug: linkedSlug,
5281
+ taskSlug,
5282
+ resolvedQuestions: parseResolvedQuestions(content)
5283
+ });
5284
+ }
5285
+ }
5286
+ }
5287
+ return { captureIdeaTasks, workflowTasksByIdeaSlug };
5288
+ }
5289
+ function parseResolvedQuestions(content) {
5290
+ const lines = content.split(`
5291
+ `);
5292
+ const results = [];
5293
+ let inSection = false;
5294
+ let currentQuestion = null;
5295
+ let inCodeFence = false;
5296
+ for (const line of lines) {
5297
+ if (line.startsWith("```")) {
5298
+ inCodeFence = !inCodeFence;
5299
+ continue;
5300
+ }
5301
+ if (inCodeFence)
5302
+ continue;
5303
+ if (line.startsWith("## ")) {
5304
+ inSection = line.trimEnd() === "## Resolved Questions";
5305
+ currentQuestion = null;
5306
+ continue;
5307
+ }
5308
+ if (!inSection)
5309
+ continue;
5310
+ if (line.startsWith("# "))
5311
+ break;
5312
+ if (line.startsWith("### ")) {
5313
+ currentQuestion = line.slice(4).trimEnd();
5314
+ continue;
5315
+ }
5316
+ if (currentQuestion !== null) {
5317
+ const decisionMatch = line.match(/^\*\*Decision:\*\*\s*(.+)$/);
5318
+ if (decisionMatch) {
5319
+ results.push({
5320
+ question: currentQuestion,
5321
+ chosenOption: decisionMatch[1].trimEnd()
5322
+ });
5323
+ currentQuestion = null;
5324
+ }
5325
+ }
5326
+ }
5327
+ return results;
5328
+ }
5329
+
5330
+ // lib/lint/validators/content-validator.ts
5331
+ var REQUIRED_TASK_HEADINGS = ["Task Type", "Blocked By", "Definition of Done"];
5332
+ var ALLOWED_TASK_TYPES = new Set(VALID_TASK_TYPES);
5165
5333
  var MAX_OPENING_SENTENCE_LENGTH = 150;
5166
5334
  var NON_IMPERATIVE_STARTERS = new Set([
5167
5335
  "the",
@@ -9586,180 +9754,6 @@ async function parsePrinciple(fileSystem, dustPath, slug) {
9586
9754
  };
9587
9755
  }
9588
9756
 
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
9757
  // lib/artifacts/index.ts
9764
9758
  var ARTIFACT_TYPES = [
9765
9759
  "facts",
@@ -11018,13 +11012,14 @@ function getCorePrinciplesPath() {
11018
11012
  // lib/cli/commands/list.ts
11019
11013
  function workflowTypeToStatus(type) {
11020
11014
  switch (type) {
11021
- case "refine-idea":
11015
+ case "refine":
11022
11016
  return "refining";
11023
- case "decompose-idea":
11017
+ case "decompose":
11024
11018
  return "decomposing";
11025
- case "shelve-idea":
11019
+ case "shelve":
11026
11020
  return "shelving";
11027
- case "expedite-idea":
11021
+ case "implement":
11022
+ case "capture":
11028
11023
  return "expediting";
11029
11024
  }
11030
11025
  }
@@ -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.105",
4
4
  "description": "Flow state for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {