@joshski/dust 0.1.69 → 0.1.70

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.
@@ -3,9 +3,9 @@ import { type Fact } from './facts';
3
3
  import { type Idea, type IdeaOpenQuestion, type IdeaOption, parseOpenQuestions } from './ideas';
4
4
  import { type Principle } from './principles';
5
5
  import { type Task } from './tasks';
6
- import { CAPTURE_IDEA_PREFIX, type CreateIdeaTransitionTaskResult, type DecomposeIdeaOptions, findAllCaptureIdeaTasks, type IdeaInProgress, type OpenQuestionResponse, type ParsedCaptureIdeaTask, type WorkflowTaskMatch } from './workflow-tasks';
7
- export type { CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedCaptureIdeaTask, Principle, Task, WorkflowTaskMatch, };
8
- export { CAPTURE_IDEA_PREFIX, findAllCaptureIdeaTasks, parseOpenQuestions };
6
+ import { type AllWorkflowTasks, CAPTURE_IDEA_PREFIX, type CreateIdeaTransitionTaskResult, type DecomposeIdeaOptions, findAllCaptureIdeaTasks, findAllWorkflowTasks, type IdeaInProgress, type OpenQuestionResponse, type ParsedCaptureIdeaTask, type WorkflowTaskMatch } from './workflow-tasks';
7
+ export type { AllWorkflowTasks, CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedCaptureIdeaTask, Principle, Task, WorkflowTaskMatch, };
8
+ export { CAPTURE_IDEA_PREFIX, findAllCaptureIdeaTasks, findAllWorkflowTasks, parseOpenQuestions, };
9
9
  export type { IdeaInProgress };
