@joshski/dust 0.1.76 → 0.1.78
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/agent-events.d.ts +1 -0
- package/dist/artifacts/index.d.ts +15 -3
- package/dist/artifacts.js +42 -0
- package/dist/audits.js +71 -1
- package/dist/bucket/repository-loop.d.ts +1 -0
- package/dist/cli/colors.d.ts +1 -1
- package/dist/cli/commands/focus.d.ts +1 -1
- package/dist/config/settings.d.ts +7 -8
- package/dist/dust.js +304 -174
- package/dist/types.d.ts +2 -0
- package/dist/validation.js +10 -2
- package/package.json +1 -1
package/dist/agent-events.d.ts
CHANGED
|
@@ -3,8 +3,19 @@ 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 { type AllWorkflowTasks, CAPTURE_IDEA_PREFIX, type CreateIdeaTransitionTaskResult, type DecomposeIdeaOptions, 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, };
|
|
6
|
+
import { type AllWorkflowTasks, CAPTURE_IDEA_PREFIX, type CreateIdeaTransitionTaskResult, type DecomposeIdeaOptions, findAllWorkflowTasks, type IdeaInProgress, type OpenQuestionResponse, type ParsedCaptureIdeaTask, type WorkflowTaskMatch, type WorkflowTaskType } from './workflow-tasks';
|
|
7
|
+
export type { AllWorkflowTasks, CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedCaptureIdeaTask, Principle, Task, WorkflowTaskMatch, WorkflowTaskType, };
|
|
8
|
+
export interface TaskGraphNode {
|
|
9
|
+
task: Task;
|
|
10
|
+
workflowType: WorkflowTaskType | null;
|
|
11
|
+
}
|
|
12
|
+
export interface TaskGraph {
|
|
13
|
+
nodes: TaskGraphNode[];
|
|
14
|
+
edges: Array<{
|
|
15
|
+
from: string;
|
|
16
|
+
to: string;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
8
19
|
export { CAPTURE_IDEA_PREFIX, findAllWorkflowTasks, parseOpenQuestions };
|
|
9
20
|
export type { IdeaInProgress };
|
|
10
21
|
export interface ArtifactsRepository {
|
|
@@ -49,6 +60,7 @@ export interface ArtifactsRepository {
|
|
|
49
60
|
parseCaptureIdeaTask(options: {
|
|
50
61
|
taskSlug: string;
|
|
51
62
|
}): Promise<ParsedCaptureIdeaTask | null>;
|
|
63
|
+
buildTaskGraph(): Promise<TaskGraph>;
|
|
52
64
|
}
|
|
53
65
|
export declare function buildArtifactsRepository(fileSystem: FileSystem, dustPath: string): ArtifactsRepository;
|
|
54
|
-
export declare function buildReadOnlyArtifactsRepository(fileSystem: ReadableFileSystem, dustPath: string): Pick<ArtifactsRepository, 'parseIdea' | 'listIdeas' | 'parsePrinciple' | 'listPrinciples' | 'parseFact' | 'listFacts' | 'parseTask' | 'listTasks' | 'findWorkflowTaskForIdea' | 'parseCaptureIdeaTask'>;
|
|
66
|
+
export declare function buildReadOnlyArtifactsRepository(fileSystem: ReadableFileSystem, dustPath: string): Pick<ArtifactsRepository, 'parseIdea' | 'listIdeas' | 'parsePrinciple' | 'listPrinciples' | 'parseFact' | 'listFacts' | 'parseTask' | 'listTasks' | 'findWorkflowTaskForIdea' | 'parseCaptureIdeaTask' | 'buildTaskGraph'>;
|
package/dist/artifacts.js
CHANGED
|
@@ -609,6 +609,27 @@ function buildArtifactsRepository(fileSystem, dustPath) {
|
|
|
609
609
|
},
|
|
610
610
|
async parseCaptureIdeaTask(options) {
|
|
611
611
|
return parseCaptureIdeaTask(fileSystem, dustPath, options.taskSlug);
|
|
612
|
+
},
|
|
613
|
+
async buildTaskGraph() {
|
|
614
|
+
const taskSlugs = await this.listTasks();
|
|
615
|
+
const allWorkflowTasks = await findAllWorkflowTasks(fileSystem, dustPath);
|
|
616
|
+
const workflowTypeByTaskSlug = new Map;
|
|
617
|
+
for (const match of allWorkflowTasks.workflowTasksByIdeaSlug.values()) {
|
|
618
|
+
workflowTypeByTaskSlug.set(match.taskSlug, match.type);
|
|
619
|
+
}
|
|
620
|
+
const nodes = [];
|
|
621
|
+
const edges = [];
|
|
622
|
+
for (const slug of taskSlugs) {
|
|
623
|
+
const task = await this.parseTask({ slug });
|
|
624
|
+
nodes.push({
|
|
625
|
+
task,
|
|
626
|
+
workflowType: workflowTypeByTaskSlug.get(slug) ?? null
|
|
627
|
+
});
|
|
628
|
+
for (const blockerSlug of task.blockedBy) {
|
|
629
|
+
edges.push({ from: blockerSlug, to: slug });
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return { nodes, edges };
|
|
612
633
|
}
|
|
613
634
|
};
|
|
614
635
|
}
|
|
@@ -663,6 +684,27 @@ function buildReadOnlyArtifactsRepository(fileSystem, dustPath) {
|
|
|
663
684
|
},
|
|
664
685
|
async parseCaptureIdeaTask(options) {
|
|
665
686
|
return parseCaptureIdeaTask(fileSystem, dustPath, options.taskSlug);
|
|
687
|
+
},
|
|
688
|
+
async buildTaskGraph() {
|
|
689
|
+
const taskSlugs = await this.listTasks();
|
|
690
|
+
const allWorkflowTasks = await findAllWorkflowTasks(fileSystem, dustPath);
|
|
691
|
+
const workflowTypeByTaskSlug = new Map;
|
|
692
|
+
for (const match of allWorkflowTasks.workflowTasksByIdeaSlug.values()) {
|
|
693
|
+
workflowTypeByTaskSlug.set(match.taskSlug, match.type);
|
|
694
|
+
}
|
|
695
|
+
const nodes = [];
|
|
696
|
+
const edges = [];
|
|
697
|
+
for (const slug of taskSlugs) {
|
|
698
|
+
const task = await this.parseTask({ slug });
|
|
699
|
+
nodes.push({
|
|
700
|
+
task,
|
|
701
|
+
workflowType: workflowTypeByTaskSlug.get(slug) ?? null
|
|
702
|
+
});
|
|
703
|
+
for (const blockerSlug of task.blockedBy) {
|
|
704
|
+
edges.push({ from: blockerSlug, to: slug });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return { nodes, edges };
|
|
666
708
|
}
|
|
667
709
|
};
|
|
668
710
|
}
|
package/dist/audits.js
CHANGED
|
@@ -831,6 +831,75 @@ function ubiquitousLanguage() {
|
|
|
831
831
|
- [ ] Proposed ideas for standardizing inconsistent terminology
|
|
832
832
|
`;
|
|
833
833
|
}
|
|
834
|
+
function uxAudit() {
|
|
835
|
+
return dedent`
|
|
836
|
+
# UX Audit
|
|
837
|
+
|
|
838
|
+
Review the end user experience by capturing visual or interactive evidence at key scenarios.
|
|
839
|
+
|
|
840
|
+
${ideasHint}
|
|
841
|
+
|
|
842
|
+
## Scope
|
|
843
|
+
|
|
844
|
+
1. **Identify key scenarios** - What are the main user journeys? (e.g., signup, login, checkout, onboarding, core workflows)
|
|
845
|
+
2. **Capture evidence** - For each scenario:
|
|
846
|
+
- Web apps: Take screenshots at each step using browser automation (Playwright, Puppeteer, Cypress, or similar)
|
|
847
|
+
- Terminal apps: Capture command output and interactive sessions
|
|
848
|
+
3. **Review captured evidence** for UX issues:
|
|
849
|
+
- Confusing or unclear states
|
|
850
|
+
- Missing feedback or loading indicators
|
|
851
|
+
- Error messages that don't guide recovery
|
|
852
|
+
- Inconsistent styling or layout
|
|
853
|
+
4. **Document findings** with screenshots/output and specific recommendations
|
|
854
|
+
|
|
855
|
+
## Applicability
|
|
856
|
+
|
|
857
|
+
Determine the application type and available tooling:
|
|
858
|
+
- If browser tests exist (Playwright, Puppeteer, Cypress), extend them to capture screenshots
|
|
859
|
+
- If no browser tests exist, write a standalone script for key scenarios
|
|
860
|
+
- For terminal apps, capture representative sessions using command output or terminal recording
|
|
861
|
+
|
|
862
|
+
If the project has no user-facing interface, document that finding and skip the detailed analysis.
|
|
863
|
+
|
|
864
|
+
## Analysis Steps
|
|
865
|
+
|
|
866
|
+
1. Identify the application type (web, terminal, hybrid, no UI)
|
|
867
|
+
2. List the key user scenarios from documentation, tests, or code analysis
|
|
868
|
+
3. Capture screenshots or output at each stage of each scenario
|
|
869
|
+
4. Store artifacts in a temporary directory for review during this audit
|
|
870
|
+
5. Review each artifact for UX issues
|
|
871
|
+
6. Document findings with evidence and specific recommendations
|
|
872
|
+
|
|
873
|
+
## Output
|
|
874
|
+
|
|
875
|
+
For each UX issue identified, provide:
|
|
876
|
+
- **Location** - Which scenario and step
|
|
877
|
+
- **Evidence** - Screenshot filename or captured output
|
|
878
|
+
- **Problem** - What's wrong from the user's perspective
|
|
879
|
+
- **Impact** - How it affects the user's ability to complete their goal
|
|
880
|
+
- **Recommendation** - Specific fix
|
|
881
|
+
- **Verification** - How to verify the fix (e.g., "Screenshot at step 3 should show success message instead of spinner")
|
|
882
|
+
|
|
883
|
+
## Principles
|
|
884
|
+
|
|
885
|
+
- [Actionable Errors](../principles/actionable-errors.md) - Error messages should tell users what to do next
|
|
886
|
+
- [Unsurprising UX](../principles/unsurprising-ux.md) - The interface should be as guessable as possible
|
|
887
|
+
|
|
888
|
+
## Blocked By
|
|
889
|
+
|
|
890
|
+
(none)
|
|
891
|
+
|
|
892
|
+
## Definition of Done
|
|
893
|
+
|
|
894
|
+
- [ ] Identified the application type (web, terminal, hybrid, or no UI)
|
|
895
|
+
- [ ] Listed key user scenarios
|
|
896
|
+
- [ ] Captured screenshots or output at each stage of key scenarios
|
|
897
|
+
- [ ] Reviewed evidence for UX issues
|
|
898
|
+
- [ ] Documented findings with evidence and recommendations
|
|
899
|
+
- [ ] Included verification criteria for each issue
|
|
900
|
+
- [ ] Created ideas for any UX improvements needed
|
|
901
|
+
`;
|
|
902
|
+
}
|
|
834
903
|
var stockAuditFunctions = {
|
|
835
904
|
"agent-developer-experience": agentDeveloperExperience,
|
|
836
905
|
"component-reuse": componentReuse,
|
|
@@ -849,7 +918,8 @@ var stockAuditFunctions = {
|
|
|
849
918
|
"slow-tests": slowTests,
|
|
850
919
|
"stale-ideas": staleIdeas,
|
|
851
920
|
"test-coverage": testCoverage,
|
|
852
|
-
"ubiquitous-language": ubiquitousLanguage
|
|
921
|
+
"ubiquitous-language": ubiquitousLanguage,
|
|
922
|
+
"ux-audit": uxAudit
|
|
853
923
|
};
|
|
854
924
|
function loadStockAudits() {
|
|
855
925
|
return Object.entries(stockAuditFunctions).sort(([a], [b]) => a.localeCompare(b)).map(([name, render]) => {
|
package/dist/cli/colors.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ interface Colors {
|
|
|
17
17
|
/**
|
|
18
18
|
* Determines whether colors should be disabled based on environment.
|
|
19
19
|
*/
|
|
20
|
-
export declare function shouldDisableColors(): boolean;
|
|
20
|
+
export declare function shouldDisableColors(env?: NodeJS.ProcessEnv): boolean;
|
|
21
21
|
/**
|
|
22
22
|
* Returns the appropriate colors object based on environment detection.
|
|
23
23
|
*/
|
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
* Usage: dust focus "add login box"
|
|
8
8
|
*/
|
|
9
9
|
import type { CommandDependencies, CommandResult } from '../types';
|
|
10
|
-
export declare function buildImplementationInstructions(bin: string, hooksInstalled: boolean, taskTitle?: string): string;
|
|
10
|
+
export declare function buildImplementationInstructions(bin: string, hooksInstalled: boolean, taskTitle?: string, taskPath?: string, installCommand?: string): string;
|
|
11
11
|
export declare function focus(dependencies: CommandDependencies): Promise<CommandResult>;
|
|
@@ -20,15 +20,14 @@ export declare function validateSettingsJson(content: string): SettingsViolation
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function detectDustCommand(cwd: string, fileSystem: ReadableFileSystem): string;
|
|
22
22
|
/**
|
|
23
|
-
* Detects the appropriate install command based on lockfiles
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* 5. Default → npm install
|
|
23
|
+
* Detects the appropriate install command based on lockfiles.
|
|
24
|
+
* Returns null when:
|
|
25
|
+
* - No recognized lockfile is found
|
|
26
|
+
* - Multiple ecosystems are detected (requires explicit configuration)
|
|
27
|
+
*
|
|
28
|
+
* Priority within each ecosystem follows the order in LOCKFILE_COMMANDS.
|
|
30
29
|
*/
|
|
31
|
-
export declare function detectInstallCommand(cwd: string, fileSystem: ReadableFileSystem): string;
|
|
30
|
+
export declare function detectInstallCommand(cwd: string, fileSystem: ReadableFileSystem): string | null;
|
|
32
31
|
/**
|
|
33
32
|
* Detects the appropriate test command based on lockfiles and environment.
|
|
34
33
|
* Priority:
|
package/dist/dust.js
CHANGED
|
@@ -170,8 +170,7 @@ function validateSettingsJson(content) {
|
|
|
170
170
|
return violations;
|
|
171
171
|
}
|
|
172
172
|
var DEFAULT_SETTINGS = {
|
|
173
|
-
dustCommand: "npx dust"
|
|
174
|
-
installCommand: "npm install"
|
|
173
|
+
dustCommand: "npx dust"
|
|
175
174
|
};
|
|
176
175
|
function detectDustCommand(cwd, fileSystem) {
|
|
177
176
|
if (fileSystem.exists(join(cwd, "bun.lockb"))) {
|
|
@@ -188,20 +187,38 @@ function detectDustCommand(cwd, fileSystem) {
|
|
|
188
187
|
}
|
|
189
188
|
return "npx dust";
|
|
190
189
|
}
|
|
190
|
+
var LOCKFILE_COMMANDS = [
|
|
191
|
+
{ file: "bun.lockb", command: "bun install", ecosystem: "js" },
|
|
192
|
+
{ file: "pnpm-lock.yaml", command: "pnpm install", ecosystem: "js" },
|
|
193
|
+
{ file: "package-lock.json", command: "npm install", ecosystem: "js" },
|
|
194
|
+
{ file: "Gemfile.lock", command: "bundle install", ecosystem: "ruby" },
|
|
195
|
+
{ file: "poetry.lock", command: "poetry install", ecosystem: "python" },
|
|
196
|
+
{ file: "Pipfile.lock", command: "pipenv install", ecosystem: "python" },
|
|
197
|
+
{
|
|
198
|
+
file: "requirements.txt",
|
|
199
|
+
command: "pip install -r requirements.txt",
|
|
200
|
+
ecosystem: "python"
|
|
201
|
+
},
|
|
202
|
+
{ file: "go.sum", command: "go mod download", ecosystem: "go" },
|
|
203
|
+
{ file: "Cargo.lock", command: "cargo build", ecosystem: "rust" },
|
|
204
|
+
{ file: "composer.lock", command: "composer install", ecosystem: "php" },
|
|
205
|
+
{ file: "mix.lock", command: "mix deps.get", ecosystem: "elixir" }
|
|
206
|
+
];
|
|
191
207
|
function detectInstallCommand(cwd, fileSystem) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
208
|
+
const foundEcosystems = new Set;
|
|
209
|
+
let firstCommand = null;
|
|
210
|
+
for (const { file, command, ecosystem } of LOCKFILE_COMMANDS) {
|
|
211
|
+
if (fileSystem.exists(join(cwd, file))) {
|
|
212
|
+
if (firstCommand === null) {
|
|
213
|
+
firstCommand = command;
|
|
214
|
+
}
|
|
215
|
+
foundEcosystems.add(ecosystem);
|
|
216
|
+
}
|
|
200
217
|
}
|
|
201
|
-
if (
|
|
202
|
-
return
|
|
218
|
+
if (foundEcosystems.size > 1) {
|
|
219
|
+
return null;
|
|
203
220
|
}
|
|
204
|
-
return
|
|
221
|
+
return firstCommand;
|
|
205
222
|
}
|
|
206
223
|
function detectTestCommand(cwd, fileSystem) {
|
|
207
224
|
if (fileSystem.exists(join(cwd, "bun.lockb")) || fileSystem.exists(join(cwd, "bun.lock"))) {
|
|
@@ -234,9 +251,12 @@ async function loadSettings(cwd, fileSystem) {
|
|
|
234
251
|
const settingsPath = join(cwd, ".dust", "config", "settings.json");
|
|
235
252
|
if (!fileSystem.exists(settingsPath)) {
|
|
236
253
|
const result = {
|
|
237
|
-
dustCommand: detectDustCommand(cwd, fileSystem)
|
|
238
|
-
installCommand: detectInstallCommand(cwd, fileSystem)
|
|
254
|
+
dustCommand: detectDustCommand(cwd, fileSystem)
|
|
239
255
|
};
|
|
256
|
+
const installCommand = detectInstallCommand(cwd, fileSystem);
|
|
257
|
+
if (installCommand !== null) {
|
|
258
|
+
result.installCommand = installCommand;
|
|
259
|
+
}
|
|
240
260
|
if (process.env.DUST_EVENTS_URL) {
|
|
241
261
|
result.eventsUrl = process.env.DUST_EVENTS_URL;
|
|
242
262
|
}
|
|
@@ -256,26 +276,37 @@ async function loadSettings(cwd, fileSystem) {
|
|
|
256
276
|
result.dustCommand = detectDustCommand(cwd, fileSystem);
|
|
257
277
|
}
|
|
258
278
|
if (!parsed.installCommand) {
|
|
259
|
-
|
|
279
|
+
const installCommand = detectInstallCommand(cwd, fileSystem);
|
|
280
|
+
if (installCommand !== null) {
|
|
281
|
+
result.installCommand = installCommand;
|
|
282
|
+
} else {
|
|
283
|
+
delete result.installCommand;
|
|
284
|
+
}
|
|
260
285
|
}
|
|
261
286
|
if (process.env.DUST_EVENTS_URL) {
|
|
262
287
|
result.eventsUrl = process.env.DUST_EVENTS_URL;
|
|
263
288
|
}
|
|
264
289
|
return result;
|
|
265
|
-
} catch {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (error.code === "ENOENT") {
|
|
292
|
+
const result = {
|
|
293
|
+
dustCommand: detectDustCommand(cwd, fileSystem)
|
|
294
|
+
};
|
|
295
|
+
const installCommand = detectInstallCommand(cwd, fileSystem);
|
|
296
|
+
if (installCommand !== null) {
|
|
297
|
+
result.installCommand = installCommand;
|
|
298
|
+
}
|
|
299
|
+
if (process.env.DUST_EVENTS_URL) {
|
|
300
|
+
result.eventsUrl = process.env.DUST_EVENTS_URL;
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
272
303
|
}
|
|
273
|
-
|
|
304
|
+
throw error;
|
|
274
305
|
}
|
|
275
306
|
}
|
|
276
307
|
|
|
277
308
|
// lib/version.ts
|
|
278
|
-
var DUST_VERSION = "0.1.
|
|
309
|
+
var DUST_VERSION = "0.1.78";
|
|
279
310
|
|
|
280
311
|
// lib/session.ts
|
|
281
312
|
var DUST_UNATTENDED = "DUST_UNATTENDED";
|
|
@@ -460,8 +491,11 @@ async function loadAgentInstructions(cwd, fileSystem, agentType) {
|
|
|
460
491
|
try {
|
|
461
492
|
const content = await fileSystem.readFile(instructionsPath);
|
|
462
493
|
return content.trim();
|
|
463
|
-
} catch {
|
|
464
|
-
|
|
494
|
+
} catch (error) {
|
|
495
|
+
if (error.code === "ENOENT") {
|
|
496
|
+
return "";
|
|
497
|
+
}
|
|
498
|
+
throw error;
|
|
465
499
|
}
|
|
466
500
|
}
|
|
467
501
|
function templateVariables(settings, hooksInstalled, env = process.env, options) {
|
|
@@ -1378,6 +1412,75 @@ function ubiquitousLanguage() {
|
|
|
1378
1412
|
- [ ] Proposed ideas for standardizing inconsistent terminology
|
|
1379
1413
|
`;
|
|
1380
1414
|
}
|
|
1415
|
+
function uxAudit() {
|
|
1416
|
+
return dedent`
|
|
1417
|
+
# UX Audit
|
|
1418
|
+
|
|
1419
|
+
Review the end user experience by capturing visual or interactive evidence at key scenarios.
|
|
1420
|
+
|
|
1421
|
+
${ideasHint}
|
|
1422
|
+
|
|
1423
|
+
## Scope
|
|
1424
|
+
|
|
1425
|
+
1. **Identify key scenarios** - What are the main user journeys? (e.g., signup, login, checkout, onboarding, core workflows)
|
|
1426
|
+
2. **Capture evidence** - For each scenario:
|
|
1427
|
+
- Web apps: Take screenshots at each step using browser automation (Playwright, Puppeteer, Cypress, or similar)
|
|
1428
|
+
- Terminal apps: Capture command output and interactive sessions
|
|
1429
|
+
3. **Review captured evidence** for UX issues:
|
|
1430
|
+
- Confusing or unclear states
|
|
1431
|
+
- Missing feedback or loading indicators
|
|
1432
|
+
- Error messages that don't guide recovery
|
|
1433
|
+
- Inconsistent styling or layout
|
|
1434
|
+
4. **Document findings** with screenshots/output and specific recommendations
|
|
1435
|
+
|
|
1436
|
+
## Applicability
|
|
1437
|
+
|
|
1438
|
+
Determine the application type and available tooling:
|
|
1439
|
+
- If browser tests exist (Playwright, Puppeteer, Cypress), extend them to capture screenshots
|
|
1440
|
+
- If no browser tests exist, write a standalone script for key scenarios
|
|
1441
|
+
- For terminal apps, capture representative sessions using command output or terminal recording
|
|
1442
|
+
|
|
1443
|
+
If the project has no user-facing interface, document that finding and skip the detailed analysis.
|
|
1444
|
+
|
|
1445
|
+
## Analysis Steps
|
|
1446
|
+
|
|
1447
|
+
1. Identify the application type (web, terminal, hybrid, no UI)
|
|
1448
|
+
2. List the key user scenarios from documentation, tests, or code analysis
|
|
1449
|
+
3. Capture screenshots or output at each stage of each scenario
|
|
1450
|
+
4. Store artifacts in a temporary directory for review during this audit
|
|
1451
|
+
5. Review each artifact for UX issues
|
|
1452
|
+
6. Document findings with evidence and specific recommendations
|
|
1453
|
+
|
|
1454
|
+
## Output
|
|
1455
|
+
|
|
1456
|
+
For each UX issue identified, provide:
|
|
1457
|
+
- **Location** - Which scenario and step
|
|
1458
|
+
- **Evidence** - Screenshot filename or captured output
|
|
1459
|
+
- **Problem** - What's wrong from the user's perspective
|
|
1460
|
+
- **Impact** - How it affects the user's ability to complete their goal
|
|
1461
|
+
- **Recommendation** - Specific fix
|
|
1462
|
+
- **Verification** - How to verify the fix (e.g., "Screenshot at step 3 should show success message instead of spinner")
|
|
1463
|
+
|
|
1464
|
+
## Principles
|
|
1465
|
+
|
|
1466
|
+
- [Actionable Errors](../principles/actionable-errors.md) - Error messages should tell users what to do next
|
|
1467
|
+
- [Unsurprising UX](../principles/unsurprising-ux.md) - The interface should be as guessable as possible
|
|
1468
|
+
|
|
1469
|
+
## Blocked By
|
|
1470
|
+
|
|
1471
|
+
(none)
|
|
1472
|
+
|
|
1473
|
+
## Definition of Done
|
|
1474
|
+
|
|
1475
|
+
- [ ] Identified the application type (web, terminal, hybrid, or no UI)
|
|
1476
|
+
- [ ] Listed key user scenarios
|
|
1477
|
+
- [ ] Captured screenshots or output at each stage of key scenarios
|
|
1478
|
+
- [ ] Reviewed evidence for UX issues
|
|
1479
|
+
- [ ] Documented findings with evidence and recommendations
|
|
1480
|
+
- [ ] Included verification criteria for each issue
|
|
1481
|
+
- [ ] Created ideas for any UX improvements needed
|
|
1482
|
+
`;
|
|
1483
|
+
}
|
|
1381
1484
|
var stockAuditFunctions = {
|
|
1382
1485
|
"agent-developer-experience": agentDeveloperExperience,
|
|
1383
1486
|
"component-reuse": componentReuse,
|
|
@@ -1396,7 +1499,8 @@ var stockAuditFunctions = {
|
|
|
1396
1499
|
"slow-tests": slowTests,
|
|
1397
1500
|
"stale-ideas": staleIdeas,
|
|
1398
1501
|
"test-coverage": testCoverage,
|
|
1399
|
-
"ubiquitous-language": ubiquitousLanguage
|
|
1502
|
+
"ubiquitous-language": ubiquitousLanguage,
|
|
1503
|
+
"ux-audit": uxAudit
|
|
1400
1504
|
};
|
|
1401
1505
|
function loadStockAudits() {
|
|
1402
1506
|
return Object.entries(stockAuditFunctions).sort(([a], [b]) => a.localeCompare(b)).map(([name, render]) => {
|
|
@@ -1451,11 +1555,11 @@ var NO_COLORS = {
|
|
|
1451
1555
|
green: "",
|
|
1452
1556
|
yellow: ""
|
|
1453
1557
|
};
|
|
1454
|
-
function shouldDisableColors() {
|
|
1455
|
-
if (
|
|
1558
|
+
function shouldDisableColors(env = process.env) {
|
|
1559
|
+
if (env.NO_COLOR !== undefined) {
|
|
1456
1560
|
return true;
|
|
1457
1561
|
}
|
|
1458
|
-
if (
|
|
1562
|
+
if (env.TERM === "dumb") {
|
|
1459
1563
|
return true;
|
|
1460
1564
|
}
|
|
1461
1565
|
if (!process.stdout.isTTY) {
|
|
@@ -1571,8 +1675,8 @@ var CREDENTIALS_DIR = ".dust";
|
|
|
1571
1675
|
var CREDENTIALS_FILE = "credentials.json";
|
|
1572
1676
|
var AUTH_TIMEOUT_MS = 120000;
|
|
1573
1677
|
var DEFAULT_DUSTBUCKET_HOST = "https://dustbucket.com";
|
|
1574
|
-
function getDustbucketHost() {
|
|
1575
|
-
return
|
|
1678
|
+
function getDustbucketHost(env = process.env) {
|
|
1679
|
+
return env.DUST_BUCKET_HOST || DEFAULT_DUSTBUCKET_HOST;
|
|
1576
1680
|
}
|
|
1577
1681
|
function credentialsPath(homeDir) {
|
|
1578
1682
|
return join4(homeDir, CREDENTIALS_DIR, CREDENTIALS_FILE);
|
|
@@ -1583,8 +1687,11 @@ async function loadStoredToken(fileSystem, homeDir) {
|
|
|
1583
1687
|
const content = await fileSystem.readFile(path);
|
|
1584
1688
|
const data = JSON.parse(content);
|
|
1585
1689
|
return typeof data.token === "string" ? data.token : null;
|
|
1586
|
-
} catch {
|
|
1587
|
-
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
if (error.code === "ENOENT") {
|
|
1692
|
+
return null;
|
|
1693
|
+
}
|
|
1694
|
+
throw error;
|
|
1588
1695
|
}
|
|
1589
1696
|
}
|
|
1590
1697
|
async function storeToken(fileSystem, homeDir, token) {
|
|
@@ -1596,7 +1703,12 @@ async function clearToken(fileSystem, homeDir) {
|
|
|
1596
1703
|
const path = credentialsPath(homeDir);
|
|
1597
1704
|
try {
|
|
1598
1705
|
await fileSystem.writeFile(path, "{}");
|
|
1599
|
-
} catch {
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
if (error.code === "ENOENT") {
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
throw error;
|
|
1711
|
+
}
|
|
1600
1712
|
}
|
|
1601
1713
|
async function defaultExchangeCode(code) {
|
|
1602
1714
|
const host = getDustbucketHost();
|
|
@@ -1777,6 +1889,134 @@ import { dirname as dirname2 } from "node:path";
|
|
|
1777
1889
|
// lib/claude/spawn-claude-code.ts
|
|
1778
1890
|
import { spawn as nodeSpawn2 } from "node:child_process";
|
|
1779
1891
|
import { createInterface as nodeCreateInterface } from "node:readline";
|
|
1892
|
+
|
|
1893
|
+
// lib/logging/index.ts
|
|
1894
|
+
import { join as join6 } from "node:path";
|
|
1895
|
+
|
|
1896
|
+
// lib/logging/match.ts
|
|
1897
|
+
function parsePatterns(debug) {
|
|
1898
|
+
if (!debug)
|
|
1899
|
+
return [];
|
|
1900
|
+
const expressions = debug.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1901
|
+
return expressions.map((expr) => {
|
|
1902
|
+
const escaped = expr.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
1903
|
+
const pattern = escaped.replace(/\*/g, ".*");
|
|
1904
|
+
return new RegExp(`^${pattern}$`);
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
function matchesAny(name, patterns) {
|
|
1908
|
+
return patterns.some((re) => re.test(name));
|
|
1909
|
+
}
|
|
1910
|
+
function formatLine(name, messages) {
|
|
1911
|
+
const text = messages.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
1912
|
+
return `${new Date().toISOString()} [${name}] ${text}
|
|
1913
|
+
`;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// lib/logging/sink.ts
|
|
1917
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
1918
|
+
import { dirname } from "node:path";
|
|
1919
|
+
|
|
1920
|
+
class FileSink {
|
|
1921
|
+
logPath;
|
|
1922
|
+
_appendFileSync;
|
|
1923
|
+
_mkdirSync;
|
|
1924
|
+
resolvedPath;
|
|
1925
|
+
ready = false;
|
|
1926
|
+
constructor(logPath, _appendFileSync = appendFileSync, _mkdirSync = mkdirSync) {
|
|
1927
|
+
this.logPath = logPath;
|
|
1928
|
+
this._appendFileSync = _appendFileSync;
|
|
1929
|
+
this._mkdirSync = _mkdirSync;
|
|
1930
|
+
}
|
|
1931
|
+
ensureLogFile() {
|
|
1932
|
+
if (this.ready)
|
|
1933
|
+
return this.resolvedPath;
|
|
1934
|
+
this.ready = true;
|
|
1935
|
+
this.resolvedPath = this.logPath;
|
|
1936
|
+
try {
|
|
1937
|
+
this._mkdirSync(dirname(this.logPath), { recursive: true });
|
|
1938
|
+
} catch {
|
|
1939
|
+
this.resolvedPath = undefined;
|
|
1940
|
+
}
|
|
1941
|
+
return this.resolvedPath;
|
|
1942
|
+
}
|
|
1943
|
+
write(line) {
|
|
1944
|
+
const path = this.ensureLogFile();
|
|
1945
|
+
if (!path)
|
|
1946
|
+
return;
|
|
1947
|
+
try {
|
|
1948
|
+
this._appendFileSync(path, line);
|
|
1949
|
+
} catch {}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// lib/logging/index.ts
|
|
1954
|
+
var DUST_LOG_FILE = "DUST_LOG_FILE";
|
|
1955
|
+
function createLoggingService() {
|
|
1956
|
+
let patterns = null;
|
|
1957
|
+
let initialized = false;
|
|
1958
|
+
let activeFileSink = null;
|
|
1959
|
+
const fileSinkCache = new Map;
|
|
1960
|
+
function init() {
|
|
1961
|
+
if (initialized)
|
|
1962
|
+
return;
|
|
1963
|
+
initialized = true;
|
|
1964
|
+
const parsed = parsePatterns(process.env.DEBUG);
|
|
1965
|
+
patterns = parsed.length > 0 ? parsed : null;
|
|
1966
|
+
}
|
|
1967
|
+
function getOrCreateFileSink(path) {
|
|
1968
|
+
let sink = fileSinkCache.get(path);
|
|
1969
|
+
if (!sink) {
|
|
1970
|
+
sink = new FileSink(path);
|
|
1971
|
+
fileSinkCache.set(path, sink);
|
|
1972
|
+
}
|
|
1973
|
+
return sink;
|
|
1974
|
+
}
|
|
1975
|
+
return {
|
|
1976
|
+
enableFileLogs(scope, sinkForTesting) {
|
|
1977
|
+
const existing = process.env[DUST_LOG_FILE];
|
|
1978
|
+
const logDir = process.env.DUST_LOG_DIR ?? join6(process.cwd(), "log");
|
|
1979
|
+
const path = existing ?? join6(logDir, `${scope}.log`);
|
|
1980
|
+
if (!existing) {
|
|
1981
|
+
process.env[DUST_LOG_FILE] = path;
|
|
1982
|
+
}
|
|
1983
|
+
activeFileSink = sinkForTesting ?? new FileSink(path);
|
|
1984
|
+
},
|
|
1985
|
+
createLogger(name, options) {
|
|
1986
|
+
let perLoggerSink;
|
|
1987
|
+
if (options?.file === false) {
|
|
1988
|
+
perLoggerSink = null;
|
|
1989
|
+
} else if (typeof options?.file === "string") {
|
|
1990
|
+
perLoggerSink = getOrCreateFileSink(options.file);
|
|
1991
|
+
}
|
|
1992
|
+
return (...messages) => {
|
|
1993
|
+
init();
|
|
1994
|
+
const line = formatLine(name, messages);
|
|
1995
|
+
if (perLoggerSink !== undefined) {
|
|
1996
|
+
if (perLoggerSink !== null) {
|
|
1997
|
+
perLoggerSink.write(line);
|
|
1998
|
+
}
|
|
1999
|
+
} else if (activeFileSink) {
|
|
2000
|
+
activeFileSink.write(line);
|
|
2001
|
+
}
|
|
2002
|
+
if (patterns && matchesAny(name, patterns)) {
|
|
2003
|
+
process.stdout.write(line);
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
},
|
|
2007
|
+
isEnabled(name) {
|
|
2008
|
+
init();
|
|
2009
|
+
return patterns !== null && matchesAny(name, patterns);
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
var defaultService = createLoggingService();
|
|
2014
|
+
var enableFileLogs = defaultService.enableFileLogs.bind(defaultService);
|
|
2015
|
+
var createLogger = defaultService.createLogger.bind(defaultService);
|
|
2016
|
+
var isEnabled = defaultService.isEnabled.bind(defaultService);
|
|
2017
|
+
|
|
2018
|
+
// lib/claude/spawn-claude-code.ts
|
|
2019
|
+
var debug = createLogger("dust.claude.spawn-claude-code");
|
|
1780
2020
|
var defaultDependencies = {
|
|
1781
2021
|
spawn: nodeSpawn2,
|
|
1782
2022
|
createInterface: nodeCreateInterface
|
|
@@ -1859,7 +2099,9 @@ async function* spawnClaudeCode(prompt, options = {}, dependencies = defaultDepe
|
|
|
1859
2099
|
continue;
|
|
1860
2100
|
try {
|
|
1861
2101
|
yield JSON.parse(line);
|
|
1862
|
-
} catch {
|
|
2102
|
+
} catch {
|
|
2103
|
+
debug("Skipping malformed JSON line: %s", line.slice(0, 200));
|
|
2104
|
+
}
|
|
1863
2105
|
}
|
|
1864
2106
|
await closePromise;
|
|
1865
2107
|
} finally {
|
|
@@ -2190,131 +2432,6 @@ async function run(prompt, options = {}, dependencies = defaultRunnerDependencie
|
|
|
2190
2432
|
await dependencies.streamEvents(events, sink, onRawEvent);
|
|
2191
2433
|
}
|
|
2192
2434
|
|
|
2193
|
-
// lib/logging/index.ts
|
|
2194
|
-
import { join as join6 } from "node:path";
|
|
2195
|
-
|
|
2196
|
-
// lib/logging/match.ts
|
|
2197
|
-
function parsePatterns(debug) {
|
|
2198
|
-
if (!debug)
|
|
2199
|
-
return [];
|
|
2200
|
-
const expressions = debug.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2201
|
-
return expressions.map((expr) => {
|
|
2202
|
-
const escaped = expr.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
2203
|
-
const pattern = escaped.replace(/\*/g, ".*");
|
|
2204
|
-
return new RegExp(`^${pattern}$`);
|
|
2205
|
-
});
|
|
2206
|
-
}
|
|
2207
|
-
function matchesAny(name, patterns) {
|
|
2208
|
-
return patterns.some((re) => re.test(name));
|
|
2209
|
-
}
|
|
2210
|
-
function formatLine(name, messages) {
|
|
2211
|
-
const text = messages.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
2212
|
-
return `${new Date().toISOString()} [${name}] ${text}
|
|
2213
|
-
`;
|
|
2214
|
-
}
|
|
2215
|
-
|
|
2216
|
-
// lib/logging/sink.ts
|
|
2217
|
-
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2218
|
-
import { dirname } from "node:path";
|
|
2219
|
-
|
|
2220
|
-
class FileSink {
|
|
2221
|
-
logPath;
|
|
2222
|
-
_appendFileSync;
|
|
2223
|
-
_mkdirSync;
|
|
2224
|
-
resolvedPath;
|
|
2225
|
-
ready = false;
|
|
2226
|
-
constructor(logPath, _appendFileSync = appendFileSync, _mkdirSync = mkdirSync) {
|
|
2227
|
-
this.logPath = logPath;
|
|
2228
|
-
this._appendFileSync = _appendFileSync;
|
|
2229
|
-
this._mkdirSync = _mkdirSync;
|
|
2230
|
-
}
|
|
2231
|
-
ensureLogFile() {
|
|
2232
|
-
if (this.ready)
|
|
2233
|
-
return this.resolvedPath;
|
|
2234
|
-
this.ready = true;
|
|
2235
|
-
this.resolvedPath = this.logPath;
|
|
2236
|
-
try {
|
|
2237
|
-
this._mkdirSync(dirname(this.logPath), { recursive: true });
|
|
2238
|
-
} catch {
|
|
2239
|
-
this.resolvedPath = undefined;
|
|
2240
|
-
}
|
|
2241
|
-
return this.resolvedPath;
|
|
2242
|
-
}
|
|
2243
|
-
write(line) {
|
|
2244
|
-
const path = this.ensureLogFile();
|
|
2245
|
-
if (!path)
|
|
2246
|
-
return;
|
|
2247
|
-
try {
|
|
2248
|
-
this._appendFileSync(path, line);
|
|
2249
|
-
} catch {}
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
// lib/logging/index.ts
|
|
2254
|
-
var DUST_LOG_FILE = "DUST_LOG_FILE";
|
|
2255
|
-
function createLoggingService() {
|
|
2256
|
-
let patterns = null;
|
|
2257
|
-
let initialized = false;
|
|
2258
|
-
let activeFileSink = null;
|
|
2259
|
-
const fileSinkCache = new Map;
|
|
2260
|
-
function init() {
|
|
2261
|
-
if (initialized)
|
|
2262
|
-
return;
|
|
2263
|
-
initialized = true;
|
|
2264
|
-
const parsed = parsePatterns(process.env.DEBUG);
|
|
2265
|
-
patterns = parsed.length > 0 ? parsed : null;
|
|
2266
|
-
}
|
|
2267
|
-
function getOrCreateFileSink(path) {
|
|
2268
|
-
let sink = fileSinkCache.get(path);
|
|
2269
|
-
if (!sink) {
|
|
2270
|
-
sink = new FileSink(path);
|
|
2271
|
-
fileSinkCache.set(path, sink);
|
|
2272
|
-
}
|
|
2273
|
-
return sink;
|
|
2274
|
-
}
|
|
2275
|
-
return {
|
|
2276
|
-
enableFileLogs(scope, sinkForTesting) {
|
|
2277
|
-
const existing = process.env[DUST_LOG_FILE];
|
|
2278
|
-
const logDir = process.env.DUST_LOG_DIR ?? join6(process.cwd(), "log");
|
|
2279
|
-
const path = existing ?? join6(logDir, `${scope}.log`);
|
|
2280
|
-
if (!existing) {
|
|
2281
|
-
process.env[DUST_LOG_FILE] = path;
|
|
2282
|
-
}
|
|
2283
|
-
activeFileSink = sinkForTesting ?? new FileSink(path);
|
|
2284
|
-
},
|
|
2285
|
-
createLogger(name, options) {
|
|
2286
|
-
let perLoggerSink;
|
|
2287
|
-
if (options?.file === false) {
|
|
2288
|
-
perLoggerSink = null;
|
|
2289
|
-
} else if (typeof options?.file === "string") {
|
|
2290
|
-
perLoggerSink = getOrCreateFileSink(options.file);
|
|
2291
|
-
}
|
|
2292
|
-
return (...messages) => {
|
|
2293
|
-
init();
|
|
2294
|
-
const line = formatLine(name, messages);
|
|
2295
|
-
if (perLoggerSink !== undefined) {
|
|
2296
|
-
if (perLoggerSink !== null) {
|
|
2297
|
-
perLoggerSink.write(line);
|
|
2298
|
-
}
|
|
2299
|
-
} else if (activeFileSink) {
|
|
2300
|
-
activeFileSink.write(line);
|
|
2301
|
-
}
|
|
2302
|
-
if (patterns && matchesAny(name, patterns)) {
|
|
2303
|
-
process.stdout.write(line);
|
|
2304
|
-
}
|
|
2305
|
-
};
|
|
2306
|
-
},
|
|
2307
|
-
isEnabled(name) {
|
|
2308
|
-
init();
|
|
2309
|
-
return patterns !== null && matchesAny(name, patterns);
|
|
2310
|
-
}
|
|
2311
|
-
};
|
|
2312
|
-
}
|
|
2313
|
-
var defaultService = createLoggingService();
|
|
2314
|
-
var enableFileLogs = defaultService.enableFileLogs.bind(defaultService);
|
|
2315
|
-
var createLogger = defaultService.createLogger.bind(defaultService);
|
|
2316
|
-
var isEnabled = defaultService.isEnabled.bind(defaultService);
|
|
2317
|
-
|
|
2318
2435
|
// lib/bucket/repository-git.ts
|
|
2319
2436
|
import { join as join7 } from "node:path";
|
|
2320
2437
|
function getRepoPath(repoName, reposDir) {
|
|
@@ -2402,11 +2519,15 @@ function titleToFilename(title) {
|
|
|
2402
2519
|
}
|
|
2403
2520
|
|
|
2404
2521
|
// lib/cli/commands/focus.ts
|
|
2405
|
-
function buildImplementationInstructions(bin, hooksInstalled, taskTitle) {
|
|
2522
|
+
function buildImplementationInstructions(bin, hooksInstalled, taskTitle, taskPath, installCommand) {
|
|
2406
2523
|
const steps = [];
|
|
2407
2524
|
let step = 1;
|
|
2408
2525
|
const hasIdeaFile = !taskTitle?.startsWith(EXPEDITE_IDEA_PREFIX);
|
|
2409
2526
|
steps.push(`Note: Do NOT run \`${bin} agent\`.`, "");
|
|
2527
|
+
if (installCommand) {
|
|
2528
|
+
steps.push(`${step}. Run \`${installCommand}\` to install dependencies`);
|
|
2529
|
+
step++;
|
|
2530
|
+
}
|
|
2410
2531
|
steps.push(`${step}. Run \`${bin} check\` to verify the project is in a good state`);
|
|
2411
2532
|
step++;
|
|
2412
2533
|
steps.push(`${step}. Implement the task`);
|
|
@@ -2416,10 +2537,11 @@ function buildImplementationInstructions(bin, hooksInstalled, taskTitle) {
|
|
|
2416
2537
|
step++;
|
|
2417
2538
|
}
|
|
2418
2539
|
const commitMessageLine = taskTitle ? ` Use this exact commit message: "${taskTitle}". Do not add any prefix.` : ' Use the task title as the commit message. Do not add prefixes like "Complete task:" - use the title directly.';
|
|
2540
|
+
const deleteTaskLine = taskPath ? ` - Deletion of the completed task file (\`${taskPath}\`)` : " - Deletion of the completed task file";
|
|
2419
2541
|
const commitItems = [
|
|
2420
2542
|
" - All implementation changes",
|
|
2421
|
-
|
|
2422
|
-
|
|
2543
|
+
deleteTaskLine,
|
|
2544
|
+
` - Updates to any facts that changed (run \`${bin} facts\` if needed)`
|
|
2423
2545
|
];
|
|
2424
2546
|
if (hasIdeaFile) {
|
|
2425
2547
|
commitItems.push(" - Deletion of the idea file that spawned this task (if remaining scope exists, create new ideas for it)");
|
|
@@ -2725,18 +2847,14 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
2725
2847
|
onLoopEvent({ type: "loop.tasks_found" });
|
|
2726
2848
|
const taskContent = await dependencies.fileSystem.readFile(`${dependencies.context.cwd}/${task.path}`);
|
|
2727
2849
|
const { dustCommand, installCommand = "npm install" } = dependencies.settings;
|
|
2728
|
-
const instructions = buildImplementationInstructions(dustCommand, hooksInstalled, task.title ?? undefined);
|
|
2729
|
-
const prompt = `
|
|
2730
|
-
|
|
2731
|
-
The following is the contents of the task file \`${task.path}\`:
|
|
2850
|
+
const instructions = buildImplementationInstructions(dustCommand, hooksInstalled, task.title ?? undefined, task.path, installCommand);
|
|
2851
|
+
const prompt = `Implement the task at \`${task.path}\`:
|
|
2732
2852
|
|
|
2733
2853
|
----------
|
|
2734
2854
|
${taskContent}
|
|
2735
2855
|
----------
|
|
2736
2856
|
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
## Instructions
|
|
2857
|
+
## How to implement the task
|
|
2740
2858
|
|
|
2741
2859
|
${instructions}`;
|
|
2742
2860
|
onAgentEvent?.({
|
|
@@ -2877,6 +2995,9 @@ function buildEventMessage(parameters) {
|
|
|
2877
2995
|
repository: parameters.repository,
|
|
2878
2996
|
event: parameters.event
|
|
2879
2997
|
};
|
|
2998
|
+
if (parameters.repoId !== undefined) {
|
|
2999
|
+
msg.repoId = parameters.repoId;
|
|
3000
|
+
}
|
|
2880
3001
|
if (parameters.agentSessionId) {
|
|
2881
3002
|
msg.agentSessionId = parameters.agentSessionId;
|
|
2882
3003
|
}
|
|
@@ -2962,6 +3083,7 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
2962
3083
|
sequence,
|
|
2963
3084
|
sessionId,
|
|
2964
3085
|
repository: repoName,
|
|
3086
|
+
repoId: repoState.repository.id,
|
|
2965
3087
|
event,
|
|
2966
3088
|
agentSessionId
|
|
2967
3089
|
}));
|
|
@@ -3965,7 +4087,12 @@ async function shutdown(state, bucketDeps, context) {
|
|
|
3965
4087
|
repoState.wakeUp?.();
|
|
3966
4088
|
}
|
|
3967
4089
|
const loopPromises = Array.from(state.repositories.values()).map((rs) => rs.loopPromise).filter((p) => p !== null);
|
|
3968
|
-
await Promise.
|
|
4090
|
+
const results = await Promise.allSettled(loopPromises);
|
|
4091
|
+
for (const result of results) {
|
|
4092
|
+
if (result.status === "rejected") {
|
|
4093
|
+
context.stderr(`Repository loop failed: ${result.reason}`);
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
3969
4096
|
for (const repoState of state.repositories.values()) {
|
|
3970
4097
|
await removeRepository(repoState.path, bucketDeps.spawn, context);
|
|
3971
4098
|
}
|
|
@@ -5624,6 +5751,7 @@ async function list(dependencies) {
|
|
|
5624
5751
|
// lib/codex/spawn-codex.ts
|
|
5625
5752
|
import { spawn as nodeSpawn5 } from "node:child_process";
|
|
5626
5753
|
import { createInterface as nodeCreateInterface2 } from "node:readline";
|
|
5754
|
+
var debug2 = createLogger("dust.codex.spawn-codex");
|
|
5627
5755
|
var defaultDependencies2 = {
|
|
5628
5756
|
spawn: nodeSpawn5,
|
|
5629
5757
|
createInterface: nodeCreateInterface2
|
|
@@ -5673,7 +5801,9 @@ async function* spawnCodex(prompt, options = {}, dependencies = defaultDependenc
|
|
|
5673
5801
|
continue;
|
|
5674
5802
|
try {
|
|
5675
5803
|
yield JSON.parse(line);
|
|
5676
|
-
} catch {
|
|
5804
|
+
} catch {
|
|
5805
|
+
debug2("Skipping malformed JSON line: %s", line.slice(0, 200));
|
|
5806
|
+
}
|
|
5677
5807
|
}
|
|
5678
5808
|
await closePromise;
|
|
5679
5809
|
} finally {
|
package/dist/types.d.ts
CHANGED
|
@@ -6,5 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export type { AgentSessionEvent, EventMessage } from './agent-events';
|
|
8
8
|
export type { Idea, IdeaOpenQuestion, IdeaOption } from './artifacts/ideas';
|
|
9
|
+
export type { TaskGraph, TaskGraphNode } from './artifacts/index';
|
|
10
|
+
export type { Task } from './artifacts/tasks';
|
|
9
11
|
export type { CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, IdeaInProgress, OpenQuestionResponse, ParsedCaptureIdeaTask, WorkflowTaskMatch, WorkflowTaskType, } from './artifacts/workflow-tasks';
|
|
10
12
|
export type { Repository } from './bucket/repository';
|
package/dist/validation.js
CHANGED
|
@@ -730,7 +730,11 @@ function createOverlayFileSystem(base, patchFiles, deletedPaths = new Set) {
|
|
|
730
730
|
entries.add(entry);
|
|
731
731
|
}
|
|
732
732
|
}
|
|
733
|
-
} catch {
|
|
733
|
+
} catch (error) {
|
|
734
|
+
if (error.code !== "ENOENT") {
|
|
735
|
+
throw error;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
734
738
|
return Array.from(entries);
|
|
735
739
|
},
|
|
736
740
|
isDirectory(path) {
|
|
@@ -821,7 +825,11 @@ async function validatePatch(fileSystem, dustPath, patch) {
|
|
|
821
825
|
const content = await overlayFs.readFile(filePath);
|
|
822
826
|
allRelationships.push(extractPrincipleRelationships(filePath, content));
|
|
823
827
|
}
|
|
824
|
-
} catch {
|
|
828
|
+
} catch (error) {
|
|
829
|
+
if (error.code !== "ENOENT") {
|
|
830
|
+
throw error;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
825
833
|
violations.push(...validateBidirectionalLinks(allRelationships));
|
|
826
834
|
violations.push(...validateNoCycles(allRelationships));
|
|
827
835
|
}
|