@joshski/dust 0.1.26 → 0.1.29

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.
package/dist/dust.js CHANGED
@@ -2209,6 +2209,10 @@ async function getUncommittedFiles(cwd, gitRunner) {
2209
2209
  }
2210
2210
  async function prePush(dependencies, gitRunner = defaultGitRunner, env = process.env) {
2211
2211
  const { context } = dependencies;
2212
+ const agent2 = detectAgent(env);
2213
+ if (agent2.type === "unknown") {
2214
+ return { exitCode: 0 };
2215
+ }
2212
2216
  if (env.DUST_UNATTENDED) {
2213
2217
  const uncommittedFiles = await getUncommittedFiles(context.cwd, gitRunner);
2214
2218
  if (uncommittedFiles.length > 0) {
@@ -2228,7 +2232,6 @@ async function prePush(dependencies, gitRunner = defaultGitRunner, env = process
2228
2232
  const changes = await getChangesFromRemote(context.cwd, gitRunner);
2229
2233
  if (changes.length > 0) {
2230
2234
  const analysis = analyzeChangesForTaskOnlyPattern(changes);
2231
- const agent2 = detectAgent(env);
2232
2235
  if (analysis.isTaskOnly && agent2.type === "claude-code-web") {
2233
2236
  context.stderr("");
2234
2237
  context.stderr("⚠️ Task-only commit detected! You added a task but did not implement it.");
@@ -1,5 +1,11 @@
1
1
  import type { FileSystem } from './cli/types';
2
2
  export declare const IDEA_TRANSITION_PREFIXES: string[];
3
+ export declare const CAPTURE_IDEA_PREFIX = "Add Idea: ";
4
+ export interface IdeaInProgress {
5
+ taskSlug: string;
6
+ ideaTitle: string;
7
+ }
8
+ export declare function findAllCaptureIdeaTasks(fileSystem: FileSystem, dustPath: string): Promise<IdeaInProgress[]>;
3
9
  /**
4
10
  * Converts a markdown title to the expected filename using deterministic rules:
5
11
  * 1. Convert to lowercase
@@ -19,7 +25,16 @@ export declare function findWorkflowTask(fileSystem: FileSystem, dustPath: strin
19
25
  export interface CreateIdeaTransitionTaskResult {
20
26
  filePath: string;
21
27
  }
28
+ export interface OpenQuestionResponse {
29
+ question: string;
30
+ chosenOption: string;
31
+ }
32
+ export interface CreateTaskFromIdeaOptions {
33
+ ideaSlug: string;
34
+ description?: string;
35
+ openQuestionResponses?: OpenQuestionResponse[];
36
+ }
22
37
  export declare function createRefineIdeaTask(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string): Promise<CreateIdeaTransitionTaskResult>;
23
- export declare function createTaskFromIdea(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string): Promise<CreateIdeaTransitionTaskResult>;
38
+ export declare function createTaskFromIdea(fileSystem: FileSystem, dustPath: string, options: CreateTaskFromIdeaOptions): Promise<CreateIdeaTransitionTaskResult>;
24
39
  export declare function createShelveIdeaTask(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string): Promise<CreateIdeaTransitionTaskResult>;
25
40
  export declare function createCaptureIdeaTask(fileSystem: FileSystem, dustPath: string, title: string, description: string): Promise<CreateIdeaTransitionTaskResult>;
@@ -4,6 +4,28 @@ var IDEA_TRANSITION_PREFIXES = [
4
4
  "Create Task From Idea: ",
5
5
  "Shelve Idea: "
6
6
  ];
7
+ var CAPTURE_IDEA_PREFIX = "Add Idea: ";
8
+ async function findAllCaptureIdeaTasks(fileSystem, dustPath) {
9
+ const tasksPath = `${dustPath}/tasks`;
10
+ if (!fileSystem.exists(tasksPath))
11
+ return [];
12
+ const files = await fileSystem.readdir(tasksPath);
13
+ const results = [];
14
+ for (const file of files.filter((f) => f.endsWith(".md")).sort()) {
15
+ const content = await fileSystem.readFile(`${tasksPath}/${file}`);
16
+ const titleMatch = content.match(/^#\s+(.+)$/m);
17
+ if (!titleMatch)
18
+ continue;
19
+ const title = titleMatch[1].trim();
20
+ if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
21
+ results.push({
22
+ taskSlug: file.replace(/\.md$/, ""),
23
+ ideaTitle: title.slice(CAPTURE_IDEA_PREFIX.length)
24
+ });
25
+ }
26
+ }
27
+ return results;
28
+ }
7
29
  function titleToFilename(title) {
8
30
  return `${title.toLowerCase().replace(/\./g, "-").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}.md`;
9
31
  }
@@ -36,14 +58,28 @@ async function readIdeaTitle(fileSystem, dustPath, ideaSlug) {
36
58
  }
37
59
  return ideaTitleMatch[1].trim();
38
60
  }
39
- function renderTask(title, openingSentence, definitionOfDone, description) {
40
- const descriptionParagraph = description !== undefined ? `
41
- ${description}
61
+ function renderResolvedQuestions(responses) {
62
+ const sections = responses.map((r) => `### ${r.question}
63
+
64
+ **Decision:** ${r.chosenOption}`);
65
+ return `## Resolved Questions
66
+
67
+ ${sections.join(`
68
+
69
+ `)}
70
+ `;
71
+ }
72
+ function renderTask(title, openingSentence, definitionOfDone, options) {
73
+ const descriptionParagraph = options?.description !== undefined ? `
74
+ ${options.description}
75
+ ` : "";
76
+ const resolvedSection = options?.resolvedQuestions && options.resolvedQuestions.length > 0 ? `
77
+ ${renderResolvedQuestions(options.resolvedQuestions)}
42
78
  ` : "";
43
79
  return `# ${title}
44
80
 
45
81
  ${openingSentence}
46
- ${descriptionParagraph}
82
+ ${descriptionParagraph}${resolvedSection}
47
83
  ## Goals
48
84
 
49
85
  (none)
@@ -58,30 +94,34 @@ ${definitionOfDone.map((item) => `- [ ] ${item}`).join(`
58
94
  `)}
59
95
  `;
60
96
  }
61
- async function createIdeaTask(fileSystem, dustPath, prefix, ideaSlug, openingSentenceTemplate, definitionOfDone, description) {
97
+ async function createIdeaTask(fileSystem, dustPath, prefix, ideaSlug, openingSentenceTemplate, definitionOfDone, taskOptions) {
62
98
  const ideaTitle = await readIdeaTitle(fileSystem, dustPath, ideaSlug);
63
99
  const taskTitle = `${prefix}${ideaTitle}`;
64
100
  const filename = titleToFilename(taskTitle);
65
101
  const filePath = `${dustPath}/tasks/${filename}`;
66
102
  const openingSentence = openingSentenceTemplate(ideaTitle);
67
- const content = renderTask(taskTitle, openingSentence, definitionOfDone, description);
103
+ const content = renderTask(taskTitle, openingSentence, definitionOfDone, taskOptions);
68
104
  await fileSystem.writeFile(filePath, content);
69
105
  return { filePath };
70
106
  }
71
107
  async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description) {
72
- return createIdeaTask(fileSystem, dustPath, "Refine Idea: ", ideaSlug, (ideaTitle) => `Research and refine this idea into a well-defined proposal. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, [
73
- "Open questions are identified and resolved",
108
+ return createIdeaTask(fileSystem, dustPath, "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. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, [
109
+ "Idea is thoroughly researched with relevant codebase context",
110
+ "Open questions are added for any ambiguous or underspecified aspects",
74
111
  "Idea file is updated with findings"
75
- ], description);
112
+ ], { description });
76
113
  }
77
- async function createTaskFromIdea(fileSystem, dustPath, ideaSlug, description) {
78
- return createIdeaTask(fileSystem, dustPath, "Create Task From Idea: ", ideaSlug, (ideaTitle) => `Create a well-defined task from this idea. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, [
114
+ async function createTaskFromIdea(fileSystem, dustPath, options) {
115
+ return createIdeaTask(fileSystem, dustPath, "Create Task From Idea: ", options.ideaSlug, (ideaTitle) => `Create a well-defined task from this idea. See [${ideaTitle}](../ideas/${options.ideaSlug}.md).`, [
79
116
  "A new task is created in .dust/tasks/",
80
117
  "The original idea is deleted or updated to reflect remaining scope"
81
- ], description);
118
+ ], {
119
+ description: options.description,
120
+ resolvedQuestions: options.openQuestionResponses
121
+ });
82
122
  }
83
123
  async function createShelveIdeaTask(fileSystem, dustPath, ideaSlug, description) {
84
- return createIdeaTask(fileSystem, dustPath, "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"], description);
124
+ return createIdeaTask(fileSystem, dustPath, "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"], { description });
85
125
  }
86
126
  async function createCaptureIdeaTask(fileSystem, dustPath, title, description) {
87
127
  if (!title || !title.trim()) {
@@ -95,19 +135,23 @@ async function createCaptureIdeaTask(fileSystem, dustPath, title, description) {
95
135
  const filePath = `${dustPath}/tasks/${filename}`;
96
136
  const ideaFilename = titleToFilename(title);
97
137
  const ideaPath = `.dust/ideas/${ideaFilename}`;
98
- const content = renderTask(taskTitle, `Create a new idea file at \`${ideaPath}\` with the title "${title}" and the following description:`, [
138
+ const content = renderTask(taskTitle, `Research this idea thoroughly, then create an idea file at \`${ideaPath}\`. 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. The idea should have the title "${title}" and start from the following description:`, [
99
139
  `Idea file exists at ${ideaPath}`,
100
- `Idea file has an H1 title matching "${title}"`
101
- ], description);
140
+ `Idea file has an H1 title matching "${title}"`,
141
+ "Idea includes relevant context from codebase exploration",
142
+ "Open questions are added for any ambiguous or underspecified aspects"
143
+ ], { description });
102
144
  await fileSystem.writeFile(filePath, content);
103
145
  return { filePath };
104
146
  }
105
147
  export {
106
148
  titleToFilename,
107
149
  findWorkflowTask,
150
+ findAllCaptureIdeaTasks,
108
151
  createTaskFromIdea,
109
152
  createShelveIdeaTask,
110
153
  createRefineIdeaTask,
111
154
  createCaptureIdeaTask,
112
- IDEA_TRANSITION_PREFIXES
155
+ IDEA_TRANSITION_PREFIXES,
156
+ CAPTURE_IDEA_PREFIX
113
157
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshski/dust",
3
- "version": "0.1.26",
3
+ "version": "0.1.29",
4
4
  "description": "Flow state for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {