@joshski/dust 0.1.29 → 0.1.30

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
@@ -572,8 +572,15 @@ function validateIdeaOpenQuestions(filePath, content) {
572
572
  `);
573
573
  let inOpenQuestions = false;
574
574
  let currentQuestionLine = null;
575
+ let inCodeBlock = false;
575
576
  for (let i = 0;i < lines.length; i++) {
576
577
  const line = lines[i];
578
+ if (line.startsWith("```")) {
579
+ inCodeBlock = !inCodeBlock;
580
+ continue;
581
+ }
582
+ if (inCodeBlock)
583
+ continue;
577
584
  if (line.startsWith("## ")) {
578
585
  if (inOpenQuestions && currentQuestionLine !== null) {
579
586
  violations.push({
@@ -588,6 +595,14 @@ function validateIdeaOpenQuestions(filePath, content) {
588
595
  }
589
596
  if (!inOpenQuestions)
590
597
  continue;
598
+ if (/^[-*] /.test(line.trimStart())) {
599
+ violations.push({
600
+ file: filePath,
601
+ message: "Open Questions must use ### headings for questions and #### headings for options, not bullet points. Run `dust new idea` to see the expected format.",
602
+ line: i + 1
603
+ });
604
+ continue;
605
+ }
591
606
  if (line.startsWith("### ")) {
592
607
  if (currentQuestionLine !== null) {
593
608
  violations.push({
@@ -1142,14 +1157,34 @@ async function check(dependencies, shellRunner = defaultShellRunner) {
1142
1157
 
1143
1158
  // lib/cli/commands/focus.ts
1144
1159
  async function focus(dependencies) {
1145
- const { context } = dependencies;
1160
+ const { context, settings } = dependencies;
1146
1161
  const objective = dependencies.arguments.join(" ").trim();
1147
1162
  if (!objective) {
1148
1163
  context.stderr("Error: No objective provided");
1149
1164
  context.stderr('Usage: dust focus "your objective here"');
1150
1165
  return { exitCode: 1 };
1151
1166
  }
1167
+ const hooksInstalled = await manageGitHooks(dependencies);
1168
+ const vars = templateVariables(settings, hooksInstalled);
1152
1169
  context.stdout(`\uD83C\uDFAF Focus: ${objective}`);
1170
+ context.stdout("");
1171
+ const steps = [];
1172
+ let step = 1;
1173
+ steps.push(`${step}. Run \`${vars.bin} check\` to verify the project is in a good state`);
1174
+ step++;
1175
+ steps.push(`${step}. Implement the task`);
1176
+ step++;
1177
+ if (!hooksInstalled) {
1178
+ steps.push(`${step}. Run \`${vars.bin} check\` before committing`);
1179
+ step++;
1180
+ }
1181
+ steps.push(`${step}. Create a single atomic commit that includes:`, " - All implementation changes", " - Deletion of the completed task file", " - Updates to any facts that changed", " - Deletion of any ideas that were fully realized", "", ' Use the task title as the commit message. Task titles are written in imperative form, which is the recommended style for git commit messages. Do not add prefixes like "Complete task:" - use the title directly.', "", ' Example: If the task title is "Add validation for user input", the commit message should be:', " ```", " Add validation for user input", " ```", "");
1182
+ step++;
1183
+ steps.push(`${step}. Push your commit to the remote repository`);
1184
+ steps.push("");
1185
+ steps.push("Keep your change small and focused. One task, one commit.");
1186
+ context.stdout(steps.join(`
1187
+ `));
1153
1188
  return { exitCode: 0 };
1154
1189
  }
1155
1190
 
@@ -1162,17 +1197,14 @@ async function help(dependencies) {
1162
1197
  return { exitCode: 0 };
1163
1198
  }
1164
1199
 
1165
- // lib/cli/template-command.ts
1166
- var createTemplateCommand = (templateName) => async (dependencies) => {
1200
+ // lib/cli/commands/implement-task.ts
1201
+ async function implementTask(dependencies) {
1167
1202
  const { context, settings } = dependencies;
1168
1203
  const hooksInstalled = await manageGitHooks(dependencies);
1169
1204
  const vars = templateVariables(settings, hooksInstalled);
1170
- context.stdout(loadTemplate(templateName, vars));
1205
+ context.stdout(`Run \`${vars.bin} focus "<task name>"\` to set your focus and see implementation instructions.`);
1171
1206
  return { exitCode: 0 };
1172
- };
1173
-
1174
- // lib/cli/commands/implement-task.ts
1175
- var implementTask = createTemplateCommand("agent-implement-task");
1207
+ }
1176
1208
 
1177
1209
  // lib/cli/colors.ts
1178
1210
  var ANSI_COLORS = {
@@ -1835,26 +1867,22 @@ function extractBlockedBy(content) {
1835
1867
  }
1836
1868
  return blockers;
1837
1869
  }
1838
- async function next(dependencies) {
1839
- const { context, fileSystem } = dependencies;
1840
- const dustPath = `${context.cwd}/.dust`;
1841
- const colors = getColors();
1870
+ async function findUnblockedTasks(cwd, fileSystem) {
1871
+ const dustPath = `${cwd}/.dust`;
1842
1872
  if (!fileSystem.exists(dustPath)) {
1843
- context.stderr("Error: .dust directory not found");
1844
- context.stderr("Run 'dust init' to initialize a Dust repository");
1845
- return { exitCode: 1 };
1873
+ return { error: ".dust directory not found", tasks: [] };
1846
1874
  }
1847
1875
  const tasksPath = `${dustPath}/tasks`;
1848
1876
  if (!fileSystem.exists(tasksPath)) {
1849
- return { exitCode: 0 };
1877
+ return { tasks: [] };
1850
1878
  }
1851
1879
  const files = await fileSystem.readdir(tasksPath);
1852
1880
  const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
1853
1881
  if (mdFiles.length === 0) {
1854
- return { exitCode: 0 };
1882
+ return { tasks: [] };
1855
1883
  }
1856
1884
  const existingTasks = new Set(mdFiles);
1857
- const unblockedTasks = [];
1885
+ const tasks = [];
1858
1886
  for (const file of mdFiles) {
1859
1887
  const filePath = `${tasksPath}/${file}`;
1860
1888
  const content = await fileSystem.readFile(filePath);
@@ -1864,15 +1892,16 @@ async function next(dependencies) {
1864
1892
  const title = extractTitle(content);
1865
1893
  const openingSentence = extractOpeningSentence(content);
1866
1894
  const relativePath = `.dust/tasks/${file}`;
1867
- unblockedTasks.push({ path: relativePath, title, openingSentence });
1895
+ tasks.push({ path: relativePath, title, openingSentence });
1868
1896
  }
1869
1897
  }
1870
- if (unblockedTasks.length === 0) {
1871
- return { exitCode: 0 };
1872
- }
1898
+ return { tasks };
1899
+ }
1900
+ function printTaskList(context, tasks) {
1901
+ const colors = getColors();
1873
1902
  context.stdout("\uD83D\uDCCB Next tasks");
1874
1903
  context.stdout("");
1875
- for (const task of unblockedTasks) {
1904
+ for (const task of tasks) {
1876
1905
  const parts = task.path.split("/");
1877
1906
  const displayTitle = task.title || parts[parts.length - 1].replace(".md", "");
1878
1907
  context.stdout(`${colors.bold}# ${displayTitle}${colors.reset}`);
@@ -1882,6 +1911,19 @@ async function next(dependencies) {
1882
1911
  context.stdout(`${colors.cyan}→ ${task.path}${colors.reset}`);
1883
1912
  context.stdout("");
1884
1913
  }
1914
+ }
1915
+ async function next(dependencies) {
1916
+ const { context, fileSystem } = dependencies;
1917
+ const result = await findUnblockedTasks(context.cwd, fileSystem);
1918
+ if (result.error) {
1919
+ context.stderr(`Error: ${result.error}`);
1920
+ context.stderr("Run 'dust init' to initialize a Dust repository");
1921
+ return { exitCode: 1 };
1922
+ }
1923
+ if (result.tasks.length === 0) {
1924
+ return { exitCode: 0 };
1925
+ }
1926
+ printTaskList(context, result.tasks);
1885
1927
  return { exitCode: 0 };
1886
1928
  }
1887
1929
 
@@ -2113,6 +2155,15 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
2113
2155
  return { exitCode: 0 };
2114
2156
  }
2115
2157
 
2158
+ // lib/cli/template-command.ts
2159
+ var createTemplateCommand = (templateName) => async (dependencies) => {
2160
+ const { context, settings } = dependencies;
2161
+ const hooksInstalled = await manageGitHooks(dependencies);
2162
+ const vars = templateVariables(settings, hooksInstalled);
2163
+ context.stdout(loadTemplate(templateName, vars));
2164
+ return { exitCode: 0 };
2165
+ };
2166
+
2116
2167
  // lib/cli/commands/new-goal.ts
2117
2168
  var newGoal = createTemplateCommand("agent-new-goal");
2118
2169
 
@@ -2123,7 +2174,26 @@ var newIdea = createTemplateCommand("agent-new-idea");
2123
2174
  var newTask = createTemplateCommand("agent-new-task");
2124
2175
 
2125
2176
  // lib/cli/commands/pick-task.ts
2126
- var pickTask = createTemplateCommand("agent-pick-task");
2177
+ async function pickTask(dependencies) {
2178
+ const { context, fileSystem, settings } = dependencies;
2179
+ await manageGitHooks(dependencies);
2180
+ const result = await findUnblockedTasks(context.cwd, fileSystem);
2181
+ if (result.error) {
2182
+ context.stderr(`Error: ${result.error}`);
2183
+ context.stderr("Run 'dust init' to initialize a Dust repository");
2184
+ return { exitCode: 1 };
2185
+ }
2186
+ if (result.tasks.length === 0) {
2187
+ context.stdout("No unblocked tasks found.");
2188
+ return { exitCode: 0 };
2189
+ }
2190
+ context.stdout("## Pick a Task");
2191
+ context.stdout("");
2192
+ printTaskList(context, result.tasks);
2193
+ const vars = templateVariables(settings, false);
2194
+ context.stdout(`Pick ONE task, read its file to understand the requirements, then run \`${vars.bin} focus "<task name>"\`.`);
2195
+ return { exitCode: 0 };
2196
+ }
2127
2197
 
2128
2198
  // lib/cli/commands/pre-push.ts
2129
2199
  function parseGitDiffNameStatus(output) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshski/dust",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "description": "Flow state for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,7 @@ Determine the user's intent and run the matching command NOW:
13
13
  1. **Pick up work from the backlog** → `{{bin}} pick task`
14
14
  User wants to start working. Examples: "work", "go", "pick a task", "what's next?"
15
15
 
16
- 2. **Implement a specific task** → `{{bin}} implement task`
16
+ 2. **Implement a specific task** → `{{bin}} focus "<task name>"`
17
17
  User mentions a particular task by name. Examples: "implement the auth task", "work on caching"
18
18
 
19
19
  3. **Capture a new task** → `{{bin}} new task`
@@ -13,7 +13,7 @@ Determine the user's intent and run the matching command NOW:
13
13
  1. **Pick up work from the backlog** → `{{bin}} pick task`
14
14
  User wants to start working. Examples: "work", "go", "pick a task", "what's next?"
15
15
 
16
- 2. **Implement a specific task** → `{{bin}} implement task`
16
+ 2. **Implement a specific task** → `{{bin}} focus "<task name>"`
17
17
  User mentions a particular task by name. Examples: "implement the auth task", "work on caching"
18
18
 
19
19
  3. **Capture a new task** → `{{bin}} new task`