@joshski/dust 0.1.5 → 0.1.7
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 +19 -85
- package/dist/dust.js +156 -107
- package/package.json +1 -1
- package/templates/agent-greeting.txt +39 -6
- package/templates/agent-help.txt +11 -0
- package/templates/agent-ideas.txt +1 -2
- package/templates/agent-tasks.txt +42 -9
- package/templates/agents-md.txt +1 -1
- package/templates/claude-md.txt +1 -1
- package/templates/help.txt +1 -60
package/README.md
CHANGED
|
@@ -4,104 +4,38 @@ A lightweight planning system and work tracker optimised for humans working with
|
|
|
4
4
|
|
|
5
5
|
[](https://github.com/joshski/dust/actions/workflows/ci.yml)
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Getting Started
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Install dust:
|
|
10
10
|
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @joshski/dust
|
|
11
13
|
```
|
|
12
|
-
.dust/
|
|
13
|
-
├── goals/ # Mission statements explaining why the project exists
|
|
14
|
-
├── ideas/ # Brief notes about future tasks (intentionally vague)
|
|
15
|
-
├── tasks/ # Detailed work plans with dependencies and definition of done
|
|
16
|
-
├── facts/ # Current state: design, architecture, rules, invariants
|
|
17
|
-
└── hooks/ # Executable scripts for CLI integration (e.g., quality gates)
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
The `goals`, `ideas`, `tasks`, and `facts` directories should be flat (no subdirectories) and contain only markdown files with slug-style names (alphanumeric and hyphens only).
|
|
21
|
-
|
|
22
|
-
The `hooks` directory contains executable scripts that integrate with the `dust` CLI. For example, the `check` hook is run by `dust check` to execute project-defined quality gates.
|
|
23
|
-
|
|
24
|
-
## CLI Commands
|
|
25
14
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
| Command | Description |
|
|
29
|
-
|---------|-------------|
|
|
30
|
-
| `dust init` | Initialize a new Dust repository with the standard directory structure |
|
|
31
|
-
| `dust prompt <name>` | Output a prompt by name from the `prompts/` directory |
|
|
32
|
-
| `dust validate` | Run validation checks on `.dust/` files (links, task structure, naming) |
|
|
33
|
-
| `dust list [type]` | List items by type (tasks, ideas, goals, facts) or all if no type specified |
|
|
34
|
-
| `dust next` | Show tasks ready to work on (not blocked by other incomplete tasks) |
|
|
35
|
-
| `dust check` | Run `validate` then execute the project's quality gate hook at `.dust/hooks/check` |
|
|
36
|
-
| `dust help` | Show help message with all available commands |
|
|
37
|
-
|
|
38
|
-
### Examples
|
|
15
|
+
Initialize dust in your repository:
|
|
39
16
|
|
|
40
17
|
```bash
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# List all tasks
|
|
45
|
-
dust list tasks
|
|
46
|
-
|
|
47
|
-
# List everything (tasks, ideas, goals, facts)
|
|
48
|
-
dust list
|
|
49
|
-
|
|
50
|
-
# Show tasks that are ready to start
|
|
51
|
-
dust next
|
|
52
|
-
|
|
53
|
-
# Validate all markdown files under ./.dust
|
|
54
|
-
dust validate
|
|
55
|
-
|
|
56
|
-
# Run quality checks (validation + custom hook)
|
|
57
|
-
dust check
|
|
58
|
-
|
|
59
|
-
# Output a prompt by name
|
|
60
|
-
dust prompt work
|
|
18
|
+
npx dust init
|
|
19
|
+
# or
|
|
20
|
+
bunx dust init
|
|
61
21
|
```
|
|
62
22
|
|
|
63
|
-
##
|
|
64
|
-
|
|
65
|
-
Dust is designed for successive cycles of human planning (AI-assisted, of course) followed by agent autonomy, followed by human planning, and so on.
|
|
66
|
-
|
|
67
|
-
In order for work to begin, there must be a task. A worker (an AI agent or human) chooses any task to work on. In a team environment, the worker must “claim” the task i.e. let the team know they are working on it. The team can use a version control system (like git) claim the task by making a branch with the same name as the task. If any attempt to claim fails (e.g. a branch with that name already exists) then the agent must choose an alternative task.
|
|
68
|
-
|
|
69
|
-
When the worker completes their task, they make a single commit that includes the work, but also deletes the task, and removes any references to the task. The commit should often update one or more facts as well.
|
|
70
|
-
|
|
71
|
-
Tasks should be small units of work that can be completed quickly and result in a single commit that leaves the system in a reasonable state (e.g. no broken or half-implemented features exposed to end users). When in doubt, workers are encouraged to split the task into smaller sub-tasks, and abandon the attempt to finish any ambitious work in one go.
|
|
72
|
-
|
|
73
|
-
Over time, new ideas emerge, and ideas become more detailed plans in the form of "tasks". This should be deferred until the last responsible moment. Since humans like control over plans, ideas become plans in the "human-in-the-loop" phase at the start of a sprint.
|
|
23
|
+
## How It Works
|
|
74
24
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Tasks are the only markdown files that have a strict structure. Tasks must have each of the following subheadings:
|
|
25
|
+
The [.dust](./.dust) directory in your repository contains 4 sets of markdown files:
|
|
78
26
|
|
|
79
27
|
```
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
28
|
+
.dust/
|
|
29
|
+
├── goals/ # Mission statements explaining why the project exists
|
|
30
|
+
├── ideas/ # Brief notes about future tasks (intentionally vague)
|
|
31
|
+
├── tasks/ # Detailed work plans with dependencies and definition of done
|
|
32
|
+
└── facts/ # Current state: design, architecture, rules, invariants
|
|
83
33
|
```
|
|
84
34
|
|
|
85
|
-
|
|
86
|
-
* Blocked by - a list relative links to other markdown files, each of which nominates a task that must be implemented before this task can be started.
|
|
87
|
-
* Definition of done - A short description of how the implementor of the task can decide when the task has been completed successfully
|
|
88
|
-
|
|
89
|
-
These special headings and sections are required, but the remainder of the document is free form.
|
|
90
|
-
|
|
91
|
-
## The single commit
|
|
92
|
-
|
|
93
|
-
Each task should be a small unit of work. If it was underestimated, the agent implementing the task should commit any progress that does not have a negative impact on end users, and create another "follow up" task to complete the remainder of the work.
|
|
94
|
-
|
|
95
|
-
## Links between documents
|
|
35
|
+
These files are used to facilitate exploration and management of AI agent workflow.
|
|
96
36
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
## Change history
|
|
100
|
-
|
|
101
|
-
Commits delete tasks, but commit history can be traversed to retrieve the thinking behind any changes. Tools can be implemented to make this easier or build indexes. The current working copy is kept intentionally free of this detail, to keep commits clean and reduce noise in the current repository state.
|
|
102
|
-
|
|
103
|
-
## Hygiene
|
|
37
|
+
## Workflow
|
|
104
38
|
|
|
105
|
-
|
|
39
|
+
Progress is tracked via changes to markdown files in the `.dust/` directory. The four directories together (`goals/`, `ideas/`, `tasks/`, `facts/`) act as a kanban system for managing work.
|
|
106
40
|
|
|
107
|
-
|
|
41
|
+
The `tasks/` directory acts as a work queue. When a task is completed, the commit typically includes both the code changes and the deletion of the task file—removing work from the queue for subsequent agents.
|
package/dist/dust.js
CHANGED
|
@@ -27,22 +27,22 @@ var AGENT_SUBCOMMANDS = [
|
|
|
27
27
|
"help"
|
|
28
28
|
];
|
|
29
29
|
function generateAgentGreeting(settings) {
|
|
30
|
-
return loadTemplate("agent-greeting", { bin: settings.
|
|
30
|
+
return loadTemplate("agent-greeting", { bin: settings.dustCommand });
|
|
31
31
|
}
|
|
32
32
|
function generateWorkInstructions(settings) {
|
|
33
|
-
return loadTemplate("agent-work", { bin: settings.
|
|
33
|
+
return loadTemplate("agent-work", { bin: settings.dustCommand });
|
|
34
34
|
}
|
|
35
35
|
function generateTasksInstructions(settings) {
|
|
36
|
-
return loadTemplate("agent-tasks", { bin: settings.
|
|
36
|
+
return loadTemplate("agent-tasks", { bin: settings.dustCommand });
|
|
37
37
|
}
|
|
38
38
|
function generateGoalsInstructions(settings) {
|
|
39
|
-
return loadTemplate("agent-goals", { bin: settings.
|
|
39
|
+
return loadTemplate("agent-goals", { bin: settings.dustCommand });
|
|
40
40
|
}
|
|
41
41
|
function generateIdeasInstructions(settings) {
|
|
42
|
-
return loadTemplate("agent-ideas", { bin: settings.
|
|
42
|
+
return loadTemplate("agent-ideas", { bin: settings.dustCommand });
|
|
43
43
|
}
|
|
44
44
|
function generateAgentHelp(settings) {
|
|
45
|
-
return loadTemplate("agent-help", { bin: settings.
|
|
45
|
+
return loadTemplate("agent-help", { bin: settings.dustCommand });
|
|
46
46
|
}
|
|
47
47
|
async function agent(ctx, args, settings) {
|
|
48
48
|
const subcommand = args[0];
|
|
@@ -76,6 +76,54 @@ async function agent(ctx, args, settings) {
|
|
|
76
76
|
// lib/cli/check.ts
|
|
77
77
|
import { spawn } from "node:child_process";
|
|
78
78
|
|
|
79
|
+
// lib/cli/settings.ts
|
|
80
|
+
import { join as join2 } from "node:path";
|
|
81
|
+
var DEFAULT_SETTINGS = {
|
|
82
|
+
dustCommand: "npx dust"
|
|
83
|
+
};
|
|
84
|
+
function detectDustCommand(cwd, fs) {
|
|
85
|
+
if (fs.exists(join2(cwd, "bun.lockb"))) {
|
|
86
|
+
return "bunx dust";
|
|
87
|
+
}
|
|
88
|
+
if (fs.exists(join2(cwd, "pnpm-lock.yaml"))) {
|
|
89
|
+
return "pnpx dust";
|
|
90
|
+
}
|
|
91
|
+
if (fs.exists(join2(cwd, "package-lock.json"))) {
|
|
92
|
+
return "npx dust";
|
|
93
|
+
}
|
|
94
|
+
if (process.env.BUN_INSTALL) {
|
|
95
|
+
return "bunx dust";
|
|
96
|
+
}
|
|
97
|
+
return "npx dust";
|
|
98
|
+
}
|
|
99
|
+
async function loadSettings(cwd, fs) {
|
|
100
|
+
const settingsPath = join2(cwd, ".dust", "config", "settings.json");
|
|
101
|
+
if (!fs.exists(settingsPath)) {
|
|
102
|
+
return {
|
|
103
|
+
dustCommand: detectDustCommand(cwd, fs)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const content = await fs.readFile(settingsPath);
|
|
108
|
+
const parsed = JSON.parse(content);
|
|
109
|
+
if (!parsed.dustCommand) {
|
|
110
|
+
return {
|
|
111
|
+
...DEFAULT_SETTINGS,
|
|
112
|
+
...parsed,
|
|
113
|
+
dustCommand: detectDustCommand(cwd, fs)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
...DEFAULT_SETTINGS,
|
|
118
|
+
...parsed
|
|
119
|
+
};
|
|
120
|
+
} catch {
|
|
121
|
+
return {
|
|
122
|
+
dustCommand: detectDustCommand(cwd, fs)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
79
127
|
// lib/cli/validate.ts
|
|
80
128
|
import { dirname as dirname2, resolve } from "node:path";
|
|
81
129
|
var REQUIRED_HEADINGS = ["## Goals", "## Blocked by", "## Definition of done"];
|
|
@@ -240,66 +288,121 @@ async function validate(ctx, fs, _args, glob) {
|
|
|
240
288
|
}
|
|
241
289
|
|
|
242
290
|
// lib/cli/check.ts
|
|
243
|
-
function
|
|
291
|
+
function createBufferedRunner(spawnFn) {
|
|
244
292
|
return {
|
|
245
|
-
|
|
293
|
+
run: (command, cwd) => {
|
|
246
294
|
return new Promise((resolve2) => {
|
|
247
|
-
const proc = spawnFn(command,
|
|
248
|
-
|
|
249
|
-
proc.on("
|
|
295
|
+
const proc = spawnFn(command, [], { cwd, shell: true });
|
|
296
|
+
const chunks = [];
|
|
297
|
+
proc.stdout?.on("data", (data) => {
|
|
298
|
+
chunks.push(data.toString());
|
|
299
|
+
});
|
|
300
|
+
proc.stderr?.on("data", (data) => {
|
|
301
|
+
chunks.push(data.toString());
|
|
302
|
+
});
|
|
303
|
+
proc.on("close", (code) => {
|
|
304
|
+
resolve2({ exitCode: code ?? 1, output: chunks.join("") });
|
|
305
|
+
});
|
|
306
|
+
proc.on("error", (err) => {
|
|
307
|
+
resolve2({ exitCode: 1, output: err.message });
|
|
308
|
+
});
|
|
250
309
|
});
|
|
251
310
|
}
|
|
252
311
|
};
|
|
253
312
|
}
|
|
254
|
-
var
|
|
255
|
-
async function
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
313
|
+
var defaultBufferedRunner = createBufferedRunner(spawn);
|
|
314
|
+
async function runConfiguredChecks(checks, cwd, runner) {
|
|
315
|
+
const promises = checks.map(async (check) => {
|
|
316
|
+
const { exitCode, output } = await runner.run(check.command, cwd);
|
|
317
|
+
return {
|
|
318
|
+
name: check.name,
|
|
319
|
+
command: check.command,
|
|
320
|
+
exitCode,
|
|
321
|
+
output
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
return Promise.all(promises);
|
|
325
|
+
}
|
|
326
|
+
async function runValidationCheck(ctx, fs, glob) {
|
|
327
|
+
const outputLines = [];
|
|
328
|
+
const bufferedCtx = {
|
|
329
|
+
cwd: ctx.cwd,
|
|
330
|
+
stdout: (msg) => outputLines.push(msg),
|
|
331
|
+
stderr: (msg) => outputLines.push(msg)
|
|
332
|
+
};
|
|
333
|
+
const result = await validate(bufferedCtx, fs, [], glob);
|
|
334
|
+
return {
|
|
335
|
+
name: "validate",
|
|
336
|
+
command: "dust validate",
|
|
337
|
+
exitCode: result.exitCode,
|
|
338
|
+
output: outputLines.join(`
|
|
339
|
+
`),
|
|
340
|
+
isBuiltIn: true
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function displayResults(results, ctx) {
|
|
344
|
+
const passed = results.filter((r) => r.exitCode === 0);
|
|
345
|
+
const failed = results.filter((r) => r.exitCode !== 0);
|
|
346
|
+
for (const result of results) {
|
|
347
|
+
if (result.exitCode === 0) {
|
|
348
|
+
ctx.stdout(`✓ ${result.name}`);
|
|
349
|
+
} else {
|
|
350
|
+
ctx.stdout(`✗ ${result.name}`);
|
|
260
351
|
}
|
|
352
|
+
}
|
|
353
|
+
for (const result of failed) {
|
|
261
354
|
ctx.stdout("");
|
|
355
|
+
ctx.stdout(`> ${result.command}`);
|
|
356
|
+
if (result.output.trim()) {
|
|
357
|
+
ctx.stdout(result.output.trimEnd());
|
|
358
|
+
}
|
|
262
359
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
360
|
+
ctx.stdout("");
|
|
361
|
+
ctx.stdout(`${passed.length}/${results.length} checks passed`);
|
|
362
|
+
return failed.length > 0 ? 1 : 0;
|
|
363
|
+
}
|
|
364
|
+
async function check(ctx, fs, _args, glob, bufferedRunner = defaultBufferedRunner) {
|
|
365
|
+
const settings = await loadSettings(ctx.cwd, fs);
|
|
366
|
+
if (!settings.checks || settings.checks.length === 0) {
|
|
367
|
+
ctx.stderr("Error: No checks configured in .dust/config/settings.json");
|
|
266
368
|
ctx.stderr("");
|
|
267
|
-
ctx.stderr("
|
|
268
|
-
ctx.stderr("
|
|
269
|
-
ctx.stderr("
|
|
270
|
-
ctx.stderr("
|
|
271
|
-
ctx.stderr("
|
|
369
|
+
ctx.stderr("Add checks to your settings.json:");
|
|
370
|
+
ctx.stderr(" {");
|
|
371
|
+
ctx.stderr(' "checks": [');
|
|
372
|
+
ctx.stderr(' { "name": "lint", "command": "npm run lint" },');
|
|
373
|
+
ctx.stderr(' { "name": "test", "command": "npm test" }');
|
|
374
|
+
ctx.stderr(" ]");
|
|
375
|
+
ctx.stderr(" }");
|
|
272
376
|
return { exitCode: 1 };
|
|
273
377
|
}
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
378
|
+
const checkPromises = [];
|
|
379
|
+
if (glob) {
|
|
380
|
+
checkPromises.push(runValidationCheck(ctx, fs, glob));
|
|
381
|
+
}
|
|
382
|
+
checkPromises.push(runConfiguredChecks(settings.checks, ctx.cwd, bufferedRunner));
|
|
383
|
+
const promiseResults = await Promise.all(checkPromises);
|
|
384
|
+
const results = [];
|
|
385
|
+
for (const result of promiseResults) {
|
|
386
|
+
if (Array.isArray(result)) {
|
|
387
|
+
results.push(...result);
|
|
388
|
+
} else {
|
|
389
|
+
results.push(result);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const exitCode = displayResults(results, ctx);
|
|
278
393
|
return { exitCode };
|
|
279
394
|
}
|
|
280
395
|
|
|
281
396
|
// lib/cli/init.ts
|
|
282
397
|
var DUST_DIRECTORIES = ["goals", "ideas", "tasks", "facts"];
|
|
283
|
-
var
|
|
398
|
+
var USE_DUST_FACT = `# Use dust for planning
|
|
284
399
|
|
|
285
|
-
|
|
400
|
+
This project uses [dust](https://github.com/joshski/dust) for planning and documentation.
|
|
286
401
|
`;
|
|
287
|
-
function detectPackageRunner(cwd, fs) {
|
|
288
|
-
if (process.versions.bun) {
|
|
289
|
-
return "bunx";
|
|
290
|
-
}
|
|
291
|
-
if (fs.exists(`${cwd}/bun.lockb`)) {
|
|
292
|
-
return "bunx";
|
|
293
|
-
}
|
|
294
|
-
if (fs.exists(`${cwd}/pnpm-lock.yaml`)) {
|
|
295
|
-
return "pnpx";
|
|
296
|
-
}
|
|
297
|
-
return "npx";
|
|
298
|
-
}
|
|
299
402
|
async function init(ctx, fs, _args) {
|
|
300
403
|
const dustPath = `${ctx.cwd}/.dust`;
|
|
301
|
-
const
|
|
302
|
-
const agentInstruction = `Always immediately run \`${
|
|
404
|
+
const dustCommand = detectDustCommand(ctx.cwd, fs);
|
|
405
|
+
const agentInstruction = `Always immediately run \`${dustCommand} agent\` when you start working in this repository.`;
|
|
303
406
|
if (fs.exists(dustPath)) {
|
|
304
407
|
ctx.stdout("Note: .dust directory already exists, skipping creation");
|
|
305
408
|
} else {
|
|
@@ -307,16 +410,16 @@ async function init(ctx, fs, _args) {
|
|
|
307
410
|
for (const dir of DUST_DIRECTORIES) {
|
|
308
411
|
await fs.mkdir(`${dustPath}/${dir}`, { recursive: true });
|
|
309
412
|
}
|
|
310
|
-
await fs.writeFile(`${dustPath}/
|
|
413
|
+
await fs.writeFile(`${dustPath}/facts/use-dust-for-planning.md`, USE_DUST_FACT);
|
|
311
414
|
ctx.stdout("Initialized Dust repository in .dust/");
|
|
312
415
|
ctx.stdout(`Created directories: ${DUST_DIRECTORIES.join(", ")}`);
|
|
313
|
-
ctx.stdout("Created initial
|
|
416
|
+
ctx.stdout("Created initial fact: .dust/facts/use-dust-for-planning.md");
|
|
314
417
|
}
|
|
315
418
|
const claudeMdPath = `${ctx.cwd}/CLAUDE.md`;
|
|
316
419
|
if (fs.exists(claudeMdPath)) {
|
|
317
420
|
ctx.stdout(`Warning: CLAUDE.md already exists. Consider adding: "${agentInstruction}"`);
|
|
318
421
|
} else {
|
|
319
|
-
const claudeContent = loadTemplate("claude-md", {
|
|
422
|
+
const claudeContent = loadTemplate("claude-md", { dustCommand });
|
|
320
423
|
await fs.writeFile(claudeMdPath, claudeContent);
|
|
321
424
|
ctx.stdout("Created CLAUDE.md with agent instructions");
|
|
322
425
|
}
|
|
@@ -324,7 +427,7 @@ async function init(ctx, fs, _args) {
|
|
|
324
427
|
if (fs.exists(agentsMdPath)) {
|
|
325
428
|
ctx.stdout(`Warning: AGENTS.md already exists. Consider adding: "${agentInstruction}"`);
|
|
326
429
|
} else {
|
|
327
|
-
const agentsContent = loadTemplate("agents-md", {
|
|
430
|
+
const agentsContent = loadTemplate("agents-md", { dustCommand });
|
|
328
431
|
await fs.writeFile(agentsMdPath, agentsContent);
|
|
329
432
|
ctx.stdout("Created AGENTS.md with agent instructions");
|
|
330
433
|
}
|
|
@@ -443,61 +546,9 @@ async function next(ctx, fs, _args) {
|
|
|
443
546
|
return { exitCode: 0 };
|
|
444
547
|
}
|
|
445
548
|
|
|
446
|
-
// lib/cli/prompt.ts
|
|
447
|
-
async function prompt(ctx, fs, args) {
|
|
448
|
-
const promptsDir = `${ctx.cwd}/prompts`;
|
|
449
|
-
if (args.length === 0) {
|
|
450
|
-
ctx.stderr("Usage: dust prompt <name>");
|
|
451
|
-
ctx.stderr("Example: dust prompt work");
|
|
452
|
-
return { exitCode: 1 };
|
|
453
|
-
}
|
|
454
|
-
const promptName = args[0];
|
|
455
|
-
const promptFile = `${promptsDir}/${promptName}.md`;
|
|
456
|
-
if (!fs.exists(promptFile)) {
|
|
457
|
-
ctx.stderr(`Error: Prompt '${promptName}' not found`);
|
|
458
|
-
ctx.stderr("Available prompts:");
|
|
459
|
-
try {
|
|
460
|
-
const files = await fs.readdir(promptsDir);
|
|
461
|
-
const prompts = files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
|
|
462
|
-
for (const p of prompts) {
|
|
463
|
-
ctx.stderr(` ${p}`);
|
|
464
|
-
}
|
|
465
|
-
} catch {
|
|
466
|
-
ctx.stderr(" (no prompts directory found)");
|
|
467
|
-
}
|
|
468
|
-
return { exitCode: 1 };
|
|
469
|
-
}
|
|
470
|
-
const content = await fs.readFile(promptFile);
|
|
471
|
-
ctx.stdout(content);
|
|
472
|
-
return { exitCode: 0 };
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// lib/cli/settings.ts
|
|
476
|
-
import { join as join2 } from "node:path";
|
|
477
|
-
var DEFAULT_SETTINGS = {
|
|
478
|
-
binaryPath: "dust"
|
|
479
|
-
};
|
|
480
|
-
async function loadSettings(cwd, fs) {
|
|
481
|
-
const settingsPath = join2(cwd, ".dust", "config", "settings.json");
|
|
482
|
-
if (!fs.exists(settingsPath)) {
|
|
483
|
-
return DEFAULT_SETTINGS;
|
|
484
|
-
}
|
|
485
|
-
try {
|
|
486
|
-
const content = await fs.readFile(settingsPath);
|
|
487
|
-
const parsed = JSON.parse(content);
|
|
488
|
-
return {
|
|
489
|
-
...DEFAULT_SETTINGS,
|
|
490
|
-
...parsed
|
|
491
|
-
};
|
|
492
|
-
} catch {
|
|
493
|
-
return DEFAULT_SETTINGS;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
549
|
// lib/cli/main.ts
|
|
498
550
|
var COMMANDS = [
|
|
499
551
|
"init",
|
|
500
|
-
"prompt",
|
|
501
552
|
"validate",
|
|
502
553
|
"list",
|
|
503
554
|
"next",
|
|
@@ -506,9 +557,9 @@ var COMMANDS = [
|
|
|
506
557
|
"help"
|
|
507
558
|
];
|
|
508
559
|
function generateHelpText(settings) {
|
|
509
|
-
return loadTemplate("help", { bin: settings.
|
|
560
|
+
return loadTemplate("help", { bin: settings.dustCommand });
|
|
510
561
|
}
|
|
511
|
-
var HELP_TEXT = generateHelpText({
|
|
562
|
+
var HELP_TEXT = generateHelpText({ dustCommand: "dust" });
|
|
512
563
|
function isHelpRequest(command) {
|
|
513
564
|
return !command || command === "help" || command === "--help" || command === "-h";
|
|
514
565
|
}
|
|
@@ -519,8 +570,6 @@ async function runCommand(command, commandArgs, ctx, fs, glob, settings) {
|
|
|
519
570
|
switch (command) {
|
|
520
571
|
case "init":
|
|
521
572
|
return init(ctx, fs, commandArgs);
|
|
522
|
-
case "prompt":
|
|
523
|
-
return prompt(ctx, fs, commandArgs);
|
|
524
573
|
case "validate":
|
|
525
574
|
return validate(ctx, fs, commandArgs, glob);
|
|
526
575
|
case "list":
|
|
@@ -528,7 +577,7 @@ async function runCommand(command, commandArgs, ctx, fs, glob, settings) {
|
|
|
528
577
|
case "next":
|
|
529
578
|
return next(ctx, fs, commandArgs);
|
|
530
579
|
case "check":
|
|
531
|
-
return check(ctx, fs, commandArgs,
|
|
580
|
+
return check(ctx, fs, commandArgs, glob);
|
|
532
581
|
case "agent":
|
|
533
582
|
return agent(ctx, commandArgs, settings);
|
|
534
583
|
case "help":
|
|
@@ -548,7 +597,7 @@ async function main(options) {
|
|
|
548
597
|
}
|
|
549
598
|
if (!isValidCommand(command)) {
|
|
550
599
|
ctx.stderr(`Unknown command: ${command}`);
|
|
551
|
-
ctx.stderr(`Run '${settings.
|
|
600
|
+
ctx.stderr(`Run '${settings.dustCommand} help' for available commands`);
|
|
552
601
|
return { exitCode: 1 };
|
|
553
602
|
}
|
|
554
603
|
return runCommand(command, commandArgs, ctx, fs, glob, settings);
|
package/package.json
CHANGED
|
@@ -2,10 +2,43 @@ Hello Claude, welcome to dust!
|
|
|
2
2
|
|
|
3
3
|
Your goal today is to make ONE SMALL CHANGE and then commit and push your changes.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The user might be prompting you to do one of many things. Depending on their intention, run
|
|
6
|
+
one of the following commands:
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
If your prompt is something like one of these:
|
|
9
|
+
|
|
10
|
+
* "work"
|
|
11
|
+
* "get to work"
|
|
12
|
+
* "go"
|
|
13
|
+
* "pick a task"
|
|
14
|
+
|
|
15
|
+
...then run:
|
|
16
|
+
|
|
17
|
+
`{{bin}} agent work`
|
|
18
|
+
|
|
19
|
+
If your prompt is something like one of these:
|
|
20
|
+
|
|
21
|
+
* "add a task ..."
|
|
22
|
+
* "task: ..."
|
|
23
|
+
|
|
24
|
+
...then you should create a new task. To find out how, you should immediately run:
|
|
25
|
+
|
|
26
|
+
`{{bin}} agent tasks`
|
|
27
|
+
|
|
28
|
+
If your prompt is something like one of these:
|
|
29
|
+
|
|
30
|
+
* "add a goal ..."
|
|
31
|
+
* "goal: ..."
|
|
32
|
+
|
|
33
|
+
...then you should create a new goal. To find out how, you should immediately run:
|
|
34
|
+
|
|
35
|
+
`{{bin}} agent goals`
|
|
36
|
+
|
|
37
|
+
If it sounds like the prompt is a vague idea about some potential change to the system,
|
|
38
|
+
then you should create an idea. To find out how, you should immediately run:
|
|
39
|
+
|
|
40
|
+
`{{bin}} agent ideas`
|
|
41
|
+
|
|
42
|
+
If it's still not clear, run:
|
|
43
|
+
|
|
44
|
+
`{{bin}} agent help`
|
package/templates/agent-help.txt
CHANGED
|
@@ -21,3 +21,14 @@ For focused guidance, run:
|
|
|
21
21
|
- `{{bin}} agent tasks` - Task management
|
|
22
22
|
- `{{bin}} agent goals` - Understanding goals
|
|
23
23
|
- `{{bin}} agent ideas` - Working with ideas
|
|
24
|
+
|
|
25
|
+
### Configuring Agent Files
|
|
26
|
+
|
|
27
|
+
Projects using dust should add a minimal pointer to their agent configuration files (CLAUDE.md, AGENTS.md, etc.):
|
|
28
|
+
|
|
29
|
+
```markdown
|
|
30
|
+
This project uses [dust](https://github.com/joshski/dust) for planning and documentation.
|
|
31
|
+
Always run `dust agent` when you start working in this repository.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This keeps agent instructions minimal and ensures agents get current documentation.
|
|
@@ -7,7 +7,6 @@ Ideas live in `.dust/ideas/` as markdown files. They are intentionally vague pro
|
|
|
7
7
|
**Converting an idea to tasks:**
|
|
8
8
|
1. Read the idea file to understand the proposal
|
|
9
9
|
2. Break it down into concrete, actionable tasks
|
|
10
|
-
3.
|
|
11
|
-
4. Delete the idea file once it's fully captured in tasks
|
|
10
|
+
3. Run `{{bin}} agent tasks` to see the workflow to apply to each individual task.
|
|
12
11
|
|
|
13
12
|
Ideas are cheap to create and easy to discard. Not every idea becomes a task.
|
|
@@ -1,15 +1,48 @@
|
|
|
1
|
-
|
|
1
|
+
# Task Management
|
|
2
2
|
|
|
3
|
-
**List tasks:** `{{bin}} list tasks`
|
|
3
|
+
**List all tasks:** `{{bin}} list tasks`
|
|
4
4
|
**Find ready tasks:** `{{bin}} next`
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
- `## Goals` - Links to goals this task supports
|
|
8
|
-
- `## Blocked by` - Tasks that must complete first
|
|
9
|
-
- `## Definition of done` - Checklist of completion criteria
|
|
6
|
+
## Implementing a Task
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
When implementing a task, follow these steps:
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
1. Install dependencies first (e.g., `npm install`, `bundle install`)
|
|
11
|
+
2. Run `{{bin}} check` before implementing anything to verify the project is in a good state
|
|
12
|
+
3. Read the task file and understand all requirements in "Definition of done"
|
|
13
|
+
4. Implement the changes, checking off items as you complete them
|
|
14
|
+
5. Run `{{bin}} check` after implementing all changes to verify nothing is broken
|
|
15
|
+
6. Create a single atomic commit that includes:
|
|
16
|
+
- All implementation changes
|
|
17
|
+
- Deletion of the completed task file
|
|
18
|
+
- Updates to any facts that changed
|
|
19
|
+
- Deletion of any ideas that were fully realized
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
## Adding a Task from scratch
|
|
22
|
+
|
|
23
|
+
To add a new task, follow these steps:
|
|
24
|
+
|
|
25
|
+
1. Create a new markdown file in `.dust/tasks/` with a descriptive kebab-case name (e.g., `add-user-authentication.md`)
|
|
26
|
+
2. Add a title as the first line using an H1 heading (e.g., `# Add user authentication`)
|
|
27
|
+
3. Write a comprehensive description of what needs to be done with technical details and references to relevant files
|
|
28
|
+
4. Add a `## Goals` section with links to relevant goals this task supports (e.g., `- [Goal Name](../goals/goal-name.md)`)
|
|
29
|
+
5. Add a `## Blocked by` section listing any tasks that must complete first, or `(none)` if there are no blockers
|
|
30
|
+
6. Add a `## Definition of done` section with a checklist of completion criteria using `- [ ]` for each item
|
|
31
|
+
7. Run `{{bin}} validate` to catch any issues with the task format
|
|
32
|
+
8. Create a single atomic commit with a message in the format "Add task: <title>"
|
|
33
|
+
|
|
34
|
+
## Converting an Idea into a Task (fleshing out an idea)
|
|
35
|
+
|
|
36
|
+
To add a new task, follow these steps:
|
|
37
|
+
|
|
38
|
+
1. Create a new markdown file in `.dust/tasks/` with a descriptive kebab-case name (e.g., `add-user-authentication.md`)
|
|
39
|
+
2. Add a title as the first line using an H1 heading (e.g., `# Add user authentication`)
|
|
40
|
+
3. Write a comprehensive description of what needs to be done with technical details and references to relevant files
|
|
41
|
+
4. Add a `## Goals` section with links to relevant goals this task supports (e.g., `- [Goal Name](../goals/goal-name.md)`)
|
|
42
|
+
5. Add a `## Blocked by` section listing any tasks that must complete first, or `(none)` if there are no blockers
|
|
43
|
+
6. Add a `## Definition of done` section with a checklist of completion criteria using `- [ ]` for each item
|
|
44
|
+
7. Run `{{bin}} validate` to catch any issues with the task format or regressions
|
|
45
|
+
8. Create a single atomic commit with the message "Add task: <title>" that includes:
|
|
46
|
+
- All implementation changes
|
|
47
|
+
- Deletion of the idea file
|
|
48
|
+
- Updates to any facts that changed
|
package/templates/agents-md.txt
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
This project uses [dust](https://github.com/joshski/dust) for planning and documentation.
|
|
4
4
|
|
|
5
|
-
Always immediately run `{{
|
|
5
|
+
Always immediately run `{{dustCommand}} agent` when you start working in this repository.
|
package/templates/claude-md.txt
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
This project uses [dust](https://github.com/joshski/dust) for planning and documentation.
|
|
4
4
|
|
|
5
|
-
Always immediately run `{{
|
|
5
|
+
Always immediately run `{{dustCommand}} agent` when you start working in this repository.
|
package/templates/help.txt
CHANGED
|
@@ -4,7 +4,6 @@ Usage: {{bin}} <command> [options]
|
|
|
4
4
|
|
|
5
5
|
Commands:
|
|
6
6
|
init Initialize a new Dust repository
|
|
7
|
-
prompt <name> Output a prompt by name (e.g., {{bin}} prompt work)
|
|
8
7
|
validate Run validation checks on .dust/ files
|
|
9
8
|
list [type] List items (tasks, ideas, goals, facts)
|
|
10
9
|
next Show tasks ready to work on (not blocked)
|
|
@@ -14,7 +13,6 @@ Commands:
|
|
|
14
13
|
|
|
15
14
|
Examples:
|
|
16
15
|
{{bin}} init
|
|
17
|
-
{{bin}} prompt work
|
|
18
16
|
{{bin}} validate
|
|
19
17
|
{{bin}} list tasks
|
|
20
18
|
{{bin}} list
|
|
@@ -22,61 +20,4 @@ Examples:
|
|
|
22
20
|
{{bin}} check
|
|
23
21
|
{{bin}} agent work
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
## Agent Guide
|
|
28
|
-
|
|
29
|
-
This section provides comprehensive guidance for AI agents working with dust.
|
|
30
|
-
|
|
31
|
-
### Directory Structure
|
|
32
|
-
|
|
33
|
-
The `.dust/` directory contains all planning artifacts:
|
|
34
|
-
|
|
35
|
-
- **`.dust/goals/`** - Mission statements and guiding principles
|
|
36
|
-
- **`.dust/ideas/`** - Future feature notes and proposals (intentionally vague)
|
|
37
|
-
- **`.dust/tasks/`** - Detailed work plans with dependencies and definitions of done
|
|
38
|
-
- **`.dust/facts/`** - Documentation of current system state and architecture
|
|
39
|
-
- **`.dust/hooks/`** - Executable scripts for quality gates (e.g., `check` hook)
|
|
40
|
-
|
|
41
|
-
All files are markdown with slug-style names (lowercase, hyphens, no spaces).
|
|
42
|
-
|
|
43
|
-
### Working on Tasks
|
|
44
|
-
|
|
45
|
-
**Run `{{bin}} check` before starting work** to verify the project is in a good state before making changes.
|
|
46
|
-
|
|
47
|
-
Run `{{bin}} next` to find tasks ready to work on. Each task file contains:
|
|
48
|
-
|
|
49
|
-
- `## Goals` - Links to goals this task supports
|
|
50
|
-
- `## Blocked by` - Tasks that must complete first (empty or "(none)" means ready)
|
|
51
|
-
- `## Definition of done` - Criteria for completion
|
|
52
|
-
|
|
53
|
-
A task is **unblocked** when its "Blocked by" section is empty, says "(none)", or all referenced task files have been deleted.
|
|
54
|
-
|
|
55
|
-
### Completing a Task
|
|
56
|
-
|
|
57
|
-
**Run `{{bin}} check` before committing** to ensure all quality gates pass.
|
|
58
|
-
|
|
59
|
-
When finishing a task, create a single atomic commit that includes:
|
|
60
|
-
|
|
61
|
-
1. All implementation changes
|
|
62
|
-
2. Deletion of the completed task file
|
|
63
|
-
3. Updates to any facts that changed
|
|
64
|
-
4. Deletion of any ideas that were fully realized
|
|
65
|
-
5. Updates to any tasks that referenced this one in their "Blocked by" sections
|
|
66
|
-
|
|
67
|
-
### Common Workflows
|
|
68
|
-
|
|
69
|
-
- **"Work on the next task"** - Run `{{bin}} next`, pick a task, implement it
|
|
70
|
-
- **"Work on task X"** - Implement `.dust/tasks/X.md` directly
|
|
71
|
-
- **"Convert idea Y to tasks"** - Break down `.dust/ideas/Y.md` into tasks
|
|
72
|
-
- **"Validate facts"** - Check `.dust/facts/` for accuracy against the codebase
|
|
73
|
-
|
|
74
|
-
### Configuring Agent Files
|
|
75
|
-
|
|
76
|
-
Projects using dust should add a minimal pointer to their agent configuration files (CLAUDE.md, AGENTS.md, etc.):
|
|
77
|
-
|
|
78
|
-
```markdown
|
|
79
|
-
This project uses [dust](https://github.com/joshski/dust) for planning and documentation.\nAlways run `dust help` when you start working in this repository.
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
This approach keeps agent instructions minimal, ensures agents get current documentation, and reduces maintenance burden.
|
|
23
|
+
For AI agent guidance, run `{{bin}} agent`.
|