10
10
  export interface ArtifactsRepository {
11
11
  parseIdea(options: {
@@ -1,7 +1,7 @@
1
1
  import type { FileSystem, ReadableFileSystem } from '../filesystem/types';
2
2
  export declare const IDEA_TRANSITION_PREFIXES: string[];
3
3
  export declare const CAPTURE_IDEA_PREFIX = "Add Idea: ";
4
- export declare const BUILD_IDEA_PREFIX = "Build Idea: ";
4
+ export declare const BUILD_IDEA_PREFIX = "Decompose Idea: ";
5
5
  export interface IdeaInProgress {
6
6
  taskSlug: string;
7
7
  ideaTitle: string;
@@ -28,6 +28,11 @@ export interface WorkflowTaskMatch {
28
28
  ideaSlug: string;
29
29
  taskSlug: string;
30
30
  }
31
+ export interface AllWorkflowTasks {
32
+ captureIdeaTasks: IdeaInProgress[];
33
+ workflowTasksByIdeaSlug: Map<string, WorkflowTaskMatch>;
34
+ }
35
+ export declare function findAllWorkflowTasks(fileSystem: ReadableFileSystem, dustPath: string): Promise<AllWorkflowTasks>;
31
36
  export declare function findWorkflowTaskForIdea(fileSystem: ReadableFileSystem, dustPath: string, ideaSlug: string): Promise<WorkflowTaskMatch | null>;
32
37
  export interface CreateIdeaTransitionTaskResult {
33
38
  filePath: string;
package/dist/artifacts.js CHANGED
@@ -272,7 +272,7 @@ async function parseTask(fileSystem, dustPath, slug) {
272
272
 
273
273
  // lib/artifacts/workflow-tasks.ts
274
274
  var CAPTURE_IDEA_PREFIX = "Add Idea: ";
275
- var BUILD_IDEA_PREFIX = "Build Idea: ";
275
+ var BUILD_IDEA_PREFIX = "Decompose Idea: ";
276
276
  async function findAllCaptureIdeaTasks(fileSystem, dustPath) {
277
277
  const tasksPath = `${dustPath}/tasks`;
278
278
  if (!fileSystem.exists(tasksPath))
@@ -290,7 +290,7 @@ async function findAllCaptureIdeaTasks(fileSystem, dustPath) {
290
290
  taskSlug: file.replace(/\.md$/, ""),
291
291
  ideaTitle: title.slice(CAPTURE_IDEA_PREFIX.length)
292
292
  });
293
- } else if (title.startsWith(BUILD_IDEA_PREFIX)) {
293
+ } else if (title.startsWith(BUILD_IDEA_PREFIX) && !content.includes("## Decomposes Idea")) {
294
294
  results.push({
295
295
  taskSlug: file.replace(/\.md$/, ""),
296
296
  ideaTitle: title.slice(BUILD_IDEA_PREFIX.length)
@@ -331,6 +331,45 @@ function extractIdeaSlugFromSection(content, sectionHeading) {
331
331
  }
332
332
  return null;
333
333
  }
334
+ async function findAllWorkflowTasks(fileSystem, dustPath) {
335
+ const tasksPath = `${dustPath}/tasks`;
336
+ const captureIdeaTasks = [];
337
+ const workflowTasksByIdeaSlug = new Map;
338
+ if (!fileSystem.exists(tasksPath)) {
339
+ return { captureIdeaTasks, workflowTasksByIdeaSlug };
340
+ }
341
+ const files = await fileSystem.readdir(tasksPath);
342
+ for (const file of files.filter((f) => f.endsWith(".md")).sort()) {
343
+ const content = await fileSystem.readFile(`${tasksPath}/${file}`);
344
+ const titleMatch = content.match(/^#\s+(.+)$/m);
345
+ if (!titleMatch)
346
+ continue;
347
+ const title = titleMatch[1].trim();
348
+ const taskSlug = file.replace(/\.md$/, "");
349
+ if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
350
+ captureIdeaTasks.push({
351
+ taskSlug,
352
+ ideaTitle: title.slice(CAPTURE_IDEA_PREFIX.length)
353
+ });
354
+ } else if (title.startsWith(BUILD_IDEA_PREFIX) && !content.includes("## Decomposes Idea")) {
355
+ captureIdeaTasks.push({
356
+ taskSlug,
357
+ ideaTitle: title.slice(BUILD_IDEA_PREFIX.length)
358
+ });
359
+ }
360
+ for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
361
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
362
+ if (linkedSlug) {
363
+ workflowTasksByIdeaSlug.set(linkedSlug, {
364
+ type,
365
+ ideaSlug: linkedSlug,
366
+ taskSlug
367
+ });
368
+ }
369
+ }
370
+ }
371
+ return { captureIdeaTasks, workflowTasksByIdeaSlug };
372
+ }
334
373
  async function findWorkflowTaskForIdea(fileSystem, dustPath, ideaSlug) {
335
374
  const ideaPath = `${dustPath}/ideas/${ideaSlug}.md`;
336
375
  if (!fileSystem.exists(ideaPath)) {
@@ -652,6 +691,7 @@ function buildReadOnlyArtifactsRepository(fileSystem, dustPath) {
652
691
  }
653
692
  export {
654
693
  parseOpenQuestions,
694
+ findAllWorkflowTasks,
655
695
  findAllCaptureIdeaTasks,
656
696
  buildReadOnlyArtifactsRepository,
657
697
  buildArtifactsRepository,
package/dist/audits.js CHANGED
@@ -614,6 +614,62 @@ function errorHandling() {
614
614
  - [ ] Proposed ideas for any error handling improvements identified
615
615
  `;
616
616
  }
617
+ function globalState() {
618
+ return dedent`
619
+ # Global State
620
+
621
+ Find global state and singletons that introduce hidden coupling and hurt testability.
622
+
623
+ ${ideasHint}
624
+
625
+ ## Scope
626
+
627
+ Focus on these patterns:
628
+
629
+ 1. **Module-level mutable variables** - Variables declared outside functions that can be modified
630
+ 2. **Singleton patterns** - Classes or objects that enforce a single instance (getInstance, static instance fields)
631
+ 3. **Global registries** - Maps, arrays, or sets that accumulate state across module loads
632
+ 4. **Implicit dependencies** - Functions that read from or write to module-level state instead of using parameters
633
+ 5. **Shared configuration objects** - Mutable config objects imported and modified by multiple modules
634
+ 6. **Lazy initialization with caching** - Values computed once and stored at module level
635
+
636
+ ## Analysis Steps
637
+
638
+ 1. Search for \`let\` and \`var\` declarations at module level (outside functions/classes)
639
+ 2. Look for singleton patterns: \`getInstance\`, \`static instance\`, \`export const instance\`
640
+ 3. Find module-level \`Map\`, \`Set\`, \`Array\`, or \`Object\` that gets modified
641
+ 4. Identify functions that reference module-level variables not passed as parameters
642
+ 5. Check for \`process.env\` access scattered throughout the codebase instead of centralized
643
+
644
+ ## Output
645
+
646
+ For each global state instance identified, provide:
647
+ - **Location** - File path and line number
648
+ - **Pattern** - Which category (mutable variable, singleton, registry, etc.)
649
+ - **Impact** - How this affects testability or coupling (e.g., "Tests must reset this state", "Cannot run tests in parallel")
650
+ - **Suggestion** - How to refactor (e.g., "Pass as dependency", "Use factory function", "Move to function scope")
651
+
652
+ ## Principles
653
+
654
+ - [Dependency Injection](../principles/dependency-injection.md) - Dependencies should be passed in, not accessed globally
655
+ - [Decoupled Code](../principles/decoupled-code.md) - Code should be organized into independent units
656
+ - [Test Isolation](../principles/test-isolation.md) - Tests should not affect each other
657
+
658
+ ## Blocked By
659
+
660
+ (none)
661
+
662
+ ## Definition of Done
663
+
664
+ - [ ] Searched for module-level mutable variables (let/var outside functions)
665
+ - [ ] Identified singleton patterns and getInstance methods
666
+ - [ ] Found global registries (Maps, Sets, Arrays modified at module level)
667
+ - [ ] Located functions with implicit dependencies on module-level state
668
+ - [ ] Checked for scattered process.env access
669
+ - [ ] Documented impact of each global state instance on testing
670
+ - [ ] Proposed ideas for refactoring global state to explicit dependencies
671
+ `;
672
+ }
617
673
  function ubiquitousLanguage() {
618
674
  return dedent`
619
675
  # Ubiquitous Language
@@ -675,6 +731,7 @@ var stockAuditFunctions = {
675
731
  "dead-code": deadCode,
676
732
  "error-handling": errorHandling,
677
733
  "facts-verification": factsVerification,
734
+ "global-state": globalState,
678
735
  "ideas-from-commits": ideasFromCommits,
679
736
  "ideas-from-principles": ideasFromPrinciples,
680
737
  "performance-review": performanceReview,
package/dist/dust.js CHANGED
@@ -275,7 +275,7 @@ async function loadSettings(cwd, fileSystem) {
275
275
  }
276
276
 
277
277
  // lib/version.ts
278
- var DUST_VERSION = "0.1.69";
278
+ var DUST_VERSION = "0.1.70";
279
279
 
280
280
  // lib/session.ts
281
281
  var DUST_UNATTENDED = "DUST_UNATTENDED";
@@ -537,6 +537,8 @@ ${vars.agentInstructions}` : "";
537
537
  6. **Unclear** → \`${vars.bin} help\`
538
538
  If none of the above clearly apply, run this to see all available commands.
539
539
 
540
+ Note: "tasks" here refers to dust task files in \`.dust/tasks/\`, not internal task tracking tools.
541
+
540
542
  Do NOT proceed without running one of these commands.${instructions}
541
543
  `;
542
544
  }
@@ -1159,6 +1161,62 @@ function errorHandling() {
1159
1161
  - [ ] Proposed ideas for any error handling improvements identified
1160
1162
  `;
1161
1163
  }
1164
+ function globalState() {
1165
+ return dedent`
1166
+ # Global State
1167
+
1168
+ Find global state and singletons that introduce hidden coupling and hurt testability.
1169
+
1170
+ ${ideasHint}
1171
+
1172
+ ## Scope
1173
+
1174
+ Focus on these patterns:
1175
+
1176
+ 1. **Module-level mutable variables** - Variables declared outside functions that can be modified
1177
+ 2. **Singleton patterns** - Classes or objects that enforce a single instance (getInstance, static instance fields)
1178
+ 3. **Global registries** - Maps, arrays, or sets that accumulate state across module loads
1179
+ 4. **Implicit dependencies** - Functions that read from or write to module-level state instead of using parameters
1180
+ 5. **Shared configuration objects** - Mutable config objects imported and modified by multiple modules
1181
+ 6. **Lazy initialization with caching** - Values computed once and stored at module level
1182
+
1183
+ ## Analysis Steps
1184
+
1185
+ 1. Search for \`let\` and \`var\` declarations at module level (outside functions/classes)
1186
+ 2. Look for singleton patterns: \`getInstance\`, \`static instance\`, \`export const instance\`
1187
+ 3. Find module-level \`Map\`, \`Set\`, \`Array\`, or \`Object\` that gets modified
1188
+ 4. Identify functions that reference module-level variables not passed as parameters
1189
+ 5. Check for \`process.env\` access scattered throughout the codebase instead of centralized
1190
+
1191
+ ## Output
1192
+
1193
+ For each global state instance identified, provide:
1194
+ - **Location** - File path and line number
1195
+ - **Pattern** - Which category (mutable variable, singleton, registry, etc.)
1196
+ - **Impact** - How this affects testability or coupling (e.g., "Tests must reset this state", "Cannot run tests in parallel")
1197
+ - **Suggestion** - How to refactor (e.g., "Pass as dependency", "Use factory function", "Move to function scope")
1198
+
1199
+ ## Principles
1200
+
1201
+ - [Dependency Injection](../principles/dependency-injection.md) - Dependencies should be passed in, not accessed globally
1202
+ - [Decoupled Code](../principles/decoupled-code.md) - Code should be organized into independent units
1203
+ - [Test Isolation](../principles/test-isolation.md) - Tests should not affect each other
1204
+
1205
+ ## Blocked By
1206
+
1207
+ (none)
1208
+
1209
+ ## Definition of Done
1210
+
1211
+ - [ ] Searched for module-level mutable variables (let/var outside functions)
1212
+ - [ ] Identified singleton patterns and getInstance methods
1213
+ - [ ] Found global registries (Maps, Sets, Arrays modified at module level)
1214
+ - [ ] Located functions with implicit dependencies on module-level state
1215
+ - [ ] Checked for scattered process.env access
1216
+ - [ ] Documented impact of each global state instance on testing
1217
+ - [ ] Proposed ideas for refactoring global state to explicit dependencies
1218
+ `;
1219
+ }
1162
1220
  function ubiquitousLanguage() {
1163
1221
  return dedent`
1164
1222
  # Ubiquitous Language
@@ -1220,6 +1278,7 @@ var stockAuditFunctions = {
1220
1278
  "dead-code": deadCode,
1221
1279
  "error-handling": errorHandling,
1222
1280
  "facts-verification": factsVerification,
1281
+ "global-state": globalState,
1223
1282
  "ideas-from-commits": ideasFromCommits,
1224
1283
  "ideas-from-principles": ideasFromPrinciples,
1225
1284
  "performance-review": performanceReview,
@@ -2226,7 +2285,7 @@ var IDEA_TRANSITION_PREFIXES = [
2226
2285
  "Decompose Idea: ",
2227
2286
  "Shelve Idea: "
2228
2287
  ];
2229
- var BUILD_IDEA_PREFIX = "Build Idea: ";
2288
+ var BUILD_IDEA_PREFIX = "Decompose Idea: ";
2230
2289
  function titleToFilename(title) {
2231
2290
  return `${title.toLowerCase().replace(/\./g, "-").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}.md`;
2232
2291
  }
@@ -3979,14 +4038,15 @@ function createDefaultUploadDependencies() {
3979
4038
  const file = Bun.file(path);
3980
4039
  return file.exists();
3981
4040
  },
3982
- uploadFile: async (url, token, fileBytes, contentType) => {
4041
+ uploadFile: async (url, token, fileBytes, contentType, fileName) => {
4042
+ const formData = new FormData;
4043
+ formData.append("file", new Blob([fileBytes.buffer], { type: contentType }), fileName);
3983
4044
  const response = await fetch(url, {
3984
4045
  method: "POST",
3985
4046
  headers: {
3986
- Authorization: `Bearer ${token}`,
3987
- "Content-Type": contentType
4047
+ Authorization: `Bearer ${token}`
3988
4048
  },
3989
- body: new Blob([fileBytes])
4049
+ body: formData
3990
4050
  });
3991
4051
  if (!response.ok) {
3992
4052
  const text = await response.text();
@@ -4071,9 +4131,10 @@ async function bucketAssetUpload(dependencies, uploadDeps = createDefaultUploadD
4071
4131
  }
4072
4132
  const fileBytes = await uploadDeps.readFileBytes(filePath);
4073
4133
  const contentType = getContentType(filePath);
4134
+ const fileName = filePath.split("/").pop();
4074
4135
  const uploadUrl = `${getDustbucketHost()}/api/assets?repositoryId=${encodeURIComponent(repositoryId)}`;
4075
4136
  try {
4076
- const result = await uploadDeps.uploadFile(uploadUrl, token, fileBytes, contentType);
4137
+ const result = await uploadDeps.uploadFile(uploadUrl, token, fileBytes, contentType, fileName);
4077
4138
  context.stdout(result.url);
4078
4139
  return { exitCode: 0 };
4079
4140
  } catch (error) {
@@ -5018,7 +5079,7 @@ async function runSingleCheck(check, cwd, runner) {
5018
5079
  output: result.output,
5019
5080
  hints: check.hints,
5020
5081
  durationMs,
5021
- timedOut: result.timedOut,
5082
+ timedOut: result.timedOut ?? false,
5022
5083
  timeoutSeconds: timeoutMs / 1000
5023
5084
  };
5024
5085
  }
@@ -5057,7 +5118,8 @@ async function runValidationCheck(dependencies) {
5057
5118
  output: outputLines.join(`
5058
5119
  `),
5059
5120
  isBuiltIn: true,
5060
- durationMs
5121
+ durationMs,
5122
+ timedOut: false
5061
5123
  };
5062
5124
  }
5063
5125
  function displayResults(results, context) {
@@ -5067,7 +5129,7 @@ function displayResults(results, context) {
5067
5129
  if (result.timedOut) {
5068
5130
  context.stdout(`✗ ${result.name} [timed out after ${result.timeoutSeconds}s]`);
5069
5131
  } else {
5070
- const timing = result.durationMs !== undefined && result.durationMs >= 1000 ? ` [${(result.durationMs / 1000).toFixed(1)}s]` : "";
5132
+ const timing = result.durationMs >= 1000 ? ` [${(result.durationMs / 1000).toFixed(1)}s]` : "";
5071
5133
  if (result.exitCode === 0) {
5072
5134
  context.stdout(`✓ ${result.name}${timing}`);
5073
5135
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshski/dust",
3
- "version": "0.1.69",
3
+ "version": "0.1.70",
4
4
  "description": "Flow state for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {