@joshski/dust 0.1.65 → 0.1.68
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/README.md +10 -0
- package/dist/artifacts.js +69 -23
- package/dist/audits/index.d.ts +5 -0
- package/dist/audits/stock-audits.d.ts +2 -1
- package/dist/audits.js +278 -1
- package/dist/bucket/events.d.ts +50 -0
- package/dist/bucket/log-buffer.d.ts +40 -0
- package/dist/bucket/paths.d.ts +26 -0
- package/dist/bucket/repository-git.d.ts +22 -0
- package/dist/bucket/repository-loop.d.ts +45 -0
- package/dist/bucket/repository.d.ts +70 -0
- package/dist/claude/event-parser.d.ts +2 -0
- package/dist/claude/run.d.ts +15 -0
- package/dist/claude/spawn-claude-code.d.ts +9 -0
- package/dist/claude/streamer.d.ts +17 -0
- package/dist/claude/tool-formatters.d.ts +13 -0
- package/dist/claude/types.d.ts +61 -0
- package/dist/cli/colors.d.ts +25 -0
- package/dist/cli/commands/agent-shared.d.ts +44 -0
- package/dist/cli/commands/focus.d.ts +11 -0
- package/dist/cli/commands/loop.d.ts +92 -0
- package/dist/cli/commands/next.d.ts +25 -0
- package/dist/cli/types.d.ts +1 -1
- package/dist/config/settings.d.ts +44 -0
- package/dist/dust.js +546 -66
- package/dist/git/hooks.d.ts +17 -0
- package/dist/session.d.ts +7 -0
- package/dist/types.d.ts +1 -0
- package/dist/version.d.ts +1 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -15,6 +15,16 @@ npx dust init
|
|
|
15
15
|
|
|
16
16
|
This creates a [.dust](./.dust/facts/dust-directory-structure.md) directory and adds an [instruction](./.dust/facts/agents-md-instruction.md) to your `AGENTS.md` file.
|
|
17
17
|
|
|
18
|
+
## Adding Tasks
|
|
19
|
+
|
|
20
|
+
Use your AI coding CLI (Claude Code, Codex, etc.) to add and refine tasks:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
claude "add a task to refactor the auth module"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Ideas (`.dust/ideas/`) are backlog items you may or may not do later. Tasks (`.dust/tasks/`) are ready to work on now. Both are markdown files that agents and humans can read and edit.
|
|
27
|
+
|
|
18
28
|
## Running Agents
|
|
19
29
|
|
|
20
30
|
Start an agent on a single task:
|
package/dist/artifacts.js
CHANGED
|
@@ -302,19 +302,53 @@ async function findAllCaptureIdeaTasks(fileSystem, dustPath) {
|
|
|
302
302
|
function titleToFilename(title) {
|
|
303
303
|
return `${title.toLowerCase().replace(/\./g, "-").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}.md`;
|
|
304
304
|
}
|
|
305
|
-
var
|
|
306
|
-
{ type: "refine",
|
|
307
|
-
{ type: "decompose-idea",
|
|
308
|
-
{ type: "shelve",
|
|
305
|
+
var WORKFLOW_SECTION_HEADINGS = [
|
|
306
|
+
{ type: "refine", heading: "Refines Idea" },
|
|
307
|
+
{ type: "decompose-idea", heading: "Decomposes Idea" },
|
|
308
|
+
{ type: "shelve", heading: "Shelves Idea" }
|
|
309
309
|
];
|
|
310
|
+
function extractIdeaSlugFromSection(content, sectionHeading) {
|
|
311
|
+
const lines = content.split(`
|
|
312
|
+
`);
|
|
313
|
+
let inSection = false;
|
|
314
|
+
for (const line of lines) {
|
|
315
|
+
if (line.startsWith("## ")) {
|
|
316
|
+
inSection = line.trimEnd() === `## ${sectionHeading}`;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (!inSection)
|
|
320
|
+
continue;
|
|
321
|
+
if (line.startsWith("# "))
|
|
322
|
+
break;
|
|
323
|
+
const linkMatch = line.match(MARKDOWN_LINK_PATTERN);
|
|
324
|
+
if (linkMatch) {
|
|
325
|
+
const target = linkMatch[2];
|
|
326
|
+
const slugMatch = target.match(/([^/]+)\.md$/);
|
|
327
|
+
if (slugMatch) {
|
|
328
|
+
return slugMatch[1];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
310
334
|
async function findWorkflowTaskForIdea(fileSystem, dustPath, ideaSlug) {
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
335
|
+
const ideaPath = `${dustPath}/ideas/${ideaSlug}.md`;
|
|
336
|
+
if (!fileSystem.exists(ideaPath)) {
|
|
337
|
+
throw new Error(`Idea not found: "${ideaSlug}" (expected file at ${ideaPath})`);
|
|
338
|
+
}
|
|
339
|
+
const tasksPath = `${dustPath}/tasks`;
|
|
340
|
+
if (!fileSystem.exists(tasksPath)) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
const files = await fileSystem.readdir(tasksPath);
|
|
344
|
+
for (const file of files.filter((f) => f.endsWith(".md")).sort()) {
|
|
345
|
+
const content = await fileSystem.readFile(`${tasksPath}/${file}`);
|
|
346
|
+
for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
|
|
347
|
+
const linkedSlug = extractIdeaSlugFromSection(content, heading);
|
|
348
|
+
if (linkedSlug === ideaSlug) {
|
|
349
|
+
const taskSlug = file.replace(/\.md$/, "");
|
|
350
|
+
return { type, ideaSlug, taskSlug };
|
|
351
|
+
}
|
|
318
352
|
}
|
|
319
353
|
}
|
|
320
354
|
return null;
|
|
@@ -342,18 +376,26 @@ ${sections.join(`
|
|
|
342
376
|
`)}
|
|
343
377
|
`;
|
|
344
378
|
}
|
|
345
|
-
function
|
|
379
|
+
function renderIdeaSection(ideaSection) {
|
|
380
|
+
return `## ${ideaSection.heading}
|
|
381
|
+
|
|
382
|
+
- [${ideaSection.ideaTitle}](../ideas/${ideaSection.ideaSlug}.md)
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
function renderTask(title, openingSentence, definitionOfDone, ideaSection, options) {
|
|
346
386
|
const descriptionParagraph = options?.description !== undefined ? `
|
|
347
387
|
${options.description}
|
|
348
388
|
` : "";
|
|
349
389
|
const resolvedSection = options?.resolvedQuestions && options.resolvedQuestions.length > 0 ? `
|
|
350
390
|
${renderResolvedQuestions(options.resolvedQuestions)}
|
|
351
391
|
` : "";
|
|
392
|
+
const ideaSectionContent = `
|
|
393
|
+
${renderIdeaSection(ideaSection)}
|
|
394
|
+
`;
|
|
352
395
|
return `# ${title}
|
|
353
396
|
|
|
354
397
|
${openingSentence}
|
|
355
|
-
${descriptionParagraph}${resolvedSection}
|
|
356
|
-
## Blocked By
|
|
398
|
+
${descriptionParagraph}${resolvedSection}${ideaSectionContent}## Blocked By
|
|
357
399
|
|
|
358
400
|
(none)
|
|
359
401
|
|
|
@@ -363,13 +405,17 @@ ${definitionOfDone.map((item) => `- [ ] ${item}`).join(`
|
|
|
363
405
|
`)}
|
|
364
406
|
`;
|
|
365
407
|
}
|
|
366
|
-
async function createIdeaTask(fileSystem, dustPath, prefix, ideaSlug, openingSentenceTemplate, definitionOfDone, taskOptions) {
|
|
408
|
+
async function createIdeaTask(fileSystem, dustPath, prefix, ideaSlug, openingSentenceTemplate, definitionOfDone, ideaSectionHeading, taskOptions) {
|
|
367
409
|
const ideaTitle = await readIdeaTitle(fileSystem, dustPath, ideaSlug);
|
|
368
410
|
const taskTitle = `${prefix}${ideaTitle}`;
|
|
369
411
|
const filename = titleToFilename(taskTitle);
|
|
370
412
|
const filePath = `${dustPath}/tasks/${filename}`;
|
|
371
413
|
const openingSentence = openingSentenceTemplate(ideaTitle);
|
|
372
|
-
const
|
|
414
|
+
const ideaSection = { heading: ideaSectionHeading, ideaTitle, ideaSlug };
|
|
415
|
+
const content = renderTask(taskTitle, openingSentence, definitionOfDone, ideaSection, {
|
|
416
|
+
description: taskOptions?.description,
|
|
417
|
+
resolvedQuestions: taskOptions?.resolvedQuestions
|
|
418
|
+
});
|
|
373
419
|
await fileSystem.writeFile(filePath, content);
|
|
374
420
|
return { filePath };
|
|
375
421
|
}
|
|
@@ -379,20 +425,20 @@ async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description)
|
|
|
379
425
|
"Open questions are added for any ambiguous or underspecified aspects",
|
|
380
426
|
"Open questions follow the required heading format and focus on high-value decisions",
|
|
381
427
|
"Idea file is updated with findings"
|
|
382
|
-
], { description });
|
|
428
|
+
], "Refines Idea", { description });
|
|
383
429
|
}
|
|
384
430
|
async function decomposeIdea(fileSystem, dustPath, options) {
|
|
385
431
|
return createIdeaTask(fileSystem, dustPath, "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. Review \`.dust/principles/\` to link relevant principles and \`.dust/facts/\` for design decisions that should inform the task. See [${ideaTitle}](../ideas/${options.ideaSlug}.md).`, [
|
|
386
432
|
"One or more new tasks are created in .dust/tasks/",
|
|
387
433
|
"Task's Principles section links to relevant principles from .dust/principles/",
|
|
388
434
|
"The original idea is deleted or updated to reflect remaining scope"
|
|
389
|
-
], {
|
|
435
|
+
], "Decomposes Idea", {
|
|
390
436
|
description: options.description,
|
|
391
437
|
resolvedQuestions: options.openQuestionResponses
|
|
392
438
|
});
|
|
393
439
|
}
|
|
394
440
|
async function createShelveIdeaTask(fileSystem, dustPath, ideaSlug, description) {
|
|
395
|
-
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 });
|
|
441
|
+
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"], "Shelves Idea", { description });
|
|
396
442
|
}
|
|
397
443
|
async function createCaptureIdeaTask(fileSystem, dustPath, options) {
|
|
398
444
|
const { title, description, buildItNow } = options;
|
|
@@ -408,7 +454,7 @@ async function createCaptureIdeaTask(fileSystem, dustPath, options) {
|
|
|
408
454
|
const filePath2 = `${dustPath}/tasks/${filename2}`;
|
|
409
455
|
const content2 = `# ${taskTitle2}
|
|
410
456
|
|
|
411
|
-
Research this idea
|
|
457
|
+
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/\`. Review \`.dust/principles/\` and \`.dust/facts/\` for relevant context.
|
|
412
458
|
|
|
413
459
|
## Idea Description
|
|
414
460
|
|
|
@@ -420,9 +466,9 @@ ${description}
|
|
|
420
466
|
|
|
421
467
|
## Definition of Done
|
|
422
468
|
|
|
423
|
-
- [ ]
|
|
424
|
-
- [ ]
|
|
425
|
-
- [ ]
|
|
469
|
+
- [ ] Idea is implemented directly OR one or more new tasks are created in \`.dust/tasks/\`
|
|
470
|
+
- [ ] If tasks were created, they link to relevant principles from \`.dust/principles/\`
|
|
471
|
+
- [ ] Changes are committed with a clear commit message
|
|
426
472
|
`;
|
|
427
473
|
await fileSystem.writeFile(filePath2, content2);
|
|
428
474
|
return { filePath: filePath2 };
|
package/dist/audits/index.d.ts
CHANGED
|
@@ -33,4 +33,9 @@ export interface AuditsRepository {
|
|
|
33
33
|
* Changes the title from "# Original Title" to "# Audit: Original Title"
|
|
34
34
|
*/
|
|
35
35
|
export declare function transformAuditContent(content: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Injects an Ad-hoc Scope section after the opening description, before ## Scope.
|
|
38
|
+
* The ad-hoc details are passed through without validation.
|
|
39
|
+
*/
|
|
40
|
+
export declare function injectAdHocScope(content: string, adHocDetails: string): string;
|
|
36
41
|
export declare function buildAuditsRepository(fileSystem: FileSystem, dustPath: string): AuditsRepository;
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Users can override any of these by placing a file with the same name
|
|
5
5
|
* in .dust/config/audits/.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
interface StockAudit {
|
|
8
8
|
name: string;
|
|
9
9
|
description: string;
|
|
10
10
|
template: string;
|
|
11
11
|
}
|
|
12
12
|
export declare function loadStockAudits(): StockAudit[];
|
|
13
|
+
export {};
|
package/dist/audits.js
CHANGED
|
@@ -64,6 +64,102 @@ function dedent(strings, ...values) {
|
|
|
64
64
|
|
|
65
65
|
// lib/audits/stock-audits.ts
|
|
66
66
|
var ideasHint = "Review existing ideas in `./.dust/ideas/` to understand what has been proposed or considered historically, then create new idea files in `./.dust/ideas/` for any issues you identify, avoiding duplication.";
|
|
67
|
+
function dataAccessReview() {
|
|
68
|
+
return dedent`
|
|
69
|
+
# Data Access Review
|
|
70
|
+
|
|
71
|
+
Review data access patterns for performance issues and optimization opportunities.
|
|
72
|
+
|
|
73
|
+
${ideasHint}
|
|
74
|
+
|
|
75
|
+
## Scope
|
|
76
|
+
|
|
77
|
+
Focus on these areas:
|
|
78
|
+
|
|
79
|
+
1. **N+1 query patterns** - Loops that make individual data requests instead of batching
|
|
80
|
+
2. **Missing indexes** - Database schema files lacking indexes on frequently queried columns
|
|
81
|
+
3. **Inefficient data loading** - Over-fetching (loading more data than needed) or under-fetching (requiring multiple round trips)
|
|
82
|
+
4. **Caching opportunities** - Repeated lookups that could benefit from memoization or caching
|
|
83
|
+
5. **Batch processing** - Sequential operations that could be parallelized or batched
|
|
84
|
+
6. **Connection management** - Connection pooling configuration and resource cleanup
|
|
85
|
+
|
|
86
|
+
## Analysis Steps
|
|
87
|
+
|
|
88
|
+
1. Search for loops containing data access calls (API requests, database queries, file reads)
|
|
89
|
+
2. Review database schema or migration files for index definitions
|
|
90
|
+
3. Identify functions that make multiple related data requests
|
|
91
|
+
4. Look for repeated identical lookups within the same request lifecycle
|
|
92
|
+
5. Check for proper resource cleanup (connection closing, stream ending)
|
|
93
|
+
|
|
94
|
+
## Applicability
|
|
95
|
+
|
|
96
|
+
This audit applies to codebases that interact with:
|
|
97
|
+
- Databases (SQL, NoSQL, ORM queries)
|
|
98
|
+
- External APIs (REST, GraphQL, gRPC)
|
|
99
|
+
- File systems (reading/writing files)
|
|
100
|
+
- Caches (Redis, Memcached, in-memory)
|
|
101
|
+
|
|
102
|
+
If none of these apply, document that finding and skip the detailed analysis.
|
|
103
|
+
|
|
104
|
+
## Principles
|
|
105
|
+
|
|
106
|
+
- [Decoupled Code](../principles/decoupled-code.md) - Data access should be isolated for testability
|
|
107
|
+
- [Fast Feedback](../principles/fast-feedback.md) - Efficient data access enables faster feedback loops
|
|
108
|
+
- [Maintainable Codebase](../principles/maintainable-codebase.md) - Good data patterns improve maintainability
|
|
109
|
+
|
|
110
|
+
## Blocked By
|
|
111
|
+
|
|
112
|
+
(none)
|
|
113
|
+
|
|
114
|
+
## Definition of Done
|
|
115
|
+
|
|
116
|
+
- [ ] Searched for N+1 query patterns (loops with data access)
|
|
117
|
+
- [ ] Reviewed database schemas for missing indexes (if applicable)
|
|
118
|
+
- [ ] Identified over-fetching or under-fetching patterns
|
|
119
|
+
- [ ] Found repeated lookups that could be cached
|
|
120
|
+
- [ ] Checked for sequential operations that could be batched
|
|
121
|
+
- [ ] Verified connection/resource cleanup is handled properly
|
|
122
|
+
- [ ] Proposed ideas for any data access improvements identified
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
function coverageExclusions() {
|
|
126
|
+
return dedent`
|
|
127
|
+
# Coverage Exclusions
|
|
128
|
+
|
|
129
|
+
Review coverage exclusion configuration to identify opportunities for removal through refactoring.
|
|
130
|
+
|
|
131
|
+
${ideasHint}
|
|
132
|
+
|
|
133
|
+
## Scope
|
|
134
|
+
|
|
135
|
+
Focus on these areas:
|
|
136
|
+
|
|
137
|
+
1. **Current exclusions** - Review all exclusions in \`vitest.config.ts\` or equivalent
|
|
138
|
+
2. **Justification** - Is each exclusion still necessary?
|
|
139
|
+
3. **Tooling limitations** - Can workarounds be found for coverage tool issues?
|
|
140
|
+
4. **Decoupling opportunities** - Can excluded code be restructured to enable testing?
|
|
141
|
+
5. **Entry point patterns** - Can hard-to-test entry points be decoupled from logic?
|
|
142
|
+
|
|
143
|
+
## Principles
|
|
144
|
+
|
|
145
|
+
- [Decoupled Code](../principles/decoupled-code.md)
|
|
146
|
+
- [Unit Test Coverage](../principles/unit-test-coverage.md)
|
|
147
|
+
- [Comprehensive Test Coverage](../principles/comprehensive-test-coverage.md)
|
|
148
|
+
- [Make Changes with Confidence](../principles/make-changes-with-confidence.md)
|
|
149
|
+
|
|
150
|
+
## Blocked By
|
|
151
|
+
|
|
152
|
+
(none)
|
|
153
|
+
|
|
154
|
+
## Definition of Done
|
|
155
|
+
|
|
156
|
+
- [ ] Identified all coverage exclusions in the project
|
|
157
|
+
- [ ] Documented the reason each exclusion exists
|
|
158
|
+
- [ ] Evaluated whether each exclusion is still necessary
|
|
159
|
+
- [ ] Identified exclusions that could be removed through decoupling
|
|
160
|
+
- [ ] Proposed ideas for refactoring where feasible
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
67
163
|
function componentReuse() {
|
|
68
164
|
return dedent`
|
|
69
165
|
# Component Reuse
|
|
@@ -274,6 +370,56 @@ function ideasFromPrinciples() {
|
|
|
274
370
|
- [ ] Proposed new ideas for unmet or underserved principles
|
|
275
371
|
`;
|
|
276
372
|
}
|
|
373
|
+
function refactoringOpportunities() {
|
|
374
|
+
return dedent`
|
|
375
|
+
# Refactoring Opportunities
|
|
376
|
+
|
|
377
|
+
Analyze recent commits to identify code needing structural improvements.
|
|
378
|
+
|
|
379
|
+
${ideasHint}
|
|
380
|
+
|
|
381
|
+
## Scope
|
|
382
|
+
|
|
383
|
+
Analyze commits since the last refactoring-opportunities audit (check \`.dust/done/\` for previous runs). Focus on these signals:
|
|
384
|
+
|
|
385
|
+
1. **File churn** - Files modified frequently across multiple commits may have unclear responsibilities or be accumulating technical debt
|
|
386
|
+
2. **Size growth** - Files that have grown significantly may benefit from decomposition
|
|
387
|
+
3. **Commit message patterns** - Look for messages containing "fix", "workaround", "temporary", "hack", or "TODO" that indicate shortcuts taken
|
|
388
|
+
|
|
389
|
+
## Analysis Steps
|
|
390
|
+
|
|
391
|
+
1. Run \`git log --since="<last-audit-date>" --name-only --pretty=format:"COMMIT:%s"\` to get commits with their messages and changed files
|
|
392
|
+
2. Count file modification frequency to identify high-churn files
|
|
393
|
+
3. Check current sizes of frequently-modified files with \`wc -l\`
|
|
394
|
+
4. Review commit messages for patterns suggesting technical debt
|
|
395
|
+
|
|
396
|
+
## Output
|
|
397
|
+
|
|
398
|
+
For each refactoring opportunity identified, provide:
|
|
399
|
+
- **File path** - The specific file needing attention
|
|
400
|
+
- **Signal** - What triggered this recommendation (churn, size, commit pattern)
|
|
401
|
+
- **Specific suggestion** - A concrete refactoring action (e.g., "Extract the validation logic into a separate module", not just "consider refactoring")
|
|
402
|
+
|
|
403
|
+
## Principles
|
|
404
|
+
|
|
405
|
+
- [Boy Scout Rule](../principles/boy-scout-rule.md) - Leave code better than found, but capture large cleanups as separate tasks
|
|
406
|
+
- [Make the Change Easy](../principles/make-the-change-easy.md) - Refactor until the change becomes straightforward
|
|
407
|
+
- [Make Changes with Confidence](../principles/make-changes-with-confidence.md) - Tests and checks enable safe refactoring
|
|
408
|
+
- [Reasonably DRY](../principles/reasonably-dry.md) - Extract only when duplication represents the same concept
|
|
409
|
+
|
|
410
|
+
## Blocked By
|
|
411
|
+
|
|
412
|
+
(none)
|
|
413
|
+
|
|
414
|
+
## Definition of Done
|
|
415
|
+
|
|
416
|
+
- [ ] Identified high-churn files (modified in 3+ commits since last audit)
|
|
417
|
+
- [ ] Flagged files exceeding 300 lines that grew significantly
|
|
418
|
+
- [ ] Noted commits with concerning message patterns
|
|
419
|
+
- [ ] Provided specific refactoring suggestions for each opportunity
|
|
420
|
+
- [ ] Created ideas for any substantial refactoring work identified
|
|
421
|
+
`;
|
|
422
|
+
}
|
|
277
423
|
function performanceReview() {
|
|
278
424
|
return dedent`
|
|
279
425
|
# Performance Review
|
|
@@ -414,17 +560,129 @@ function testCoverage() {
|
|
|
414
560
|
- [ ] Proposed ideas for any test coverage gaps identified
|
|
415
561
|
`;
|
|
416
562
|
}
|
|
563
|
+
function errorHandling() {
|
|
564
|
+
return dedent`
|
|
565
|
+
# Error Handling
|
|
566
|
+
|
|
567
|
+
Review error handling patterns for consistency and agent-friendliness.
|
|
568
|
+
|
|
569
|
+
${ideasHint}
|
|
570
|
+
|
|
571
|
+
## Scope
|
|
572
|
+
|
|
573
|
+
Focus on these areas:
|
|
574
|
+
|
|
575
|
+
1. **Silently swallowed errors** - Empty catch blocks, \`.catch(() => {})\`, errors caught but not logged or re-thrown
|
|
576
|
+
2. **Missing error context** - Errors converted to booleans or generic messages that lose details
|
|
577
|
+
3. **Fire-and-forget promises** - Promises without \`.catch()\` or \`await\` that may fail silently
|
|
578
|
+
4. **Non-actionable error messages** - Error messages that say what went wrong but not how to fix it
|
|
579
|
+
5. **Inconsistent error recovery** - Similar error scenarios handled differently across the codebase
|
|
580
|
+
|
|
581
|
+
## Analysis Steps
|
|
582
|
+
|
|
583
|
+
1. Search for empty catch blocks: \`catch {}\`, \`catch () {}\`, \`.catch(() => {})\`
|
|
584
|
+
2. Look for patterns that discard error details: \`catch { return false }\`, \`catch { return null }\`
|
|
585
|
+
3. Find promises without error handling: unassigned or not-awaited promises
|
|
586
|
+
4. Review error messages in \`throw\` statements and \`context.stderr()\` calls for actionability
|
|
587
|
+
5. Compare error handling patterns across similar operations for consistency
|
|
588
|
+
|
|
589
|
+
## Output
|
|
590
|
+
|
|
591
|
+
For each error handling issue identified, provide:
|
|
592
|
+
- **Location** - File path and line number
|
|
593
|
+
- **Pattern** - Which category of issue (swallowed, missing context, fire-and-forget, etc.)
|
|
594
|
+
- **Impact** - What failures could go unnoticed or be hard to debug
|
|
595
|
+
- **Suggestion** - Specific fix (add logging, propagate error, add recovery guidance)
|
|
596
|
+
|
|
597
|
+
## Principles
|
|
598
|
+
|
|
599
|
+
- [Actionable Errors](../principles/actionable-errors.md) - Error messages should tell you what to do next
|
|
600
|
+
- [Debugging Tooling](../principles/debugging-tooling.md) - Agents need readable, structured error output
|
|
601
|
+
- [Stop the Line](../principles/stop-the-line.md) - Problems should be fixed at source, not hidden
|
|
602
|
+
|
|
603
|
+
## Blocked By
|
|
604
|
+
|
|
605
|
+
(none)
|
|
606
|
+
|
|
607
|
+
## Definition of Done
|
|
608
|
+
|
|
609
|
+
- [ ] Searched for empty catch blocks and silent error swallowing
|
|
610
|
+
- [ ] Identified patterns that discard error details
|
|
611
|
+
- [ ] Found fire-and-forget promises without error handling
|
|
612
|
+
- [ ] Reviewed error messages for actionability
|
|
613
|
+
- [ ] Compared error handling consistency across similar operations
|
|
614
|
+
- [ ] Proposed ideas for any error handling improvements identified
|
|
615
|
+
`;
|
|
616
|
+
}
|
|
617
|
+
function ubiquitousLanguage() {
|
|
618
|
+
return dedent`
|
|
619
|
+
# Ubiquitous Language
|
|
620
|
+
|
|
621
|
+
Verify terminology consistency across code, documentation, and user interface.
|
|
622
|
+
|
|
623
|
+
${ideasHint}
|
|
624
|
+
|
|
625
|
+
## Scope
|
|
626
|
+
|
|
627
|
+
Focus on these areas:
|
|
628
|
+
|
|
629
|
+
1. **Terminology drift** - Do recent changes introduce terms that deviate from established vocabulary?
|
|
630
|
+
2. **Code-to-docs alignment** - Are variables, functions, and types named consistently with documentation?
|
|
631
|
+
3. **User interface consistency** - Do UI labels and messages match the terms used in code and docs?
|
|
632
|
+
4. **Glossary adherence** - If a glossary exists, is it being followed?
|
|
633
|
+
5. **Acronym and abbreviation usage** - Are shortened forms used consistently?
|
|
634
|
+
|
|
635
|
+
## Analysis Steps
|
|
636
|
+
|
|
637
|
+
1. Identify key domain terms from documentation, README, or existing glossary
|
|
638
|
+
2. Review recent commits for new terminology or naming choices
|
|
639
|
+
3. Compare code identifiers against documented terminology
|
|
640
|
+
4. Check user-facing strings for consistency with technical naming
|
|
641
|
+
5. Flag deviations where the same concept uses different names
|
|
642
|
+
|
|
643
|
+
## Output
|
|
644
|
+
|
|
645
|
+
For each terminology issue identified, provide:
|
|
646
|
+
- **Term in question** - The inconsistent or unclear term
|
|
647
|
+
- **Where found** - File paths and locations where the term appears
|
|
648
|
+
- **Recommended action** - Standardize on existing term, or propose a new canonical name
|
|
649
|
+
|
|
650
|
+
## Principles
|
|
651
|
+
|
|
652
|
+
- [Naming Matters](../principles/naming-matters.md) - Good naming reduces waste by eliminating confusion
|
|
653
|
+
- [Consistent Naming](../principles/consistent-naming.md) - Names should follow established conventions
|
|
654
|
+
- [Clarity Over Brevity](../principles/clarity-over-brevity.md) - Names should be descriptive and self-documenting
|
|
655
|
+
|
|
656
|
+
## Blocked By
|
|
657
|
+
|
|
658
|
+
(none)
|
|
659
|
+
|
|
660
|
+
## Definition of Done
|
|
661
|
+
|
|
662
|
+
- [ ] Identified key domain terms from project documentation
|
|
663
|
+
- [ ] Reviewed recent commits for terminology consistency
|
|
664
|
+
- [ ] Compared code naming against documentation vocabulary
|
|
665
|
+
- [ ] Checked user-facing text for alignment with code terms
|
|
666
|
+
- [ ] Documented any terminology drift or inconsistencies found
|
|
667
|
+
- [ ] Proposed ideas for standardizing inconsistent terminology
|
|
668
|
+
`;
|
|
669
|
+
}
|
|
417
670
|
var stockAuditFunctions = {
|
|
418
671
|
"agent-developer-experience": agentDeveloperExperience,
|
|
419
672
|
"component-reuse": componentReuse,
|
|
673
|
+
"coverage-exclusions": coverageExclusions,
|
|
674
|
+
"data-access-review": dataAccessReview,
|
|
420
675
|
"dead-code": deadCode,
|
|
676
|
+
"error-handling": errorHandling,
|
|
421
677
|
"facts-verification": factsVerification,
|
|
422
678
|
"ideas-from-commits": ideasFromCommits,
|
|
423
679
|
"ideas-from-principles": ideasFromPrinciples,
|
|
424
680
|
"performance-review": performanceReview,
|
|
681
|
+
"refactoring-opportunities": refactoringOpportunities,
|
|
425
682
|
"security-review": securityReview,
|
|
426
683
|
"stale-ideas": staleIdeas,
|
|
427
|
-
"test-coverage": testCoverage
|
|
684
|
+
"test-coverage": testCoverage,
|
|
685
|
+
"ubiquitous-language": ubiquitousLanguage
|
|
428
686
|
};
|
|
429
687
|
function loadStockAudits() {
|
|
430
688
|
return Object.entries(stockAuditFunctions).sort(([a], [b]) => a.localeCompare(b)).map(([name, render]) => {
|
|
@@ -443,6 +701,24 @@ function transformAuditContent(content) {
|
|
|
443
701
|
const originalTitle = titleMatch[1];
|
|
444
702
|
return content.replace(/^#\s+.+$/m, `# Audit: ${originalTitle}`);
|
|
445
703
|
}
|
|
704
|
+
function injectAdHocScope(content, adHocDetails) {
|
|
705
|
+
const scopeMatch = content.match(/\n## Scope\n/);
|
|
706
|
+
if (scopeMatch?.index !== undefined) {
|
|
707
|
+
const insertIndex = scopeMatch.index;
|
|
708
|
+
const adHocSection = `
|
|
709
|
+
## Ad-hoc Scope
|
|
710
|
+
|
|
711
|
+
${adHocDetails}
|
|
712
|
+
`;
|
|
713
|
+
return content.slice(0, insertIndex) + adHocSection + content.slice(insertIndex);
|
|
714
|
+
}
|
|
715
|
+
return `${content}
|
|
716
|
+
|
|
717
|
+
## Ad-hoc Scope
|
|
718
|
+
|
|
719
|
+
${adHocDetails}
|
|
720
|
+
`;
|
|
721
|
+
}
|
|
446
722
|
function buildAuditsRepository(fileSystem, dustPath) {
|
|
447
723
|
const userAuditsPath = `${dustPath}/config/audits`;
|
|
448
724
|
const tasksPath = `${dustPath}/tasks`;
|
|
@@ -511,5 +787,6 @@ function buildAuditsRepository(fileSystem, dustPath) {
|
|
|
511
787
|
export {
|
|
512
788
|
transformAuditContent,
|
|
513
789
|
loadStockAudits,
|
|
790
|
+
injectAdHocScope,
|
|
514
791
|
buildAuditsRepository
|
|
515
792
|
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bucket event types and WebSocket event sending.
|
|
3
|
+
*
|
|
4
|
+
* BucketEvent types are local-only (UI lifecycle). Wire events use
|
|
5
|
+
* EventMessage from agent-events.ts and are sent via createEventMessageSender.
|
|
6
|
+
*/
|
|
7
|
+
import type { EventMessage } from '../agent-events';
|
|
8
|
+
export declare const WS_OPEN = 1;
|
|
9
|
+
export declare const WS_CLOSED = 3;
|
|
10
|
+
export interface WebSocketLike {
|
|
11
|
+
onopen: (() => void) | null;
|
|
12
|
+
onclose: ((event: {
|
|
13
|
+
code: number;
|
|
14
|
+
reason: string;
|
|
15
|
+
}) => void) | null;
|
|
16
|
+
onerror: ((error: Error) => void) | null;
|
|
17
|
+
onmessage: ((event: {
|
|
18
|
+
data: string;
|
|
19
|
+
}) => void) | null;
|
|
20
|
+
close: () => void;
|
|
21
|
+
send: (data: string) => void;
|
|
22
|
+
readyState: number;
|
|
23
|
+
}
|
|
24
|
+
interface BucketConnectedEvent {
|
|
25
|
+
type: 'bucket.connected';
|
|
26
|
+
}
|
|
27
|
+
interface BucketDisconnectedEvent {
|
|
28
|
+
type: 'bucket.disconnected';
|
|
29
|
+
code: number;
|
|
30
|
+
reason: string;
|
|
31
|
+
}
|
|
32
|
+
export interface BucketRepositoryAddedEvent {
|
|
33
|
+
type: 'bucket.repository_added';
|
|
34
|
+
repository: string;
|
|
35
|
+
}
|
|
36
|
+
export interface BucketRepositoryRemovedEvent {
|
|
37
|
+
type: 'bucket.repository_removed';
|
|
38
|
+
repository: string;
|
|
39
|
+
}
|
|
40
|
+
export interface BucketErrorEvent {
|
|
41
|
+
type: 'bucket.error';
|
|
42
|
+
repository?: string;
|
|
43
|
+
error: string;
|
|
44
|
+
}
|
|
45
|
+
export type BucketEvent = BucketConnectedEvent | BucketDisconnectedEvent | BucketRepositoryAddedEvent | BucketRepositoryRemovedEvent | BucketErrorEvent;
|
|
46
|
+
export type BucketEmitFn = (event: BucketEvent) => void;
|
|
47
|
+
export type SendEventFn = (msg: EventMessage) => void;
|
|
48
|
+
export declare function formatBucketEvent(event: BucketEvent): string;
|
|
49
|
+
export declare function createEventMessageSender(getWebSocket: () => WebSocketLike | null): SendEventFn;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ring buffer for capturing subprocess output in dust bucket.
|
|
3
|
+
*
|
|
4
|
+
* Each repository has its own log buffer with a fixed maximum size.
|
|
5
|
+
* When the buffer exceeds the limit, it trims to the target size.
|
|
6
|
+
*/
|
|
7
|
+
export interface LogLine {
|
|
8
|
+
text: string;
|
|
9
|
+
stream: 'stdout' | 'stderr';
|
|
10
|
+
timestamp: number;
|
|
11
|
+
}
|
|
12
|
+
export interface LogBuffer {
|
|
13
|
+
lines: LogLine[];
|
|
14
|
+
maxLines: number;
|
|
15
|
+
trimToLines: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create a new log buffer with default settings.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createLogBuffer(maxLines?: number, trimToLines?: number): LogBuffer;
|
|
21
|
+
/**
|
|
22
|
+
* Append a log line to the buffer, trimming if necessary.
|
|
23
|
+
*/
|
|
24
|
+
export declare function appendLogLine(buffer: LogBuffer, line: LogLine): void;
|
|
25
|
+
/**
|
|
26
|
+
* Create a log line from raw output.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createLogLine(text: string, stream: 'stdout' | 'stderr', timestamp?: number): LogLine;
|
|
29
|
+
/**
|
|
30
|
+
* Get all lines from the buffer.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getLogLines(buffer: LogBuffer): readonly LogLine[];
|
|
33
|
+
/**
|
|
34
|
+
* Get the most recent N lines from the buffer.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getRecentLines(buffer: LogBuffer, count: number): LogLine[];
|
|
37
|
+
/**
|
|
38
|
+
* Clear all lines from the buffer.
|
|
39
|
+
*/
|
|
40
|
+
export declare function clearLogBuffer(buffer: LogBuffer): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path utilities for dust bucket operations.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions that compute paths from explicit parameters,
|
|
5
|
+
* following the "functional core, imperative shell" pattern.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Environment variables used by getReposDir.
|
|
9
|
+
* Includes an index signature for compatibility with process.env.
|
|
10
|
+
*/
|
|
11
|
+
interface ReposDirEnv {
|
|
12
|
+
DUST_REPOS_DIR?: string;
|
|
13
|
+
[key: string]: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Compute the repositories directory path.
|
|
17
|
+
*
|
|
18
|
+
* If DUST_REPOS_DIR is set in the environment, returns that value.
|
|
19
|
+
* Otherwise, returns the default path: ~/.dust/repos
|
|
20
|
+
*
|
|
21
|
+
* @param env - Environment variables object
|
|
22
|
+
* @param homeDir - User's home directory path
|
|
23
|
+
* @returns The resolved repositories directory path
|
|
24
|
+
*/
|
|
25
|
+
export declare function getReposDir(env: ReposDirEnv, homeDir: string): string;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git operations for dust bucket repository management.
|
|
3
|
+
*
|
|
4
|
+
* Handles cloning, removing, and path resolution for repositories.
|
|
5
|
+
*/
|
|
6
|
+
import type { spawn as nodeSpawn } from 'node:child_process';
|
|
7
|
+
import type { CommandDependencies } from '../cli/types';
|
|
8
|
+
/**
|
|
9
|
+
* Get the directory path for a repository.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getRepoPath(repoName: string, reposDir: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Clone a repository to a temporary directory.
|
|
14
|
+
*/
|
|
15
|
+
export declare function cloneRepository(repository: {
|
|
16
|
+
name: string;
|
|
17
|
+
gitUrl: string;
|
|
18
|
+
}, targetPath: string, spawn: typeof nodeSpawn, context: CommandDependencies['context']): Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* Remove a repository directory.
|
|
21
|
+
*/
|
|
22
|
+
export declare function removeRepository(path: string, spawn: typeof nodeSpawn, context: CommandDependencies['context']): Promise<boolean>;
|